Use vendored jsqlite
ober
3eb8b705df26e12e1898e0d4b0dea441d6d48a0e
--- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Build artifacts (jerbuild build) /jerboa-edge +vendor/ /*-main.c support/ffi-symbols.gen *.so --- a/.jerbuild +++ b/.jerbuild @@ -2,12 +2,11 @@ ;; ;; jerbuild bundles Chez Scheme + the jerboa stdlib + the jerboa-native Rust ;; crate, so this needs only jerbuild + a C compiler + cargo — no jerboa -;; source checkout. jerboa-native provides rustls TLS, crypto, and -;; sqlite-native (no DuckDB). +;; source checkout. jerboa-native provides rustls TLS and crypto. (entry "edge.ss") (output "jerboa-edge") -(libdirs ".") +(libdirs "." "vendor/jsqlite/src") ;; Static binary: a custom main.c sets JERBOA_STATIC=1 so the bundled std ;; modules skip dlopen of libjerboa_native and use the symbols registered from ;; the list below (the linked-in archive members would otherwise be @@ -16,5 +15,5 @@ (ffi-symbols "support/ffi-symbols.gen") (rust-crates ("@bundle/jerboa-native-rs/Cargo.toml" - features: "tls,crypto,sqlite" + features: "tls,crypto" no-default-features: #t)) --- a/Makefile +++ b/Makefile @@ -10,18 +10,28 @@ endif NATIVE_A := $(JH)/jerboa-native-rs/target/release/libjerboa_native.a BIN := jerboa-edge BIN_DIR := $(HOME)/.local/bin +VENDOR := $(CURDIR)/vendor +JSQLITE_REPO ?= $(VENDOR)/jsqlite +JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite -.PHONY: all build binary install clean +.PHONY: all build binary install clean ensure-jsqlite all: binary +ensure-jsqlite: + @if [ ! -f "$(JSQLITE_REPO)/src/jsqlite/api.ss" ]; then \ + mkdir -p "$(VENDOR)"; \ + git clone --depth 1 "$(JSQLITE_URL)" "$(JSQLITE_REPO)"; \ + fi + @test -f "$(JSQLITE_REPO)/src/jsqlite/api.ss" + # Standalone native binary via .jerbuild (entry edge.ss -> jerboa-edge). # Two passes: the first cargo-builds jerboa-native into jerbuild's cache; we # then regenerate the FFI symbol list from that archive (so it matches this # platform — e.g. epoll on Linux) and relink. A static main.c sets # JERBOA_STATIC=1 so the std modules use the registered symbols, not dlopen. -binary: - @touch support/ffi-symbols.gen +binary: ensure-jsqlite + @: > support/ffi-symbols.gen $(JERBUILD) build sh support/gen-ffi-symbols.sh $(JERBUILD) build --- a/edge.ss +++ b/edge.ss @@ -10,8 +10,8 @@ ;;; - HMAC-SHA256 signature verification ;;; - Prometheus metrics (/metrics endpoint) ;;; - Structured JSON logging (greppable by Loki/Splunk) -;;; - Graceful SIGTERM shutdown (drain, flush SQLite WAL) -;;; - Optional SQLite persistence (EDGE_DB_PATH) +;;; - Graceful SIGTERM shutdown (drain, flush jsqlite WAL) +;;; - Optional jsqlite persistence (EDGE_DB_PATH) ;;; - Optional TLS termination (EDGE_TLS_CERT + EDGE_TLS_KEY) ;;; ;;; Zero external dependencies. Everything is Jerboa stdlib. @@ -51,10 +51,38 @@ (std misc timeout) (std metrics) (std os signal) - (std db sqlite-native) + (prefix (jsqlite api) japi:) + (prefix (jsqlite constants) jconst:) (std net tls-rustls) (std net tcp-raw)) +(def sqlite-open japi:sqlite-open) +(def sqlite-close japi:sqlite-close) +(def sqlite-exec japi:sqlite-exec) +(def sqlite-execute japi:sqlite-exec) + +(def (bind-sqlite-args! stmt vals) + (let loop ([i 1] [vs vals]) + (unless (null? vs) + (japi:sqlite-bind! stmt i (car vs)) + (loop (+ i 1) (cdr vs))))) + +(def (sqlite-query db sql . vals) + (let ([stmt (japi:sqlite-prepare db sql)]) + (bind-sqlite-args! stmt vals) + (let ([names (japi:sqlite-columns stmt)]) + (let loop ([acc '()]) + (let ([rc (japi:sqlite-step stmt)]) + (cond + [(= rc jconst:SQLITE_ROW) + (loop (cons (map cons names (japi:sqlite-row stmt)) acc))] + [(= rc jconst:SQLITE_DONE) + (japi:sqlite-finalize stmt) + (reverse acc)] + [else + (japi:sqlite-finalize stmt) + (reverse acc)])))))) + ;; ═══════════════════════════════════════════════════════════════ ;; Configuration ;; ═══════════════════════════════════════════════════════════════ @@ -112,18 +140,18 @@ (displayln (json-object->string ht)))) ;; ═══════════════════════════════════════════════════════════════ -;; SQLite Persistence (Phase 3.3) +;; jsqlite Persistence (Phase 3.3) ;; ;; Optional: set EDGE_DB_PATH to a file path to enable persistence. ;; On startup, existing events and dead-letters are loaded into STM. -;; Every store-result! / store-dead-letter! also writes to SQLite. +;; Every store-result! / store-dead-letter! also writes to jsqlite. ;; WAL mode is used for concurrent reads with no reader blocking. ;; ;; All DB writes are fire-and-forget: guarded with (guard (e [#t (void)])) ;; so a SQLite error never crashes a worker. ;; ═══════════════════════════════════════════════════════════════ -(def *db* #f) ;; SQLite handle — #f when EDGE_DB_PATH is not set +(def *db* #f) ;; jsqlite handle — #f when EDGE_DB_PATH is not set (def (db-init!) (when *db-path* --- a/support/static-main.c +++ b/support/static-main.c @@ -2,8 +2,8 @@ * .jerbuild as main-c). Identical to jerbuild's stock template with ONE * addition: it sets JERBOA_STATIC=1 before building the heap. * - * The bundled std modules — (std db sqlite-native), (std crypto native-rust), - * etc. — read JERBOA_STATIC at library-load time and, when set, trust the + * The bundled std modules that use native FFI read JERBOA_STATIC at library-load + * time and, when set, trust the * jerboa-native symbols that are linked into this binary (and registered via * register_ffi_symbols() below from the .jerbuild ffi-symbols list) instead of * trying to dlopen a libjerboa_native.{so,dylib} that does not exist in a