Expand description
The execution layer’s traits, owned by no driver.
Layer 1 builds a statement — Query::build() hands back (String, Vec<Value>), synchronously, with no driver in the loop. This crate is
everything between that pair and a mapped Rust value coming back out of a
real database, expressed as traits a backend crate implements:
Executor— object-safe, three methods,&self. A pool, a connection and aTransactionall implement it, so&dyn Executoris the type application code, generated models and hooks pass around.Execute— the ergonomic verbs, blanket-implemented on everyQuery:q.fetch_all(&db),q.fetch_one(&db),q.execute(&db). Tracing (featuretracing) lives here, in the one funnel every backend flows through.TransactionandBegin— an owned, lifetime-free transaction that consumes itself on commit/rollback; savepoints are closures.BeginWithadds isolation levels and access modes (TxOptions), refusing per engine anything that engine would only appear to honour.Atomicis the one a reusable unit of work takes: a transaction at the top, a savepoint inside one, and the same call site either way.RowandFromRow— rows decoded once, at the driver seam, intoValues; every decode error names its column.RawConnection— the seam a backend implements per driver; this crate owns the transaction SQL (BEGIN/COMMIT/SAVEPOINT …) so its semantics cannot drift between backends.
§Which one does my function take?
The question every signature in an application asks, and the answer is a capability, not a style. Each row may do everything above it:
| parameter | what the function may do |
|---|---|
db: &dyn Executor | run statements |
db: impl Atomic | …and carve one all-or-nothing block out of wherever it turns out to be |
db: impl Begin (a pool) | …and start a transaction, with an isolation level |
db: &Transaction | …and commit or roll it back — and, on purpose, not begin: nesting is spelled savepoint |
Take the weakest row that does the job. A repository method that runs
one statement takes &dyn Executor; a unit of work that must not
half-apply takes impl Atomic; a usecase saying “a transaction
begins here” takes a pool and calls within.
The ladder only goes downward. An impl Atomic can be handed on as
&dyn Executor, and so can a Transaction — but nothing recovers a
scope from &dyn Executor, because erasing it threw away whether a
transaction is open. That one-way street is a safety property rather than
a limitation: a hook receives &dyn Executor not because hooks are
trusted, but because the type it is given has no method that could end the
caller’s transaction.
It is also why the spellings differ. Executor’s three methods are
object-safe, so it is erased and compiles once; Atomic::atomic takes
the caller’s closure, whose type differs at every call site, so it can
only be generic — and being generic is exactly what lets it open a scope.
impl Atomic still accepts everything: &pool, pool, Arc<pool>,
&dyn Begin, and the &Transaction a scope closure hands you.
The full design, with every rejected alternative, is docs/execution.md.
The type-by-type binding contract backends implement against is
docs/type-mappings.md.
No public type here carries a lifetime parameter (the house rule); the only
lifetimes are the transient '_ on futures borrowed from &self for one
call. Nothing here names a driver: Layer 2’s generated models depend on
this crate and pick up a backend only in the application’s own Cargo.toml.
§Where this sits
Layer 2 of keelson, and the half of it that names no driver. Below:
keelson-core and the dialect crates, which build the
(String, Vec<Value>) these traits carry. Beside: keelson-sqlx,
the backend that implements them over sqlx’s PostgreSQL, MySQL and SQLite
drivers. Above: keelson-models, whose generated models
execute through &dyn Executor and therefore through whatever backend the
application picked. The whole map is the keelson facade crate.
Macros§
- bind_
newtype - Implement
ToValueandFromValuefor a single-field newtype by delegating to the inner type — the “derivable for newtypes” story, without a proc-macro crate.
Structs§
- Column
- One column of a result set’s header.
- Exec
Result - What a side-effect statement reports back.
- Row
- One decoded row: a shared column header and one
Valueper column. - RowStream
- An owned stream of rows (house rule: no lifetime parameter).
- Statement
- What crosses the executor boundary: exactly what
build()produces, plus the statement-kind hint core already carries. - Transaction
- An open transaction. Owned, lifetime-free, and an
Executor— any function written asfn f(db: &dyn Executor)accepts it, which is what lets model hooks run inside the caller’s transaction without knowing one exists. - TxConflict
Error - The error a backend reports for a
TxConflict, carrying the engine’s own code and message and (usually) the driver error as itssource. - TxOptions
- What a transaction is opened with: the parts of transaction control the three engines do not agree on.
Enums§
- Access
- A transaction’s access mode.
- Exec
Error - Everything that can go wrong while executing a statement.
- Family
- Which engine family an executor talks to.
- Isolation
- A SQL-standard isolation level, as asked for.
- Sqlite
Begin - SQLite’s begin modes — not isolation levels, and named so nobody can mistake them for a portable knob.
- TxConflict
- A concurrency conflict the engine raised: this transaction lost, and the only correct response is to run the whole thing again.
Traits§
- Atomic
- All-or-nothing here, wherever “here” turns out to be: a transaction when nothing is open, a savepoint when a transaction already is.
- Begin
- Something a transaction can be begun on: a pool or a connection.
- Begin
Ext - The closure form of a transaction: commit on
Ok, roll back onErr. - Begin
With - Opt-in transaction options:
begin_withbesidebegin. - Begin
With Ext BeginExt::withinwith options.- Bind
- What a column’s Rust type must be able to do: bind in
(
ToValue) and read back out (FromValue). - Execute
- The ergonomic verbs, hung on every
Query. - Executor
- Anything that can run a built statement: a pool, a connection, a
Transaction. - FromRow
- A type that can be built from a whole row.
- RawConnection
- One raw connection, exclusively held. The seam a backend implements.
- Stream
Executor - Opt-in streaming. A backend that can stream implements it; nothing requires it, because drivers differ too much here for it to belong in the minimum contract (native streams borrow their connection; ours must not).
Functions§
- assert_
bind - Assert at compile time that
Tcan bind as a column.
Type Aliases§
- Exec
Future - The boxed future every trait method returns.
- Exec
Hook - The hook payload the execution layer fixes
QueryExtensions’Hookparameter to: an async function of&dyn Executor. - Exec
Loader - The loader payload: like
ExecHook, plus the rows the query produced.