CRITICAL FIX: SQLite was compiled single-threaded but the library provides a ConnectionPool which implies multi-threaded usage. THREADSAFE=2 (multi-thread mode) allows different connections to be used from different threads, which is exactly what ConnectionPool needs. Updated: build.zig, README.md, CLAUDE.md, docs/ARCHITECTURE.md Reported by: Gemini audit (2025-12-14) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
405 lines
12 KiB
Markdown
405 lines
12 KiB
Markdown
# zcatsql - Arquitectura Tecnica
|
|
|
|
> **Version**: 0.6
|
|
> **Ultima actualizacion**: 2025-12-08
|
|
|
|
## Vision General
|
|
|
|
zcatsql es un wrapper de SQLite para Zig que compila SQLite amalgamation directamente en el binario. El objetivo es proveer una API idiomatica Zig mientras se mantiene acceso completo a las capacidades de SQLite.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Aplicacion Zig │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ zcatsql API │
|
|
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────┐ │
|
|
│ │ Database │ │ Statement │ │ Backup │ │ConnPool │ │
|
|
│ │ │ │ │ │ Blob │ │ Functions │ │
|
|
│ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └─────┬─────┘ │
|
|
├───────┼──────────────┼─────────────┼──────────────┼────────┤
|
|
│ │ │ │ │ │
|
|
│ └──────────────┴─────────────┴──────────────┘ │
|
|
│ @cImport │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ SQLite 3.47.2 (C) │
|
|
│ (amalgamation) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Componentes Principales
|
|
|
|
### 1. Database
|
|
|
|
Representa una conexion a una base de datos SQLite.
|
|
|
|
```zig
|
|
pub const Database = struct {
|
|
handle: ?*c.sqlite3,
|
|
|
|
pub fn open(path: [:0]const u8) Error!Self { ... }
|
|
pub fn openWithFlags(path: [:0]const u8, flags: OpenFlags) Error!Self { ... }
|
|
pub fn close(self: *Self) void { ... }
|
|
pub fn exec(self: *Self, sql: [:0]const u8) Error!void { ... }
|
|
pub fn prepare(self: *Self, sql: [:0]const u8) Error!Statement { ... }
|
|
// ...
|
|
};
|
|
```
|
|
|
|
**Responsabilidades:**
|
|
- Gestionar ciclo de vida de conexion SQLite
|
|
- Ejecutar SQL directo (exec)
|
|
- Crear prepared statements
|
|
- Gestionar transacciones
|
|
- Configurar pragmas (foreign keys, etc.)
|
|
|
|
### 2. Statement
|
|
|
|
Representa un prepared statement de SQLite.
|
|
|
|
```zig
|
|
pub const Statement = struct {
|
|
handle: ?*c.sqlite3_stmt,
|
|
db: *Database,
|
|
|
|
pub fn finalize(self: *Self) void { ... }
|
|
pub fn reset(self: *Self) Error!void { ... }
|
|
pub fn step(self: *Self) Error!bool { ... }
|
|
pub fn bindInt(self: *Self, index: u32, value: i64) Error!void { ... }
|
|
pub fn bindTextNamed(self: *Self, name: [:0]const u8, value: []const u8) Error!void { ... }
|
|
pub fn columnText(self: *Self, index: u32) ?[]const u8 { ... }
|
|
// ...
|
|
};
|
|
```
|
|
|
|
**Responsabilidades:**
|
|
- Bind de parametros posicionales (1-indexed)
|
|
- Bind de parametros named (:name, @name, $name)
|
|
- Ejecucion paso a paso (step)
|
|
- Acceso a columnas de resultados
|
|
- Metadata (sql, isReadOnly, parameterCount, etc.)
|
|
- Reset para reutilizacion
|
|
|
|
### 3. Error
|
|
|
|
Mapeo de codigos de error SQLite a errores Zig.
|
|
|
|
```zig
|
|
pub const Error = error{
|
|
SqliteError,
|
|
InternalError,
|
|
PermissionDenied,
|
|
Busy,
|
|
Locked,
|
|
OutOfMemory,
|
|
// ... 25+ errores mapeados
|
|
};
|
|
|
|
fn resultToError(result: c_int) Error { ... }
|
|
```
|
|
|
|
**Diseno:**
|
|
- Cada codigo SQLite tiene su propio error Zig
|
|
- Permite manejo especifico por tipo de error
|
|
- SQLITE_ROW y SQLITE_DONE manejados especialmente en step()
|
|
|
|
### 4. OpenFlags
|
|
|
|
Configuracion para apertura de base de datos.
|
|
|
|
```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,
|
|
};
|
|
```
|
|
|
|
### 5. ColumnType
|
|
|
|
Enum para tipos de columna SQLite.
|
|
|
|
```zig
|
|
pub const ColumnType = enum {
|
|
integer,
|
|
float,
|
|
text,
|
|
blob,
|
|
null_value,
|
|
};
|
|
```
|
|
|
|
### 6. Backup
|
|
|
|
API para copiar bases de datos con control de progreso.
|
|
|
|
```zig
|
|
pub const Backup = struct {
|
|
handle: ?*c.sqlite3_backup,
|
|
dest_db: *Database,
|
|
source_db: *Database,
|
|
|
|
pub fn init(dest: *Database, dest_name: [:0]const u8,
|
|
source: *Database, source_name: [:0]const u8) Error!Self { ... }
|
|
pub fn step(self: *Self, n_pages: i32) Error!bool { ... }
|
|
pub fn stepAll(self: *Self) Error!void { ... }
|
|
pub fn remaining(self: *Self) i32 { ... }
|
|
pub fn pageCount(self: *Self) i32 { ... }
|
|
pub fn progress(self: *Self) u8 { ... }
|
|
pub fn finish(self: *Self) Error!void { ... }
|
|
pub fn deinit(self: *Self) void { ... }
|
|
};
|
|
```
|
|
|
|
**Responsabilidades:**
|
|
- Copiar base de datos pagina por pagina
|
|
- Control granular del progreso
|
|
- Permitir backups incrementales
|
|
|
|
### 7. User-Defined Functions
|
|
|
|
Soporte para funciones personalizadas en SQL.
|
|
|
|
```zig
|
|
pub const FunctionContext = struct {
|
|
ctx: *c.sqlite3_context,
|
|
|
|
pub fn setNull(self: Self) void { ... }
|
|
pub fn setInt(self: Self, value: i64) void { ... }
|
|
pub fn setFloat(self: Self, value: f64) void { ... }
|
|
pub fn setText(self: Self, value: []const u8) void { ... }
|
|
pub fn setBlob(self: Self, value: []const u8) void { ... }
|
|
pub fn setError(self: Self, msg: []const u8) void { ... }
|
|
};
|
|
|
|
pub const FunctionValue = struct {
|
|
value: *c.sqlite3_value,
|
|
|
|
pub fn getType(self: Self) ColumnType { ... }
|
|
pub fn isNull(self: Self) bool { ... }
|
|
pub fn asInt(self: Self) i64 { ... }
|
|
pub fn asFloat(self: Self) f64 { ... }
|
|
pub fn asText(self: Self) ?[]const u8 { ... }
|
|
pub fn asBlob(self: Self) ?[]const u8 { ... }
|
|
};
|
|
|
|
pub const ScalarFn = *const fn (ctx: FunctionContext, args: []const FunctionValue) void;
|
|
```
|
|
|
|
**Patron de uso:**
|
|
```zig
|
|
fn myDouble(ctx: FunctionContext, args: []const FunctionValue) void {
|
|
if (args[0].isNull()) {
|
|
ctx.setNull();
|
|
return;
|
|
}
|
|
ctx.setInt(args[0].asInt() * 2);
|
|
}
|
|
|
|
try db.createScalarFunction("double", 1, myDouble);
|
|
// SELECT double(5) => 10
|
|
```
|
|
|
|
### 8. Custom Collations
|
|
|
|
Soporte para ordenamiento personalizado.
|
|
|
|
```zig
|
|
pub const CollationFn = *const fn (a: []const u8, b: []const u8) i32;
|
|
```
|
|
|
|
**Patron de uso:**
|
|
```zig
|
|
fn reverseOrder(a: []const u8, b: []const u8) i32 {
|
|
return -std.mem.order(u8, a, b);
|
|
}
|
|
|
|
try db.createCollation("REVERSE", reverseOrder);
|
|
// SELECT * FROM t ORDER BY col COLLATE REVERSE
|
|
```
|
|
|
|
## Integracion con SQLite C
|
|
|
|
### @cImport
|
|
|
|
Usamos `@cImport` para importar los headers de SQLite:
|
|
|
|
```zig
|
|
const c = @cImport({
|
|
@cInclude("sqlite3.h");
|
|
});
|
|
```
|
|
|
|
Esto nos da acceso directo a:
|
|
- `c.sqlite3` - Handle de conexion
|
|
- `c.sqlite3_stmt` - Handle de statement
|
|
- `c.sqlite3_open()`, `c.sqlite3_close()`, etc.
|
|
- Todas las constantes (`c.SQLITE_OK`, `c.SQLITE_ROW`, etc.)
|
|
|
|
### SQLITE_TRANSIENT
|
|
|
|
Para strings y blobs, usamos `c.SQLITE_TRANSIENT` que indica a SQLite que debe copiar los datos:
|
|
|
|
```zig
|
|
const result = c.sqlite3_bind_text(
|
|
self.handle,
|
|
@intCast(index),
|
|
value.ptr,
|
|
@intCast(value.len),
|
|
c.SQLITE_TRANSIENT, // SQLite copia el string
|
|
);
|
|
```
|
|
|
|
Esto es necesario porque Zig puede mover/liberar la memoria del slice original.
|
|
|
|
### Conversion de Indices
|
|
|
|
SQLite usa diferentes convenciones de indices:
|
|
- **Bind parameters**: 1-indexed (SQLite convencion)
|
|
- **Column access**: 0-indexed (SQLite convencion)
|
|
|
|
Mantenemos las mismas convenciones en la API publica para consistencia con documentacion SQLite.
|
|
|
|
## Build System
|
|
|
|
### build.zig
|
|
|
|
```zig
|
|
// Compilar SQLite como C source
|
|
zcatsql_mod.addCSourceFile(.{
|
|
.file = b.path("vendor/sqlite3.c"),
|
|
.flags = sqlite_flags,
|
|
});
|
|
|
|
// Flags de optimizacion
|
|
const sqlite_flags: []const []const u8 = &.{
|
|
"-DSQLITE_DQS=0",
|
|
"-DSQLITE_THREADSAFE=2",
|
|
"-DSQLITE_DEFAULT_MEMSTATUS=0",
|
|
// ...
|
|
};
|
|
```
|
|
|
|
### Flags de Compilacion
|
|
|
|
| Flag | Proposito |
|
|
|------|-----------|
|
|
| `SQLITE_DQS=0` | Deshabilita double-quoted strings como identificadores |
|
|
| `SQLITE_THREADSAFE=2` | Multi-thread (ConnectionPool) |
|
|
| `SQLITE_DEFAULT_MEMSTATUS=0` | Sin tracking de memoria |
|
|
| `SQLITE_ENABLE_FTS5` | Full-text search habilitado |
|
|
| `SQLITE_ENABLE_JSON1` | Funciones JSON habilitadas |
|
|
| `SQLITE_ENABLE_RTREE` | R-Tree para geospatial |
|
|
| `SQLITE_OMIT_LOAD_EXTENSION` | Sin extensiones dinamicas (seguridad) |
|
|
|
|
## Patrones de Uso
|
|
|
|
### Patron RAII con defer
|
|
|
|
```zig
|
|
var db = try sqlite.openMemory();
|
|
defer db.close();
|
|
|
|
var stmt = try db.prepare("SELECT * FROM users");
|
|
defer stmt.finalize();
|
|
```
|
|
|
|
### Patron Transaccion con errdefer
|
|
|
|
```zig
|
|
try db.begin();
|
|
errdefer db.rollback() catch {};
|
|
|
|
try db.exec("INSERT ...");
|
|
try db.exec("UPDATE ...");
|
|
|
|
try db.commit();
|
|
```
|
|
|
|
### Iteracion de Resultados
|
|
|
|
```zig
|
|
while (try stmt.step()) {
|
|
const id = stmt.columnInt(0);
|
|
const name = stmt.columnText(1) orelse "(null)";
|
|
// procesar fila
|
|
}
|
|
```
|
|
|
|
## Decisiones de Diseno
|
|
|
|
### 1. Estructura Modular
|
|
|
|
El codigo esta organizado en modulos especializados:
|
|
|
|
```
|
|
src/
|
|
├── root.zig # Re-exports publicos + tests (~1100 lineas)
|
|
├── c.zig # @cImport centralizado (24 lineas)
|
|
├── errors.zig # Error enum y resultToError (142 lineas)
|
|
├── types.zig # OpenFlags, ColumnType, enums (154 lineas)
|
|
├── database.zig # Database struct (795 lineas)
|
|
├── statement.zig # Statement struct (378 lineas)
|
|
├── functions.zig # UDFs, hooks, callbacks (567 lineas)
|
|
├── backup.zig # Backup y Blob I/O (292 lineas)
|
|
└── pool.zig # ConnectionPool (151 lineas)
|
|
```
|
|
|
|
**Total**: ~3600 lineas (vs 4200 monoliticas anteriores)
|
|
|
|
Cada modulo tiene una responsabilidad clara:
|
|
- **c.zig**: Unico punto de @cImport para SQLite
|
|
- **errors.zig**: Mapeo completo de errores SQLite -> Zig
|
|
- **types.zig**: Tipos compartidos (flags, enums)
|
|
- **database.zig**: Conexion y operaciones de base de datos
|
|
- **statement.zig**: Prepared statements y bindings
|
|
- **functions.zig**: Funciones definidas por usuario y hooks
|
|
- **backup.zig**: API de backup y blob streaming
|
|
- **pool.zig**: Pool de conexiones thread-safe
|
|
- **root.zig**: Re-exporta API publica + contiene tests
|
|
|
|
### 2. Error Union vs Nullable
|
|
|
|
- Operaciones que pueden fallar: `Error!T`
|
|
- Operaciones de lectura que pueden ser NULL: `?T`
|
|
|
|
```zig
|
|
// Puede fallar (error de SQLite)
|
|
pub fn open(path: [:0]const u8) Error!Self
|
|
|
|
// Puede ser NULL (columna NULL)
|
|
pub fn columnText(self: *Self, index: u32) ?[]const u8
|
|
```
|
|
|
|
### 3. Slices vs Null-Terminated
|
|
|
|
- API publica acepta `[:0]const u8` para strings SQL (null-terminated)
|
|
- Variantes `*Alloc` aceptan `[]const u8` y agregan null terminator
|
|
|
|
### 4. Indices Consistentes con SQLite
|
|
|
|
Mantenemos las convenciones de SQLite:
|
|
- Bind: 1-indexed
|
|
- Columns: 0-indexed
|
|
|
|
Esto facilita traducir ejemplos de documentacion SQLite.
|
|
|
|
## Roadmap Arquitectural
|
|
|
|
### Completado: Modularizacion
|
|
|
|
La estructura modular ya esta implementada (ver seccion "Estructura Modular").
|
|
|
|
### Siguiente: Optimizaciones
|
|
|
|
- [ ] Reducir duplicacion en funciones de binding
|
|
- [ ] Crear helpers de test para reducir boilerplate
|
|
- [ ] Considerar vtable API si se necesitan mas extensiones
|
|
|
|
---
|
|
|
|
**© zcatsql - Arquitectura Tecnica**
|
|
*2025-12-08*
|