# 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*
