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

Column names and raw rows from query_rows.

pub type Rows =
  @internal Rows

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

  • Immediate

    Take the write lock at BEGIN. Use this for any transaction that writes: it can’t fail halfway through because another connection wrote first.

  • Deferred

    Take locks as statements need them. Fine for read-only transactions.

  • Exclusive

    Take 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 bool(value: Bool) -> value.Value

A boolean, stored as the integer 0 or 1.

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.

✨ Search Document