sqlite bindings: idiomatic FFI string types

Whole C strings IN are [:0]u8 (the std convention, popen/getenv) with
terminated copies built via cstring(); nullable C strings OUT are *u8
with the getenv-style cast null-check — usize said nothing about what
crossed the boundary. prepare_v2/bind_text stay [*]u8 + explicit byte
length on purpose: those are (ptr, nByte) APIs, which is what lets
un-terminated sx string views pass without copying. Handles stay usize
so sqlite3_open's out-param is a single-level *usize.
This commit is contained in:
agra
2026-06-12 12:41:12 +03:00
parent 29e09fa924
commit ad34fe88f2

View File

@@ -9,11 +9,16 @@
// `sqlite3_libversion()` to the vendored version so a silent fallback
// to the system library fails the suite.
//
// Handles cross the FFI as `usize` (`sqlite3*` / `sqlite3_stmt*` are
// opaque), C strings as `[*]u8` + explicit length or null-termination.
// Strings READ from SQLite (column_text, errmsg, libversion) are COPIED
// into `context.allocator` before returning — sqlite's buffers die on
// the next step/finalize/close.
// FFI type choices: handles cross as `usize` (`sqlite3*` /
// `sqlite3_stmt*` are opaque and never dereferenced; `usize` keeps
// `sqlite3_open`'s out-param a single-level `*usize` instead of
// `**void`). Whole C strings IN are `[:0]u8` (the std FFI convention —
// build terminated copies with `cstring`); `prepare_v2`/`bind_text`
// take `[*]u8` + explicit byte length instead, so un-terminated sx
// string views pass without copying. Nullable C strings OUT are `*u8`
// + a cast null-check (the `getenv` convention — `[:0]u8` has no null),
// and are COPIED into `context.allocator` before returning — sqlite's
// buffers die on the next step/finalize/close.
//
// The wrapper surface is exactly what the storage layer needs: open /
// exec / prepare / bind / step / column / finalize, last_insert_rowid,
@@ -25,9 +30,9 @@
sqlib :: #library "sqlite3";
sqlite3_open :: (path: [*]u8, out_db: *usize) -> i32 #foreign sqlib "dist_sqlite3_open";
sqlite3_open :: (path: [:0]u8, out_db: *usize) -> i32 #foreign sqlib "dist_sqlite3_open";
sqlite3_close :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_close";
sqlite3_exec :: (db: usize, sql: [*]u8, cb: usize, arg: usize, errmsg: usize) -> i32 #foreign sqlib "dist_sqlite3_exec";
sqlite3_exec :: (db: usize, sql: [:0]u8, cb: usize, arg: usize, errmsg: usize) -> i32 #foreign sqlib "dist_sqlite3_exec";
sqlite3_prepare_v2 :: (db: usize, sql: [*]u8, nbyte: i32, out_stmt: *usize, out_tail: usize) -> i32 #foreign sqlib "dist_sqlite3_prepare_v2";
sqlite3_step :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_step";
sqlite3_finalize :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_finalize";
@@ -36,12 +41,12 @@ sqlite3_bind_text :: (stmt: usize, idx: i32, text: [*]u8, n: i32, destructor:
sqlite3_bind_int64 :: (stmt: usize, idx: i32, v: i64) -> i32 #foreign sqlib "dist_sqlite3_bind_int64";
sqlite3_bind_null :: (stmt: usize, idx: i32) -> i32 #foreign sqlib "dist_sqlite3_bind_null";
sqlite3_column_int64 :: (stmt: usize, icol: i32) -> i64 #foreign sqlib "dist_sqlite3_column_int64";
sqlite3_column_text :: (stmt: usize, icol: i32) -> usize #foreign sqlib "dist_sqlite3_column_text";
sqlite3_column_text :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_text";
sqlite3_column_bytes :: (stmt: usize, icol: i32) -> i32 #foreign sqlib "dist_sqlite3_column_bytes";
sqlite3_column_type :: (stmt: usize, icol: i32) -> i32 #foreign sqlib "dist_sqlite3_column_type";
sqlite3_column_count :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_column_count";
sqlite3_errmsg :: (db: usize) -> usize #foreign sqlib "dist_sqlite3_errmsg";
sqlite3_libversion :: () -> usize #foreign sqlib "dist_sqlite3_libversion";
sqlite3_errmsg :: (db: usize) -> *u8 #foreign sqlib "dist_sqlite3_errmsg";
sqlite3_libversion :: () -> *u8 #foreign sqlib "dist_sqlite3_libversion";
sqlite3_last_insert_rowid :: (db: usize) -> i64 #foreign sqlib "dist_sqlite3_last_insert_rowid";
sqlite3_changes :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_changes";
@@ -67,17 +72,17 @@ SqliteErr :: error {
Step,
}
// `s` as a null-terminated heap copy (C string), from context.allocator.
sq_cstr :: (s: string) -> [*]u8 {
raw : [*]u8 = xx context.allocator.alloc_bytes(s.len + 1);
if s.len > 0 { memcpy(raw, s.ptr, s.len); }
raw[s.len] = 0;
return raw;
// `s` as a null-terminated heap copy, passable where `[:0]u8` is expected.
sq_cstr :: (s: string) -> string {
z := cstring(s.len);
if s.len > 0 { memcpy(z.ptr, s.ptr, s.len); }
return z;
}
// Copy the C string at `p` (0 = empty) into context.allocator.
sq_from_cstr :: (p: usize) -> string {
if p == 0 { return ""; }
// Copy the C string at `p` (null = empty) into context.allocator.
sq_from_cstr :: (p: *u8) -> string {
addr : i64 = xx p;
if addr == 0 { return ""; }
cp : [*]u8 = xx p;
n := 0;
while cp[n] != 0 { n += 1; }
@@ -128,7 +133,8 @@ SqliteStmt :: struct {
// Copied into context.allocator; a NULL column reads as "".
column_text :: (self: *SqliteStmt, icol: i64) -> string {
p := sqlite3_column_text(self.handle, xx icol);
if p == 0 { return ""; }
addr : i64 = xx p;
if addr == 0 { return ""; }
n := sqlite3_column_bytes(self.handle, xx icol);
cp : [*]u8 = xx p;
raw : [*]u8 = xx context.allocator.alloc_bytes(xx (n + 1));