P5.1: vendor SQLite 3.53.2 + sx bindings
Subplan 02 Slice 2 foundation. vendor/sqlite/ holds the amalgamation (provenance + upgrade notes in its README); make build compiles it into build/vendor/libsqlite3.a (statically linked into dist via -L) and build/vendor/jit/libsqlite3.dylib (dlopen'd by sx run via tests/run.sh's -L flag) — separate directories because the macOS linker prefers a dylib over an archive in one search dir. The sx JIT resolves #foreign symbols via dlsym(RTLD_DEFAULT), where the already-loaded OS libsqlite3 wins by load order — so the vendored build renames its API to dist_sqlite3_* (vendor/sqlite/rename.h, -include'd), making resolution unambiguous in both modes: those symbols exist only in the vendored products. src/db/sqlite.sx binds the renamed surface behind Sqlite/SqliteStmt (open/exec/prepare/bind/step/column/finalize, errmsg, last_insert_rowid, changes, libversion); opaque handles cross the FFI as usize, strings read from sqlite are copied before its buffers die. make test 20/20 (new: sqlite_smoke.sx — pins the loaded version to the vendored 3.53.2, round trip, reopen persistence, BEGIN/ROLLBACK, errmsg; also verified as an AOT binary with no libsqlite3 in otool -L).
This commit is contained in:
28
Makefile
28
Makefile
@@ -12,13 +12,35 @@ BUILD_DIR := build
|
||||
SMOKE := tests/smoke.sx
|
||||
DIST := src/dist.sx
|
||||
|
||||
.PHONY: build test publish-example clean
|
||||
# Vendored SQLite (vendor/sqlite/README.md): one amalgamation, two build
|
||||
# products in two DIRECTORIES — the macOS linker prefers a dylib over an
|
||||
# archive in the same -L directory, and `sx build` must link the static
|
||||
# copy while `sx run` (the test runner) dlopens the dylib.
|
||||
VENDOR_DIR := $(BUILD_DIR)/vendor
|
||||
SQLITE_SRC := vendor/sqlite/sqlite3.c
|
||||
SQLITE_DEFS := -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_MEMSTATUS=0 \
|
||||
-DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_SHARED_CACHE \
|
||||
-DSQLITE_LIKE_DOESNT_MATCH_BLOBS \
|
||||
-include vendor/sqlite/rename.h
|
||||
|
||||
$(VENDOR_DIR)/libsqlite3.a: $(SQLITE_SRC) vendor/sqlite/sqlite3.h vendor/sqlite/rename.h
|
||||
@mkdir -p $(VENDOR_DIR)
|
||||
cc $(SQLITE_DEFS) -O2 -c $(SQLITE_SRC) -o $(VENDOR_DIR)/sqlite3.o
|
||||
ar rcs $@ $(VENDOR_DIR)/sqlite3.o
|
||||
|
||||
$(VENDOR_DIR)/jit/libsqlite3.dylib: $(SQLITE_SRC) vendor/sqlite/sqlite3.h vendor/sqlite/rename.h
|
||||
@mkdir -p $(VENDOR_DIR)/jit
|
||||
cc $(SQLITE_DEFS) -O2 -dynamiclib $(SQLITE_SRC) -o $@
|
||||
|
||||
.PHONY: build test publish-example vendor clean
|
||||
|
||||
vendor: $(VENDOR_DIR)/libsqlite3.a $(VENDOR_DIR)/jit/libsqlite3.dylib
|
||||
|
||||
# Compile the product sources (and the smoke program) without running.
|
||||
build:
|
||||
build: vendor
|
||||
@mkdir -p $(BUILD_DIR)
|
||||
$(SX) build -o $(BUILD_DIR)/smoke $(SMOKE)
|
||||
$(SX) build -o $(BUILD_DIR)/dist $(DIST)
|
||||
$(SX) build -o $(BUILD_DIR)/dist $(DIST) -L $(VENDOR_DIR)
|
||||
|
||||
# Run the test runner over every tests/**/*.sx. Exits non-zero on any
|
||||
# failure. Depends on `build` so the CLI acceptance test (tests/cli_*.sx)
|
||||
|
||||
201
src/db/sqlite.sx
Normal file
201
src/db/sqlite.sx
Normal file
@@ -0,0 +1,201 @@
|
||||
// =====================================================================
|
||||
// sqlite.sx — thin sx bindings over the VENDORED SQLite amalgamation
|
||||
// (vendor/sqlite/, see its README for version + upgrade notes).
|
||||
//
|
||||
// `#library "sqlite3"` resolves to the vendored build products under
|
||||
// build/vendor/ (never the OS copy): `sx build` links the static
|
||||
// archive via `-L build/vendor`; `sx run` dlopens the dylib via
|
||||
// `-L build/vendor/jit` (tests/run.sh). tests/sqlite_smoke.sx pins
|
||||
// `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.
|
||||
//
|
||||
// The wrapper surface is exactly what the storage layer needs: open /
|
||||
// exec / prepare / bind / step / column / finalize, last_insert_rowid,
|
||||
// and errmsg for failure detail. Everything else stays behind the FFI
|
||||
// line until needed.
|
||||
// =====================================================================
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
sqlib :: #library "sqlite3";
|
||||
|
||||
sqlite3_open :: (path: [*]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_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";
|
||||
sqlite3_reset :: (stmt: usize) -> i32 #foreign sqlib "dist_sqlite3_reset";
|
||||
sqlite3_bind_text :: (stmt: usize, idx: i32, text: [*]u8, n: i32, destructor: isize) -> i32 #foreign sqlib "dist_sqlite3_bind_text";
|
||||
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_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_last_insert_rowid :: (db: usize) -> i64 #foreign sqlib "dist_sqlite3_last_insert_rowid";
|
||||
sqlite3_changes :: (db: usize) -> i32 #foreign sqlib "dist_sqlite3_changes";
|
||||
|
||||
// Result codes (the subset the wrappers interpret).
|
||||
SQLITE_OK :: 0;
|
||||
SQLITE_ROW :: 100;
|
||||
SQLITE_DONE :: 101;
|
||||
|
||||
// sqlite3_bind_text destructor sentinel: copy the bytes NOW — sx-side
|
||||
// buffers (arena strings, stack temporaries) don't outlive the call.
|
||||
SQLITE_TRANSIENT : isize : -1;
|
||||
|
||||
// Column types (sqlite3_column_type).
|
||||
SQLITE_INTEGER :: 1;
|
||||
SQLITE_TEXT :: 3;
|
||||
SQLITE_NULL :: 5;
|
||||
|
||||
SqliteErr :: error {
|
||||
Open,
|
||||
Exec,
|
||||
Prepare,
|
||||
Bind,
|
||||
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;
|
||||
}
|
||||
|
||||
// Copy the C string at `p` (0 = empty) into context.allocator.
|
||||
sq_from_cstr :: (p: usize) -> string {
|
||||
if p == 0 { return ""; }
|
||||
cp : [*]u8 = xx p;
|
||||
n := 0;
|
||||
while cp[n] != 0 { n += 1; }
|
||||
raw : [*]u8 = xx context.allocator.alloc_bytes(n + 1);
|
||||
if n > 0 { memcpy(raw, cp, xx n); }
|
||||
raw[n] = 0;
|
||||
return string.{ ptr = raw, len = n };
|
||||
}
|
||||
|
||||
// The linked/loaded SQLite's version string (e.g. "3.53.2").
|
||||
sqlite_version :: () -> string {
|
||||
return sq_from_cstr(sqlite3_libversion());
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
bind_text :: (self: *SqliteStmt, idx: i64, s: string) -> !SqliteErr {
|
||||
rc := sqlite3_bind_text(self.handle, xx idx, s.ptr, xx s.len, SQLITE_TRANSIENT);
|
||||
if rc != SQLITE_OK { raise error.Bind; }
|
||||
return;
|
||||
}
|
||||
bind_int64 :: (self: *SqliteStmt, idx: i64, v: i64) -> !SqliteErr {
|
||||
rc := sqlite3_bind_int64(self.handle, xx idx, v);
|
||||
if rc != SQLITE_OK { raise error.Bind; }
|
||||
return;
|
||||
}
|
||||
bind_null :: (self: *SqliteStmt, idx: i64) -> !SqliteErr {
|
||||
rc := sqlite3_bind_null(self.handle, xx idx);
|
||||
if rc != SQLITE_OK { raise error.Bind; }
|
||||
return;
|
||||
}
|
||||
|
||||
// SQLITE_ROW / SQLITE_DONE on success; anything else raises with the
|
||||
// detail left in the connection's errmsg.
|
||||
step :: (self: *SqliteStmt) -> (i64, !SqliteErr) {
|
||||
rc := sqlite3_step(self.handle);
|
||||
if rc != SQLITE_ROW and rc != SQLITE_DONE { raise error.Step; }
|
||||
return xx rc;
|
||||
}
|
||||
|
||||
column_int64 :: (self: *SqliteStmt, icol: i64) -> i64 {
|
||||
return sqlite3_column_int64(self.handle, xx icol);
|
||||
}
|
||||
// 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 ""; }
|
||||
n := sqlite3_column_bytes(self.handle, xx icol);
|
||||
cp : [*]u8 = xx p;
|
||||
raw : [*]u8 = xx context.allocator.alloc_bytes(xx (n + 1));
|
||||
if n > 0 { memcpy(raw, cp, xx n); }
|
||||
raw[n] = 0;
|
||||
return string.{ ptr = raw, len = xx n };
|
||||
}
|
||||
column_type :: (self: *SqliteStmt, icol: i64) -> i64 {
|
||||
return xx sqlite3_column_type(self.handle, xx icol);
|
||||
}
|
||||
column_count :: (self: *SqliteStmt) -> i64 {
|
||||
return xx sqlite3_column_count(self.handle);
|
||||
}
|
||||
|
||||
reset :: (self: *SqliteStmt) {
|
||||
sqlite3_reset(self.handle);
|
||||
}
|
||||
finalize :: (self: *SqliteStmt) {
|
||||
sqlite3_finalize(self.handle);
|
||||
self.handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// One connection. `open` creates the file (and parent-less path) per
|
||||
// SQLite's defaults; `close` is safe on an already-closed handle.
|
||||
Sqlite :: struct {
|
||||
handle: usize;
|
||||
|
||||
open :: (path: string) -> (Sqlite, !SqliteErr) {
|
||||
h : usize = 0;
|
||||
rc := sqlite3_open(sq_cstr(path), @h);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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, sq_cstr(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 };
|
||||
}
|
||||
|
||||
errmsg :: (self: *Sqlite) -> string {
|
||||
return sq_from_cstr(sqlite3_errmsg(self.handle));
|
||||
}
|
||||
|
||||
last_insert_rowid :: (self: *Sqlite) -> i64 {
|
||||
return sqlite3_last_insert_rowid(self.handle);
|
||||
}
|
||||
|
||||
changes :: (self: *Sqlite) -> i64 {
|
||||
return xx sqlite3_changes(self.handle);
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,16 @@
|
||||
# process.assert — and exits non-zero on failure.
|
||||
#
|
||||
# Locate the compiler via SX (overridable); defaults to the sibling sx repo.
|
||||
#
|
||||
# `-L build/vendor/jit` lets the JIT dlopen the VENDORED libsqlite3.dylib
|
||||
# (built by `make build`) instead of falling back to the OS copy — the
|
||||
# version assert in tests/sqlite_smoke.sx depends on it.
|
||||
set -u
|
||||
|
||||
SX="${SX:-/Users/agra/projects/sx/zig-out/bin/sx}"
|
||||
TESTS_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||
REPO_DIR=$(CDPATH= cd -- "$TESTS_DIR/.." && pwd)
|
||||
SX_RUN_FLAGS="-L $REPO_DIR/build/vendor/jit"
|
||||
|
||||
pass=0
|
||||
fail=0
|
||||
@@ -20,7 +26,7 @@ fail=0
|
||||
# pass/fail counters survive).
|
||||
for t in $(find "$TESTS_DIR" -name '*.sx' -type f | sort); do
|
||||
name=${t#"$TESTS_DIR"/}
|
||||
if "$SX" run "$t" >/dev/null 2>&1; then
|
||||
if "$SX" run "$t" $SX_RUN_FLAGS >/dev/null 2>&1; then
|
||||
printf ' %-44s ok\n' "$name"
|
||||
pass=$((pass + 1))
|
||||
else
|
||||
|
||||
173
tests/sqlite_smoke.sx
Normal file
173
tests/sqlite_smoke.sx
Normal file
@@ -0,0 +1,173 @@
|
||||
// Pinned acceptance for P5.1 — the VENDORED SQLite is linked and usable
|
||||
// through src/db/sqlite.sx.
|
||||
//
|
||||
// * version: sqlite3_libversion() equals the vendored amalgamation's
|
||||
// version (vendor/sqlite/README.md) — a silent fallback to the OS
|
||||
// libsqlite3 fails here, in both `sx run` (dlopen via -L
|
||||
// build/vendor/jit) and `sx build` (static link via -L build/vendor).
|
||||
// * round trip: create table, INSERT via bound parameters (text +
|
||||
// int64 + null), SELECT back with WHERE binding, typed columns.
|
||||
// * persistence: rows survive close + reopen of the same file.
|
||||
// * transactions: BEGIN/ROLLBACK leaves no row behind.
|
||||
// * errors: bad SQL raises, and errmsg names the problem.
|
||||
#import "modules/std.sx";
|
||||
#import "modules/std/fs.sx";
|
||||
process :: #import "modules/std/process.sx";
|
||||
sq :: #import "../src/db/sqlite.sx";
|
||||
|
||||
VENDORED_VERSION :: "3.53.2";
|
||||
DBDIR :: ".sx-tmp/sqlite_smoke";
|
||||
DBPATH :: ".sx-tmp/sqlite_smoke/t.db";
|
||||
|
||||
run_case :: (label: string, ok: bool) -> i32 {
|
||||
if ok { print(" PASS {}\n", label); return 0; }
|
||||
print(" FAIL {}\n", label);
|
||||
return 1;
|
||||
}
|
||||
|
||||
check_version :: () -> bool {
|
||||
v := sq.sqlite_version();
|
||||
if v != VENDORED_VERSION {
|
||||
print(" loaded sqlite {} but vendor/sqlite is {}\n", v, VENDORED_VERSION);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
check_roundtrip :: () -> bool {
|
||||
db, oe := sq.Sqlite.open(DBPATH);
|
||||
if oe { return false; }
|
||||
|
||||
ee := false;
|
||||
db.exec("CREATE TABLE apps (id INTEGER PRIMARY KEY, slug TEXT NOT NULL, owner TEXT, created_at INTEGER NOT NULL)") catch { ee = true; };
|
||||
if ee { db.close(); return false; }
|
||||
|
||||
ins, pe := db.prepare("INSERT INTO apps (slug, owner, created_at) VALUES (?1, ?2, ?3)");
|
||||
if pe { db.close(); return false; }
|
||||
berr := false;
|
||||
ins.bind_text(1, "acme-app") catch { berr = true; };
|
||||
ins.bind_text(2, "ci") catch { berr = true; };
|
||||
ins.bind_int64(3, 1781250000) catch { berr = true; };
|
||||
se := false;
|
||||
rc := ins.step() catch { se = true; 0 };
|
||||
ins.finalize();
|
||||
if berr or se or rc != sq.SQLITE_DONE { db.close(); return false; }
|
||||
rowid := db.last_insert_rowid();
|
||||
if rowid != 1 { db.close(); return false; }
|
||||
|
||||
// second row with a NULL owner
|
||||
ins2, pe2 := db.prepare("INSERT INTO apps (slug, owner, created_at) VALUES (?1, ?2, ?3)");
|
||||
if pe2 { db.close(); return false; }
|
||||
ins2.bind_text(1, "other-app") catch { berr = true; };
|
||||
ins2.bind_null(2) catch { berr = true; };
|
||||
ins2.bind_int64(3, 1781250001) catch { berr = true; };
|
||||
ins2.step() catch { se = true; 0 };
|
||||
ins2.finalize();
|
||||
if berr or se { db.close(); return false; }
|
||||
|
||||
// select the first row back through a WHERE binding
|
||||
sel, pe3 := db.prepare("SELECT id, slug, owner, created_at FROM apps WHERE slug = ?1");
|
||||
if pe3 { db.close(); return false; }
|
||||
sel.bind_text(1, "acme-app") catch { berr = true; };
|
||||
src := sel.step() catch { se = true; 0 };
|
||||
ok := !berr and !se and src == sq.SQLITE_ROW;
|
||||
if ok {
|
||||
ok = sel.column_count() == 4
|
||||
and sel.column_int64(0) == 1
|
||||
and sel.column_text(1) == "acme-app"
|
||||
and sel.column_text(2) == "ci"
|
||||
and sel.column_int64(3) == 1781250000
|
||||
and sel.column_type(1) == sq.SQLITE_TEXT;
|
||||
}
|
||||
if ok {
|
||||
drc := sel.step() catch { se = true; 0 };
|
||||
ok = !se and drc == sq.SQLITE_DONE; // exactly one matching row
|
||||
}
|
||||
sel.finalize();
|
||||
|
||||
// NULL column reads as "" with column_type NULL
|
||||
if ok {
|
||||
sel2, pe4 := db.prepare("SELECT owner FROM apps WHERE slug = 'other-app'");
|
||||
if pe4 { db.close(); return false; }
|
||||
nrc := sel2.step() catch { se = true; 0 };
|
||||
ok = !se and nrc == sq.SQLITE_ROW
|
||||
and sel2.column_type(0) == sq.SQLITE_NULL
|
||||
and sel2.column_text(0) == "";
|
||||
sel2.finalize();
|
||||
}
|
||||
|
||||
db.close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
check_persistence :: () -> bool {
|
||||
db, oe := sq.Sqlite.open(DBPATH);
|
||||
if oe { return false; }
|
||||
sel, pe := db.prepare("SELECT COUNT(*) FROM apps");
|
||||
if pe { db.close(); return false; }
|
||||
se := false;
|
||||
rc := sel.step() catch { se = true; 0 };
|
||||
ok := !se and rc == sq.SQLITE_ROW and sel.column_int64(0) == 2;
|
||||
sel.finalize();
|
||||
db.close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
check_transaction_rollback :: () -> bool {
|
||||
db, oe := sq.Sqlite.open(DBPATH);
|
||||
if oe { return false; }
|
||||
ee := false;
|
||||
db.exec("BEGIN") catch { ee = true; };
|
||||
db.exec("INSERT INTO apps (slug, owner, created_at) VALUES ('doomed', 'x', 0)") catch { ee = true; };
|
||||
db.exec("ROLLBACK") catch { ee = true; };
|
||||
if ee { db.close(); return false; }
|
||||
|
||||
sel, pe := db.prepare("SELECT COUNT(*) FROM apps WHERE slug = 'doomed'");
|
||||
if pe { db.close(); return false; }
|
||||
se := false;
|
||||
rc := sel.step() catch { se = true; 0 };
|
||||
ok := !se and rc == sq.SQLITE_ROW and sel.column_int64(0) == 0;
|
||||
sel.finalize();
|
||||
db.close();
|
||||
return ok;
|
||||
}
|
||||
|
||||
check_error_path :: () -> bool {
|
||||
db, oe := sq.Sqlite.open(DBPATH);
|
||||
if oe { return false; }
|
||||
failed := false;
|
||||
db.exec("FROBNICATE THE DATABASE") catch { failed = true; };
|
||||
msg := db.errmsg();
|
||||
db.close();
|
||||
if !failed { return false; }
|
||||
// errmsg names the offending token
|
||||
i := 0;
|
||||
found := false;
|
||||
while i + 10 <= msg.len {
|
||||
v := string.{ ptr = @msg[i], len = 10 };
|
||||
if v == "FROBNICATE" { found = true; break; }
|
||||
i += 1;
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
process.run(concat("rm -rf ", DBDIR));
|
||||
process.run(concat("mkdir -p ", DBDIR));
|
||||
|
||||
failures : i32 = 0;
|
||||
failures += run_case("vendored version is loaded", check_version());
|
||||
failures += run_case("create/insert/select round trip", check_roundtrip());
|
||||
failures += run_case("rows persist across reopen", check_persistence());
|
||||
failures += run_case("BEGIN/ROLLBACK leaves no row", check_transaction_rollback());
|
||||
failures += run_case("bad SQL raises with errmsg detail", check_error_path());
|
||||
|
||||
process.run(concat("rm -rf ", DBDIR));
|
||||
print("------------------------------------------------\n");
|
||||
if failures == 0 {
|
||||
print("sqlite_smoke: ALL CASES PASS\n");
|
||||
return 0;
|
||||
}
|
||||
print("sqlite_smoke: {} CASE(S) FAILED\n", failures);
|
||||
return 1;
|
||||
}
|
||||
21
vendor/sqlite/README.md
vendored
Normal file
21
vendor/sqlite/README.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Vendored SQLite
|
||||
|
||||
- Version: **3.53.2** (`SQLITE_VERSION` in `sqlite3.h`)
|
||||
- Source: <https://sqlite.org/2026/sqlite-amalgamation-3530200.zip>
|
||||
- Zip sha256: `8a310d0a16c7a90cacd4c884e70faa51c902afed2a89f63aaa0126ab83558a32`
|
||||
- Files kept: `sqlite3.c`, `sqlite3.h` (the amalgamation; `shell.c` and
|
||||
`sqlite3ext.h` dropped — no shell, no loadable extensions)
|
||||
- License: public domain (<https://sqlite.org/copyright.html>)
|
||||
|
||||
`make build` compiles this into `build/vendor/libsqlite3.a` (statically
|
||||
linked into the `dist` binary via `-L build/vendor`) and
|
||||
`build/vendor/jit/libsqlite3.dylib` (dlopen'd by `sx run`, which is how
|
||||
`make test` executes the test programs). The two locations are separate
|
||||
on purpose: the macOS linker prefers a dylib over an archive in the same
|
||||
search directory, and the AOT binary must link the static copy.
|
||||
`tests/sqlite_smoke.sx` asserts `sqlite3_libversion()` equals the version
|
||||
above, so a fallback to the OS libsqlite3 fails loudly in both modes.
|
||||
|
||||
To upgrade: replace `sqlite3.c`/`sqlite3.h` with a newer amalgamation,
|
||||
update this file and the version constant in `tests/sqlite_smoke.sx`, and
|
||||
run `make clean test`.
|
||||
34
vendor/sqlite/rename.h
vendored
Normal file
34
vendor/sqlite/rename.h
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Symbol prefix for the vendored SQLite build.
|
||||
*
|
||||
* The sx JIT resolves #foreign symbols via dlsym(RTLD_DEFAULT), which
|
||||
* searches every image already loaded into the process — and the OS
|
||||
* libsqlite3 is usually among them, so the standard names would silently
|
||||
* bind to the system copy instead of this vendored one. Prefixing the
|
||||
* API surface makes resolution unambiguous in both JIT (dlopen) and AOT
|
||||
* (static link) modes: dist_sqlite3_* exists ONLY in the vendored build.
|
||||
*
|
||||
* Only the functions bound in src/db/sqlite.sx are renamed; extend BOTH
|
||||
* files together when new API is needed. Injected via `-include` in the
|
||||
* Makefile's SQLITE_DEFS, so sqlite3.c and its embedded sqlite3.h see
|
||||
* the renames consistently.
|
||||
*/
|
||||
#define sqlite3_open dist_sqlite3_open
|
||||
#define sqlite3_close dist_sqlite3_close
|
||||
#define sqlite3_exec dist_sqlite3_exec
|
||||
#define sqlite3_prepare_v2 dist_sqlite3_prepare_v2
|
||||
#define sqlite3_step dist_sqlite3_step
|
||||
#define sqlite3_finalize dist_sqlite3_finalize
|
||||
#define sqlite3_reset dist_sqlite3_reset
|
||||
#define sqlite3_bind_text dist_sqlite3_bind_text
|
||||
#define sqlite3_bind_int64 dist_sqlite3_bind_int64
|
||||
#define sqlite3_bind_null dist_sqlite3_bind_null
|
||||
#define sqlite3_column_int64 dist_sqlite3_column_int64
|
||||
#define sqlite3_column_text dist_sqlite3_column_text
|
||||
#define sqlite3_column_bytes dist_sqlite3_column_bytes
|
||||
#define sqlite3_column_type dist_sqlite3_column_type
|
||||
#define sqlite3_column_count dist_sqlite3_column_count
|
||||
#define sqlite3_errmsg dist_sqlite3_errmsg
|
||||
#define sqlite3_libversion dist_sqlite3_libversion
|
||||
#define sqlite3_last_insert_rowid dist_sqlite3_last_insert_rowid
|
||||
#define sqlite3_changes dist_sqlite3_changes
|
||||
269376
vendor/sqlite/sqlite3.c
vendored
Normal file
269376
vendor/sqlite/sqlite3.c
vendored
Normal file
File diff suppressed because it is too large
Load Diff
14347
vendor/sqlite/sqlite3.h
vendored
Normal file
14347
vendor/sqlite/sqlite3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user