SQLite wrapper for Zig - zero runtime dependencies
Find a file
R.Eugenio 0f3edd6c30 build: Migrar a Zig 0.16
- Propagar io: std.Io en el sistema de auditoria (Writer, Log, Verify).
- Migrar de std.fs a std.Io.Dir y std.Io.File.
- Reemplazar ArrayList.writer por std.Io.Writer.Allocating.
- Corregir uso de clock_gettime y añadir helper microTimestamp.
- Actualizar tests para proporcionar std.testing.io.

Co-Authored-By: Gemini <noreply@google.com>
2026-01-18 01:30:36 +01:00
.claude/commands Actualizar /init para leer credenciales e infraestructura 2026-01-07 00:40:20 +01:00
docs fix: Change SQLITE_THREADSAFE=0 to THREADSAFE=2 2025-12-14 19:38:45 +01:00
examples refactor: rename zsqlite to zcatsql 2025-12-09 02:19:52 +01:00
src build: Migrar a Zig 0.16 2026-01-18 01:30:36 +01:00
vendor Initial commit: zsqlite - SQLite wrapper for Zig 2025-12-08 16:45:28 +01:00
.gitignore Añadir /init optimizado (lee NORMAS_ESENCIALES + teamdocs) 2025-12-23 13:28:54 +01:00
build.zig feat: Export module for external dependents 2026-01-08 23:19:09 +01:00
CHANGELOG.md docs: Documentar WAL automático en CHANGELOG 2026-01-12 12:06:55 +01:00
claude.md Estandarizar: CLAUDE.md → claude.md + refs CREDENCIALES 2026-01-11 15:13:39 +01:00
README.md fix: Change SQLITE_THREADSAFE=0 to THREADSAFE=2 2025-12-14 19:38:45 +01:00
REFERENCE.md refactor: rename zsqlite to zcatsql 2025-12-09 02:19:52 +01:00
test_arraylist refactor: rename zsqlite to zcatsql 2025-12-09 02:19:52 +01:00
VERIFIED_FEATURES.md docs: Add VERIFIED_FEATURES.md - tracking production-tested features 2025-12-10 12:00:47 +01:00
ZIG_VERSION_NOTES.md build: Migrar a Zig 0.16 2026-01-18 01:30:36 +01:00

zcatsql

SQLite wrapper idiomático para Zig 0.15.2+

Compila el SQLite amalgamation directamente en el binario, resultando en un ejecutable único sin dependencias externas.

Características

  • Zero dependencias runtime - SQLite embebido en el binario
  • API idiomática Zig - Errores, allocators, iteradores
  • Type-safe - Binding y row mapping con verificación en comptime
  • Completo - Todas las funcionalidades de SQLite expuestas
  • 63 tests - Cobertura exhaustiva

Instalación

  1. Clonar el repositorio:
git clone git@git.reugenio.com:reugenio/zcatsql.git
  1. Añadir como dependencia en build.zig.zon:
.dependencies = .{
    .zcatsql = .{
        .path = "../zcatsql",
    },
},
  1. En build.zig:
const zcatsql = b.dependency("zcatsql", .{});
exe.root_module.addImport("zcatsql", zcatsql.module("zcatsql"));

Uso Básico

const sqlite = @import("zcatsql");

pub fn main() !void {
    // Abrir base de datos
    var db = try sqlite.open("test.db");
    defer db.close();

    // Ejecutar SQL directo
    try db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");

    // Prepared statement con binding
    var stmt = try db.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
    defer stmt.finalize();

    try stmt.bindAll(.{ "Alice", @as(i64, 30) });
    _ = try stmt.step();

    // Query con row mapping a struct
    const User = struct { id: i64, name: []const u8, age: i64 };

    var query = try db.prepare("SELECT id, name, age FROM users");
    defer query.finalize();

    var iter = query.iterator();
    while (try iter.next()) |row| {
        const user = row.to(User);
        std.debug.print("User {}: {s}, age {}\n", .{ user.id, user.name, user.age });
    }
}

Módulos

Core

Módulo Descripción
Database Conexión, transacciones, pragmas
Statement Prepared statements, binding, iteradores
Row Acceso a columnas, mapping a structs
Backup Online backup API
Blob Incremental blob I/O
ConnectionPool Pool de conexiones thread-safe

Extensiones SQLite

Módulo Descripción
fts5.Fts5 Full-text search
json.Json JSON1 functions
rtree.RTree R-Tree spatial index
vtable Virtual table API

Funcionalidades Avanzadas

Módulo Descripción
serialize Serialize/Deserialize API
session.Session Change tracking y changesets
Database.Snapshot Consistent reads en WAL mode

Ejemplos por Funcionalidad

Batch Binding

// Bind múltiples valores de una vez
try stmt.bindAll(.{ "Alice", @as(i64, 30), @as(f64, 95.5) });

// Rebind para ejecutar múltiples veces
try stmt.rebind(.{ "Bob", @as(i64, 25), @as(f64, 87.0) });
_ = try stmt.step();

Row Mapping a Structs

const User = struct {
    id: i64,
    name: ?[]const u8,  // Nullable para columnas que pueden ser NULL
    score: f64,
    active: bool,
};

var iter = stmt.iterator();
while (try iter.next()) |row| {
    // Non-allocating - slices apuntan a buffers de SQLite
    const user = row.to(User);

    // O con allocación para persistir datos
    const user_copy = try row.toAlloc(User, allocator);
    defer Row.freeStruct(User, user_copy, allocator);
}

Transacciones

try db.begin();
errdefer db.rollback() catch {};

try db.exec("INSERT INTO accounts VALUES (1, 1000)");
try db.exec("INSERT INTO accounts VALUES (2, 2000)");

try db.commit();

// O con helper automático
try db.transaction(struct {
    fn run(tx_db: *Database) !void {
        try tx_db.exec("...");
    }
}.run);

Full-Text Search (FTS5)

var fts = sqlite.Fts5.init(&db, allocator);

// Crear tabla FTS5
try fts.createSimpleTable("documents", &.{ "title", "content" });

// Insertar documentos
try db.exec("INSERT INTO documents VALUES ('Zig Guide', 'A guide to programming in Zig')");

// Buscar
var results = try fts.search("documents", "programming", &.{ "title", "content" }, 10);
defer results.finalize();

while (try results.step()) {
    const title = results.columnText(0) orelse "";
    std.debug.print("Found: {s}\n", .{title});
}

// Con highlight
var highlighted = try fts.searchWithHighlight("documents", "Zig", 0, "<b>", "</b>", 10);

JSON

var json = sqlite.Json.init(&db, allocator);

// Validar JSON
const is_valid = try json.isValid("{\"name\": \"Alice\"}");

// Extraer valores
const name = try json.extract("{\"user\": {\"name\": \"Alice\"}}", "$.user.name");
defer allocator.free(name.?);

// Modificar JSON
const updated = try json.setInt("{\"count\": 0}", "$.count", 42);
defer allocator.free(updated);

// Crear objetos
const obj = try json.createObject(
    &.{ "id", "name" },
    &.{ "1", "\"Alice\"" },
);

R-Tree (Spatial Index)

var rt = sqlite.RTree.init(&db, allocator);

// Crear índice 2D
try rt.createSimpleTable2D("locations");

// Insertar puntos
try rt.insertPoint2D("locations", 1, 10.5, 20.3);
try rt.insertPoint2D("locations", 2, 15.0, 25.0);

// Query por bounding box
const box = sqlite.BoundingBox2D{
    .min_x = 5.0, .max_x = 20.0,
    .min_y = 15.0, .max_y = 30.0,
};
const ids = try rt.getIntersectingIds2D("locations", box);
defer rt.freeIds(ids);

// Distancia geográfica (Haversine)
const london = sqlite.GeoCoord{ .latitude = 51.5074, .longitude = -0.1278 };
const paris = sqlite.GeoCoord{ .latitude = 48.8566, .longitude = 2.3522 };
const distance_km = london.distanceKm(paris);  // ~343 km

Serialize/Deserialize

// Serializar base de datos a bytes
const bytes = try sqlite.serialize.toBytes(&db, allocator, "main");
defer allocator.free(bytes);

// Guardar/enviar bytes...

// Deserializar a nueva base de datos
var db2 = try sqlite.serialize.fromBytes(bytes, ":memory:");
defer db2.close();

// Clonar base de datos en memoria
var clone = try sqlite.serialize.cloneToMemory(&db, allocator);
defer clone.close();

Session (Change Tracking)

// Crear session para trackear cambios
var session = try sqlite.Session.init(&db, "main");
defer session.deinit();

// Attach tablas a trackear
try session.attach("users");
try session.attach("orders");

// Hacer cambios...
try db.exec("INSERT INTO users VALUES (1, 'Alice')");
try db.exec("UPDATE orders SET status = 'shipped' WHERE id = 5");

// Generar changeset
const changeset = try session.changeset(allocator);
defer allocator.free(changeset);

// Aplicar changeset a otra base de datos
try sqlite.applyChangeset(&other_db, changeset, null, null);

// Invertir changeset (para undo)
const undo = try sqlite.invertChangeset(changeset, allocator);
defer allocator.free(undo);

VACUUM INTO

// Crear copia compactada
try db.vacuumInto("backup.sqlite");

// VACUUM in-place
try db.vacuum();

Snapshots (WAL mode)

// Habilitar WAL
try db.exec("PRAGMA journal_mode=WAL");

// Obtener snapshot
try db.begin();
var snapshot = try db.getSnapshot("main");
defer snapshot.deinit();

// En otra conexión, abrir el mismo snapshot
var reader = try sqlite.open("mydb.sqlite");
try reader.exec("PRAGMA journal_mode=WAL");
try reader.begin();
try reader.openSnapshot("main", &snapshot);
// reader ahora ve datos del momento del snapshot

User-Defined Functions

// Función escalar
try db.createScalarFunction("double", 1, .{}, struct {
    fn impl(ctx: *sqlite.FunctionContext, args: []?*sqlite.FunctionValue) void {
        if (args[0]) |arg| {
            ctx.resultInt(arg.getInt() * 2);
        } else {
            ctx.resultNull();
        }
    }
}.impl);

// Uso: SELECT double(21); -- devuelve 42

Connection Pool

var pool = try sqlite.ConnectionPool.init(allocator, "database.db", 10);
defer pool.deinit();

const conn = try pool.acquire();
defer pool.release(conn);

try conn.exec("...");

Flags de Compilación SQLite

El build incluye los siguientes flags optimizados:

-DSQLITE_DQS=0                 # Disable double-quoted strings
-DSQLITE_THREADSAFE=2          # Multi-thread (required for ConnectionPool) (más rápido)
-DSQLITE_ENABLE_FTS5           # Full-text search v5
-DSQLITE_ENABLE_JSON1          # JSON functions
-DSQLITE_ENABLE_RTREE          # R-Tree spatial index
-DSQLITE_ENABLE_SESSION        # Session extension
-DSQLITE_ENABLE_SNAPSHOT       # Snapshot API
-DSQLITE_ENABLE_COLUMN_METADATA
-DSQLITE_ENABLE_PREUPDATE_HOOK

Tests

zig build test

63 tests cubriendo todas las funcionalidades.

Estadísticas

Métrica Valor
Líneas de código 7,563
Módulos 15
Tests 63
Versión SQLite 3.45+
Versión Zig 0.15.2+

Licencia

MIT

Créditos

Inspirado en: