glite
glite: a SQLite driver for Gleam on the Erlang VM.
One connection is one actor that owns the SQLite handle and runs every
call on it, one at a time. Share a Connection between processes freely:
calls queue up, and a transaction runs as one call, so nothing else can
mix into it.
import glite
import glite/config
import glite/decode
pub fn main() {
let assert Ok(conn) = glite.connect(config.new("app.db"))
let assert Ok(Nil) =
glite.exec_script(conn, "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
let assert Ok(id) =
glite.query_one(
conn,
"INSERT INTO users (name) VALUES (?) RETURNING id",
[glite.text("Alice")],
{
use id <- decode.element(0, decode.int)
decode.success(id)
},
)
let assert Ok(Nil) = glite.disconnect(conn)
}
Types
What backup wrote.
pub type Backup {
Backup(path: String, bytes: Int)
}
Constructors
-
Backup(path: String, bytes: Int)
A connection to one SQLite database.
pub opaque type Connection
A message to a connection’s actor. Use it to name a supervised
connection: process.new_name("db") gives a Name(glite.Message).
pub type Message =
@internal Message
A query parameter.
pub type Param =
value.Value
Why a transaction did not commit.
pub type TransactionError(e) {
RolledBack(e)
Database(error.Error)
}
Constructors
-
RolledBack(e)The callback returned
Error(e); everything it did was rolled back. -
Database(error.Error)The database or the connection failed; nothing was committed.
How a transaction takes its locks.
pub type TransactionMode {
Immediate
Deferred
Exclusive
}
Constructors
-
ImmediateTake the write lock at BEGIN. Use this for any transaction that writes: it can’t fail halfway through because another connection wrote first.
-
DeferredTake locks as statements need them. Fine for read-only transactions.
-
ExclusiveTake the write lock at BEGIN and keep readers out (outside WAL mode).
Values
pub fn backup(
conn: Connection,
to path: String,
) -> Result(Backup, error.Error)
Write a consistent copy of the database to path with VACUUM INTO,
check the copy with PRAGMA integrity_check, and fsync it. path must
not exist yet.
Only the copy runs on the connection, within call_timeout_ms; writers
wait while it is made. The check and the fsync run in the calling process
afterwards, so they never hold up the connection, and they take as long as
the database’s size needs. A copy that fails, is interrupted, or doesn’t
pass the check is deleted, so the same path can be tried again.
Never pass a path that came from user input: the copy is written wherever the VM can write.
pub fn blob(value: BitArray) -> value.Value
pub fn connect(
config: config.Config,
) -> Result(Connection, error.Error)
Open a database and start its actor, linked to the calling process. Every
setting in the config is checked after opening; if SQLite didn’t apply
one, such as WAL mode, this returns PragmaMismatch.
pub fn disconnect(conn: Connection) -> Result(Nil, error.Error)
Close the database and stop the actor. Closing a connection that is
already closed returns Ok(Nil). Stopping a supervised connection makes
its supervisor start a new one; stop the supervisor instead.
pub fn exec(
conn: Connection,
sql: String,
params: List(value.Value),
) -> Result(Int, error.Error)
Run one statement and return the number of rows it changed: always 0 for statements other than INSERT, UPDATE, DELETE and REPLACE.
The SQL must be one statement, with exactly as many parameters as it has placeholders.
pub fn exec_many(
conn: Connection,
sql: String,
params: List(List(value.Value)),
) -> Result(Int, error.Error)
Run one statement once per parameter list, all or nothing: if one run fails, the runs before it are rolled back. Returns the rows changed in total.
pub fn exec_script(
conn: Connection,
sql: String,
) -> Result(Nil, error.Error)
Run a script of several statements, without parameters, such as a schema migration.
pub fn float(value: Float) -> value.Value
pub fn int(value: Int) -> value.Value
pub fn named(name: process.Name(Message)) -> Connection
A connection to the supervised actor registered under name. Calls
return ConnectionClosed while no actor is registered.
pub fn null() -> value.Value
pub fn nullable(
value: option.Option(a),
to_param: fn(a) -> value.Value,
) -> value.Value
None becomes NULL; Some(x) becomes to_param(x).
pub fn query(
conn: Connection,
sql: String,
params: List(value.Value),
decoder: decode.RowDecoder(a),
) -> Result(List(a), error.Error)
Run a query and decode every row. More than max_rows rows is an error.
pub fn query_one(
conn: Connection,
sql: String,
params: List(value.Value),
decoder: decode.RowDecoder(a),
) -> Result(a, error.Error)
Run a query that returns exactly one row, and decode it. No rows, or more
than one, is a Decode error. It stops reading at the second row, so it
is cheap even on a query that matches many.
pub fn query_rows(
conn: Connection,
sql: String,
params: List(value.Value),
) -> Result(Rows, error.Error)
Run a query and return its column names and raw rows.
pub fn set_user_version(
conn: Connection,
version: Int,
) -> Result(Nil, error.Error)
Set the database’s user_version.
pub fn supervised(
config: config.Config,
name: process.Name(Message),
) -> supervision.ChildSpecification(Connection)
A child specification for a supervisor. The connection’s actor registers
under name; get a Connection for it with named. If the actor
crashes, the supervisor restarts it, which opens the database again.
let db_name = process.new_name("db")
let assert Ok(_) =
static_supervisor.new(static_supervisor.OneForOne)
|> static_supervisor.add(glite.supervised(config, db_name))
|> static_supervisor.start
let conn = glite.named(db_name)
pub fn text(value: String) -> value.Value
pub fn transaction(
conn: Connection,
mode: TransactionMode,
body: fn(tx.Tx) -> Result(a, e),
) -> Result(a, TransactionError(e))
Run body in a transaction: commit if it returns Ok, roll back if it
returns Error or panics. A panic is rolled back and then re-raised in
the calling process.
The body runs inside the connection’s actor, so no other caller’s
statements can mix into the transaction. Use the Tx it gets, not the
Connection: calling the connection from inside the body panics.
The whole transaction must finish within call_timeout_ms. If it
doesn’t, its running statement is interrupted, it is rolled back, and the
result is Database(Timeout(_)).
A timeout can interrupt SQL but nothing else. A body that waits for a
message, loops, or calls a slow service keeps the connection busy until it
returns, so keep the body to statements on the Tx and quick Gleam code,
and never call another glite connection from it.
pub fn user_version(conn: Connection) -> Result(Int, error.Error)
The database’s user_version, for schema migrations.