# zsqlite 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: ```bash git clone git@git.reugenio.com:reugenio/zsqlite.git ``` 2. Añadir como dependencia en `build.zig.zon`: ```zig .dependencies = .{ .zsqlite = .{ .path = "../zsqlite", }, }, ``` 3. En `build.zig`: ```zig const zsqlite = b.dependency("zsqlite", .{}); exe.root_module.addImport("zsqlite", zsqlite.module("zsqlite")); ``` ## Uso Básico ```zig const sqlite = @import("zsqlite"); 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 ```zig // 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 ```zig 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 ```zig 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) ```zig 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, "", "", 10); ``` ### JSON ```zig 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) ```zig 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 ```zig // 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) ```zig // 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 ```zig // Crear copia compactada try db.vacuumInto("backup.sqlite"); // VACUUM in-place try db.vacuum(); ``` ### Snapshots (WAL mode) ```zig // 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 ```zig // 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 ```zig 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=0 # Single-threaded (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 ```bash 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: - [rusqlite](https://github.com/rusqlite/rusqlite) (Rust) - [zig-sqlite](https://github.com/vrischmann/zig-sqlite) (Zig) - [zqlite.zig](https://github.com/karlseguin/zqlite.zig) (Zig)