sqlite bindings: nullable C-string returns as ?*u8

The null moves into the type: errmsg/errstr/libversion/sourceid/
column_text/blob/name/decltype/*_name/db_filename/sql/expanded_sql/
bind_parameter_name/serialize return ?*u8 instead of *u8 with manual
cast null-checks, and the helpers take the optional directly. Verified
that an optional pointer crosses the FFI with the null-pointer niche in
both JIT and AOT modes.

?[:0]u8 would be the ideal shape (nullable null-terminated string) but
does not resolve in sx yet — it panics LLVM emission; filed as an sx
followup. The header comment records the constraint.
This commit is contained in:
agra
2026-06-12 13:16:49 +03:00
parent fabc9062cb
commit 9d8b334e32

View File

@@ -37,10 +37,11 @@
// convention — build terminated copies with `cstring`);
// `prepare_v2/v3` and `bind_text/blob` 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.
// copying. Nullable C strings OUT are `?*u8` — the null lives in the
// type (`?[:0]u8` would be ideal but does not resolve in sx yet, and a
// sentinel-slice return would need a synthesized strlen) — and are
// COPIED into `context.allocator` before returning — sqlite's buffers
// die on the next step/finalize/close.
// =====================================================================
#import "modules/std.sx";
@@ -56,8 +57,8 @@ sqlite3_close_v2 :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_close_v2"
// ── FFI: errors ───────────────────────────────────────────────────────
sqlite3_errcode :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_errcode";
sqlite3_extended_errcode :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_extended_errcode";
sqlite3_errmsg :: (db: usize) -> *u8 #foreign sqlib "dist_sqlite3_errmsg";
sqlite3_errstr :: (code: i32) -> *u8 #foreign sqlib "dist_sqlite3_errstr";
sqlite3_errmsg :: (db: usize) -> ?*u8 #foreign sqlib "dist_sqlite3_errmsg";
sqlite3_errstr :: (code: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_errstr";
sqlite3_error_offset :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_error_offset";
sqlite3_extended_result_codes :: (db: usize, onoff: i32) -> i32 #foreign sqlib "dist_sqlite3_extended_result_codes";
@@ -67,7 +68,7 @@ sqlite3_interrupt :: (db: usize) #foreign sqlib "dist_sqlite3_interrupt
sqlite3_is_interrupted :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_is_interrupted";
sqlite3_get_autocommit :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_get_autocommit";
sqlite3_txn_state :: (db: usize, schema: [:0]u8) -> i32 #foreign sqlib "dist_sqlite3_txn_state";
sqlite3_db_filename :: (db: usize, db_name: [:0]u8) -> *u8 #foreign sqlib "dist_sqlite3_db_filename";
sqlite3_db_filename :: (db: usize, db_name: [:0]u8) -> ?*u8 #foreign sqlib "dist_sqlite3_db_filename";
sqlite3_db_readonly :: (db: usize, db_name: [:0]u8) -> i32 #foreign sqlib "dist_sqlite3_db_readonly";
sqlite3_db_cacheflush :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_db_cacheflush";
sqlite3_db_release_memory :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_db_release_memory";
@@ -86,8 +87,8 @@ sqlite3_step :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_step";
sqlite3_reset :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_reset";
sqlite3_finalize :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_finalize";
sqlite3_clear_bindings :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_clear_bindings";
sqlite3_sql :: (stmt: usize) -> *u8 #foreign sqlib "dist_sqlite3_sql";
sqlite3_expanded_sql :: (stmt: usize) -> *u8 #foreign sqlib "dist_sqlite3_expanded_sql";
sqlite3_sql :: (stmt: usize) -> ?*u8 #foreign sqlib "dist_sqlite3_sql";
sqlite3_expanded_sql :: (stmt: usize) -> ?*u8 #foreign sqlib "dist_sqlite3_expanded_sql";
sqlite3_stmt_busy :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_stmt_busy";
sqlite3_stmt_readonly :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_stmt_readonly";
sqlite3_stmt_isexplain :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_stmt_isexplain";
@@ -99,7 +100,7 @@ sqlite3_next_stmt :: (db: usize, stmt: usize) -> usize #foreign sqlib "dist_s
// ── FFI: binding ──────────────────────────────────────────────────────
sqlite3_bind_parameter_count :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_bind_parameter_count";
sqlite3_bind_parameter_index :: (stmt: usize, name: [:0]u8) -> i32 #foreign sqlib "dist_sqlite3_bind_parameter_index";
sqlite3_bind_parameter_name :: (stmt: usize, idx: i32) -> *u8 #foreign sqlib "dist_sqlite3_bind_parameter_name";
sqlite3_bind_parameter_name :: (stmt: usize, idx: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_bind_parameter_name";
sqlite3_bind_text :: (stmt: usize, idx: i32, text: [*]u8, n: i32, destructor: isize) -> i32 #foreign sqlib "dist_sqlite3_bind_text";
sqlite3_bind_blob :: (stmt: usize, idx: i32, data: [*]u8, n: i32, destructor: isize) -> i32 #foreign sqlib "dist_sqlite3_bind_blob";
sqlite3_bind_double :: (stmt: usize, idx: i32, v: f64) -> i32 #foreign sqlib "dist_sqlite3_bind_double";
@@ -108,18 +109,18 @@ sqlite3_bind_null :: (stmt: usize, idx: i32) -> i32 #foreign sqlib "dist_sqli
sqlite3_bind_zeroblob64 :: (stmt: usize, idx: i32, n: u64) -> i32 #foreign sqlib "dist_sqlite3_bind_zeroblob64";
// ── FFI: result columns ───────────────────────────────────────────────
sqlite3_column_blob :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_blob";
sqlite3_column_blob :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_column_blob";
sqlite3_column_double :: (stmt: usize, icol: i32) -> f64 #foreign sqlib "dist_sqlite3_column_double";
sqlite3_column_int64 :: (stmt: usize, icol: i32) -> i64 #foreign sqlib "dist_sqlite3_column_int64";
sqlite3_column_text :: (stmt: usize, icol: i32) -> *u8 #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_column_name :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_name";
sqlite3_column_decltype :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_decltype";
sqlite3_column_database_name :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_database_name";
sqlite3_column_table_name :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_table_name";
sqlite3_column_origin_name :: (stmt: usize, icol: i32) -> *u8 #foreign sqlib "dist_sqlite3_column_origin_name";
sqlite3_column_name :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_column_name";
sqlite3_column_decltype :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_column_decltype";
sqlite3_column_database_name :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_column_database_name";
sqlite3_column_table_name :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_column_table_name";
sqlite3_column_origin_name :: (stmt: usize, icol: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_column_origin_name";
sqlite3_data_count :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_data_count";
// ── FFI: incremental blob I/O ─────────────────────────────────────────
@@ -138,16 +139,16 @@ sqlite3_backup_remaining :: (bk: usize) -> i32 #foreign sqlib "dist_sqlite3_back
sqlite3_backup_pagecount :: (bk: usize) -> i32 #foreign sqlib "dist_sqlite3_backup_pagecount";
// ── FFI: serialization ────────────────────────────────────────────────
sqlite3_serialize :: (db: usize, schema: [:0]u8, out_size: *i64, flags: u32) -> usize #foreign sqlib "dist_sqlite3_serialize";
sqlite3_serialize :: (db: usize, schema: [:0]u8, out_size: *i64, flags: u32) -> ?*u8 #foreign sqlib "dist_sqlite3_serialize";
sqlite3_deserialize :: (db: usize, schema: [:0]u8, data: usize, sz_db: i64, sz_buf: i64, flags: u32) -> i32 #foreign sqlib "dist_sqlite3_deserialize";
// ── FFI: library utilities ────────────────────────────────────────────
sqlite3_libversion :: () -> *u8 #foreign sqlib "dist_sqlite3_libversion";
sqlite3_libversion :: () -> ?*u8 #foreign sqlib "dist_sqlite3_libversion";
sqlite3_libversion_number :: () -> i32 #foreign sqlib "dist_sqlite3_libversion_number";
sqlite3_sourceid :: () -> *u8 #foreign sqlib "dist_sqlite3_sourceid";
sqlite3_sourceid :: () -> ?*u8 #foreign sqlib "dist_sqlite3_sourceid";
sqlite3_threadsafe :: () -> i32 #foreign sqlib "dist_sqlite3_threadsafe";
sqlite3_compileoption_used :: (name: [:0]u8) -> i32 #foreign sqlib "dist_sqlite3_compileoption_used";
sqlite3_compileoption_get :: (n: i32) -> *u8 #foreign sqlib "dist_sqlite3_compileoption_get";
sqlite3_compileoption_get :: (n: i32) -> ?*u8 #foreign sqlib "dist_sqlite3_compileoption_get";
sqlite3_complete :: (sql: [:0]u8) -> i32 #foreign sqlib "dist_sqlite3_complete";
sqlite3_free :: (p: usize) #foreign sqlib "dist_sqlite3_free";
sqlite3_malloc64 :: (n: u64) -> usize #foreign sqlib "dist_sqlite3_malloc64";
@@ -304,10 +305,9 @@ sq_cstr :: (s: string) -> string {
}
// 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;
sq_from_cstr :: (p: ?*u8) -> string {
if p == null { return ""; }
cp : [*]u8 = xx p!;
n := 0;
while cp[n] != 0 { n += 1; }
raw : [*]u8 = xx context.allocator.alloc_bytes(n + 1);
@@ -317,10 +317,9 @@ sq_from_cstr :: (p: *u8) -> string {
}
// `n` raw bytes at `p` as a context.allocator copy ("" when p is null).
sq_copy_bytes :: (p: *u8, n: i64) -> string {
addr : i64 = xx p;
if addr == 0 or n <= 0 { return ""; }
cp : [*]u8 = xx p;
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;
@@ -523,8 +522,7 @@ SqliteStmt :: struct {
expanded_sql :: (self: *SqliteStmt) -> string {
p := sqlite3_expanded_sql(self.handle);
s := sq_from_cstr(p);
addr : i64 = xx p;
if addr != 0 { sqlite3_free(xx addr); }
if p != null { sqlite3_free(xx p!); }
return s;
}
busy :: (self: *SqliteStmt) -> bool {
@@ -688,8 +686,8 @@ Sqlite :: struct {
ai : i32 = 0;
rc := sqlite3_table_column_metadata(self.handle, sq_cstr("main"), sq_cstr(table), sq_cstr(column), @dt, @cs, @nn, @pk, @ai);
if rc != SQLITE_OK { raise error.Metadata; }
dtp : *u8 = xx dt;
csp : *u8 = xx cs;
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,
@@ -701,10 +699,9 @@ Sqlite :: struct {
serialize :: (self: *Sqlite) -> (string, !SqliteErr) {
size : i64 = 0;
p := sqlite3_serialize(self.handle, sq_cstr("main"), @size, 0);
if p == 0 { raise error.Serialize; }
bp : *u8 = xx p;
out := sq_copy_bytes(bp, size);
sqlite3_free(p);
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