# zsqlite

SQLite wrapper idiomático para Zig 0.15.2+

Compila el SQLite amalgamation directamente en el binario, resultando en un ejecutable único sin dependencias externas.

## Características

- **Zero dependencias runtime** - SQLite embebido en el binario
- **API idiomática Zig** - Errores, allocators, iteradores
- **Type-safe** - Binding y row mapping con verificación en comptime
- **Completo** - Todas las funcionalidades de SQLite expuestas
- **63 tests** - Cobertura exhaustiva

## Instalación

1. Clonar el repositorio:
```bash
git clone git@git.reugenio.com:reugenio/zsqlite.git
```

2. Añadir como dependencia en `build.zig.zon`:
```zig
.dependencies = .{
    .zsqlite = .{
        .path = "../zsqlite",
    },
},
```

3. En `build.zig`:
```zig
const zsqlite = b.dependency("zsqlite", .{});
exe.root_module.addImport("zsqlite", zsqlite.module("zsqlite"));
```

## Uso Básico

```zig
const sqlite = @import("zsqlite");

pub fn main() !void {
    // Abrir base de datos
    var db = try sqlite.open("test.db");
    defer db.close();

    // Ejecutar SQL directo
    try db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");

    // Prepared statement con binding
    var stmt = try db.prepare("INSERT INTO users (name, age) VALUES (?, ?)");
    defer stmt.finalize();

    try stmt.bindAll(.{ "Alice", @as(i64, 30) });
    _ = try stmt.step();

    // Query con row mapping a struct
    const User = struct { id: i64, name: []const u8, age: i64 };

    var query = try db.prepare("SELECT id, name, age FROM users");
    defer query.finalize();

    var iter = query.iterator();
    while (try iter.next()) |row| {
        const user = row.to(User);
        std.debug.print("User {}: {s}, age {}\n", .{ user.id, user.name, user.age });
    }
}
```

## Módulos

### Core
| Módulo | Descripción |
|--------|-------------|
| `Database` | Conexión, transacciones, pragmas |
| `Statement` | Prepared statements, binding, iteradores |
| `Row` | Acceso a columnas, mapping a structs |
| `Backup` | Online backup API |
| `Blob` | Incremental blob I/O |
| `ConnectionPool` | Pool de conexiones thread-safe |

### Extensiones SQLite
| Módulo | Descripción |
|--------|-------------|
| `fts5.Fts5` | Full-text search |
| `json.Json` | JSON1 functions |
| `rtree.RTree` | R-Tree spatial index |
| `vtable` | Virtual table API |

### Funcionalidades Avanzadas
| Módulo | Descripción |
|--------|-------------|
| `serialize` | Serialize/Deserialize API |
| `session.Session` | Change tracking y changesets |
| `Database.Snapshot` | Consistent reads en WAL mode |

## Ejemplos por Funcionalidad

### Batch Binding

```zig
// Bind múltiples valores de una vez
try stmt.bindAll(.{ "Alice", @as(i64, 30), @as(f64, 95.5) });

// Rebind para ejecutar múltiples veces
try stmt.rebind(.{ "Bob", @as(i64, 25), @as(f64, 87.0) });
_ = try stmt.step();
```

### Row Mapping a Structs

```zig
const User = struct {
    id: i64,
    name: ?[]const u8,  // Nullable para columnas que pueden ser NULL
    score: f64,
    active: bool,
};

var iter = stmt.iterator();
while (try iter.next()) |row| {
    // Non-allocating - slices apuntan a buffers de SQLite
    const user = row.to(User);

    // O con allocación para persistir datos
    const user_copy = try row.toAlloc(User, allocator);
    defer Row.freeStruct(User, user_copy, allocator);
}
```

### Transacciones

```zig
try db.begin();
errdefer db.rollback() catch {};

try db.exec("INSERT INTO accounts VALUES (1, 1000)");
try db.exec("INSERT INTO accounts VALUES (2, 2000)");

try db.commit();

// O con helper automático
try db.transaction(struct {
    fn run(tx_db: *Database) !void {
        try tx_db.exec("...");
    }
}.run);
```

### Full-Text Search (FTS5)

```zig
var fts = sqlite.Fts5.init(&db, allocator);

// Crear tabla FTS5
try fts.createSimpleTable("documents", &.{ "title", "content" });

// Insertar documentos
try db.exec("INSERT INTO documents VALUES ('Zig Guide', 'A guide to programming in Zig')");

// Buscar
var results = try fts.search("documents", "programming", &.{ "title", "content" }, 10);
defer results.finalize();

while (try results.step()) {
    const title = results.columnText(0) orelse "";
    std.debug.print("Found: {s}\n", .{title});
}

// Con highlight
var highlighted = try fts.searchWithHighlight("documents", "Zig", 0, "<b>", "</b>", 10);
```

### JSON

```zig
var json = sqlite.Json.init(&db, allocator);

// Validar JSON
const is_valid = try json.isValid("{\"name\": \"Alice\"}");

// Extraer valores
const name = try json.extract("{\"user\": {\"name\": \"Alice\"}}", "$.user.name");
defer allocator.free(name.?);

// Modificar JSON
const updated = try json.setInt("{\"count\": 0}", "$.count", 42);
defer allocator.free(updated);

// Crear objetos
const obj = try json.createObject(
    &.{ "id", "name" },
    &.{ "1", "\"Alice\"" },
);
```

### R-Tree (Spatial Index)

```zig
var rt = sqlite.RTree.init(&db, allocator);

// Crear índice 2D
try rt.createSimpleTable2D("locations");

// Insertar puntos
try rt.insertPoint2D("locations", 1, 10.5, 20.3);
try rt.insertPoint2D("locations", 2, 15.0, 25.0);

// Query por bounding box
const box = sqlite.BoundingBox2D{
    .min_x = 5.0, .max_x = 20.0,
    .min_y = 15.0, .max_y = 30.0,
};
const ids = try rt.getIntersectingIds2D("locations", box);
defer rt.freeIds(ids);

// Distancia geográfica (Haversine)
const london = sqlite.GeoCoord{ .latitude = 51.5074, .longitude = -0.1278 };
const paris = sqlite.GeoCoord{ .latitude = 48.8566, .longitude = 2.3522 };
const distance_km = london.distanceKm(paris);  // ~343 km
```

### Serialize/Deserialize

```zig
// Serializar base de datos a bytes
const bytes = try sqlite.serialize.toBytes(&db, allocator, "main");
defer allocator.free(bytes);

// Guardar/enviar bytes...

// Deserializar a nueva base de datos
var db2 = try sqlite.serialize.fromBytes(bytes, ":memory:");
defer db2.close();

// Clonar base de datos en memoria
var clone = try sqlite.serialize.cloneToMemory(&db, allocator);
defer clone.close();
```

### Session (Change Tracking)

```zig
// Crear session para trackear cambios
var session = try sqlite.Session.init(&db, "main");
defer session.deinit();

// Attach tablas a trackear
try session.attach("users");
try session.attach("orders");

// Hacer cambios...
try db.exec("INSERT INTO users VALUES (1, 'Alice')");
try db.exec("UPDATE orders SET status = 'shipped' WHERE id = 5");

// Generar changeset
const changeset = try session.changeset(allocator);
defer allocator.free(changeset);

// Aplicar changeset a otra base de datos
try sqlite.applyChangeset(&other_db, changeset, null, null);

// Invertir changeset (para undo)
const undo = try sqlite.invertChangeset(changeset, allocator);
defer allocator.free(undo);
```

### VACUUM INTO

```zig
// Crear copia compactada
try db.vacuumInto("backup.sqlite");

// VACUUM in-place
try db.vacuum();
```

### Snapshots (WAL mode)

```zig
// Habilitar WAL
try db.exec("PRAGMA journal_mode=WAL");

// Obtener snapshot
try db.begin();
var snapshot = try db.getSnapshot("main");
defer snapshot.deinit();

// En otra conexión, abrir el mismo snapshot
var reader = try sqlite.open("mydb.sqlite");
try reader.exec("PRAGMA journal_mode=WAL");
try reader.begin();
try reader.openSnapshot("main", &snapshot);
// reader ahora ve datos del momento del snapshot
```

### User-Defined Functions

```zig
// Función escalar
try db.createScalarFunction("double", 1, .{}, struct {
    fn impl(ctx: *sqlite.FunctionContext, args: []?*sqlite.FunctionValue) void {
        if (args[0]) |arg| {
            ctx.resultInt(arg.getInt() * 2);
        } else {
            ctx.resultNull();
        }
    }
}.impl);

// Uso: SELECT double(21); -- devuelve 42
```

### Connection Pool

```zig
var pool = try sqlite.ConnectionPool.init(allocator, "database.db", 10);
defer pool.deinit();

const conn = try pool.acquire();
defer pool.release(conn);

try conn.exec("...");
```

## Flags de Compilación SQLite

El build incluye los siguientes flags optimizados:

```
-DSQLITE_DQS=0                 # Disable double-quoted strings
-DSQLITE_THREADSAFE=0          # Single-threaded (más rápido)
-DSQLITE_ENABLE_FTS5           # Full-text search v5
-DSQLITE_ENABLE_JSON1          # JSON functions
-DSQLITE_ENABLE_RTREE          # R-Tree spatial index
-DSQLITE_ENABLE_SESSION        # Session extension
-DSQLITE_ENABLE_SNAPSHOT       # Snapshot API
-DSQLITE_ENABLE_COLUMN_METADATA
-DSQLITE_ENABLE_PREUPDATE_HOOK
```

## Tests

```bash
zig build test
```

63 tests cubriendo todas las funcionalidades.

## Estadísticas

| Métrica | Valor |
|---------|-------|
| Líneas de código | 7,563 |
| Módulos | 15 |
| Tests | 63 |
| Versión SQLite | 3.45+ |
| Versión Zig | 0.15.2+ |

## Licencia

MIT

## Créditos

Inspirado en:
- [rusqlite](https://github.com/rusqlite/rusqlite) (Rust)
- [zig-sqlite](https://github.com/vrischmann/zig-sqlite) (Zig)
- [zqlite.zig](https://github.com/karlseguin/zqlite.zig) (Zig)
