Skip to content

Statement

A compiled SQL statement that can be run repeatedly with different parameters.

Returned by Connection.prepare.

Parameter binding

A statement is prepared from a template, and a template says two different things about the values in it.

An interpolation -- $name or ${...} -- carries a value the program already has. It is bound when the statement is prepared and stays bound for the life of the statement.

A parameter -- ${#name} or ${#0} -- is a hole left for each call to fill. A named parameter is filled by a keyword argument and a numbered one by the positional argument in that place; a hole used twice is one parameter, filled once.

let cutoff = "2023-01-01"
conn.prepare t"UPDATE users SET status = ${#status} WHERE created < $cutoff"
  do |stmt|
    stmt.execute status: "archived"

Filling is exhaustive: every parameter must be supplied on every call, and an argument naming no parameter raises. To fix some values ahead of time, build the template in a function that interpolates them.

Neither form ever becomes SQL text -- only the template's literal text does -- so an interpolated value cannot alter the statement it appears in, whatever it contains.

Rejected at prepare

Condition Why
The SQL is a Str A Str cannot say which of its text is data
A :name or ? in the template's own text The binder cannot see it, so it would step as NULL
A quoted interpolation, as in '$name' Quoting buries the value in a literal, binding none
A specification, as in ${#0:>10} A bound value is never rendered, so it takes none

The quoting case is worth naming, because it is the habit a plain string teaches: t"... WHERE name = '$name'" needs no quotes, since $name is bound rather than pasted. Written with them, it raises rather than doing the wrong thing quietly.

Accepted value types

Do type SQLite type Example
nil NULL stmt.execute value: nil
Bool INTEGER stmt.execute active: true
Int INTEGER stmt.execute id: 42
Float REAL stmt.execute price: 19.99
Str TEXT stmt.execute name: "Alice"
Bin BLOB stmt.execute data: b"\x01\x02\x03"

A Bool is stored as 0 or 1. The same types are accepted for an interpolated value.

Concurrent use

Only one query can be active on a statement at a time. Starting another query, or executing the statement, invalidates the iterator the previous query returned, which then raises a concurrency error on use.

open "mydb.sqlite" do |conn|
  conn.prepare t"SELECT * FROM users" do |stmt|
    let rows = stmt.query()
    let rows2 = stmt.query()
    # rows has been invalidated at this point

Methods

close()

Closes the statement and releases its resources.

execute ...args -> Int

Runs the statement and returns the number of rows it affected.

Outside a transaction, busy errors are retried according to the connection's retry settings.

Parameters

NameTypeDescription
...args Values filling the statement's parameters. See Parameter binding.

Returns

The number of rows affected.

Errors

Exception Condition
MissingPosError A numbered parameter unfilled
MissingKeyError A named parameter unfilled
UnexpectedPosError A positional argument unused
UnexpectedKeyError A keyword argument unused

Example

conn.prepare t"UPDATE users SET status = ${#status} WHERE created < ${#date}"
  do |stmt|
    let affected = stmt.execute status: "archived" date: "2023-01-01"
    echo "Archived $affected users"

query ...args -> Iter[Row]

Runs the statement and iterates its result rows.

Outside a transaction, a busy error while fetching the next row is retried according to the connection's retry settings.

Parameters

NameTypeDescription
...args Values filling the statement's parameters. See Parameter binding.

Returns

An Iter yielding Rows. Only the most recently yielded row stays valid -- see Row validity.

Errors

The same as execute.

Example

conn.prepare t"SELECT * FROM users WHERE age > ${#min_age}" do |stmt|
  for row = stmt.query min_age: 18
    echo "$(row["name"]) is $(row["age"]) years old"