default_config : Path -> Config
Conservative defaults for a conventional web application.
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.
Treat query as trusted, application-owned SQL. Never interpolate request
data or other untrusted values into it. Pass data values through params,
which uses SQLite parameter binding rather than manual escaping. Parameters
cannot represent identifiers or SQL syntax; choose dynamic identifiers and
clauses from an application-controlled allowlist before constructing a
query.
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.
default_query_limits : QueryLimits
Conservative defaults for ordinary request-scoped queries.
Open and validate a bounded database pool.
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 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.
Sqlite.Db :: # (opaque)
A host-owned SQLite connection pool, safe to retain in immutable context.
to_inspect : Db -> Str
A raw SQLite value. Most applications use derived record codecs instead.
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.
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.
from_bytes : List(U8) -> Blob
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.
Sqlite.ParamsEncoding :: # (opaque)
SQLite named-parameter encoding.
rename_field : ParamsEncoding, Str -> Str
encode_str : Str, ParamsState -> Try(ParamsState, QueryError)
encode_i64 : I64, ParamsState -> Try(ParamsState, QueryError)
encode_f64 : F64, ParamsState -> Try(ParamsState, QueryError)
encode_bytes : List(U8), ParamsState -> Try(ParamsState, QueryError)
encode_null : ParamsState -> 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.
Sqlite.RowEncoding :: # (opaque)
SQLite row encoding consumed by parser_for.
rename_field : RowEncoding, Str -> Str
parse_record_start : RowEncoding, RowState -> Try([Counted({ len : U64, rest : RowState }), Uncounted(RowState)], QueryError)
parse_bytes : RowEncoding, RowState -> Try({ value : List(U8), rest : RowState }, QueryError)
invalid_value : RowEncoding, 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(RowState),
Done(RowState),
],
QueryError,
)
parse_record_after_field : RowEncoding, RowState -> Try([Continue(RowState), Done(RowState)], QueryError)
skip_record_field : RowEncoding, RowState -> Try(RowState, QueryError)
Sqlite.Stmt :: # (opaque)
Represents a prepared statement that can be executed many times.
to_inspect : Stmt -> Str
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.
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.
to_inspect : Transaction -> Str
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),
]
TransactionMode : [Deferred, Immediate, Exclusive]
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.