# zcatsql - 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 **zcatsql** 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 zcatsql = b.dependency("zcatsql", .{ .target = target, .optimize = optimize, }); exe.root_module.addImport("zcatsql", zcatsql.module("zcatsql")); ``` ### Uso básico ```zig const sqlite = @import("zcatsql"); 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("zcatsql"); 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("zcatsql"); 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 zcatsql v1.0.0 - Diciembre 2025*