Consistent naming with zcat ecosystem (zcatui, zcatgui, zcatsql). All lowercase per Zig naming conventions. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1566 lines
39 KiB
Markdown
1566 lines
39 KiB
Markdown
# 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
|
|
"<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`
|
|
|
|
```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
|
|
"<mark>",
|
|
"</mark>",
|
|
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*
|