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
- Introducción
- Instalación y Uso
- Estructura del Proyecto
- API Principal
- Gestión de Errores
- Transacciones
- Funciones de Usuario
- Hooks y Callbacks
- Extensiones
- Características Avanzadas
- Audit Log
- Tipos y Constantes
- 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
const zsqlite = b.dependency("zsqlite", .{
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("zsqlite", zsqlite.module("zsqlite"));
Uso básico
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 |
// 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) |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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?) |
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 |
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 |
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
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 |
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
// 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
// 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
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
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
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
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
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
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
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
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
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
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
db.clearHooks(); // Elimina todos los hooks
Extensiones
FTS5 - Búsqueda Full-Text
Archivo: src/fts5.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
"<b>",
"</b>",
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
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
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
// 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
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
// 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
// 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
// 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
// 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
// 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
// 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
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
// 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
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
// 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
// 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):
{"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
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
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
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
pub const UpdateOperation = enum(c_int) {
insert = 18,
delete = 9,
update = 23,
pub fn fromInt(value: c_int) ?UpdateOperation { ... }
};
AuthAction
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
pub const AuthResult = enum(c_int) {
ok = 0,
deny = 1,
ignore = 2,
};
Ejemplos de Uso
Ejemplo Básico Completo
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
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
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
"<mark>",
"</mark>",
10,
);
defer allocator.free(results);
for (results) |result| {
std.debug.print("Match: {s}\n", .{result});
}
Ejemplo con Audit Log
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
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