From ebb7fbb0a2a441a6b93f01256bd846488e26d918 Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Fri, 19 Jun 2026 08:59:16 +0200 Subject: [PATCH 1/5] Add prepared statement API (prepare, exec-prepared, reset-stmt, finalize-stmt) --- sqlite3.carp | 70 +++++++++++++++++++++++++++++ sqlite3_helper.h | 56 ++++++++++++++++++++++++ test/sqlite3.carp | 109 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 234 insertions(+), 1 deletion(-) diff --git a/sqlite3.carp b/sqlite3.carp index c67109d..78d57f2 100644 --- a/sqlite3.carp +++ b/sqlite3.carp @@ -198,6 +198,76 @@ If it fails, we return an error message using `Result.Error`.") (doc close "closes a database.") (register close (Fn [SQLite] ()) "SQLite3_close_c") + ; ========================================================================= + ; Prepared statements + ; ========================================================================= + + (doc Stmt "is an opaque prepared statement type. Prepare a statement once +with [prepare](#prepare) and execute it many times with +[exec-prepared](#exec-prepared). Call [finalize-stmt](#finalize-stmt) when +done.") + (register-type Stmt) + + (private stmt-init) + (hidden stmt-init) + (register stmt-init (Fn [] Stmt) "SQLite3_stmt_init") + (private errmsg) + (hidden errmsg) + (register errmsg (Fn [&SQLite] String) "SQLite3_errmsg_c") + (private exec-prepared-) + (hidden exec-prepared-) + (register exec-prepared- + (Fn [&Stmt &(Array SQLiteColumn)] SQLiteRes) + "SQLite3_exec_prepared_c") + (private prepare-) + (hidden prepare-) + (register prepare- (Fn [&SQLite (Ptr CChar) &Stmt] Int) "SQLite3_prepare_c") + + (doc prepare "prepares a SQL statement for repeated execution. + +Returns a `Stmt` handle that can be executed multiple times with different +parameters via [exec-prepared](#exec-prepared). + +When you are done with the statement, call [finalize-stmt](#finalize-stmt) +to release its resources. + +Example: +``` +(let [stmt (Result.unsafe-from-success + (SQLite3.prepare &db \"INSERT INTO t VALUES (?1, ?2);\"))] + (ignore (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @\"a\")])) + (ignore (SQLite3.exec-prepared &stmt &[(to-sqlite3 2) (to-sqlite3 @\"b\")])) + (SQLite3.finalize-stmt stmt)) +```") + (defn prepare [db sql] + (let [stmt (stmt-init) + res (prepare- db (cstr sql) &stmt)] + (if (= res sql_ok) (Result.Success stmt) (Result.Error (errmsg db))))) + + (doc exec-prepared "executes a prepared statement with the given parameters. + +The statement is automatically reset after execution, so it can be called +again with new parameters without calling [reset-stmt](#reset-stmt).") + (defn exec-prepared [stmt p] + (let [r (exec-prepared- stmt + &(Array.copy-map &(fn [x] + (Type.to-sqlite3-internal @x)) + p))] + (if (SQLiteRes.ok? &r) + (Result.Success (SQLiteRes.to-array r)) + (Result.Error (from-cstr (SQLiteRes.error r)))))) + + (doc reset-stmt "resets a prepared statement so it can be executed again. + +This is called automatically by [exec-prepared](#exec-prepared), but can be +called manually if needed (e.g. to abort a partially-executed statement).") + (register reset-stmt (Fn [&Stmt] ()) "SQLite3_reset_stmt_c") + + (doc finalize-stmt "finalizes a prepared statement, releasing its resources. + +After calling this, the statement must not be used again.") + (register finalize-stmt (Fn [Stmt] ()) "SQLite3_finalize_stmt_c") + (doc last-insert-rowid "returns the row ID of the last successful INSERT.") (register last-insert-rowid (Fn [&SQLite] Long) "SQLite3_last_insert_rowid") diff --git a/sqlite3_helper.h b/sqlite3_helper.h index bb96920..f784b28 100644 --- a/sqlite3_helper.h +++ b/sqlite3_helper.h @@ -319,3 +319,59 @@ char* SQLite3_error_and_close(SQLite db) { sqlite3_close_v2(db.handle); return copy; } + +// --- Prepared statement API --- + +typedef struct { + sqlite3_stmt* handle; +} Stmt; + +Stmt SQLite3_stmt_init() { + Stmt res; + res.handle = NULL; + return res; +} + +int SQLite3_prepare_c(SQLite* db, const char* sql, Stmt* stmt) { + return sqlite3_prepare_v2(db->handle, sql, -1, &stmt->handle, NULL); +} + +char* SQLite3_errmsg_c(SQLite* db) { + const char* msg = sqlite3_errmsg(db->handle); + size_t len = strlen(msg); + char* copy = CARP_MALLOC(len + 1); + memcpy(copy, msg, len + 1); + return copy; +} + +SQLiteRes SQLite3_exec_prepared_c(Stmt* stmt, Array* p) { + SQLiteRes res; + res.is = OK; + res.rows = SQLiteRows_new_rows(); + + const char* err = SQLite3_bind(stmt->handle, p); + if (err) goto fail; + + err = SQLite3_exec_internal(stmt->handle, &res.rows); + if (err) goto fail; + + sqlite3_reset(stmt->handle); + sqlite3_clear_bindings(stmt->handle); + return res; + +fail: + sqlite3_reset(stmt->handle); + sqlite3_clear_bindings(stmt->handle); + res.is = ERR; + res.err = err; + return res; +} + +void SQLite3_reset_stmt_c(Stmt* stmt) { + sqlite3_reset(stmt->handle); + sqlite3_clear_bindings(stmt->handle); +} + +void SQLite3_finalize_stmt_c(Stmt stmt) { + if (stmt.handle) sqlite3_finalize(stmt.handle); +} diff --git a/test/sqlite3.carp b/test/sqlite3.carp index f803dea..1edaf56 100644 --- a/test/sqlite3.carp +++ b/test/sqlite3.carp @@ -303,4 +303,111 @@ (SQLite3.query &db "SELECT * FROM t;" &[]))] (SQLite3.close db) r)) - "blob with embedded nulls round-trips correctly")) + "blob with embedded nulls round-trips correctly") + + ; ========================================================================= + ; Prepared statements - prepare / exec-prepared / finalize-stmt + ; ========================================================================= + + (assert-true test + (let [db (open-memory)] + (let-do [r (SQLite3.prepare &db "SELECT 1;")] + (match r + (Result.Success stmt) + (do (SQLite3.finalize-stmt stmt) (SQLite3.close db) true) + (Result.Error _) (do (SQLite3.close db) false)))) + "prepare succeeds for valid SQL") + + (assert-true test + (let [db (open-memory)] + (let-do [r (SQLite3.prepare &db "NOT VALID SQL")] + (SQLite3.close db) + (Result.error? &r))) + "prepare fails for invalid SQL") + + (assert-equal test + &(Result.Success [[(SQLite3.Type.Integer 42l)]]) + &(let [db (open-memory)] + (let-do [stmt (Result.unsafe-from-success (SQLite3.prepare &db + "SELECT ?1;")) + r (SQLite3.exec-prepared &stmt &[(to-sqlite3 42)])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + r)) + "exec-prepared returns results") + + (assert-equal test + &(Result.Success + [[(SQLite3.Type.Integer 1l) (SQLite3.Type.Text @"a")] + [(SQLite3.Type.Integer 2l) (SQLite3.Type.Text @"b")] + [(SQLite3.Type.Integer 3l) (SQLite3.Type.Text @"c")]]) + &(let [db (open-memory)] + (let-do [_ (ignore + (SQLite3.query &db "CREATE TABLE t (id INT, name TEXT);" &[])) + stmt (Result.unsafe-from-success + (SQLite3.prepare &db "INSERT INTO t VALUES (?1, ?2);"))] + (ignore + (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @"a")])) + (ignore + (SQLite3.exec-prepared &stmt &[(to-sqlite3 2) (to-sqlite3 @"b")])) + (ignore + (SQLite3.exec-prepared &stmt &[(to-sqlite3 3) (to-sqlite3 @"c")])) + (SQLite3.finalize-stmt stmt) + (let-do [r (SQLite3.query &db "SELECT * FROM t ORDER BY id;" &[])] + (SQLite3.close db) + r))) + "exec-prepared reuses statement for multiple inserts") + + (assert-equal test + &(Result.Success (the (Array (Array SQLite3.Type)) [])) + &(let [db (open-memory)] + (let-do [_ (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) + stmt (Result.unsafe-from-success (SQLite3.prepare &db + "SELECT * FROM t;"))] + (let-do [r (SQLite3.exec-prepared &stmt &[])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + r))) + "exec-prepared with no rows returns empty array") + + (assert-equal test + &(Result.Success + [[(SQLite3.Type.Integer 1l)] + [(SQLite3.Type.Integer 2l)] + [(SQLite3.Type.Integer 3l)]]) + &(let-do [db (open-memory)] + (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) + (ignore (SQLite3.query &db "INSERT INTO t VALUES (1);" &[])) + (ignore (SQLite3.query &db "INSERT INTO t VALUES (2);" &[])) + (ignore (SQLite3.query &db "INSERT INTO t VALUES (3);" &[])) + (let-do [stmt (Result.unsafe-from-success + (SQLite3.prepare &db "SELECT * FROM t WHERE x > ?1;")) + r (SQLite3.exec-prepared &stmt &[(to-sqlite3 0)])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + r)) + "exec-prepared with parameterized SELECT works") + + (assert-true test + (let [db (open-memory)] + (let-do [_ (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) + stmt (Result.unsafe-from-success + (SQLite3.prepare &db "INSERT INTO t VALUES (?1);"))] + (SQLite3.reset-stmt &stmt) + (let-do [r (SQLite3.exec-prepared &stmt &[(to-sqlite3 99)])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + (Result.success? &r)))) + "reset-stmt allows re-execution") + + (assert-equal test + 2l + (let-do [db (open-memory)] + (ignore (SQLite3.query &db "CREATE TABLE t (id INTEGER PRIMARY KEY);" &[])) + (let-do [stmt (Result.unsafe-from-success + (SQLite3.prepare &db "INSERT INTO t VALUES (NULL);"))] + (ignore (SQLite3.exec-prepared &stmt &[])) + (ignore (SQLite3.exec-prepared &stmt &[])) + (SQLite3.finalize-stmt stmt) + (let-do [r (SQLite3.last-insert-rowid &db)] (SQLite3.close db) r))) + "last-insert-rowid works with prepared inserts")) From 2de03174a3b1047d75a27f10d23922f261461b88 Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Fri, 19 Jun 2026 15:48:15 +0200 Subject: [PATCH 2/5] Replace Result.unsafe-from-success with match in tests --- test/sqlite3.carp | 108 +++++++++++++++++++++++++--------------------- 1 file changed, 58 insertions(+), 50 deletions(-) diff --git a/test/sqlite3.carp b/test/sqlite3.carp index 1edaf56..505858a 100644 --- a/test/sqlite3.carp +++ b/test/sqlite3.carp @@ -328,12 +328,13 @@ (assert-equal test &(Result.Success [[(SQLite3.Type.Integer 42l)]]) &(let [db (open-memory)] - (let-do [stmt (Result.unsafe-from-success (SQLite3.prepare &db - "SELECT ?1;")) - r (SQLite3.exec-prepared &stmt &[(to-sqlite3 42)])] - (SQLite3.finalize-stmt stmt) - (SQLite3.close db) - r)) + (match (SQLite3.prepare &db "SELECT ?1;") + (Result.Success stmt) + (let-do [r (SQLite3.exec-prepared &stmt &[(to-sqlite3 42)])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + r) + (Result.Error e) (do (SQLite3.close db) (Result.Error e)))) "exec-prepared returns results") (assert-equal test @@ -341,33 +342,35 @@ [[(SQLite3.Type.Integer 1l) (SQLite3.Type.Text @"a")] [(SQLite3.Type.Integer 2l) (SQLite3.Type.Text @"b")] [(SQLite3.Type.Integer 3l) (SQLite3.Type.Text @"c")]]) - &(let [db (open-memory)] - (let-do [_ (ignore - (SQLite3.query &db "CREATE TABLE t (id INT, name TEXT);" &[])) - stmt (Result.unsafe-from-success - (SQLite3.prepare &db "INSERT INTO t VALUES (?1, ?2);"))] - (ignore - (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @"a")])) - (ignore - (SQLite3.exec-prepared &stmt &[(to-sqlite3 2) (to-sqlite3 @"b")])) - (ignore - (SQLite3.exec-prepared &stmt &[(to-sqlite3 3) (to-sqlite3 @"c")])) - (SQLite3.finalize-stmt stmt) - (let-do [r (SQLite3.query &db "SELECT * FROM t ORDER BY id;" &[])] - (SQLite3.close db) - r))) + &(let-do [db (open-memory)] + (ignore (SQLite3.query &db "CREATE TABLE t (id INT, name TEXT);" &[])) + (match (SQLite3.prepare &db "INSERT INTO t VALUES (?1, ?2);") + (Result.Success stmt) + (do + (ignore + (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @"a")])) + (ignore + (SQLite3.exec-prepared &stmt &[(to-sqlite3 2) (to-sqlite3 @"b")])) + (ignore + (SQLite3.exec-prepared &stmt &[(to-sqlite3 3) (to-sqlite3 @"c")])) + (SQLite3.finalize-stmt stmt) + (let-do [r (SQLite3.query &db "SELECT * FROM t ORDER BY id;" &[])] + (SQLite3.close db) + r)) + (Result.Error e) (do (SQLite3.close db) (Result.Error e)))) "exec-prepared reuses statement for multiple inserts") (assert-equal test &(Result.Success (the (Array (Array SQLite3.Type)) [])) - &(let [db (open-memory)] - (let-do [_ (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) - stmt (Result.unsafe-from-success (SQLite3.prepare &db - "SELECT * FROM t;"))] - (let-do [r (SQLite3.exec-prepared &stmt &[])] - (SQLite3.finalize-stmt stmt) - (SQLite3.close db) - r))) + &(let-do [db (open-memory)] + (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) + (match (SQLite3.prepare &db "SELECT * FROM t;") + (Result.Success stmt) + (let-do [r (SQLite3.exec-prepared &stmt &[])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + r) + (Result.Error e) (do (SQLite3.close db) (Result.Error e)))) "exec-prepared with no rows returns empty array") (assert-equal test @@ -380,34 +383,39 @@ (ignore (SQLite3.query &db "INSERT INTO t VALUES (1);" &[])) (ignore (SQLite3.query &db "INSERT INTO t VALUES (2);" &[])) (ignore (SQLite3.query &db "INSERT INTO t VALUES (3);" &[])) - (let-do [stmt (Result.unsafe-from-success - (SQLite3.prepare &db "SELECT * FROM t WHERE x > ?1;")) - r (SQLite3.exec-prepared &stmt &[(to-sqlite3 0)])] - (SQLite3.finalize-stmt stmt) - (SQLite3.close db) - r)) + (match (SQLite3.prepare &db "SELECT * FROM t WHERE x > ?1;") + (Result.Success stmt) + (let-do [r (SQLite3.exec-prepared &stmt &[(to-sqlite3 0)])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + r) + (Result.Error e) (do (SQLite3.close db) (Result.Error e)))) "exec-prepared with parameterized SELECT works") (assert-true test - (let [db (open-memory)] - (let-do [_ (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) - stmt (Result.unsafe-from-success - (SQLite3.prepare &db "INSERT INTO t VALUES (?1);"))] - (SQLite3.reset-stmt &stmt) - (let-do [r (SQLite3.exec-prepared &stmt &[(to-sqlite3 99)])] - (SQLite3.finalize-stmt stmt) - (SQLite3.close db) - (Result.success? &r)))) + (let-do [db (open-memory)] + (ignore (SQLite3.query &db "CREATE TABLE t (x INT);" &[])) + (match (SQLite3.prepare &db "INSERT INTO t VALUES (?1);") + (Result.Success stmt) + (do + (SQLite3.reset-stmt &stmt) + (let-do [r (SQLite3.exec-prepared &stmt &[(to-sqlite3 99)])] + (SQLite3.finalize-stmt stmt) + (SQLite3.close db) + (Result.success? &r))) + (Result.Error _) (do (SQLite3.close db) false))) "reset-stmt allows re-execution") (assert-equal test 2l (let-do [db (open-memory)] (ignore (SQLite3.query &db "CREATE TABLE t (id INTEGER PRIMARY KEY);" &[])) - (let-do [stmt (Result.unsafe-from-success - (SQLite3.prepare &db "INSERT INTO t VALUES (NULL);"))] - (ignore (SQLite3.exec-prepared &stmt &[])) - (ignore (SQLite3.exec-prepared &stmt &[])) - (SQLite3.finalize-stmt stmt) - (let-do [r (SQLite3.last-insert-rowid &db)] (SQLite3.close db) r))) + (match (SQLite3.prepare &db "INSERT INTO t VALUES (NULL);") + (Result.Success stmt) + (do + (ignore (SQLite3.exec-prepared &stmt &[])) + (ignore (SQLite3.exec-prepared &stmt &[])) + (SQLite3.finalize-stmt stmt) + (let-do [r (SQLite3.last-insert-rowid &db)] (SQLite3.close db) r)) + (Result.Error _) (do (SQLite3.close db) 0l))) "last-insert-rowid works with prepared inserts")) From f2366820308b9ffcd4ebcc015dc0071630e53437 Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Sat, 20 Jun 2026 20:48:21 +0200 Subject: [PATCH 3/5] Fix comment hygiene: tighten doc strings, fix doc example - Remove section separator (not used elsewhere in source file) - Replace Result.unsafe-from-success with match in prepare doc example - Tighten doc strings to match existing terse style --- sqlite3.carp | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/sqlite3.carp b/sqlite3.carp index 78d57f2..26ebb58 100644 --- a/sqlite3.carp +++ b/sqlite3.carp @@ -198,14 +198,9 @@ If it fails, we return an error message using `Result.Error`.") (doc close "closes a database.") (register close (Fn [SQLite] ()) "SQLite3_close_c") - ; ========================================================================= - ; Prepared statements - ; ========================================================================= - - (doc Stmt "is an opaque prepared statement type. Prepare a statement once -with [prepare](#prepare) and execute it many times with -[exec-prepared](#exec-prepared). Call [finalize-stmt](#finalize-stmt) when -done.") + (doc Stmt "is an opaque prepared statement type. Prepare once with +[prepare](#prepare), execute with [exec-prepared](#exec-prepared), and +release with [finalize-stmt](#finalize-stmt).") (register-type Stmt) (private stmt-init) @@ -223,21 +218,18 @@ done.") (hidden prepare-) (register prepare- (Fn [&SQLite (Ptr CChar) &Stmt] Int) "SQLite3_prepare_c") - (doc prepare "prepares a SQL statement for repeated execution. - -Returns a `Stmt` handle that can be executed multiple times with different -parameters via [exec-prepared](#exec-prepared). - -When you are done with the statement, call [finalize-stmt](#finalize-stmt) -to release its resources. + (doc prepare "prepares a SQL statement for repeated execution via +[exec-prepared](#exec-prepared). Release with [finalize-stmt](#finalize-stmt) +when done. Example: ``` -(let [stmt (Result.unsafe-from-success - (SQLite3.prepare &db \"INSERT INTO t VALUES (?1, ?2);\"))] - (ignore (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @\"a\")])) - (ignore (SQLite3.exec-prepared &stmt &[(to-sqlite3 2) (to-sqlite3 @\"b\")])) - (SQLite3.finalize-stmt stmt)) +(match (SQLite3.prepare &db \"INSERT INTO t VALUES (?1, ?2);\") + (Result.Success stmt) + (do + (ignore (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @\"a\")])) + (SQLite3.finalize-stmt stmt)) + (Result.Error e) (IO.errorln &e)) ```") (defn prepare [db sql] (let [stmt (stmt-init) @@ -245,9 +237,7 @@ Example: (if (= res sql_ok) (Result.Success stmt) (Result.Error (errmsg db))))) (doc exec-prepared "executes a prepared statement with the given parameters. - -The statement is automatically reset after execution, so it can be called -again with new parameters without calling [reset-stmt](#reset-stmt).") +Automatically resets the statement afterward for reuse.") (defn exec-prepared [stmt p] (let [r (exec-prepared- stmt &(Array.copy-map &(fn [x] @@ -257,15 +247,12 @@ again with new parameters without calling [reset-stmt](#reset-stmt).") (Result.Success (SQLiteRes.to-array r)) (Result.Error (from-cstr (SQLiteRes.error r)))))) - (doc reset-stmt "resets a prepared statement so it can be executed again. - -This is called automatically by [exec-prepared](#exec-prepared), but can be -called manually if needed (e.g. to abort a partially-executed statement).") + (doc reset-stmt "manually resets a prepared statement and clears its bindings. +Called automatically by [exec-prepared](#exec-prepared).") (register reset-stmt (Fn [&Stmt] ()) "SQLite3_reset_stmt_c") - (doc finalize-stmt "finalizes a prepared statement, releasing its resources. - -After calling this, the statement must not be used again.") + (doc finalize-stmt "releases a prepared statement’s resources. Must not be +used afterward.") (register finalize-stmt (Fn [Stmt] ()) "SQLite3_finalize_stmt_c") (doc last-insert-rowid "returns the row ID of the last successful INSERT.") From bdc371e26bfd4e3d247699b0c89ad7daa070af7d Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Sun, 21 Jun 2026 18:17:09 +0200 Subject: [PATCH 4/5] Remove markdown fences from doc strings, tighten style --- sqlite3.carp | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/sqlite3.carp b/sqlite3.carp index 26ebb58..369df35 100644 --- a/sqlite3.carp +++ b/sqlite3.carp @@ -220,17 +220,7 @@ release with [finalize-stmt](#finalize-stmt).") (doc prepare "prepares a SQL statement for repeated execution via [exec-prepared](#exec-prepared). Release with [finalize-stmt](#finalize-stmt) -when done. - -Example: -``` -(match (SQLite3.prepare &db \"INSERT INTO t VALUES (?1, ?2);\") - (Result.Success stmt) - (do - (ignore (SQLite3.exec-prepared &stmt &[(to-sqlite3 1) (to-sqlite3 @\"a\")])) - (SQLite3.finalize-stmt stmt)) - (Result.Error e) (IO.errorln &e)) -```") +when done.") (defn prepare [db sql] (let [stmt (stmt-init) res (prepare- db (cstr sql) &stmt)] @@ -280,18 +270,8 @@ or DELETE.") (Result.Success _) (Result.Success ()) (Result.Error e) (Result.Error e))) - (doc with-transaction "executes `body` inside a transaction. If `body` -evaluates to a `Result.Error`, the transaction is rolled back; otherwise it is -committed. Returns the result of `body`, or the error from `begin`/`commit` -if those fail. - -Example: -``` -(SQLite3.with-transaction &db - (do - (ignore (SQLite3.query &db \"INSERT INTO t VALUES (1);\" &[])) - (SQLite3.query &db \"SELECT * FROM t;\" &[]))) -```") + (doc with-transaction "executes `body` inside a transaction. Rolls back on +`Result.Error`, commits on success.") (defmacro with-transaction [db body] (list 'let (array '__wtx_begin (list 'SQLite3.begin db)) From 1a12b6aba82cb1d1a607f6f1e811daefda501476 Mon Sep 17 00:00:00 2001 From: "carpentry-heartbeat[bot]" Date: Thu, 25 Jun 2026 06:09:49 +0200 Subject: [PATCH 5/5] Remove banner separator comments from test and C source --- sqlite3_helper.h | 6 ------ test/sqlite3.carp | 4 ---- 2 files changed, 10 deletions(-) diff --git a/sqlite3_helper.h b/sqlite3_helper.h index f784b28..bf0bb46 100644 --- a/sqlite3_helper.h +++ b/sqlite3_helper.h @@ -1,7 +1,5 @@ #include "sqlite3.h" -// --- BEGIN HELPERS --- - typedef struct { sqlite3* handle; } SQLite; @@ -145,8 +143,6 @@ char* SQLiteRes_error(SQLiteRes r) { return (char*)r.err; } -// --- END HELPERS --- - SQLite SQLite3_init() { SQLite res; res.handle = NULL; @@ -320,8 +316,6 @@ char* SQLite3_error_and_close(SQLite db) { return copy; } -// --- Prepared statement API --- - typedef struct { sqlite3_stmt* handle; } Stmt; diff --git a/test/sqlite3.carp b/test/sqlite3.carp index 505858a..0b54adc 100644 --- a/test/sqlite3.carp +++ b/test/sqlite3.carp @@ -305,10 +305,6 @@ r)) "blob with embedded nulls round-trips correctly") - ; ========================================================================= - ; Prepared statements - prepare / exec-prepared / finalize-stmt - ; ========================================================================= - (assert-true test (let [db (open-memory)] (let-do [r (SQLite3.prepare &db "SELECT 1;")]