# zsqlite - Manual de Referencia Técnica
> **Versión**: 1.0.0
> **Zig**: 0.15.2
> **SQLite**: 3.47.2
> **Fecha**: 2025-12-08
## Tabla de Contenidos
1. [Introducción](#introducción)
2. [Instalación y Uso](#instalación-y-uso)
3. [Estructura del Proyecto](#estructura-del-proyecto)
4. [API Principal](#api-principal)
- [Database](#database)
- [Statement](#statement)
- [Row y RowIterator](#row-y-rowiterator)
5. [Gestión de Errores](#gestión-de-errores)
6. [Transacciones](#transacciones)
7. [Funciones de Usuario](#funciones-de-usuario)
8. [Hooks y Callbacks](#hooks-y-callbacks)
9. [Extensiones](#extensiones)
- [FTS5 - Búsqueda Full-Text](#fts5---búsqueda-full-text)
- [JSON1 - Funciones JSON](#json1---funciones-json)
- [R-Tree - Índices Espaciales](#r-tree---índices-espaciales)
- [Virtual Tables](#virtual-tables)
10. [Características Avanzadas](#características-avanzadas)
- [Backup y Blob I/O](#backup-y-blob-io)
- [Connection Pool](#connection-pool)
- [Serialización](#serialización)
- [Session y Changesets](#session-y-changesets)
- [Snapshots](#snapshots)
11. [Audit Log](#audit-log)
12. [Tipos y Constantes](#tipos-y-constantes)
13. [Ejemplos de Uso](#ejemplos-de-uso)
---
## Introducción
**zsqlite** es un wrapper idiomático de SQLite para Zig que compila SQLite amalgamation directamente en el binario, resultando en un ejecutable único sin dependencias externas.
### Características principales:
- Zero dependencias runtime
- API idiomática Zig (errores, allocators, iteradores)
- Binario único y portable
- Compatible con bases de datos SQLite existentes
- Soporte completo para FTS5, JSON1, R-Tree
- Sistema de auditoría con cadena de hash
---
## Instalación y Uso
### Como dependencia en build.zig
```zig
const zsqlite = b.dependency("zsqlite", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zsqlite", zsqlite.module("zsqlite"));
```
### Uso básico
```zig
const sqlite = @import("zsqlite");
pub fn main() !void {
var db = try sqlite.open("test.db");
defer db.close();
try db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
var stmt = try db.prepare("INSERT INTO users (name) VALUES (?)");
defer stmt.finalize();
try stmt.bindText(1, "Alice");
_ = try stmt.step();
}
```
---
## Estructura del Proyecto
```
src/
├── root.zig # Exports públicos principales
├── c.zig # Bindings C de SQLite
├── errors.zig # Gestión de errores
├── types.zig # Tipos comunes (OpenFlags, ColumnType, etc.)
├── database.zig # Conexión a base de datos
├── statement.zig # Prepared statements y Row
├── functions.zig # Funciones de usuario y hooks
├── backup.zig # Backup y Blob I/O
├── pool.zig # Connection pooling
├── serialize.zig # Serialización/deserialización
├── session.zig # Change tracking y changesets
├── vtable.zig # Virtual tables
├── fts5.zig # Full-text search
├── json.zig # Funciones JSON
├── rtree.zig # Índices espaciales
└── audit/
├── mod.zig # Exports del módulo audit
├── entry.zig # Estructura Entry
├── context.zig # Contexto de auditoría
├── log.zig # Sistema principal de audit
├── index.zig # Gestión de índice
├── writer.zig # Escritura de logs
└── verify.zig # Verificación de integridad
```
---
## API Principal
### Database
**Archivo**: `src/database.zig`
**Re-exportado en**: `root.zig`
#### Apertura y Cierre
| Función | Descripción |
|---------|-------------|
| `open(path: [:0]const u8) !Database` | Abre base de datos en path |
| `openWithFlags(path, flags: OpenFlags) !Database` | Abre con flags específicos |
| `openUri(uri: [:0]const u8) !Database` | Abre usando URI |
| `openUriAlloc(allocator, uri) !Database` | Abre URI (versión allocator) |
| `close() void` | Cierra la conexión |
| `filename(db_name: [:0]const u8) ?[]const u8` | Obtiene path del archivo |
```zig
// Ejemplos
var db = try sqlite.open("mydb.sqlite");
defer db.close();
var db2 = try sqlite.openWithFlags("readonly.db", .{ .read_only = true });
var db3 = try sqlite.openUri("file:memdb?mode=memory&cache=shared");
```
#### Ejecución de Consultas
| Función | Descripción |
|---------|-------------|
| `exec(sql: [:0]const u8) !void` | Ejecuta SQL directamente |
| `execAlloc(allocator, sql) !void` | Ejecuta SQL (versión allocator) |
| `prepare(sql: [:0]const u8) !Statement` | Crea prepared statement |
| `prepareAlloc(allocator, sql) !Statement` | Prepara (versión allocator) |
```zig
try db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
var stmt = try db.prepare("SELECT * FROM users WHERE id = ?");
defer stmt.finalize();
```
#### Transacciones
| Función | Descripción |
|---------|-------------|
| `begin() !void` | Inicia transacción (DEFERRED) |
| `beginImmediate() !void` | Inicia transacción IMMEDIATE |
| `beginExclusive() !void` | Inicia transacción EXCLUSIVE |
| `commit() !void` | Confirma transacción |
| `rollback() !void` | Revierte transacción |
| `savepoint(allocator, name) !void` | Crea savepoint |
| `release(allocator, name) !void` | Libera savepoint |
| `rollbackTo(allocator, name) !void` | Revierte a savepoint |
```zig
try db.begin();
errdefer db.rollback() catch {};
try db.exec("INSERT INTO users (name) VALUES ('Alice')");
try db.exec("INSERT INTO users (name) VALUES ('Bob')");
try db.commit();
```
#### Información de la Base de Datos
| Función | Descripción |
|---------|-------------|
| `lastInsertRowId() i64` | Último rowid insertado |
| `changes() i32` | Cambios en transacción actual |
| `totalChanges() i32` | Total de cambios desde apertura |
| `errorMessage() []const u8` | Último mensaje de error |
| `errorCode() c_int` | Código de error |
| `extendedErrorCode() c_int` | Código de error extendido |
| `isReadOnly(db_name) bool` | Verifica si es solo lectura |
#### Configuración (PRAGMA)
| Función | Descripción |
|---------|-------------|
| `setForeignKeys(enabled: bool) !void` | Habilita/deshabilita foreign keys |
| `setJournalMode(allocator, mode) !void` | Configura modo de journal |
| `setSynchronous(allocator, mode) !void` | Configura modo de sincronización |
| `enableWalMode(allocator) !void` | Habilita modo WAL |
| `setAutoVacuum(allocator, mode) !void` | Configura auto-vacuum |
| `setCacheSize(allocator, size) !void` | Tamaño de caché |
| `setCaseSensitiveLike(enabled) !void` | LIKE case-sensitive |
| `setDeferForeignKeys(enabled) !void` | Diferir verificación FK |
| `setLockingMode(allocator, mode) !void` | Modo de bloqueo |
| `setQueryOnly(enabled) !void` | Solo lectura |
| `setRecursiveTriggers(enabled) !void` | Triggers recursivos |
| `setSecureDelete(enabled) !void` | Borrado seguro |
| `setPageSize(allocator, size) !void` | Tamaño de página |
| `setMaxPageCount(allocator, count) !void` | Máximo de páginas |
| `setTempStore(allocator, mode) !void` | Almacenamiento temporal |
| `setWalAutoCheckpoint(allocator, pages) !void` | Checkpoint automático WAL |
```zig
try db.setForeignKeys(true);
try db.enableWalMode(allocator);
try db.setCacheSize(allocator, 10000);
```
#### Mantenimiento
| Función | Descripción |
|---------|-------------|
| `vacuum() !void` | Compacta la base de datos |
| `incrementalVacuum(allocator, pages) !void` | Vacuum incremental |
| `integrityCheck(allocator) ![]const u8` | Verifica integridad |
| `quickCheck(allocator) ![]const u8` | Verificación rápida |
| `optimize() !void` | Optimiza planificador |
| `walCheckpoint(allocator, mode) !void` | Checkpoint WAL |
#### Bases de Datos Adjuntas
| Función | Descripción |
|---------|-------------|
| `attach(allocator, path, schema) !void` | Adjunta base de datos |
| `attachMemory(allocator, schema) !void` | Adjunta DB en memoria |
| `detach(allocator, schema) !void` | Desadjunta base de datos |
| `listDatabases(allocator) ![]DatabaseInfo` | Lista DBs adjuntas |
```zig
try db.attach(allocator, "/path/to/other.db", "other");
// Ahora puedes usar: SELECT * FROM other.table_name
try db.detach(allocator, "other");
```
#### Límites
| Función | Descripción |
|---------|-------------|
| `getLimit(limit_type: Limit) i32` | Obtiene valor de límite |
| `setLimit(limit_type, value) i32` | Establece límite |
```zig
const max_sql = db.getLimit(.sql_length);
_ = db.setLimit(.sql_length, 500000);
```
#### Control de Archivos
| Función | Descripción |
|---------|-------------|
| `setFileControlInt(db_name, op, value) !void` | Control de archivo |
| `getPersistWal(db_name) !bool` | Obtiene persistencia WAL |
| `setPersistWal(db_name, persist) !void` | Establece persistencia WAL |
| `setChunkSize(db_name, size) !void` | Tamaño de chunk |
| `getDataVersion(db_name) !u32` | Versión de datos |
---
### Statement
**Archivo**: `src/statement.zig`
**Re-exportado en**: `root.zig`
#### Ciclo de Vida
| Función | Descripción |
|---------|-------------|
| `finalize() void` | Finaliza statement |
| `reset() !void` | Resetea para re-ejecución |
| `clearBindings() !void` | Limpia todos los bindings |
#### Metadatos
| Función | Descripción |
|---------|-------------|
| `sql() []const u8` | Texto SQL original |
| `expandedSql(allocator) ![]u8` | SQL con parámetros expandidos |
| `isReadOnly() bool` | Verifica si es solo lectura |
| `parameterCount() i32` | Número de parámetros |
| `parameterIndex(name) ?i32` | Índice de parámetro nombrado |
| `parameterName(index) ?[]const u8` | Nombre de parámetro |
| `columnCount() i32` | Número de columnas |
| `columnName(index) []const u8` | Nombre de columna |
| `columnType(index) ColumnType` | Tipo de columna |
| `columnDeclType(index) ?[]const u8` | Tipo declarado |
| `columnDatabaseName(index) ?[]const u8` | Base de datos origen |
| `columnTableName(index) ?[]const u8` | Tabla origen |
| `columnOriginName(index) ?[]const u8` | Columna origen |
#### Binding de Parámetros (índice 1-based)
| Función | Descripción |
|---------|-------------|
| `bindNull(index) !void` | Vincula NULL |
| `bindInt(index, value: i64) !void` | Vincula entero |
| `bindFloat(index, value: f64) !void` | Vincula flotante |
| `bindText(index, value: []const u8) !void` | Vincula texto |
| `bindBlob(index, value: []const u8) !void` | Vincula blob |
| `bindBool(index, value: bool) !void` | Vincula booleano |
| `bindZeroblob(index, size: i32) !void` | Vincula zeroblob |
| `bindTimestamp(index, ts: i64) !void` | Vincula timestamp ISO 8601 |
| `bindCurrentTime(index) !void` | Vincula tiempo actual |
```zig
var stmt = try db.prepare("INSERT INTO users (name, age, active) VALUES (?, ?, ?)");
defer stmt.finalize();
try stmt.bindText(1, "Alice");
try stmt.bindInt(2, 30);
try stmt.bindBool(3, true);
_ = try stmt.step();
```
#### Binding con Nombres
| Función | Descripción |
|---------|-------------|
| `bindNullNamed(name) !void` | Vincula NULL por nombre |
| `bindIntNamed(name, value) !void` | Vincula entero por nombre |
| `bindFloatNamed(name, value) !void` | Vincula flotante por nombre |
| `bindTextNamed(name, value) !void` | Vincula texto por nombre |
| `bindBlobNamed(name, value) !void` | Vincula blob por nombre |
| `bindBoolNamed(name, value) !void` | Vincula booleano por nombre |
| `bindTimestampNamed(name, ts) !void` | Vincula timestamp por nombre |
| `bindCurrentTimeNamed(name) !void` | Vincula tiempo actual por nombre |
```zig
var stmt = try db.prepare("INSERT INTO users (name, age) VALUES (:name, :age)");
try stmt.bindTextNamed(":name", "Bob");
try stmt.bindIntNamed(":age", 25);
```
#### Binding por Lotes
| Función | Descripción |
|---------|-------------|
| `bindAll(values: anytype) !void` | Vincula tupla de valores |
| `bindValue(index, value) !void` | Vincula valor tipado |
| `rebind(values: anytype) !void` | Reset + bind |
```zig
var stmt = try db.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
try stmt.bindAll(.{ "Charlie", @as(i64, 35) });
_ = try stmt.step();
try stmt.rebind(.{ "Diana", @as(i64, 28) });
_ = try stmt.step();
```
#### Ejecución
| Función | Descripción |
|---------|-------------|
| `step() !bool` | Ejecuta un paso (retorna: ¿hay fila?) |
```zig
while (try stmt.step()) {
const name = stmt.columnText(0);
std.debug.print("Name: {s}\n", .{name});
}
```
#### Lectura de Columnas
| Función | Descripción |
|---------|-------------|
| `columnInt(index) i64` | Lee como i64 |
| `columnFloat(index) f64` | Lee como f64 |
| `columnText(index) []const u8` | Lee como texto |
| `columnBlob(index) []const u8` | Lee como blob |
| `columnBool(index) bool` | Lee como bool |
| `columnBytes(index) i32` | Obtiene tamaño en bytes |
| `columnIsNull(index) bool` | Verifica si es NULL |
#### Iteradores
| Función | Descripción |
|---------|-------------|
| `iterator() RowIterator` | Obtiene iterador de filas |
| `forEach(callback) !void` | Itera con callback |
| `collectAll(allocator) ![]Row` | Colecta todas las filas |
```zig
var stmt = try db.prepare("SELECT id, name FROM users");
defer stmt.finalize();
var iter = stmt.iterator();
while (iter.next()) |row| {
const id = row.int(0);
const name = row.text(1);
std.debug.print("User {}: {s}\n", .{id, name});
}
```
---
### Row y RowIterator
**Archivo**: `src/statement.zig`
#### RowIterator
| Función | Descripción |
|---------|-------------|
| `next() ?Row` | Obtiene siguiente fila o null |
#### Row - Acceso a Datos
| Función | Descripción |
|---------|-------------|
| `int(index) i64` | Lee entero |
| `float(index) f64` | Lee flotante |
| `text(index) []const u8` | Lee texto |
| `blob(index) []const u8` | Lee blob |
| `boolean(index) bool` | Lee booleano |
| `isNull(index) bool` | Verifica NULL |
| `columnType(index) ColumnType` | Tipo de columna |
| `columnName(index) []const u8` | Nombre de columna |
| `columnCount() i32` | Número de columnas |
#### Row - Mapeo a Estructuras
| Función | Descripción |
|---------|-------------|
| `to(comptime T) !T` | Mapea a struct (stack) |
| `toAlloc(T, allocator) !T` | Mapea a struct (heap) |
| `freeStruct(T, value, allocator) void` | Libera struct asignado |
| `toValues(allocator) ![]Value` | Colecta todos los valores |
```zig
const User = struct {
id: i64,
name: []const u8,
email: ?[]const u8,
};
var stmt = try db.prepare("SELECT id, name, email FROM users");
defer stmt.finalize();
var iter = stmt.iterator();
while (iter.next()) |row| {
const user = try row.to(User);
std.debug.print("User: {s}\n", .{user.name});
}
```
---
## Gestión de Errores
**Archivo**: `src/errors.zig`
### Error Enum
```zig
pub const Error = error{
SqliteError, // Error genérico
InternalError, // Error interno de SQLite
PermissionDenied, // Permiso denegado
Abort, // Operación abortada
Busy, // Base de datos ocupada
Locked, // Tabla bloqueada
OutOfMemory, // Sin memoria
ReadOnly, // Base de datos solo lectura
Interrupt, // Operación interrumpida
IoError, // Error de I/O
Corrupt, // Base de datos corrupta
NotFound, // No encontrado
Full, // Disco lleno
CantOpen, // No se puede abrir
Protocol, // Error de protocolo
Empty, // Vacío
Schema, // Schema cambió
TooBig, // Demasiado grande
Constraint, // Violación de constraint
Mismatch, // Tipo no coincide
Misuse, // Uso incorrecto de API
NoLfs, // LFS no soportado
Auth, // Autorización denegada
Format, // Error de formato
Range, // Fuera de rango
NotADatabase, // No es una base de datos
Notice, // Notificación
Warning, // Advertencia
Row, // Hay fila disponible
Done, // Operación completada
};
```
### Funciones de Error
| Función | Descripción |
|---------|-------------|
| `resultToError(result: c_int) ?Error` | Convierte código SQLite a Error |
| `errorDescription(err: Error) []const u8` | Obtiene descripción del error |
```zig
db.exec("INVALID SQL") catch |err| {
const desc = sqlite.errorDescription(err);
std.debug.print("Error: {s}\n", .{desc});
std.debug.print("SQLite message: {s}\n", .{db.errorMessage()});
};
```
---
## Transacciones
### Transacciones Básicas
```zig
// Transacción simple
try db.begin();
errdefer db.rollback() catch {};
try db.exec("INSERT INTO accounts (balance) VALUES (1000)");
try db.exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
try db.commit();
```
### Tipos de Transacción
```zig
// DEFERRED (por defecto) - bloquea al primer acceso
try db.begin();
// IMMEDIATE - bloquea inmediatamente para escritura
try db.beginImmediate();
// EXCLUSIVE - bloqueo exclusivo completo
try db.beginExclusive();
```
### Savepoints
```zig
try db.begin();
try db.exec("INSERT INTO log (msg) VALUES ('step 1')");
try db.savepoint(allocator, "checkpoint1");
try db.exec("INSERT INTO log (msg) VALUES ('step 2')");
// Algo salió mal, revertir solo al savepoint
try db.rollbackTo(allocator, "checkpoint1");
// O liberar el savepoint si todo está bien
// try db.release(allocator, "checkpoint1");
try db.commit();
```
---
## Funciones de Usuario
**Archivo**: `src/functions.zig`
### Funciones Escalares
```zig
fn myUpperFn(ctx: *sqlite.FunctionContext, args: []const *sqlite.FunctionValue) void {
if (args.len != 1) {
ctx.setError("Expected 1 argument");
return;
}
const text = args[0].asText() orelse {
ctx.setNull();
return;
};
// Convertir a mayúsculas (simplificado)
var result: [256]u8 = undefined;
const len = @min(text.len, result.len);
for (text[0..len], 0..) |c, i| {
result[i] = std.ascii.toUpper(c);
}
ctx.setText(result[0..len]);
}
// Registrar función
try db.createScalarFunction("my_upper", 1, myUpperFn);
// Usar en SQL
try db.exec("SELECT my_upper(name) FROM users");
// Eliminar función
try db.removeFunction("my_upper", 1);
```
### Funciones de Agregación
```zig
const SumState = struct {
total: i64 = 0,
};
fn sumStepFn(ctx: *sqlite.AggregateContext, args: []const *sqlite.FunctionValue) void {
const state = ctx.getAggregateContext(SumState) orelse return;
if (args[0].asInt()) |val| {
state.total += val;
}
}
fn sumFinalFn(ctx: *sqlite.AggregateContext) void {
const state = ctx.getAggregateContext(SumState) orelse {
ctx.setInt(0);
return;
};
ctx.setInt(state.total);
}
try db.createAggregateFunction("my_sum", 1, sumStepFn, sumFinalFn);
```
### Collations Personalizadas
```zig
fn caseInsensitiveCompare(a: []const u8, b: []const u8) i32 {
// Comparación case-insensitive
const len = @min(a.len, b.len);
for (a[0..len], b[0..len]) |ca, cb| {
const la = std.ascii.toLower(ca);
const lb = std.ascii.toLower(cb);
if (la < lb) return -1;
if (la > lb) return 1;
}
if (a.len < b.len) return -1;
if (a.len > b.len) return 1;
return 0;
}
try db.createCollation("NOCASE_CUSTOM", caseInsensitiveCompare);
// Usar en SQL
try db.exec("SELECT * FROM users ORDER BY name COLLATE NOCASE_CUSTOM");
```
---
## Hooks y Callbacks
**Archivo**: `src/functions.zig` y `src/database.zig`
### Commit/Rollback Hooks
```zig
fn onCommit(user_data: ?*anyopaque) bool {
std.debug.print("Transaction committed!\n", .{});
return false; // false = permitir commit, true = rollback
}
fn onRollback(user_data: ?*anyopaque) void {
std.debug.print("Transaction rolled back!\n", .{});
}
db.setCommitHook(onCommit);
db.setRollbackHook(onRollback);
```
### Update Hook
```zig
fn onUpdate(
user_data: ?*anyopaque,
op: sqlite.UpdateOperation,
db_name: []const u8,
table_name: []const u8,
rowid: i64,
) void {
const op_str = switch (op) {
.insert => "INSERT",
.update => "UPDATE",
.delete => "DELETE",
};
std.debug.print("{s} on {s}.{s} rowid={d}\n", .{op_str, db_name, table_name, rowid});
}
db.setUpdateHook(onUpdate);
```
### Pre-Update Hook
```zig
fn onPreUpdate(ctx: *sqlite.PreUpdateContext) void {
// Acceder a valores antes y después del cambio
if (ctx.oldValue(0)) |old_val| {
std.debug.print("Old value: {any}\n", .{old_val.asInt()});
}
if (ctx.newValue(0)) |new_val| {
std.debug.print("New value: {any}\n", .{new_val.asInt()});
}
}
db.setPreUpdateHook(onPreUpdate);
```
### Authorizer
```zig
fn authorizer(
user_data: ?*anyopaque,
action: sqlite.AuthAction,
arg1: ?[]const u8,
arg2: ?[]const u8,
db_name: ?[]const u8,
trigger_name: ?[]const u8,
) sqlite.AuthResult {
// Denegar DROP TABLE
if (action == .drop_table) {
return .deny;
}
return .ok;
}
db.setAuthorizer(authorizer);
```
### Progress Handler
```zig
fn progressCallback(user_data: ?*anyopaque) bool {
// Llamado cada N operaciones
// Retornar true para interrumpir la operación
return false;
}
// Llamar cada 1000 operaciones
db.setProgressHandler(1000, progressCallback);
```
### Busy Handler
```zig
fn busyHandler(user_data: ?*anyopaque, count: i32) bool {
// count = número de veces que se ha llamado
if (count > 5) return false; // Rendirse después de 5 intentos
std.time.sleep(100 * std.time.ns_per_ms); // Esperar 100ms
return true; // Reintentar
}
db.setBusyHandler(busyHandler);
// O usar timeout simple
try db.setBusyTimeout(5000); // 5 segundos
```
### Limpiar Hooks
```zig
db.clearHooks(); // Elimina todos los hooks
```
---
## Extensiones
### FTS5 - Búsqueda Full-Text
**Archivo**: `src/fts5.zig`
```zig
var fts = sqlite.Fts5.init(&db, allocator);
// Crear tabla FTS5
try fts.createSimpleTable("documents", &.{ "title", "content" });
// O con tokenizer específico
try fts.createTableWithTokenizer("docs", &.{ "title", "body" }, .porter);
// Insertar datos
try db.exec("INSERT INTO documents (title, content) VALUES ('Hello', 'World content')");
// Búsqueda simple
const results = try fts.search("documents", "world", &.{ "title", "content" }, 10);
defer allocator.free(results);
// Búsqueda con highlighting
const highlighted = try fts.searchWithHighlight(
"documents",
"world",
1, // columna content
"",
"",
10,
);
// Búsqueda con ranking BM25
const ranked = try fts.searchWithBM25("documents", "world", &.{ "title", "content" }, 10);
```
#### Tokenizers Disponibles
| Tokenizer | Descripción |
|-----------|-------------|
| `.unicode61` | Por defecto, soporta Unicode |
| `.ascii` | Solo ASCII |
| `.porter` | Stemming Porter (inglés) |
| `.trigram` | N-gramas de 3 caracteres |
---
### JSON1 - Funciones JSON
**Archivo**: `src/json.zig`
```zig
var json_helper = sqlite.Json.init(&db, allocator);
// Validar JSON
const valid = try json_helper.isValid("{\"name\": \"Alice\"}");
// Extraer valores
const name = try json_helper.extractText("{\"user\": {\"name\": \"Bob\"}}", "$.user.name");
const age = try json_helper.extractInt("{\"age\": 30}", "$.age");
// Modificar JSON
const updated = try json_helper.set(
"{\"name\": \"Alice\"}",
"$.age",
"30",
);
// Crear objetos
const obj = try json_helper.createObject(&.{
.{ "name", "\"Charlie\"" },
.{ "age", "25" },
});
// Crear arrays
const arr = try json_helper.createArray(&.{ "1", "2", "3" });
// Longitud de array
const len = try json_helper.arrayLength("[1, 2, 3, 4]", "$");
```
---
### R-Tree - Índices Espaciales
**Archivo**: `src/rtree.zig`
```zig
var rtree = sqlite.RTree.init(&db, allocator);
// Crear tabla R-Tree 2D
try rtree.createSimpleTable2D("locations");
// Insertar puntos
try rtree.insertPoint2D("locations", 1, 40.7128, -74.0060); // NYC
try rtree.insertPoint2D("locations", 2, 34.0522, -118.2437); // LA
// Insertar bounding box
try rtree.insertBox2D("locations", 3, 40.0, 41.0, -75.0, -73.0);
// Búsqueda espacial
const bbox = sqlite.BoundingBox2D{
.min_x = 39.0,
.max_x = 42.0,
.min_y = -76.0,
.max_y = -72.0,
};
const ids = try rtree.getIntersectingIds2D("locations", bbox);
defer rtree.freeIds(ids);
for (ids) |id| {
std.debug.print("Found location: {d}\n", .{id});
}
```
#### BoundingBox2D
```zig
// Crear desde centro
const box = sqlite.BoundingBox2D.fromCenter(0, 0, 10, 10);
// Crear desde punto
const point = sqlite.BoundingBox2D.fromPoint(5, 5);
// Operaciones
const intersects = box.intersects(other_box);
const contains = box.contains(other_box);
const contains_point = box.containsPoint(3, 3);
const area = box.area();
const center = box.center(); // returns {x, y}
```
#### GeoCoord
```zig
const nyc = sqlite.GeoCoord{ .lat = 40.7128, .lon = -74.0060 };
const la = sqlite.GeoCoord{ .lat = 34.0522, .lon = -118.2437 };
const distance_km = nyc.distanceKm(la); // Fórmula Haversine
```
---
### Virtual Tables
**Archivo**: `src/vtable.zig`
```zig
// Definir estructura de fila
const FileRow = struct {
name: []const u8,
size: i64,
modified: i64,
};
// Crear tabla virtual simple
const FilesVTable = sqlite.SimpleVTable(FileRow);
// Implementar métodos requeridos
const files_module = FilesVTable.Module{
.create = createFilesTable,
.destroy = destroyFilesTable,
.open = openCursor,
.close = closeCursor,
.next = nextRow,
.column = getColumn,
.rowid = getRowId,
.eof = isEof,
};
// Registrar módulo
try db.createModule("files", files_module);
// Usar en SQL
try db.exec("CREATE VIRTUAL TABLE my_files USING files('/path/to/dir')");
try db.exec("SELECT name, size FROM my_files WHERE size > 1000");
```
---
## Características Avanzadas
### Backup y Blob I/O
**Archivo**: `src/backup.zig`
#### Backup
```zig
// Backup simple a archivo
try sqlite.backupToFile(&source_db, "/path/to/backup.db");
// Backup con control detallado
var backup = try sqlite.Backup.initMain(&dest_db, &source_db);
defer backup.deinit();
while (true) {
const done = try backup.step(100); // 100 páginas por paso
if (done) break;
const remaining = backup.remaining();
const total = backup.pagecount();
std.debug.print("Progress: {d}/{d}\n", .{total - remaining, total});
}
try backup.finish();
// Cargar desde archivo
var db = try sqlite.loadFromFile("/path/to/backup.db");
```
#### Blob I/O
```zig
// Abrir blob para lectura/escritura
var blob = try sqlite.Blob.open(
&db,
"main",
"files",
"content",
rowid,
true, // writable
);
defer blob.deinit();
// Leer
const size = blob.bytes();
var buffer: [1024]u8 = undefined;
try blob.read(&buffer, 0);
// Escribir
try blob.write("new data", 0);
// Leer todo
const all_data = try blob.readAll(allocator);
defer allocator.free(all_data);
```
---
### Connection Pool
**Archivo**: `src/pool.zig`
```zig
// Crear pool con máximo 10 conexiones
var pool = try sqlite.ConnectionPool.init(allocator, "mydb.sqlite", 10);
defer pool.deinit();
// Adquirir conexión
var conn = try pool.acquire();
defer pool.release(conn);
// Usar conexión
try conn.exec("SELECT * FROM users");
// Estadísticas
const capacity = pool.capacity();
const open = pool.openCount();
const in_use = pool.inUseCount();
```
---
### Serialización
**Archivo**: `src/serialize.zig`
```zig
// Serializar a bytes
const bytes = try sqlite.serialize.toBytes(&db, allocator, "main");
defer allocator.free(bytes);
// Guardar a archivo
try sqlite.serialize.saveToFile(&db, allocator, "/path/to/db.bin");
// Deserializar desde bytes
var db2 = try sqlite.serialize.fromBytes(bytes, ":memory:");
defer db2.close();
// Cargar desde archivo
var db3 = try sqlite.serialize.loadFromFile(allocator, "/path/to/db.bin");
defer db3.close();
// Clonar a memoria
var mem_db = try sqlite.serialize.cloneToMemory(&db, allocator);
defer mem_db.close();
// Comparar bases de datos
const equal = try sqlite.serialize.equals(&db1, &db2, allocator);
// Obtener tamaño serializado
const size = try sqlite.serialize.serializedSize(&db, "main");
```
---
### Session y Changesets
**Archivo**: `src/session.zig`
```zig
// Crear sesión
var session = try sqlite.Session.init(&db, "main");
defer session.deinit();
// Rastrear tabla
try session.attach("users");
// Realizar cambios
try db.exec("INSERT INTO users (name) VALUES ('Alice')");
try db.exec("UPDATE users SET name = 'Bob' WHERE id = 1");
// Obtener changeset
const changeset = try session.changeset(allocator);
defer allocator.free(changeset);
// Invertir changeset
const inverse = try sqlite.invertChangeset(changeset, allocator);
defer allocator.free(inverse);
// Aplicar changeset a otra base de datos
try sqlite.applyChangeset(&other_db, changeset, conflictHandler, null);
// Concatenar changesets
const combined = try sqlite.concatChangesets(&.{ changeset1, changeset2 }, allocator);
```
#### Manejo de Conflictos
```zig
fn conflictHandler(
user_data: ?*anyopaque,
conflict_type: sqlite.ConflictType,
iter: *sqlite.ChangesetIterator,
) sqlite.ConflictAction {
return switch (conflict_type) {
.data => .replace, // Reemplazar datos existentes
.not_found => .omit, // Omitir si no existe
.conflict => .abort, // Abortar en conflicto
.constraint => .abort, // Abortar en violación de constraint
.foreign_key => .abort,
};
}
```
---
### Snapshots
```zig
// Crear snapshot (requiere WAL mode)
try db.enableWalMode(allocator);
const snapshot = try db.getSnapshot("main");
defer snapshot.deinit();
// Abrir snapshot en otra conexión
try other_db.openSnapshot("main", snapshot);
// Recuperar snapshot
try db.recoverSnapshot("main");
```
---
## Audit Log
**Archivo**: `src/audit/`
El sistema de Audit Log proporciona registro completo de operaciones de base de datos con verificación de integridad mediante cadena de hash.
### Configuración
```zig
const sqlite = @import("zsqlite");
var db = try sqlite.open("myapp.db");
defer db.close();
// Inicializar audit log
var audit = try sqlite.AuditLog.init(allocator, &db, .{
.log_dir = "/var/log/myapp/audit",
.app_name = "my_application",
.user = "admin",
.rotation = .{
.max_bytes = 100 * 1024 * 1024, // 100MB
.max_age_days = 30,
},
.capture = .{
.sql = true,
.before_values = true,
.after_values = true,
.exclude_tables = &.{ "sqlite_sequence", "_migrations" },
},
});
defer audit.deinit();
// IMPORTANTE: Llamar start() después de que el struct esté en su ubicación final
audit.start();
```
### Uso
```zig
// Cambiar usuario actual
try audit.setUser("john_doe");
// Cambiar nombre de aplicación
try audit.setApp("admin_panel");
// Las operaciones de base de datos se registran automáticamente
try db.exec("INSERT INTO users (name) VALUES ('Alice')");
try db.exec("UPDATE users SET name = 'Bob' WHERE id = 1");
try db.exec("DELETE FROM users WHERE id = 1");
// Forzar rotación de archivo
try audit.rotate();
// Flush a disco
try audit.flush();
// Obtener estadísticas
const stats = audit.stats();
std.debug.print("Entries: {d}, Bytes: {d}, Files: {d}\n", .{
stats.total_entries,
stats.total_bytes,
stats.file_count,
});
```
### Verificación de Integridad
```zig
// Verificar cadena de hash completa
var result = try sqlite.verifyAuditChain(allocator, "/var/log/myapp/audit");
defer result.deinit(allocator);
if (result.valid) {
std.debug.print("Audit log verified: {d} entries in {d} files\n", .{
result.entries_verified,
result.files_verified,
});
} else {
std.debug.print("INTEGRITY ERROR at seq {?d}: {s}\n", .{
result.first_invalid_seq,
result.error_message orelse "unknown error",
});
}
```
### Formato de Log
Los logs se almacenan en formato JSON Lines (un objeto JSON por línea):
```json
{"seq":1,"ts":1733667135000000,"tx_id":1,"ctx":{"user":"admin","app":"myapp","host":"server1","pid":12345},"op":"INSERT","table":"users","rowid":1,"prev_hash":"0000...","hash":"a1b2..."}
{"seq":2,"ts":1733667135000100,"tx_id":1,"ctx":{"user":"admin","app":"myapp","host":"server1","pid":12345},"op":"UPDATE","table":"users","rowid":1,"before":{"col0":1,"col1":"Alice"},"prev_hash":"a1b2...","hash":"c3d4..."}
```
### Estructura del Directorio
```
/var/log/myapp/audit/
├── index.json # Metadatos de todos los archivos
├── 0001_20251208_143200.log # Primer archivo de log
├── 0002_20251208_153000.log # Segundo archivo (después de rotación)
└── ...
```
### Componentes del Módulo Audit
| Archivo | Descripción |
|---------|-------------|
| `mod.zig` | Exports públicos |
| `entry.zig` | Estructura Entry y serialización JSON |
| `context.zig` | AuditContext (user, app, host, pid) |
| `log.zig` | AuditLog principal con hooks SQLite |
| `index.zig` | Gestión de index.json |
| `writer.zig` | Escritura de archivos con rotación |
| `verify.zig` | Verificación de cadena de hash |
---
## Tipos y Constantes
**Archivo**: `src/types.zig`
### OpenFlags
```zig
pub const OpenFlags = struct {
read_only: bool = false,
read_write: bool = true,
create: bool = true,
uri: bool = false,
memory: bool = false,
no_mutex: bool = false,
full_mutex: bool = false,
shared_cache: bool = false,
private_cache: bool = false,
no_follow: bool = false,
pub fn toInt(self: OpenFlags) c_int { ... }
};
```
### ColumnType
```zig
pub const ColumnType = enum(c_int) {
integer = 1,
float = 2,
text = 3,
blob = 4,
null_value = 5,
pub fn fromInt(value: c_int) ?ColumnType { ... }
};
```
### Limit
```zig
pub const Limit = enum(c_int) {
length = 0,
sql_length = 1,
column = 2,
expr_depth = 3,
compound_select = 4,
vdbe_op = 5,
function_arg = 6,
attached = 7,
like_pattern_length = 8,
variable_number = 9,
trigger_depth = 10,
worker_threads = 11,
};
```
### UpdateOperation
```zig
pub const UpdateOperation = enum(c_int) {
insert = 18,
delete = 9,
update = 23,
pub fn fromInt(value: c_int) ?UpdateOperation { ... }
};
```
### AuthAction
```zig
pub const AuthAction = enum(c_int) {
create_index = 1,
create_table = 2,
create_temp_index = 3,
create_temp_table = 4,
create_temp_trigger = 5,
create_temp_view = 6,
create_trigger = 7,
create_view = 8,
delete = 9,
drop_index = 10,
drop_table = 11,
drop_temp_index = 12,
drop_temp_table = 13,
drop_temp_trigger = 14,
drop_temp_view = 15,
drop_trigger = 16,
drop_view = 17,
insert = 18,
pragma = 19,
read = 20,
select = 21,
transaction = 22,
update = 23,
attach = 24,
detach = 25,
alter_table = 26,
reindex = 27,
analyze = 28,
create_vtable = 29,
drop_vtable = 30,
function = 31,
savepoint = 32,
recursive = 33,
pub fn fromInt(value: c_int) ?AuthAction { ... }
};
```
### AuthResult
```zig
pub const AuthResult = enum(c_int) {
ok = 0,
deny = 1,
ignore = 2,
};
```
---
## Ejemplos de Uso
### Ejemplo Básico Completo
```zig
const std = @import("std");
const sqlite = @import("zsqlite");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Abrir base de datos
var db = try sqlite.open("example.db");
defer db.close();
// Configurar
try db.setForeignKeys(true);
try db.enableWalMode(allocator);
// Crear tablas
try db.exec(
\\CREATE TABLE IF NOT EXISTS users (
\\ id INTEGER PRIMARY KEY,
\\ name TEXT NOT NULL,
\\ email TEXT UNIQUE,
\\ created_at TEXT DEFAULT CURRENT_TIMESTAMP
\\)
);
try db.exec(
\\CREATE TABLE IF NOT EXISTS posts (
\\ id INTEGER PRIMARY KEY,
\\ user_id INTEGER NOT NULL REFERENCES users(id),
\\ title TEXT NOT NULL,
\\ content TEXT,
\\ created_at TEXT DEFAULT CURRENT_TIMESTAMP
\\)
);
// Transacción con inserción de datos
try db.begin();
errdefer db.rollback() catch {};
var insert_user = try db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
defer insert_user.finalize();
try insert_user.bindAll(.{ "Alice", "alice@example.com" });
_ = try insert_user.step();
const alice_id = db.lastInsertRowId();
try insert_user.rebind(.{ "Bob", "bob@example.com" });
_ = try insert_user.step();
var insert_post = try db.prepare("INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)");
defer insert_post.finalize();
try insert_post.bindAll(.{ alice_id, "Hello World", "My first post!" });
_ = try insert_post.step();
try db.commit();
// Consultar datos
var query = try db.prepare(
\\SELECT u.name, p.title, p.content
\\FROM posts p
\\JOIN users u ON u.id = p.user_id
\\ORDER BY p.created_at DESC
);
defer query.finalize();
std.debug.print("\nPosts:\n", .{});
std.debug.print("-" ** 50 ++ "\n", .{});
var iter = query.iterator();
while (iter.next()) |row| {
const author = row.text(0);
const title = row.text(1);
const content = row.text(2);
std.debug.print("'{s}' by {s}\n", .{ title, author });
std.debug.print(" {s}\n\n", .{content});
}
}
```
### Ejemplo con Mapeo a Estructuras
```zig
const User = struct {
id: i64,
name: []const u8,
email: ?[]const u8,
};
var stmt = try db.prepare("SELECT id, name, email FROM users WHERE id = ?");
defer stmt.finalize();
try stmt.bindInt(1, user_id);
var iter = stmt.iterator();
if (iter.next()) |row| {
const user = try row.toAlloc(User, allocator);
defer row.freeStruct(User, user, allocator);
std.debug.print("User: {s} <{s}>\n", .{
user.name,
user.email orelse "no email",
});
}
```
### Ejemplo con FTS5
```zig
var fts = sqlite.Fts5.init(&db, allocator);
try fts.createSimpleTable("articles", &.{ "title", "body" });
try db.exec("INSERT INTO articles (title, body) VALUES ('Zig Programming', 'Zig is a systems programming language...')");
try db.exec("INSERT INTO articles (title, body) VALUES ('SQLite Tutorial', 'SQLite is a lightweight database...')");
const results = try fts.searchWithHighlight(
"articles",
"programming",
1, // body column
"",
"",
10,
);
defer allocator.free(results);
for (results) |result| {
std.debug.print("Match: {s}\n", .{result});
}
```
### Ejemplo con Audit Log
```zig
var db = try sqlite.open("production.db");
defer db.close();
var audit = try sqlite.AuditLog.init(allocator, &db, .{
.log_dir = "/var/log/app/audit",
.app_name = "production_app",
});
defer audit.deinit();
audit.start();
// En cada request, establecer el usuario actual
try audit.setUser(current_user.id);
// Todas las operaciones quedan registradas automáticamente
try db.exec("UPDATE accounts SET balance = balance - 100 WHERE id = 1");
// Periódicamente verificar integridad
var result = try sqlite.verifyAuditChain(allocator, "/var/log/app/audit");
defer result.deinit(allocator);
if (!result.valid) {
// ALERTA: Integridad comprometida
log.err("Audit integrity check failed: {s}", .{result.error_message.?});
}
```
---
## Información de Versión
```zig
const version_str = sqlite.version(); // "3.47.2"
const version_num = sqlite.versionNumber(); // 3047002
```
---
## Licencia
Este proyecto está bajo licencia MIT. SQLite es de dominio público.
---
*Generado para zsqlite v1.0.0 - Diciembre 2025*