Sqlite

Sqlite :: # (opaque)

Execute bounded SQLite statements with statically dispatched parameter encoders and row parsers.

Query parameters are ordinary flat records. Their field names map to SQLite parameters with a leading colon, so { status } binds :status. Query results are selected by the expected Roc type. Structural records derive their parser automatically when their fields use supported SQLite types.

Todo : { id : I64, task : Str }

todos : List(Todo)
db = Sqlite.open!(Sqlite.default_config(db_path))?
todos = Sqlite.query_many!({
    db,
    query: "SELECT id, task FROM todos WHERE status = :status",
    params: { status: "open" },
    limits: Sqlite.default_query_limits,
})?

SQLite INTEGER maps strictly to I64, REAL to F64, TEXT to Str, and BLOB to Sqlite.Blob. No text/number coercions are performed. TODO: Add derived nullable fields once the compiler can compose their parser errors through a platform-defined encoding.

A Db owns a bounded host connection pool. Statements are immutable logical query descriptors; each execution briefly leases a connection and a cached native prepared statement. Executions are safe to use concurrently. Their final ARC release resets the native statement and returns the connection on every success and error path.

default_config : Path -> Config

Conservative defaults for a conventional web application.

open! : Config => Try(Db, QueryError)

Open and validate a bounded database pool.

prepare! : { db : Db, query : Str } => Try(Stmt, QueryError)

Prepare a reusable logical statement and cache its result-column metadata.

execute! : { db : Db, query : Str, params : params } => Try({  }, QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
    ]

Execute a one-shot statement that must not return rows.

query! : { db : Db, query : Str, params : params, limits : QueryLimits } => Try(row, QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
        row.parser_for : RowEncoding -> RowState -> Try({ value : row, rest : RowState }, QueryError),
    ]

Execute a one-shot query returning exactly one inferred result value.

query_many! : { db : Db, query : Str, params : params, limits : QueryLimits } => Try(List(row), QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
        row.parser_for : RowEncoding -> RowState -> Try({ value : row, rest : RowState }, QueryError),
    ]

Execute a one-shot query returning a list of inferred result values.

begin! : Db, TransactionMode => Try(Transaction, QueryError)

Begin a transaction on one connection leased from the pool.

errcode_to_str : [
    Abort,
    AuthDenied,
    Busy,
    CanNotOpen,
    Constraint,
    Corrupt,
    Done,
    Empty,
    Error,
    Format,
    Full,
    IOErr,
    Internal,
    Interrupt,
    Locked,
    Mismatch,
    Misuse,
    NoLFS,
    NoMem,
    NotADatabase,
    NotFound,
    Notice,
    OutOfRange,
    Perm,
    Protocol,
    ReadOnly,
    Row,
    Schema,
    TooBig,
    Warning,
    Unknown(
        I64,
    ),
] -> Str

Convert an ErrCode to a display string.

Config : {
    path : Path,
    max_connections : U64,
    acquire_timeout_ms : U64,
    busy_timeout_ms : U64,
    max_cached_statements_per_connection : U64,
    journal_mode : JournalMode,
    synchronous : Synchronous,
}

Configuration for one bounded host-owned connection pool. max_connections is 1-64, both timeouts are at most ten minutes, and each connection caches at most 256 native statements.

JournalMode : [Delete, Wal]

SQLite rollback-journal policy applied and verified on every connection.

Synchronous : [Full, Normal]

Full is the durable default. Normal trades power-loss durability for substantially faster commits while preserving database consistency.

Db

Sqlite.Db :: # (opaque)

A host-owned SQLite connection pool, safe to retain in immutable context.

Value : [
    Null,
    Real(F64),
    Integer(I64),
    String(Str),
    Bytes(List(U8)),
]

A raw SQLite value. Most applications use derived record codecs instead.

Binding : {
    name : Str,
    value : Value,
}

A raw named binding used internally by the derived parameter encoder.

QueryLimits : {
    max_bytes : U64,
    max_rows : U64,
    timeout_ms : U64,
}

Bounds on a materialized query. max_bytes covers the host-side SQLite value storage handed to Roc; max_rows also bounds per-row record overhead after decoding. timeout_ms interrupts SQLite virtual-machine execution; pool acquisition has its own database-level timeout.

ValueType : [Blob, Integer, Null, Real, Text]

SQLite's five runtime storage classes, used in decode diagnostics.

QueryError : [
    DuplicateColumn(Str),
    ExpectedSingleColumn({ actual : U64 }),
    InvalidValue({ column : Str }),
    MalformedRow,
    MissingRequiredField(Str),
    MultipleValuesForParameter,
    NestedParameterRecord,
    NoRowsReturned,
    ParameterValueMissing(Str),
    ParameterValueOutsideRecord,
    PoolSaturated,
    QueryTimedOut,
    ResourceSaturated,
    ResultTooLarge({ max_bytes : U64 }),
    RowsReturnedUseQueryInstead,
    SqliteErr(ErrCode, Str),
    TooManyRows({ max_rows : U64 }),
    TooManyRowsReturned,
    ConcurrentTransactionUse,
    TransactionFinished,
    UnconsumedColumns,
    UnexpectedType({ actual : ValueType, column : Str, expected : ValueType }),
]

Every failure produced by SQLite operations and their derived codecs.

Blob

Sqlite.Blob :: # (opaque)

A SQLite BLOB. The nominal wrapper distinguishes blobs from ordinary Roc lists for generic parsing and encoding.

TODO: Use Blob inside mixed result records once the compiler composes a custom nominal parser's errors with sibling derived fields.

parser_for : RowEncoding -> RowState -> Try(
    {
        rest : RowState,
        value : Blob,
    },
    [
        MalformedRow,
        UnconsumedColumns,
        ..[
            ConcurrentTransactionUse,
            DuplicateColumn(
                Str,
            ),
            ExpectedSingleColumn(
                {
                    actual : U64,
                },
            ),
            InvalidValue(
                {
                    column : Str,
                },
            ),
            MissingRequiredField(
                Str,
            ),
            MultipleValuesForParameter,
            NestedParameterRecord,
            NoRowsReturned,
            ParameterValueMissing(
                Str,
            ),
            ParameterValueOutsideRecord,
            PoolSaturated,
            QueryTimedOut,
            ResourceSaturated,
            ResultTooLarge(
                {
                    max_bytes : U64,
                },
            ),
            RowsReturnedUseQueryInstead,
            SqliteErr(
                ErrCode,
                Str,
            ),
            TooManyRows(
                {
                    max_rows : U64,
                },
            ),
            TooManyRowsReturned,
            TransactionFinished,
            UnexpectedType(
                {
                    actual : ValueType,
                    column : Str,
                    expected : ValueType,
                },
            ),
        ],
    ],
)
encoder_for : encoding -> Blob, state -> Try(state, err)
    where [
        encoding.encode_bytes : List(U8), state -> Try(state, err),
    ]
ParamsState : {
    bindings : List(Binding),
    field : [Field(Str), NoField],
    value : [Encoded(Value), NoValue],
}

State used by the derived parameter-record encoder.

ParamsEncoding

Sqlite.ParamsEncoding :: # (opaque)

SQLite named-parameter encoding.

encode_record : ParamsState, U64, (ParamsState, (ParamsState, Str, (ParamsState -> Try(ParamsState, QueryError)) -> Try(ParamsState, QueryError)) -> Try(ParamsState, QueryError)) -> Try(ParamsState, QueryError)
RowState : {
    columns : List(Str),
    current : [Current({ name : Str, value : Value }), NoCurrent],
    next : U64,
    values : List(Value),
}

Pure state used by a compiler-derived SQLite row parser.

RowEncoding

Sqlite.RowEncoding :: # (opaque)

SQLite row encoding consumed by parser_for.

parse_str : RowEncoding, RowState -> Try({ value : Str, rest : RowState }, QueryError)
parse_i64 : RowEncoding, RowState -> Try({ value : I64, rest : RowState }, QueryError)
parse_f64 : RowEncoding, RowState -> Try({ value : F64, rest : RowState }, QueryError)
parse_record_field : RowEncoding, FieldNames(_shape), RowState -> Try(
    [
        Field({ field : FieldName(_shape), rest : RowState }),
        TryField({ name : Str, rest : RowState }),
        TryFieldCaseless({ name : Str, rest : RowState }),
        Continue({ rest : RowState }),
        Done({ rest : RowState }),
    ],
    QueryError,
)

Stmt

Sqlite.Stmt :: # (opaque)

Represents a prepared statement that can be executed many times.

execute! : Stmt, params => Try({  }, QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
    ]

Execute a prepared statement that must not return rows.

query! : Stmt, params, QueryLimits => Try(row, QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
        row.parser_for : RowEncoding -> RowState -> Try({ value : row, rest : RowState }, QueryError),
    ]

Decode exactly one row as the expected result type.

query_many! : Stmt, params, QueryLimits => Try(List(row), QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
        row.parser_for : RowEncoding -> RowState -> Try({ value : row, rest : RowState }, QueryError),
    ]

Decode all rows as the expected list item type.

Transaction

Sqlite.Transaction :: # (opaque)

A transaction pinned to one pooled connection. Dropping its final Roc reference before commit! or rollback! rolls it back automatically. Operations within one transaction are sequential; overlapping use returns ConcurrentTransactionUse.

execute! : Transaction, { query : Str, params : params } => Try({  }, QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
    ]
query! : Transaction, { query : Str, params : params, limits : QueryLimits } => Try(row, QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
        row.parser_for : RowEncoding -> RowState -> Try({ value : row, rest : RowState }, QueryError),
    ]
query_many! : Transaction, { query : Str, params : params, limits : QueryLimits } => Try(List(row), QueryError)
    where [
        params.encoder_for : ParamsEncoding -> params, ParamsState -> Try(ParamsState, QueryError),
        row.parser_for : RowEncoding -> RowState -> Try({ value : row, rest : RowState }, QueryError),
    ]
ErrCode : [
    Error,
    Internal,
    Perm,
    Abort,
    Busy,
    Locked,
    NoMem,
    ReadOnly,
    Interrupt,
    IOErr,
    Corrupt,
    NotFound,
    Full,
    CanNotOpen,
    Protocol,
    Empty,
    Schema,
    TooBig,
    Constraint,
    Mismatch,
    Misuse,
    NoLFS,
    AuthDenied,
    Format,
    OutOfRange,
    NotADatabase,
    Notice,
    Warning,
    Row,
    Done,
    Unknown(I64),
]

Represents SQLite result codes.