library: vendors/sqlite — SQLite ships with sx

The vendored amalgamation (3.53.2, public domain) plus the curated
bindings move from the distribution repo into the sx library:
'#import "vendors/sqlite/sqlite.sx"' gives any sx program SQLite
with no system dependency and no build flags — the bindings declare
the C as a named #import c unit (pinned defines + -O2), compiled
through the object cache and shadow-proof via unit-first resolution.
examples/1624 pins the version and a typed round trip in-suite.
This commit is contained in:
agra
2026-06-12 17:39:26 +03:00
parent 8dedacb23f
commit 2097772ec9
8 changed files with 284614 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
// The sx library ships SQLite: `#import "vendors/sqlite/sqlite.sx"`
// resolves through the stdlib search paths, compiles the vendored
// amalgamation as a `#import c` unit (cached), and the bindings just
// work — no system dependency, no flags. Pins the vendored version
// (an OS libsqlite3 of another version must never shadow the unit)
// and a typed round trip.
#import "modules/std.sx";
sq :: #import "vendors/sqlite/sqlite.sx";
main :: () -> i32 {
v := sq.sqlite_version();
if v != "3.53.2" {
print("unexpected sqlite version: {}\n", v);
return 1;
}
db, oe := sq.Sqlite.open(":memory:");
if oe { print("open failed\n"); return 1; }
ee := false;
db.exec("CREATE TABLE t (name TEXT, n INTEGER); INSERT INTO t VALUES ('a', 1), ('b', 2)") catch { ee = true; };
if ee { print("exec failed: {}\n", db.errmsg()); db.close(); return 1; }
st, pe := db.prepare("SELECT name, n FROM t WHERE n > ?1 ORDER BY n");
if pe { print("prepare failed\n"); db.close(); return 1; }
st.bind_int64(1, 1) catch { ee = true; };
rc, se := st.step();
serr := false;
if se { serr = true; }
if serr or ee { print("step failed\n"); st.finalize(); db.close(); return 1; }
if rc == sq.SQLITE_ROW {
print("row: {} {}\n", st.column_text(0), st.column_int64(1));
}
st.finalize();
db.close();
print("vendored sqlite {} ok\n", v);
0
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,2 @@
row: b 2
vendored sqlite 3.53.2 ok

40
library/vendors/sqlite/README.md vendored Normal file
View File

@@ -0,0 +1,40 @@
# vendors/sqlite — SQLite for sx programs
- Version: **3.53.2** (`SQLITE_VERSION` in `c/sqlite3.h`)
- Source: <https://sqlite.org/2026/sqlite-amalgamation-3530200.zip>
- Zip sha256: `8a310d0a16c7a90cacd4c884e70faa51c902afed2a89f63aaa0126ab83558a32`
- Files kept: `c/sqlite3.c`, `c/sqlite3.h` (the amalgamation; `shell.c`
and `sqlite3ext.h` dropped — no shell, no loadable extensions)
- License: public domain (<https://sqlite.org/copyright.html>)
`#import "vendors/sqlite/sqlite.sx"` gives any sx program SQLite with
no system dependency and no build flags. The bindings declare the
amalgamation as a named `#import c` unit carrying the pinned compile
options (`SQLITE_DQS=0`, `SQLITE_THREADSAFE=0`,
`SQLITE_DEFAULT_MEMSTATUS=0`, `SQLITE_OMIT_DEPRECATED`,
`SQLITE_OMIT_SHARED_CACHE`, `SQLITE_LIKE_DOESNT_MATCH_BLOBS`,
`SQLITE_ENABLE_COLUMN_METADATA`, `-O2`); sx compiles the unit through
its content-addressed object cache (`.sx-cache/`), so the 250k-line
source builds once per machine. `sx build` links the objects into the
binary; `sx run` loads them as a PRIORITY symbol-search target ahead
of the process images, so an OS libsqlite3 of a different version can
never shadow this copy. `examples/1624-vendor-sqlite-module.sx` pins
the version and a typed round trip in the sx suite.
## Bound surface
`sqlite.sx` maps the full practical C API (~100 functions): connection
lifecycle + open_v2 flags, errors (extended codes included), statements
with the complete bind/column families, parameter and column
introspection (built with `SQLITE_ENABLE_COLUMN_METADATA`), incremental
blob I/O, the online backup API, serialize/deserialize, and the library
utilities. Not bound, by design: callback-taking APIs (hooks, UDFs,
collations, authorizers — they need C→sx callbacks), the
`sqlite3_value_*` family (UDF-coupled), varargs configuration, UTF-16
variants, and subsystems this build omits (mutex/VFS under
`SQLITE_THREADSAFE=0`, sessions/snapshots/vtabs, deprecated API).
To upgrade: replace `c/sqlite3.c`/`c/sqlite3.h` with a newer
amalgamation, update this file and the version pins in consuming test
suites, and rebuild (the object cache keys on the source bytes, so the
new amalgamation recompiles automatically).

269376
library/vendors/sqlite/c/sqlite3.c vendored Normal file

File diff suppressed because it is too large Load Diff

14347
library/vendors/sqlite/c/sqlite3.h vendored Normal file

File diff suppressed because it is too large Load Diff

811
library/vendors/sqlite/sqlite.sx vendored Normal file
View File

@@ -0,0 +1,811 @@
// =====================================================================
// vendors/sqlite — sx bindings over the VENDORED SQLite amalgamation
// (c/, see README.md for version + provenance + upgrade notes).
//
// `#import "vendors/sqlite/sqlite.sx"` gives any sx program SQLite
// with no system dependency and no build flags: the named `#import c`
// unit below compiles c/sqlite3.c with the pinned options (through
// sx's content-addressed object cache, so it compiles once per
// machine, not once per run) and every `#foreign sqlib` decl binds
// against it. Under `sx run` the unit's dylib is a PRIORITY
// symbol-search target consulted before the process images, so an OS
// libsqlite3 of a different version can never shadow the vendored
// copy; under `sx build` the unit's objects link directly into the
// binary. `sqlite_version()` answers the vendored version — pin it in
// the importing project's tests to make any fallback loud.
//
// COVERAGE — the full practical C API, ONE variant per function (the
// modern/64-bit form where duplicates exist; `bind_text`/`bind_blob`
// keep the 32-bit length forms, which skip text64's encoding arg —
// per-value sizes beyond 2 GiB are out of scope). Deliberately NOT
// bound, each a real boundary rather than an omission:
// * callback-taking APIs (exec's row callback, busy_handler,
// hooks, trace/progress, authorizer, create_function/collation)
// — they need C->sx function-pointer callbacks; busy_timeout and
// callback-less exec cover the storage layer's needs;
// * the sqlite3_value_* family + bind/column_value — only
// meaningful with user-defined functions (callbacks, above);
// * varargs APIs (sqlite3_config, db_config, log, mprintf) —
// configuration happens via the unit's compile-time defines;
// * UTF-16 variants (we are UTF-8 only), the mutex/VFS layer
// (built SQLITE_THREADSAFE=0), sessions/snapshots/vtabs/loadable
// extensions (not compiled in), deprecated API (compiled out via
// SQLITE_OMIT_DEPRECATED).
//
// FFI type choices: handles cross as `usize` (`sqlite3*`,
// `sqlite3_stmt*`, `sqlite3_blob*`, `sqlite3_backup*` are opaque and
// never dereferenced; `usize` keeps out-params single-level `*usize`
// instead of `**void`). Whole C strings cross as `cstring` — IN via
// `to_cstring` (literals coerce), OUT as `?cstring` copied through
// `sq_from_cstr` before sqlite's buffer dies on the next
// step/finalize/close. `prepare_v2/v3` and `bind_text/blob` take
// `[*]u8` + explicit byte length instead, so un-terminated sx string
// views pass without copying — and `column_text`/`column_blob`/
// `serialize` return `?*u8` + explicit byte counts, never `cstring`:
// their payloads may carry interior NULs that a strlen would truncate.
// Integer widths are SQLite's own, on the wrappers too: `i32` where
// the C API says `int` (result codes, counts, indexes, byte sizes),
// `u32`/`u64` where it says unsigned, and `i64` only for the genuinely
// 64-bit APIs (rowids, changes64, memory accounting, serialize sizes).
// =====================================================================
#import "modules/std.sx";
sqlib :: #import c {
#define "SQLITE_DQS=0";
#define "SQLITE_THREADSAFE=0";
#define "SQLITE_DEFAULT_MEMSTATUS=0";
#define "SQLITE_OMIT_DEPRECATED";
#define "SQLITE_OMIT_SHARED_CACHE";
#define "SQLITE_LIKE_DOESNT_MATCH_BLOBS";
#define "SQLITE_ENABLE_COLUMN_METADATA";
#flags "-O2";
#source "c/sqlite3.c";
};
// ── FFI: connection lifecycle ─────────────────────────────────────────
sqlite3_open :: (path: cstring, out_db: *usize) -> i32 #foreign sqlib "sqlite3_open";
sqlite3_open_v2 :: (path: cstring, out_db: *usize, flags: i32, vfs: usize) -> i32 #foreign sqlib "sqlite3_open_v2";
sqlite3_close :: (db: usize) -> i32 #foreign sqlib "sqlite3_close";
sqlite3_close_v2 :: (db: usize) -> i32 #foreign sqlib "sqlite3_close_v2";
// ── FFI: errors ───────────────────────────────────────────────────────
sqlite3_errcode :: (db: usize) -> i32 #foreign sqlib "sqlite3_errcode";
sqlite3_extended_errcode :: (db: usize) -> i32 #foreign sqlib "sqlite3_extended_errcode";
sqlite3_errmsg :: (db: usize) -> ?cstring #foreign sqlib "sqlite3_errmsg";
sqlite3_errstr :: (code: i32) -> ?cstring #foreign sqlib "sqlite3_errstr";
sqlite3_error_offset :: (db: usize) -> i32 #foreign sqlib "sqlite3_error_offset";
sqlite3_extended_result_codes :: (db: usize, onoff: i32) -> i32 #foreign sqlib "sqlite3_extended_result_codes";
// ── FFI: connection state & control ───────────────────────────────────
sqlite3_busy_timeout :: (db: usize, ms: i32) -> i32 #foreign sqlib "sqlite3_busy_timeout";
sqlite3_interrupt :: (db: usize) #foreign sqlib "sqlite3_interrupt";
sqlite3_is_interrupted :: (db: usize) -> i32 #foreign sqlib "sqlite3_is_interrupted";
sqlite3_get_autocommit :: (db: usize) -> i32 #foreign sqlib "sqlite3_get_autocommit";
sqlite3_txn_state :: (db: usize, schema: cstring) -> i32 #foreign sqlib "sqlite3_txn_state";
sqlite3_db_filename :: (db: usize, db_name: cstring) -> ?cstring #foreign sqlib "sqlite3_db_filename";
sqlite3_db_readonly :: (db: usize, db_name: cstring) -> i32 #foreign sqlib "sqlite3_db_readonly";
sqlite3_db_cacheflush :: (db: usize) -> i32 #foreign sqlib "sqlite3_db_cacheflush";
sqlite3_db_release_memory :: (db: usize) -> i32 #foreign sqlib "sqlite3_db_release_memory";
sqlite3_last_insert_rowid :: (db: usize) -> i64 #foreign sqlib "sqlite3_last_insert_rowid";
sqlite3_set_last_insert_rowid :: (db: usize, rowid: i64) #foreign sqlib "sqlite3_set_last_insert_rowid";
sqlite3_changes64 :: (db: usize) -> i64 #foreign sqlib "sqlite3_changes64";
sqlite3_total_changes64 :: (db: usize) -> i64 #foreign sqlib "sqlite3_total_changes64";
sqlite3_limit :: (db: usize, id: i32, new_val: i32) -> i32 #foreign sqlib "sqlite3_limit";
sqlite3_exec :: (db: usize, sql: cstring, cb: usize, arg: usize, errmsg: usize) -> i32 #foreign sqlib "sqlite3_exec";
sqlite3_table_column_metadata :: (db: usize, db_name: cstring, table: cstring, column: cstring, out_data_type: *usize, out_coll_seq: *usize, out_not_null: *i32, out_primary_key: *i32, out_autoinc: *i32) -> i32 #foreign sqlib "sqlite3_table_column_metadata";
// ── FFI: statements ───────────────────────────────────────────────────
sqlite3_prepare_v2 :: (db: usize, sql: [*]u8, nbyte: i32, out_stmt: *usize, out_tail: usize) -> i32 #foreign sqlib "sqlite3_prepare_v2";
sqlite3_prepare_v3 :: (db: usize, sql: [*]u8, nbyte: i32, prep_flags: u32, out_stmt: *usize, out_tail: usize) -> i32 #foreign sqlib "sqlite3_prepare_v3";
sqlite3_step :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_step";
sqlite3_reset :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_reset";
sqlite3_finalize :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_finalize";
sqlite3_clear_bindings :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_clear_bindings";
sqlite3_sql :: (stmt: usize) -> ?cstring #foreign sqlib "sqlite3_sql";
sqlite3_expanded_sql :: (stmt: usize) -> ?cstring #foreign sqlib "sqlite3_expanded_sql";
sqlite3_stmt_busy :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_stmt_busy";
sqlite3_stmt_readonly :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_stmt_readonly";
sqlite3_stmt_isexplain :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_stmt_isexplain";
sqlite3_stmt_explain :: (stmt: usize, mode: i32) -> i32 #foreign sqlib "sqlite3_stmt_explain";
sqlite3_stmt_status :: (stmt: usize, op: i32, reset: i32) -> i32 #foreign sqlib "sqlite3_stmt_status";
sqlite3_db_handle :: (stmt: usize) -> usize #foreign sqlib "sqlite3_db_handle";
sqlite3_next_stmt :: (db: usize, stmt: usize) -> usize #foreign sqlib "sqlite3_next_stmt";
// ── FFI: binding ──────────────────────────────────────────────────────
sqlite3_bind_parameter_count :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_bind_parameter_count";
sqlite3_bind_parameter_index :: (stmt: usize, name: cstring) -> i32 #foreign sqlib "sqlite3_bind_parameter_index";
sqlite3_bind_parameter_name :: (stmt: usize, idx: i32) -> ?cstring #foreign sqlib "sqlite3_bind_parameter_name";
sqlite3_bind_text :: (stmt: usize, idx: i32, text: [*]u8, n: i32, destructor: isize) -> i32 #foreign sqlib "sqlite3_bind_text";
sqlite3_bind_blob :: (stmt: usize, idx: i32, data: [*]u8, n: i32, destructor: isize) -> i32 #foreign sqlib "sqlite3_bind_blob";
sqlite3_bind_double :: (stmt: usize, idx: i32, v: f64) -> i32 #foreign sqlib "sqlite3_bind_double";
sqlite3_bind_int64 :: (stmt: usize, idx: i32, v: i64) -> i32 #foreign sqlib "sqlite3_bind_int64";
sqlite3_bind_null :: (stmt: usize, idx: i32) -> i32 #foreign sqlib "sqlite3_bind_null";
sqlite3_bind_zeroblob64 :: (stmt: usize, idx: i32, n: u64) -> i32 #foreign sqlib "sqlite3_bind_zeroblob64";
// ── FFI: result columns ───────────────────────────────────────────────
sqlite3_column_blob :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "sqlite3_column_blob";
sqlite3_column_double :: (stmt: usize, icol: i32) -> f64 #foreign sqlib "sqlite3_column_double";
sqlite3_column_int64 :: (stmt: usize, icol: i32) -> i64 #foreign sqlib "sqlite3_column_int64";
sqlite3_column_text :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "sqlite3_column_text";
sqlite3_column_bytes :: (stmt: usize, icol: i32) -> i32 #foreign sqlib "sqlite3_column_bytes";
sqlite3_column_type :: (stmt: usize, icol: i32) -> i32 #foreign sqlib "sqlite3_column_type";
sqlite3_column_count :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_column_count";
sqlite3_column_name :: (stmt: usize, icol: i32) -> ?cstring #foreign sqlib "sqlite3_column_name";
sqlite3_column_decltype :: (stmt: usize, icol: i32) -> ?cstring #foreign sqlib "sqlite3_column_decltype";
sqlite3_column_database_name :: (stmt: usize, icol: i32) -> ?cstring #foreign sqlib "sqlite3_column_database_name";
sqlite3_column_table_name :: (stmt: usize, icol: i32) -> ?cstring #foreign sqlib "sqlite3_column_table_name";
sqlite3_column_origin_name :: (stmt: usize, icol: i32) -> ?cstring #foreign sqlib "sqlite3_column_origin_name";
sqlite3_data_count :: (stmt: usize) -> i32 #foreign sqlib "sqlite3_data_count";
// ── FFI: incremental blob I/O ─────────────────────────────────────────
sqlite3_blob_open :: (db: usize, db_name: cstring, table: cstring, column: cstring, rowid: i64, flags: i32, out_blob: *usize) -> i32 #foreign sqlib "sqlite3_blob_open";
sqlite3_blob_reopen :: (blob: usize, rowid: i64) -> i32 #foreign sqlib "sqlite3_blob_reopen";
sqlite3_blob_close :: (blob: usize) -> i32 #foreign sqlib "sqlite3_blob_close";
sqlite3_blob_bytes :: (blob: usize) -> i32 #foreign sqlib "sqlite3_blob_bytes";
sqlite3_blob_read :: (blob: usize, buf: [*]u8, n: i32, offset: i32) -> i32 #foreign sqlib "sqlite3_blob_read";
sqlite3_blob_write :: (blob: usize, buf: [*]u8, n: i32, offset: i32) -> i32 #foreign sqlib "sqlite3_blob_write";
// ── FFI: online backup ────────────────────────────────────────────────
sqlite3_backup_init :: (dst: usize, dst_name: cstring, src: usize, src_name: cstring) -> usize #foreign sqlib "sqlite3_backup_init";
sqlite3_backup_step :: (bk: usize, n_page: i32) -> i32 #foreign sqlib "sqlite3_backup_step";
sqlite3_backup_finish :: (bk: usize) -> i32 #foreign sqlib "sqlite3_backup_finish";
sqlite3_backup_remaining :: (bk: usize) -> i32 #foreign sqlib "sqlite3_backup_remaining";
sqlite3_backup_pagecount :: (bk: usize) -> i32 #foreign sqlib "sqlite3_backup_pagecount";
// ── FFI: serialization ────────────────────────────────────────────────
sqlite3_serialize :: (db: usize, schema: cstring, out_size: *i64, flags: u32) -> ?*u8 #foreign sqlib "sqlite3_serialize";
sqlite3_deserialize :: (db: usize, schema: cstring, data: usize, sz_db: i64, sz_buf: i64, flags: u32) -> i32 #foreign sqlib "sqlite3_deserialize";
// ── FFI: library utilities ────────────────────────────────────────────
sqlite3_libversion :: () -> ?cstring #foreign sqlib "sqlite3_libversion";
sqlite3_libversion_number :: () -> i32 #foreign sqlib "sqlite3_libversion_number";
sqlite3_sourceid :: () -> ?cstring #foreign sqlib "sqlite3_sourceid";
sqlite3_threadsafe :: () -> i32 #foreign sqlib "sqlite3_threadsafe";
sqlite3_compileoption_used :: (name: cstring) -> i32 #foreign sqlib "sqlite3_compileoption_used";
sqlite3_compileoption_get :: (n: i32) -> ?cstring #foreign sqlib "sqlite3_compileoption_get";
sqlite3_complete :: (sql: cstring) -> i32 #foreign sqlib "sqlite3_complete";
sqlite3_free :: (p: usize) #foreign sqlib "sqlite3_free";
sqlite3_malloc64 :: (n: u64) -> usize #foreign sqlib "sqlite3_malloc64";
sqlite3_msize :: (p: usize) -> u64 #foreign sqlib "sqlite3_msize";
sqlite3_memory_used :: () -> i64 #foreign sqlib "sqlite3_memory_used";
sqlite3_memory_highwater :: (reset: i32) -> i64 #foreign sqlib "sqlite3_memory_highwater";
sqlite3_release_memory :: (n: i32) -> i32 #foreign sqlib "sqlite3_release_memory";
sqlite3_soft_heap_limit64 :: (n: i64) -> i64 #foreign sqlib "sqlite3_soft_heap_limit64";
sqlite3_hard_heap_limit64 :: (n: i64) -> i64 #foreign sqlib "sqlite3_hard_heap_limit64";
sqlite3_randomness :: (n: i32, buf: [*]u8) #foreign sqlib "sqlite3_randomness";
sqlite3_sleep :: (ms: i32) -> i32 #foreign sqlib "sqlite3_sleep";
sqlite3_stricmp :: (a: cstring, b: cstring) -> i32 #foreign sqlib "sqlite3_stricmp";
sqlite3_strnicmp :: (a: cstring, b: cstring, n: i32) -> i32 #foreign sqlib "sqlite3_strnicmp";
sqlite3_strglob :: (glob: cstring, s: cstring) -> i32 #foreign sqlib "sqlite3_strglob";
sqlite3_strlike :: (like: cstring, s: cstring, esc: u32) -> i32 #foreign sqlib "sqlite3_strlike";
// ── Result codes (primary; full set) ──────────────────────────────────
SQLITE_OK :: 0;
SQLITE_ERROR :: 1;
SQLITE_INTERNAL :: 2;
SQLITE_PERM :: 3;
SQLITE_ABORT :: 4;
SQLITE_BUSY :: 5;
SQLITE_LOCKED :: 6;
SQLITE_NOMEM :: 7;
SQLITE_READONLY :: 8;
SQLITE_INTERRUPT :: 9;
SQLITE_IOERR :: 10;
SQLITE_CORRUPT :: 11;
SQLITE_NOTFOUND :: 12;
SQLITE_FULL :: 13;
SQLITE_CANTOPEN :: 14;
SQLITE_PROTOCOL :: 15;
SQLITE_EMPTY :: 16;
SQLITE_SCHEMA :: 17;
SQLITE_TOOBIG :: 18;
SQLITE_CONSTRAINT :: 19;
SQLITE_MISMATCH :: 20;
SQLITE_MISUSE :: 21;
SQLITE_NOLFS :: 22;
SQLITE_AUTH :: 23;
SQLITE_FORMAT :: 24;
SQLITE_RANGE :: 25;
SQLITE_NOTADB :: 26;
SQLITE_NOTICE :: 27;
SQLITE_WARNING :: 28;
SQLITE_ROW :: 100;
SQLITE_DONE :: 101;
// Extended result codes are `primary | (n << 8)`; the families a storage
// layer actually branches on are spelled out, others compute by formula.
SQLITE_ABORT_ROLLBACK :: 516; // ABORT | (2<<8)
SQLITE_BUSY_RECOVERY :: 261; // BUSY | (1<<8)
SQLITE_BUSY_SNAPSHOT :: 517; // BUSY | (2<<8)
SQLITE_BUSY_TIMEOUT :: 773; // BUSY | (3<<8)
SQLITE_LOCKED_SHAREDCACHE :: 262; // LOCKED | (1<<8)
SQLITE_LOCKED_VTAB :: 518; // LOCKED | (2<<8)
SQLITE_CONSTRAINT_CHECK :: 275; // CONSTRAINT | (1<<8)
SQLITE_CONSTRAINT_COMMITHOOK :: 531;
SQLITE_CONSTRAINT_FOREIGNKEY :: 787;
SQLITE_CONSTRAINT_FUNCTION :: 1043;
SQLITE_CONSTRAINT_NOTNULL :: 1299;
SQLITE_CONSTRAINT_PRIMARYKEY :: 1555;
SQLITE_CONSTRAINT_TRIGGER :: 1811;
SQLITE_CONSTRAINT_UNIQUE :: 2067;
SQLITE_CONSTRAINT_VTAB :: 2323;
SQLITE_CONSTRAINT_ROWID :: 2579;
SQLITE_CONSTRAINT_PINNED :: 2835;
SQLITE_CONSTRAINT_DATATYPE :: 3091;
// ── Fundamental datatypes (column_type) ───────────────────────────────
SQLITE_INTEGER :: 1;
SQLITE_FLOAT :: 2;
SQLITE_TEXT :: 3;
SQLITE_BLOB :: 4;
SQLITE_NULL :: 5;
// ── open_v2 flags ─────────────────────────────────────────────────────
SQLITE_OPEN_READONLY :: 0x00000001;
SQLITE_OPEN_READWRITE :: 0x00000002;
SQLITE_OPEN_CREATE :: 0x00000004;
SQLITE_OPEN_URI :: 0x00000040;
SQLITE_OPEN_MEMORY :: 0x00000080;
SQLITE_OPEN_NOMUTEX :: 0x00008000;
SQLITE_OPEN_FULLMUTEX :: 0x00010000;
SQLITE_OPEN_NOFOLLOW :: 0x01000000;
SQLITE_OPEN_EXRESCODE :: 0x02000000;
// ── prepare_v3 flags ──────────────────────────────────────────────────
SQLITE_PREPARE_PERSISTENT :: 0x01;
SQLITE_PREPARE_NO_VTAB :: 0x04;
// ── txn_state values ──────────────────────────────────────────────────
SQLITE_TXN_NONE :: 0;
SQLITE_TXN_READ :: 1;
SQLITE_TXN_WRITE :: 2;
// ── limit ids ─────────────────────────────────────────────────────────
SQLITE_LIMIT_LENGTH :: 0;
SQLITE_LIMIT_SQL_LENGTH :: 1;
SQLITE_LIMIT_COLUMN :: 2;
SQLITE_LIMIT_EXPR_DEPTH :: 3;
SQLITE_LIMIT_COMPOUND_SELECT :: 4;
SQLITE_LIMIT_VDBE_OP :: 5;
SQLITE_LIMIT_FUNCTION_ARG :: 6;
SQLITE_LIMIT_ATTACHED :: 7;
SQLITE_LIMIT_LIKE_PATTERN_LENGTH :: 8;
SQLITE_LIMIT_VARIABLE_NUMBER :: 9;
SQLITE_LIMIT_TRIGGER_DEPTH :: 10;
SQLITE_LIMIT_WORKER_THREADS :: 11;
// ── stmt_status ops ───────────────────────────────────────────────────
SQLITE_STMTSTATUS_FULLSCAN_STEP :: 1;
SQLITE_STMTSTATUS_SORT :: 2;
SQLITE_STMTSTATUS_AUTOINDEX :: 3;
SQLITE_STMTSTATUS_VM_STEP :: 4;
SQLITE_STMTSTATUS_REPREPARE :: 5;
SQLITE_STMTSTATUS_RUN :: 6;
SQLITE_STMTSTATUS_FILTER_MISS :: 7;
SQLITE_STMTSTATUS_FILTER_HIT :: 8;
SQLITE_STMTSTATUS_MEMUSED :: 99;
// ── serialize / deserialize flags ─────────────────────────────────────
SQLITE_SERIALIZE_NOCOPY :: 1;
SQLITE_DESERIALIZE_FREEONCLOSE :: 1;
SQLITE_DESERIALIZE_RESIZEABLE :: 2;
SQLITE_DESERIALIZE_READONLY :: 4;
// bind destructor sentinels: TRANSIENT = copy the bytes NOW (sx-side
// buffers — arena strings, stack temporaries — don't outlive the call);
// STATIC = the caller guarantees the bytes outlive the statement.
SQLITE_STATIC : isize : 0;
SQLITE_TRANSIENT : isize : -1;
SqliteErr :: error {
Open,
Exec,
Prepare,
Bind,
Step,
Blob,
Backup,
Serialize,
Metadata,
}
// ── string helpers ────────────────────────────────────────────────────
// Copy the C string at `p` (null = empty) into context.allocator —
// sqlite's buffer dies on the next step/finalize/close, so the view from
// `from_cstring` must not escape.
sq_from_cstr :: (p: ?cstring) -> string {
if p == null { return ""; }
v := from_cstring(p!);
if v.len == 0 { return ""; }
return substr(v, 0, v.len);
}
// `n` raw bytes at `p` as a context.allocator copy ("" when p is null).
sq_copy_bytes :: (p: ?*u8, n: i64) -> string {
if p == null or n <= 0 { return ""; }
cp : [*]u8 = xx p!;
raw : [*]u8 = xx context.allocator.alloc_bytes(n + 1);
memcpy(raw, cp, n);
raw[n] = 0;
return string.{ ptr = raw, len = n };
}
// ── library-level utilities ───────────────────────────────────────────
// The linked/loaded SQLite's version string (e.g. "3.53.2").
sqlite_version :: () -> string {
return sq_from_cstr(sqlite3_libversion());
}
sqlite_version_number :: () -> i32 {
return sqlite3_libversion_number();
}
sqlite_sourceid :: () -> string {
return sq_from_cstr(sqlite3_sourceid());
}
sqlite_threadsafe :: () -> bool {
return sqlite3_threadsafe() != 0;
}
sqlite_compileoption_used :: (name: string) -> bool {
return sqlite3_compileoption_used(to_cstring(name)) != 0;
}
// The n'th compile option ("" past the end).
sqlite_compileoption_get :: (n: i32) -> string {
return sq_from_cstr(sqlite3_compileoption_get(n));
}
// Human text for a result code (static storage; copied anyway).
sqlite_errstr :: (code: i32) -> string {
return sq_from_cstr(sqlite3_errstr(code));
}
// True iff `sql` ends in a complete SQL statement.
sqlite_complete :: (sql: string) -> bool {
return sqlite3_complete(to_cstring(sql)) != 0;
}
// `n` bytes from SQLite's CSPRNG.
sqlite_randomness :: (n: i32) -> string {
len : i64 = n;
raw : [*]u8 = xx context.allocator.alloc_bytes(len + 1);
sqlite3_randomness(n, raw);
raw[len] = 0;
return string.{ ptr = raw, len = len };
}
sqlite_sleep :: (ms: i32) -> i32 {
return sqlite3_sleep(ms);
}
// memory_used/highwater read 0 in this build: SQLITE_DEFAULT_MEMSTATUS=0.
sqlite_memory_used :: () -> i64 {
return sqlite3_memory_used();
}
sqlite_memory_highwater :: (reset: bool) -> i64 {
return sqlite3_memory_highwater(if reset then 1 else 0);
}
sqlite_release_memory :: (n: i32) -> i32 {
return sqlite3_release_memory(n);
}
sqlite_soft_heap_limit :: (n: i64) -> i64 {
return sqlite3_soft_heap_limit64(n);
}
sqlite_hard_heap_limit :: (n: i64) -> i64 {
return sqlite3_hard_heap_limit64(n);
}
// GLOB / LIKE / case-insensitive compare, sqlite's own semantics.
// strglob/strlike answer 0 on MATCH (C convention) — exposed as bools.
sqlite_strglob :: (glob: string, s: string) -> bool {
return sqlite3_strglob(to_cstring(glob), to_cstring(s)) == 0;
}
sqlite_strlike :: (like: string, s: string, esc: u32) -> bool {
return sqlite3_strlike(to_cstring(like), to_cstring(s), esc) == 0;
}
sqlite_stricmp :: (a: string, b: string) -> i32 {
return sqlite3_stricmp(to_cstring(a), to_cstring(b));
}
// ── prepared statements ───────────────────────────────────────────────
// One prepared statement. `finalize` releases it; bind indexes are
// 1-based and column indexes 0-based, as in the C API.
SqliteStmt :: struct {
handle: usize;
db: usize;
// ── binding ──
bind_text :: (self: *SqliteStmt, idx: i32, s: string) -> !SqliteErr {
rc := sqlite3_bind_text(self.handle, idx, s.ptr, xx s.len, SQLITE_TRANSIENT);
if rc != SQLITE_OK { raise error.Bind; }
return;
}
bind_blob :: (self: *SqliteStmt, idx: i32, bytes: string) -> !SqliteErr {
rc := sqlite3_bind_blob(self.handle, idx, bytes.ptr, xx bytes.len, SQLITE_TRANSIENT);
if rc != SQLITE_OK { raise error.Bind; }
return;
}
bind_double :: (self: *SqliteStmt, idx: i32, v: f64) -> !SqliteErr {
rc := sqlite3_bind_double(self.handle, idx, v);
if rc != SQLITE_OK { raise error.Bind; }
return;
}
bind_int64 :: (self: *SqliteStmt, idx: i32, v: i64) -> !SqliteErr {
rc := sqlite3_bind_int64(self.handle, idx, v);
if rc != SQLITE_OK { raise error.Bind; }
return;
}
bind_null :: (self: *SqliteStmt, idx: i32) -> !SqliteErr {
rc := sqlite3_bind_null(self.handle, idx);
if rc != SQLITE_OK { raise error.Bind; }
return;
}
// Reserve an `n`-byte zero-filled blob (fill it via SqliteBlob I/O).
bind_zeroblob :: (self: *SqliteStmt, idx: i32, n: u64) -> !SqliteErr {
rc := sqlite3_bind_zeroblob64(self.handle, idx, n);
if rc != SQLITE_OK { raise error.Bind; }
return;
}
clear_bindings :: (self: *SqliteStmt) {
sqlite3_clear_bindings(self.handle);
}
// ── parameters ──
parameter_count :: (self: *SqliteStmt) -> i32 {
return sqlite3_bind_parameter_count(self.handle);
}
// 1-based index of a named parameter (":name" / "@name" / "?N"), 0 if absent.
parameter_index :: (self: *SqliteStmt, name: string) -> i32 {
return sqlite3_bind_parameter_index(self.handle, to_cstring(name));
}
// The name of parameter `idx` ("" for nameless `?` parameters).
parameter_name :: (self: *SqliteStmt, idx: i32) -> string {
return sq_from_cstr(sqlite3_bind_parameter_name(self.handle, idx));
}
// ── execution ──
// SQLITE_ROW / SQLITE_DONE on success; anything else raises with the
// detail left in the connection's errmsg.
step :: (self: *SqliteStmt) -> (i32, !SqliteErr) {
rc := sqlite3_step(self.handle);
if rc != SQLITE_ROW and rc != SQLITE_DONE { raise error.Step; }
return rc;
}
reset :: (self: *SqliteStmt) {
sqlite3_reset(self.handle);
}
finalize :: (self: *SqliteStmt) {
sqlite3_finalize(self.handle);
self.handle = 0;
}
// ── columns (copies into context.allocator; NULL reads as ""/0) ──
column_int64 :: (self: *SqliteStmt, icol: i32) -> i64 {
return sqlite3_column_int64(self.handle, icol);
}
column_double :: (self: *SqliteStmt, icol: i32) -> f64 {
return sqlite3_column_double(self.handle, icol);
}
column_text :: (self: *SqliteStmt, icol: i32) -> string {
p := sqlite3_column_text(self.handle, icol);
n := sqlite3_column_bytes(self.handle, icol);
return sq_copy_bytes(p, xx n);
}
column_blob :: (self: *SqliteStmt, icol: i32) -> string {
p := sqlite3_column_blob(self.handle, icol);
n := sqlite3_column_bytes(self.handle, icol);
return sq_copy_bytes(p, xx n);
}
column_bytes :: (self: *SqliteStmt, icol: i32) -> i32 {
return sqlite3_column_bytes(self.handle, icol);
}
column_type :: (self: *SqliteStmt, icol: i32) -> i32 {
return sqlite3_column_type(self.handle, icol);
}
column_count :: (self: *SqliteStmt) -> i32 {
return sqlite3_column_count(self.handle);
}
column_name :: (self: *SqliteStmt, icol: i32) -> string {
return sq_from_cstr(sqlite3_column_name(self.handle, icol));
}
// The declared type from the schema ("" for expressions).
column_decltype :: (self: *SqliteStmt, icol: i32) -> string {
return sq_from_cstr(sqlite3_column_decltype(self.handle, icol));
}
column_database_name :: (self: *SqliteStmt, icol: i32) -> string {
return sq_from_cstr(sqlite3_column_database_name(self.handle, icol));
}
column_table_name :: (self: *SqliteStmt, icol: i32) -> string {
return sq_from_cstr(sqlite3_column_table_name(self.handle, icol));
}
column_origin_name :: (self: *SqliteStmt, icol: i32) -> string {
return sq_from_cstr(sqlite3_column_origin_name(self.handle, icol));
}
data_count :: (self: *SqliteStmt) -> i32 {
return sqlite3_data_count(self.handle);
}
// ── introspection ──
sql :: (self: *SqliteStmt) -> string {
return sq_from_cstr(sqlite3_sql(self.handle));
}
// The SQL with bound parameters substituted; sqlite allocates, we
// copy + free.
expanded_sql :: (self: *SqliteStmt) -> string {
p := sqlite3_expanded_sql(self.handle);
s := sq_from_cstr(p);
if p != null { sqlite3_free(xx p!); }
return s;
}
busy :: (self: *SqliteStmt) -> bool {
return sqlite3_stmt_busy(self.handle) != 0;
}
readonly :: (self: *SqliteStmt) -> bool {
return sqlite3_stmt_readonly(self.handle) != 0;
}
// 0 = normal, 1 = EXPLAIN, 2 = EXPLAIN QUERY PLAN.
isexplain :: (self: *SqliteStmt) -> i32 {
return sqlite3_stmt_isexplain(self.handle);
}
status :: (self: *SqliteStmt, op: i32, reset: bool) -> i32 {
return sqlite3_stmt_status(self.handle, op, if reset then 1 else 0);
}
}
// ── table column metadata (ENABLE_COLUMN_METADATA build) ─────────────
ColumnMeta :: struct {
data_type: string;
coll_seq: string;
not_null: bool;
primary_key: bool;
autoinc: bool;
}
// ── connections ───────────────────────────────────────────────────────
// One connection. `open` creates the file per SQLite's defaults;
// `open_v2` takes SQLITE_OPEN_* flags; `close` is safe on an
// already-closed handle. Schema-qualified queries default to "main".
Sqlite :: struct {
handle: usize;
open :: (path: string) -> (Sqlite, !SqliteErr) {
h : usize = 0;
rc := sqlite3_open(to_cstring(path), @h);
if rc != SQLITE_OK {
if h != 0 { sqlite3_close(h); }
raise error.Open;
}
return Sqlite.{ handle = h };
}
open_v2 :: (path: string, flags: i32) -> (Sqlite, !SqliteErr) {
h : usize = 0;
rc := sqlite3_open_v2(to_cstring(path), @h, flags, 0);
if rc != SQLITE_OK {
if h != 0 { sqlite3_close(h); }
raise error.Open;
}
return Sqlite.{ handle = h };
}
close :: (self: *Sqlite) {
if self.handle != 0 { sqlite3_close(self.handle); }
self.handle = 0;
}
// Deferred close: succeeds even with unfinalized statements, the
// handle dies once the last one is finalized.
close_v2 :: (self: *Sqlite) {
if self.handle != 0 { sqlite3_close_v2(self.handle); }
self.handle = 0;
}
// Run one or more ;-separated statements with no result rows
// (DDL, pragmas, BEGIN/COMMIT). Detail via errmsg on failure.
exec :: (self: *Sqlite, sql: string) -> !SqliteErr {
rc := sqlite3_exec(self.handle, to_cstring(sql), 0, 0, 0);
if rc != SQLITE_OK { raise error.Exec; }
return;
}
prepare :: (self: *Sqlite, sql: string) -> (SqliteStmt, !SqliteErr) {
sh : usize = 0;
rc := sqlite3_prepare_v2(self.handle, sql.ptr, xx sql.len, @sh, 0);
if rc != SQLITE_OK { raise error.Prepare; }
return SqliteStmt.{ handle = sh, db = self.handle };
}
// prepare with SQLITE_PREPARE_* flags (e.g. PERSISTENT for the
// statement cache a storage layer keeps hot).
prepare_v3 :: (self: *Sqlite, sql: string, flags: u32) -> (SqliteStmt, !SqliteErr) {
sh : usize = 0;
rc := sqlite3_prepare_v3(self.handle, sql.ptr, xx sql.len, flags, @sh, 0);
if rc != SQLITE_OK { raise error.Prepare; }
return SqliteStmt.{ handle = sh, db = self.handle };
}
// ── errors ──
errmsg :: (self: *Sqlite) -> string {
return sq_from_cstr(sqlite3_errmsg(self.handle));
}
errcode :: (self: *Sqlite) -> i32 {
return sqlite3_errcode(self.handle);
}
extended_errcode :: (self: *Sqlite) -> i32 {
return sqlite3_extended_errcode(self.handle);
}
// Byte offset of the most recent error's token in its SQL, -1 if n/a.
error_offset :: (self: *Sqlite) -> i32 {
return sqlite3_error_offset(self.handle);
}
extended_result_codes :: (self: *Sqlite, on: bool) {
sqlite3_extended_result_codes(self.handle, if on then 1 else 0);
}
// ── state & control ──
busy_timeout :: (self: *Sqlite, ms: i32) {
sqlite3_busy_timeout(self.handle, ms);
}
interrupt :: (self: *Sqlite) {
sqlite3_interrupt(self.handle);
}
is_interrupted :: (self: *Sqlite) -> bool {
return sqlite3_is_interrupted(self.handle) != 0;
}
get_autocommit :: (self: *Sqlite) -> bool {
return sqlite3_get_autocommit(self.handle) != 0;
}
// SQLITE_TXN_NONE / _READ / _WRITE for the "main" schema.
txn_state :: (self: *Sqlite) -> i32 {
return sqlite3_txn_state(self.handle, to_cstring("main"));
}
db_filename :: (self: *Sqlite) -> string {
return sq_from_cstr(sqlite3_db_filename(self.handle, to_cstring("main")));
}
// 1 readonly, 0 read-write, -1 no such database name.
db_readonly :: (self: *Sqlite) -> i32 {
return sqlite3_db_readonly(self.handle, to_cstring("main"));
}
cacheflush :: (self: *Sqlite) {
sqlite3_db_cacheflush(self.handle);
}
db_release_memory :: (self: *Sqlite) {
sqlite3_db_release_memory(self.handle);
}
last_insert_rowid :: (self: *Sqlite) -> i64 {
return sqlite3_last_insert_rowid(self.handle);
}
set_last_insert_rowid :: (self: *Sqlite, rowid: i64) {
sqlite3_set_last_insert_rowid(self.handle, rowid);
}
changes :: (self: *Sqlite) -> i64 {
return sqlite3_changes64(self.handle);
}
total_changes :: (self: *Sqlite) -> i64 {
return sqlite3_total_changes64(self.handle);
}
// Set a SQLITE_LIMIT_* runtime limit; answers the PRIOR value.
// new_val < 0 reads without changing.
limit :: (self: *Sqlite, id: i32, new_val: i32) -> i32 {
return sqlite3_limit(self.handle, id, new_val);
}
// Schema introspection for one column of "main".`table`.
table_column_metadata :: (self: *Sqlite, table: string, column: string) -> (ColumnMeta, !SqliteErr) {
dt : usize = 0;
cs : usize = 0;
nn : i32 = 0;
pk : i32 = 0;
ai : i32 = 0;
rc := sqlite3_table_column_metadata(self.handle, to_cstring("main"), to_cstring(table), to_cstring(column), @dt, @cs, @nn, @pk, @ai);
if rc != SQLITE_OK { raise error.Metadata; }
dtp : ?*u8 = if dt != 0 then cast(?*u8) cast(*u8) dt else null;
csp : ?*u8 = if cs != 0 then cast(?*u8) cast(*u8) cs else null;
return ColumnMeta.{
data_type = sq_from_cstr(dtp), coll_seq = sq_from_cstr(csp),
not_null = nn != 0, primary_key = pk != 0, autoinc = ai != 0,
};
}
// ── serialization ──
// The whole "main" database as bytes (a valid database image).
serialize :: (self: *Sqlite) -> (string, !SqliteErr) {
size : i64 = 0;
p := sqlite3_serialize(self.handle, to_cstring("main"), @size, 0);
if p == null { raise error.Serialize; }
out := sq_copy_bytes(p, size);
sqlite3_free(xx p!);
return out;
}
// Replace "main" with the database image in `bytes`. The image is
// copied into sqlite-owned memory (FREEONCLOSE+RESIZEABLE), so
// `bytes` may die immediately after.
deserialize :: (self: *Sqlite, bytes: string) -> !SqliteErr {
buf := sqlite3_malloc64(xx (bytes.len));
if buf == 0 { raise error.Serialize; }
bp : [*]u8 = xx buf;
memcpy(bp, bytes.ptr, bytes.len);
rc := sqlite3_deserialize(self.handle, to_cstring("main"), buf, bytes.len, bytes.len,
SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_RESIZEABLE);
if rc != SQLITE_OK { raise error.Serialize; }
return;
}
}
// ── incremental blob I/O ──────────────────────────────────────────────
// An open handle onto one blob cell ("main".`table`.`column` at `rowid`).
// Reserve space with bind_zeroblob, then stream through this. The handle
// cannot change a blob's SIZE — that's an UPDATE.
SqliteBlob :: struct {
handle: usize;
open :: (db: *Sqlite, table: string, column: string, rowid: i64, writable: bool) -> (SqliteBlob, !SqliteErr) {
h : usize = 0;
rc := sqlite3_blob_open(db.handle, to_cstring("main"), to_cstring(table), to_cstring(column),
rowid, if writable then 1 else 0, @h);
if rc != SQLITE_OK { raise error.Blob; }
return SqliteBlob.{ handle = h };
}
// Point this handle at the same column in a DIFFERENT row (cheaper
// than close + open).
reopen :: (self: *SqliteBlob, rowid: i64) -> !SqliteErr {
rc := sqlite3_blob_reopen(self.handle, rowid);
if rc != SQLITE_OK { raise error.Blob; }
return;
}
bytes :: (self: *SqliteBlob) -> i32 {
return sqlite3_blob_bytes(self.handle);
}
read :: (self: *SqliteBlob, offset: i32, n: i32) -> (string, !SqliteErr) {
len : i64 = n;
raw : [*]u8 = xx context.allocator.alloc_bytes(len + 1);
rc := sqlite3_blob_read(self.handle, raw, n, offset);
if rc != SQLITE_OK { raise error.Blob; }
raw[len] = 0;
return string.{ ptr = raw, len = len };
}
write :: (self: *SqliteBlob, offset: i32, data: string) -> !SqliteErr {
rc := sqlite3_blob_write(self.handle, data.ptr, xx data.len, offset);
if rc != SQLITE_OK { raise error.Blob; }
return;
}
close :: (self: *SqliteBlob) {
if self.handle != 0 { sqlite3_blob_close(self.handle); }
self.handle = 0;
}
}
// ── online backup ─────────────────────────────────────────────────────
// Copy `src`'s "main" into `dst`'s "main" page by page while both stay
// usable. `run` drives it to completion in one call; `step` exposes the
// incremental form.
SqliteBackup :: struct {
handle: usize;
init :: (dst: *Sqlite, src: *Sqlite) -> (SqliteBackup, !SqliteErr) {
h := sqlite3_backup_init(dst.handle, to_cstring("main"), src.handle, to_cstring("main"));
if h == 0 { raise error.Backup; }
return SqliteBackup.{ handle = h };
}
// Copy up to `n` pages (-1 = all remaining). SQLITE_OK = more to do,
// SQLITE_DONE = complete; BUSY/LOCKED are retryable per the C API.
step :: (self: *SqliteBackup, n: i32) -> i32 {
return sqlite3_backup_step(self.handle, n);
}
remaining :: (self: *SqliteBackup) -> i32 {
return sqlite3_backup_remaining(self.handle);
}
pagecount :: (self: *SqliteBackup) -> i32 {
return sqlite3_backup_pagecount(self.handle);
}
finish :: (self: *SqliteBackup) -> i32 {
rc := sqlite3_backup_finish(self.handle);
self.handle = 0;
return rc;
}
}
// One-shot full copy of src."main" into dst."main".
sqlite_backup_run :: (dst: *Sqlite, src: *Sqlite) -> !SqliteErr {
bk := try SqliteBackup.init(dst, src);
rc := bk.step(-1);
frc := bk.finish();
if rc != SQLITE_DONE or frc != SQLITE_OK { raise error.Backup; }
return;
}