Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ databases.
Because `open` and `query` return `Result` types, we could also use
combinators!

### Prepared statements

For a statement you run many times, prepare it once and reuse it. The
`with-prepared` macro handles the whole lifecycle: it prepares the statement,
binds it for the body, and finalizes it on every exit path — even when the body
short-circuits. The `params` macro builds the parameter array so you don't have
to wrap each value in `to-sqlite3` by hand.

```clojure
(let-do [db (Result.unsafe-from-success (SQLite3.open "db"))]
(ignore (SQLite3.query &db "CREATE TABLE mytable (i INT, s TEXT);" &[]))
(ignore
(SQLite3.with-prepared [stmt &db "INSERT INTO mytable VALUES (?1, ?2);"]
(do
(for [i 0 100]
(ignore (SQLite3.exec-prepared &stmt (SQLite3.params i @"row"))))
(Result.Success ()))))
(SQLite3.close db))
```

The body must evaluate to a `Result` (like [`with-transaction`](#usage)):
`with-prepared` returns the prepare error if the statement can't be prepared,
otherwise the `Result` the body produced. A failed prepare and an error the
body returns both surface as `Result.Error String`, so the caller can't tell
them apart by type — the same tradeoff as `with-transaction`.

For more information, check out [the
documentation](https://veitheller.de/sqlite3)!

Expand Down
22 changes: 22 additions & 0 deletions docs/SQLite3.Type.html
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,28 @@ <h3 id="str">

</p>
</div>
<div class="binder">
<a class="anchor" href="#to-sqlite3">
<h3 id="to-sqlite3">
to-sqlite3
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(Fn [SQLite3.Type] SQLite3.Type)
</p>
<pre class="args">
(to-sqlite3 t)
</pre>
<p class="doc">
<p>is the identity on values that are already a
<code>SQLite3.Type</code>, so pre-built <code>Null</code> and <code>Blob</code> values flow through
<a href="#params">params</a> next to primitives.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#to-sqlite3-internal">
<h3 id="to-sqlite3-internal">
Expand Down
179 changes: 169 additions & 10 deletions docs/SQLite3.html
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ <h3 id="SQLite">

</p>
</div>
<div class="binder">
<a class="anchor" href="#Stmt">
<h3 id="Stmt">
Stmt
</h3>
</a>
<div class="description">
meta-stub
</div>
<p class="sig">
a
</p>
<span>

</span>
<p class="doc">
<p>is an opaque prepared statement type. Prepare once with
<a href="#prepare">prepare</a>, execute with <a href="#exec-prepared">exec-prepared</a>, and
release with <a href="#finalize-stmt">finalize-stmt</a>.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#Type">
<h3 id="Type">
Expand Down Expand Up @@ -214,6 +236,48 @@ <h3 id="commit">

</p>
</div>
<div class="binder">
<a class="anchor" href="#exec-prepared">
<h3 id="exec-prepared">
exec-prepared
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(Fn [(Ref Stmt a), (Ref (Array SQLite3.Type) b)] (Result (Array (Array SQLite3.Type)) String))
</p>
<pre class="args">
(exec-prepared stmt p)
</pre>
<p class="doc">
<p>executes a prepared statement with the given parameters.
Automatically resets the statement afterward for reuse.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#finalize-stmt">
<h3 id="finalize-stmt">
finalize-stmt
</h3>
</a>
<div class="description">
external
</div>
<p class="sig">
(Fn [Stmt] ())
</p>
<span>

</span>
<p class="doc">
<p>releases a prepared statement’s resources. Must not be
used afterward.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#last-insert-rowid">
<h3 id="last-insert-rowid">
Expand Down Expand Up @@ -255,6 +319,55 @@ <h3 id="open">

</p>
</div>
<div class="binder">
<a class="anchor" href="#params">
<h3 id="params">
params
</h3>
</a>
<div class="description">
macro
</div>
<p class="sig">
Macro
</p>
<pre class="args">
(params :rest args)
</pre>
<p class="doc">
<p>wraps each argument in <code>to-sqlite3</code> and returns a reference to
the resulting parameter array, ready to hand to <a href="#query">query</a> or
<a href="#exec-prepared">exec-prepared</a>.</p>
<p><code>(SQLite3.params id @&quot;name&quot;)</code> expands to
<code>&amp;[(to-sqlite3 id) (to-sqlite3 @&quot;name&quot;)]</code>, removing the per-argument
<code>to-sqlite3</code> ceremony. Values that are already a <code>SQLite3.Type</code> — such as
<code>(SQLite3.Type.Null)</code> or a <code>Blob</code> — pass straight through, so they can be mixed
in with primitives.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#prepare">
<h3 id="prepare">
prepare
</h3>
</a>
<div class="description">
defn
</div>
<p class="sig">
(Fn [(Ref SQLite a), (Ref String b)] (Result Stmt String))
</p>
<pre class="args">
(prepare db sql)
</pre>
<p class="doc">
<p>prepares a SQL statement for repeated execution via
<a href="#exec-prepared">exec-prepared</a>. Release with <a href="#finalize-stmt">finalize-stmt</a>
when done.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#query">
<h3 id="query">
Expand All @@ -277,6 +390,27 @@ <h3 id="query">

</p>
</div>
<div class="binder">
<a class="anchor" href="#reset-stmt">
<h3 id="reset-stmt">
reset-stmt
</h3>
</a>
<div class="description">
external
</div>
<p class="sig">
(Fn [(Ref Stmt a)] ())
</p>
<span>

</span>
<p class="doc">
<p>manually resets a prepared statement and clears its bindings.
Called automatically by <a href="#exec-prepared">exec-prepared</a>.</p>

</p>
</div>
<div class="binder">
<a class="anchor" href="#rollback">
<h3 id="rollback">
Expand All @@ -297,6 +431,39 @@ <h3 id="rollback">

</p>
</div>
<div class="binder">
<a class="anchor" href="#with-prepared">
<h3 id="with-prepared">
with-prepared
</h3>
</a>
<div class="description">
macro
</div>
<p class="sig">
Macro
</p>
<pre class="args">
(with-prepared binding body)
</pre>
<p class="doc">
<p>prepares <code>sql</code> on the database <code>db</code>, binds the resulting
statement to <code>stmt</code>, evaluates <code>body</code>, and finalizes the statement on every
exit path — including when <code>body</code> short-circuits.</p>
<p>Like the body of <a href="#with-transaction">with-transaction</a>, <code>body</code> must evaluate to
a <code>Result</code>. The whole form returns the prepare error if the statement can’t be
prepared, otherwise the <code>Result</code> that <code>body</code> produced. A failed prepare and an
error the body returns are both <code>Result.Error String</code>, so a caller can’t tell
them apart by type.</p>
<pre><code>(SQLite3.with-prepared [stmt &amp;db &quot;INSERT INTO t VALUES (?1, ?2)&quot;]
(do
(for [i 0 100]
(ignore (SQLite3.exec-prepared &amp;stmt (SQLite3.params i @&quot;row&quot;))))
(Result.Success ())))
</code></pre>

</p>
</div>
<div class="binder">
<a class="anchor" href="#with-transaction">
<h3 id="with-transaction">
Expand All @@ -313,16 +480,8 @@ <h3 id="with-transaction">
(with-transaction db body)
</pre>
<p class="doc">
<p>executes <code>body</code> inside a transaction. If <code>body</code>
evaluates to a <code>Result.Error</code>, the transaction is rolled back; otherwise it is
committed. Returns the result of <code>body</code>, or the error from <code>begin</code>/<code>commit</code>
if those fail.</p>
<p>Example:</p>
<pre><code>(SQLite3.with-transaction &amp;db
(do
(ignore (SQLite3.query &amp;db &quot;INSERT INTO t VALUES (1);&quot; &amp;[]))
(SQLite3.query &amp;db &quot;SELECT * FROM t;&quot; &amp;[])))
</code></pre>
<p>executes <code>body</code> inside a transaction. Rolls back on
<code>Result.Error</code>, commits on success.</p>

</p>
</div>
Expand Down
54 changes: 53 additions & 1 deletion sqlite3.carp
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,50 @@ or DELETE.")
(list 'SQLite3.rollback db))
(list 'Result.Error '__wtx_cerr))
(list 'Result.Success '_)
(list 'Result.Success '__wtx_val))))))))
(list 'Result.Success '__wtx_val)))))))

(doc with-prepared "prepares `sql` on the database `db`, binds the resulting
statement to `stmt`, evaluates `body`, and finalizes the statement on every
exit path — including when `body` short-circuits.

Like the body of [with-transaction](#with-transaction), `body` must evaluate to
a `Result`. The whole form returns the prepare error if the statement can’t be
prepared, otherwise the `Result` that `body` produced. A failed prepare and an
error the body returns are both `Result.Error String`, so a caller can’t tell
them apart by type.

```
(SQLite3.with-prepared [stmt &db \"INSERT INTO t VALUES (?1, ?2)\"]
(do
(for [i 0 100]
(ignore (SQLite3.exec-prepared &stmt (SQLite3.params i @\"row\"))))
(Result.Success ())))
```")
(defmacro with-prepared [binding body]
(let [stmt (car binding)
db (cadr binding)
sql (car (cddr binding))]
(list 'match
(list 'SQLite3.prepare db sql)
(list 'Result.Error '__wp_err)
(list 'Result.Error '__wp_err)
(list 'Result.Success stmt)
(list 'let-do
(array '__wp_res body)
(list 'SQLite3.finalize-stmt stmt)
'__wp_res))))

(doc params "wraps each argument in `to-sqlite3` and returns a reference to
the resulting parameter array, ready to hand to [query](#query) or
[exec-prepared](#exec-prepared).

`(SQLite3.params id @\"name\")` expands to
`&[(to-sqlite3 id) (to-sqlite3 @\"name\")]`, removing the per-argument
`to-sqlite3` ceremony. Values that are already a `SQLite3.Type` — such as
`(SQLite3.Type.Null)` or a `Blob` — pass straight through, so they can be mixed
in with primitives.")
(defmacro params [:rest args]
(list 'ref (collect-into (map (fn [a] (list 'to-sqlite3 a)) args) array))))

(definterface to-sqlite3 (Fn [a] SQLite3.Type))

Expand All @@ -331,3 +374,12 @@ or DELETE.")
(defmodule String
(defn to-sqlite3 [s] (SQLite3.Type.Text s))
(implements to-sqlite3 String.to-sqlite3))

(defmodule SQLite3
(defmodule Type
(doc to-sqlite3 "is the identity on values that are already a
`SQLite3.Type`, so pre-built `Null` and `Blob` values flow through
[params](#params) next to primitives.")
(sig to-sqlite3 (Fn [SQLite3.Type] SQLite3.Type))
(defn to-sqlite3 [t] t)
(implements to-sqlite3 SQLite3.Type.to-sqlite3)))
17 changes: 17 additions & 0 deletions test/memory.carp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,23 @@
(ignore (exec-prepared &stmt &[(to-sqlite3 1)]))
(finalize-stmt stmt))
(Result.Error _) ())
; with-prepared must finalize on the success path...
(ignore
(with-prepared
[stmt &db "INSERT INTO t VALUES (?1, ?2, ?3);"]
(do
(for [i 0 20]
(ignore
(exec-prepared &stmt
(params i
@"with-prepared text"
(SQLite3.Type.Blob [5b 6b 0b 7b])))))
(Result.Success ()))))
; ...and when the body short-circuits with an error.
(ignore
(with-prepared
[stmt &db "INSERT INTO t VALUES (?1, ?2, ?3);"]
(the (Result () String) (Result.Error @"body short-circuited"))))
(close db))))

(deftest test
Expand Down
Loading
Loading