Consistent naming with zcat ecosystem (zcatui, zcatgui, zcatsql). All lowercase per Zig naming conventions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
370 lines
8.8 KiB
Markdown
370 lines
8.8 KiB
Markdown
# 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:
|
|
```bash
|
|
git clone git@git.reugenio.com:reugenio/zcatsql.git
|
|
```
|
|
|
|
2. Añadir como dependencia en `build.zig.zon`:
|
|
```zig
|
|
.dependencies = .{
|
|
.zcatsql = .{
|
|
.path = "../zcatsql",
|
|
},
|
|
},
|
|
```
|
|
|
|
3. En `build.zig`:
|
|
```zig
|
|
const zcatsql = b.dependency("zcatsql", .{});
|
|
exe.root_module.addImport("zcatsql", zcatsql.module("zcatsql"));
|
|
```
|
|
|
|
## Uso Básico
|
|
|
|
```zig
|
|
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
|
|
|
|
```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, "<b>", "</b>", 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)
|