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
|
// `sqlite3_libversion()` to the vendored version so a silent fallback
|
||||||
// to the system library fails the suite.
|
// to the system library fails the suite.
|
||||||
//
|
//
|
||||||
// Handles cross the FFI as `usize` (`sqlite3*` / `sqlite3_stmt*` are
|
// FFI type choices: handles cross as `usize` (`sqlite3*` /
|
||||||
// opaque), C strings as `[*]u8` + explicit length or null-termination.
|
// `sqlite3_stmt*` are opaque and never dereferenced; `usize` keeps
|
||||||
// Strings READ from SQLite (column_text, errmsg, libversion) are COPIED
|
// `sqlite3_open`'s out-param a single-level `*usize` instead of
|
||||||
// into `context.allocator` before returning — sqlite's buffers die on
|
// `**void`). Whole C strings IN are `[:0]u8` (the std FFI convention —
|
||||||
// the next step/finalize/close.
|
// 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 /
|
// The wrapper surface is exactly what the storage layer needs: open /
|
||||||
// exec / prepare / bind / step / column / finalize, last_insert_rowid,
|
// exec / prepare / bind / step / column / finalize, last_insert_rowid,
|
||||||
@@ -25,9 +30,9 @@
|
|||||||
|
|
||||||
sqlib :: #library "sqlite3";
|
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_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_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_step :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_step";
|
||||||
sqlite3_finalize :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_finalize";
|
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_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_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_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_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_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_count :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_column_count";
|
||||||
sqlite3_errmsg :: (db: usize) -> usize #foreign sqlib "dist_sqlite3_errmsg";
|
sqlite3_errmsg :: (db: usize) -> *u8 #foreign sqlib "dist_sqlite3_errmsg";
|
||||||
sqlite3_libversion :: () -> usize #foreign sqlib "dist_sqlite3_libversion";
|
sqlite3_libversion :: () -> *u8 #foreign sqlib "dist_sqlite3_libversion";
|
||||||
sqlite3_last_insert_rowid :: (db: usize) -> i64 #foreign sqlib "dist_sqlite3_last_insert_rowid";
|
sqlite3_last_insert_rowid :: (db: usize) -> i64 #foreign sqlib "dist_sqlite3_last_insert_rowid";
|
||||||
sqlite3_changes :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_changes";
|
sqlite3_changes :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_changes";
|
||||||
|
|
||||||
@@ -67,17 +72,17 @@ SqliteErr :: error {
|
|||||||
Step,
|
Step,
|
||||||
}
|
}
|
||||||
|
|
||||||
// `s` as a null-terminated heap copy (C string), from context.allocator.
|
// `s` as a null-terminated heap copy, passable where `[:0]u8` is expected.
|
||||||
sq_cstr :: (s: string) -> [*]u8 {
|
sq_cstr :: (s: string) -> string {
|
||||||
raw : [*]u8 = xx context.allocator.alloc_bytes(s.len + 1);
|
z := cstring(s.len);
|
||||||
if s.len > 0 { memcpy(raw, s.ptr, s.len); }
|
if s.len > 0 { memcpy(z.ptr, s.ptr, s.len); }
|
||||||
raw[s.len] = 0;
|
return z;
|
||||||
return raw;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the C string at `p` (0 = empty) into context.allocator.
|
// Copy the C string at `p` (null = empty) into context.allocator.
|
||||||
sq_from_cstr :: (p: usize) -> string {
|
sq_from_cstr :: (p: *u8) -> string {
|
||||||
if p == 0 { return ""; }
|
addr : i64 = xx p;
|
||||||
|
if addr == 0 { return ""; }
|
||||||
cp : [*]u8 = xx p;
|
cp : [*]u8 = xx p;
|
||||||
n := 0;
|
n := 0;
|
||||||
while cp[n] != 0 { n += 1; }
|
while cp[n] != 0 { n += 1; }
|
||||||
@@ -128,7 +133,8 @@ SqliteStmt :: struct {
|
|||||||
// Copied into context.allocator; a NULL column reads as "".
|
// Copied into context.allocator; a NULL column reads as "".
|
||||||
column_text :: (self: *SqliteStmt, icol: i64) -> string {
|
column_text :: (self: *SqliteStmt, icol: i64) -> string {
|
||||||
p := sqlite3_column_text(self.handle, xx icol);
|
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);
|
n := sqlite3_column_bytes(self.handle, xx icol);
|
||||||
cp : [*]u8 = xx p;
|
cp : [*]u8 = xx p;
|
||||||
raw : [*]u8 = xx context.allocator.alloc_bytes(xx (n + 1));
|
raw : [*]u8 = xx context.allocator.alloc_bytes(xx (n + 1));
|
||||||
|
|||||||
Reference in New Issue
Block a user