zcatsql/README.md
reugenio fac8bcba62 feat: v1.0 - add row mapping, serialize, session, vacuum into, snapshots
New features:
- Row-to-struct mapping (Row.to(), Row.toAlloc(), Row.freeStruct())
- Serialize/Deserialize API (toBytes, fromBytes, cloneToMemory, etc.)
- Session extension for change tracking (changesets, patchsets, diff)
- VACUUM INTO for creating compacted database copies
- Snapshot API for consistent reads in WAL mode
- Comprehensive README.md documentation

Technical changes:
- Enable -DSQLITE_ENABLE_SESSION and -DSQLITE_ENABLE_SNAPSHOT
- Add c.zig defines for session/snapshot headers
- Fix connection pool test to use temp file instead of shared cache
- 63 tests passing, 7563 lines of code across 15 modules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 21:04:24 +01:00

370 lines
8.8 KiB
Markdown

# 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, "<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)