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:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user