Guide 43 of 47
Database
SQLite, SQL transpilation, query, execute and connection management.
On this page
std::database exports Database, DbError, DbErrorKind, SQL and Blob.
Opening, executing and querying return Result. Use ? in fallible functions,
or unwrap() in small examples.
Bound queries¶
use std::database::Database
let db = Database::open("sqlite://:memory:").unwrap()
defer db.close()
db.execute("CREATE TABLE stock (id BIGINT PRIMARY KEY, available BIGINT NOT NULL)", []).unwrap()
db.execute("INSERT INTO stock VALUES (?, ?)", [1, 20]).unwrap()
let changed = db.execute(
"UPDATE stock SET available = available - ? WHERE id = ? AND available >= ?",
[3, 1, 3],
).unwrap()
assert(changed == 1)
let rows = db.query("SELECT available FROM stock WHERE id = ?", [1]).unwrap()
assert(rows[0]["available"] == 17)Values stay bound to the driver; never concatenate user input into SQL.
Portable ? placeholders and quoted identifiers are compiled for the connected
driver. PostgreSQL binds parameters and returns actual affected-row counts,
including commands with RETURNING. Use query when the returned rows matter.
Rows are materialized; this API is synchronous and does not expose a streaming
cursor.
PostgreSQL connections and pooling¶
use std::database::Database
let db = Database::open(
"postgres://user:password@localhost:5432/erp?pool_size=8&pool_timeout=5000",
).unwrap()
defer db.close()
assert(db.driver() == "postgres")
db.ping().unwrap()pool_size limits PostgreSQL sessions. pool_timeout is the connection
acquisition timeout in milliseconds. A transaction retains its leased
connection until completion. Other requests can acquire other available leases.
A connection opened without a pool owns a single session.
Repeated opens of the same URL share a process resource. Closing one shared handle closes that resource for its other ordinary aliases; long-running applications should own the pool at application scope.
The PostgreSQL driver currently connects with NoTls. TLS/SCRAM deployment
configuration must not be inferred from support for parameter binding or pooling.
The new server execution, pool and migration paths have source regressions;
runtime/database validation remains pending in this development pass.
Callback transactions¶
use std::database::{Database, DbError}
fn reserve(db: Database, id: int, quantity: int) -> Result<int, DbError> {
let result = db.transaction(|tx| -> Result<int, DbError> {
assert(tx.is_transaction())
return tx.execute(
"UPDATE stock SET available = available - ? WHERE id = ? AND available >= ?",
[quantity, id, quantity],
)
})?
return result as Result<int, DbError>
}Use the callback handle for every operation belonging to the transaction.
It is a distinct handle to the pinned session. A panic or returned Result::Err
rolls back; a successful return commits. The outer Result describes transaction
setup/finalization and preserves the callback's exact value, so a fallible
callback produces a nested result.
A dedicated parent connection rejects commands while its callback is active.
A pool may serve independent operations through other leases; those operations
do not join the callback's transaction. Nested transactions are unsupported.
Raw BEGIN, COMMIT, ROLLBACK and savepoint control inside the callback are
rejected because the callback owns completion.
A callback handle expires after return. Returning or storing it does not extend
the transaction: later queries return TransactionFailed.
Closing the callback handle aborts it without closing its owner.
Database.driver() returns the driver name even after close.
Database.is_transaction() is true only for an active callback handle.
Structured errors¶
use std::database::{DbError, DbErrorKind}
fn transient(error: DbError) -> bool {
return error.is_retryable()
}
fn duplicate(error: DbError) -> bool {
return error.is(DbErrorKind::UniqueViolation)
}Constraint failures retain their categories: UniqueViolation,
ForeignKeyViolation, NotNullViolation, CheckViolation and
ConstraintViolation. PostgreSQL and MySQL also classify transient
SerializationFailure, Deadlock and LockTimeout errors.
is_retryable() identifies those transient categories. Retrying requires
re-running the whole transaction with idempotent application effects.
A generic TransactionFailed, SQL syntax error or uniqueness violation is
not automatically retryable. Messages and driver identity are available through
message() and driver().
Exact decimals and binary data¶
ORM models now accept native decimal/bigdecimal fields without a manual
codec: numeric storage defaults to DECIMAL(28,2) and DECIMAL(38,2), respectively.
Generated bindings use canonical decimal text and validate the destination
precision/scale. Query operands retain their own precision. Explicit Text
storage preserves the native value on SQLite but does not provide numeric SQL
semantics. For raw Database parameter arrays, encode decimal values explicitly
with to_plain_string(); raw result rows remain driver scalars and require
decimal::parse/bigdecimal::parse when no model decoder is involved.
PostgreSQL NUMERIC(p,s) accepts canonical decimal strings as bound values and
returns decimal strings without a floating-point conversion. The driver rejects
a floating-point parameter targeting NUMERIC. An ORM codec can provide a
nominal domain over this exact physical value.
SQLite NUMERIC affinity can convert values to floating point. Exact numeric
ORM schemas and migration snapshots therefore require PostgreSQL; text-backed
decimal codecs remain appropriate for exact storage without SQL arithmetic.
Blob::from_bytes([0, 255, 65]) builds opaque binary data. Bind blob.unwrap();
empty bytes remain distinct from NULL and text.
Schema migrations¶
Configure the schema entry in zolo.toml:
[database]
schema = "src/schema.zolo"
dialect = "postgres"
migrations = "migrations"
url_env = "DATABASE_URL"zolo db generate initial
zolo db status
zolo db migrate
zolo db check
zolo db rollback 1Generation reads compiler/ORM schema metadata and writes reviewable portable SQL
plus deterministic snapshots. It does not run the application or connect to the
database. Review up.sql and down.sql before applying.
PostgreSQL execution uses a dedicated session, ignores application pool options,
and obtains a transaction-scoped advisory lock for the current schema. Another
runner receives an explicit conflict. DDL, schema verification and checksum
history commit together; failures roll back. check coordinates with the same
lock. baseline adopts an existing matching schema with no prior history.
Plans support tables, columns, defaults/nullability, reviewed type changes,
primary/unique/foreign keys and supported indexes. Foreign keys are created after
their referenced keys/indexes, including cyclic relationships. Generated integer
keys carry an explicit marker that compiles to BIGINT GENERATED BY DEFAULT AS IDENTITY; an ordinary integer primary key is not inferred to be generated.
Numeric snapshots retain precision and scale. Exact numeric defaults are emitted
as text literals that PostgreSQL casts, avoiding float-based SQL reformatting.
--allow-destructive is required for drops and type changes. Adding a required
column to an existing table needs a SQL default or a staged nullable/backfill
migration. Dropped required columns must be restorable on rollback.
--allow-rebuild remains SQLite-only.
The PostgreSQL snapshot contract covers the current schema with ASCII identifiers of at most 63 bytes. It fails explicitly on unsupported features such as custom triggers, views, partitions, inheritance, row policies, generated expressions, custom sequence options, deferrable constraints, custom collations or unsupported index contracts. Constraint names must match the reviewed generated names; baseline reports expected and actual names for an unmanaged schema. MySQL migration generation/execution is not implemented.
Standalone SQL rendering¶
use std::database::SQL
let postgres = SQL::compile(
"SELECT id FROM stock WHERE available >= ? ORDER BY id LIMIT 10",
"postgres",
).unwrap()
print(postgres)Rendering a dialect does not imply that its driver, migrations or every SQL feature is implemented. The API declaration in database.zolo is the complete public surface.
DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.