SQLite wrapper for Zig - zero runtime dependencies
- 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> |
||
|---|---|---|
| .claude/commands | ||
| docs | ||
| examples | ||
| src | ||
| vendor | ||
| .gitignore | ||
| build.zig | ||
| CHANGELOG.md | ||
| claude.md | ||
| README.md | ||
| REFERENCE.md | ||
| test_arraylist | ||
| VERIFIED_FEATURES.md | ||
| ZIG_VERSION_NOTES.md | ||
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
- Clonar el repositorio:
git clone git@git.reugenio.com:reugenio/zcatsql.git
- Añadir como dependencia en
build.zig.zon:
.dependencies = .{
.zcatsql = .{
.path = "../zcatsql",
},
},
- 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:
- rusqlite (Rust)
- zig-sqlite (Zig)
- zqlite.zig (Zig)