Skip to main content

keelson_exec/
lib.rs

1//! The execution layer's traits, owned by no driver.
2//!
3//! Layer 1 builds a statement — `Query::build()` hands back `(String,
4//! Vec<Value>)`, synchronously, with no driver in the loop. This crate is
5//! everything between that pair and a mapped Rust value coming back out of a
6//! real database, expressed as traits a backend crate implements:
7//!
8//! - [`Executor`] — object-safe, three methods, `&self`. A pool, a connection
9//!   and a [`Transaction`] all implement it, so `&dyn Executor` is the type
10//!   application code, generated models and hooks pass around.
11//! - [`Execute`] — the ergonomic verbs, blanket-implemented on every
12//!   [`Query`](keelson_core::Query): `q.fetch_all(&db)`, `q.fetch_one(&db)`,
13//!   `q.execute(&db)`. Tracing (feature `tracing`) lives here, in the one
14//!   funnel every backend flows through.
15//! - [`Transaction`] and [`Begin`] — an owned, lifetime-free transaction that
16//!   consumes itself on commit/rollback; savepoints are closures.
17//!   [`BeginWith`] adds isolation levels and access modes ([`TxOptions`]),
18//!   refusing per engine anything that engine would only appear to honour.
19//!   [`Atomic`] is the one a *reusable* unit of work takes: a transaction at
20//!   the top, a savepoint inside one, and the same call site either way.
21//! - [`Row`] and [`FromRow`] — rows decoded once, at the driver seam, into
22//!   [`Value`](keelson_core::Value)s; every decode error names its column.
23//! - [`RawConnection`] — the seam a backend implements per driver; this crate
24//!   owns the transaction SQL (`BEGIN`/`COMMIT`/`SAVEPOINT …`) so its
25//!   semantics cannot drift between backends.
26//!
27//! # Which one does my function take?
28//!
29//! The question every signature in an application asks, and the answer is a
30//! **capability**, not a style. Each row may do everything above it:
31//!
32//! | parameter | what the function may do |
33//! |---|---|
34//! | `db: &dyn Executor` | run statements |
35//! | `db: impl Atomic` | …and carve one all-or-nothing block out of wherever it turns out to be |
36//! | `db: impl Begin` (a pool) | …and start a transaction, with an isolation level |
37//! | `db: &Transaction` | …and commit or roll it back — and, on purpose, *not* `begin`: nesting is spelled [`savepoint`](Transaction::savepoint) |
38//!
39//! **Take the weakest row that does the job.** A repository method that runs
40//! one statement takes `&dyn Executor`; a unit of work that must not
41//! half-apply takes [`impl Atomic`](Atomic); a usecase saying "a transaction
42//! begins here" takes a pool and calls [`within`](BeginExt::within).
43//!
44//! The ladder only goes downward. An `impl Atomic` can be handed on as
45//! `&dyn Executor`, and so can a [`Transaction`] — but nothing recovers a
46//! scope from `&dyn Executor`, because erasing it threw away whether a
47//! transaction is open. That one-way street is a safety property rather than
48//! a limitation: a hook receives `&dyn Executor` not because hooks are
49//! trusted, but because the type it is given has no method that could end the
50//! caller's transaction.
51//!
52//! It is also why the spellings differ. [`Executor`]'s three methods are
53//! object-safe, so it is erased and compiles once; [`Atomic::atomic`] takes
54//! the caller's closure, whose type differs at every call site, so it can
55//! only be generic — and being generic is exactly what lets it open a scope.
56//! `impl Atomic` still accepts everything: `&pool`, `pool`, `Arc<pool>`,
57//! `&dyn Begin`, and the `&Transaction` a scope closure hands you.
58//!
59//! The full design, with every rejected alternative, is `docs/execution.md`.
60//! The type-by-type binding contract backends implement against is
61//! `docs/type-mappings.md`.
62//!
63//! No public type here carries a lifetime parameter (the house rule); the only
64//! lifetimes are the transient `'_` on futures borrowed from `&self` for one
65//! call. Nothing here names a driver: Layer 2's generated models depend on
66//! this crate and pick up a backend only in the application's own `Cargo.toml`.
67//!
68//! # Where this sits
69//!
70//! Layer 2 of keelson, and the half of it that names no driver. Below:
71//! [keelson-core](https://docs.rs/keelson-core) and the dialect crates, which build the
72//! `(String, Vec<Value>)` these traits carry. Beside: [keelson-sqlx](https://docs.rs/keelson-sqlx),
73//! the backend that implements them over sqlx's PostgreSQL, MySQL and SQLite
74//! drivers. Above: [keelson-models](https://docs.rs/keelson-models), whose generated models
75//! execute through `&dyn Executor` and therefore through whatever backend the
76//! application picked. The whole map is the [keelson](https://docs.rs/keelson) facade crate.
77#![warn(missing_docs)]
78
79mod bind;
80mod error;
81mod execute;
82mod executor;
83mod row;
84mod transaction;
85
86pub use bind::{Bind, assert_bind};
87pub use error::ExecError;
88pub use execute::Execute;
89pub use executor::{
90    ExecFuture, ExecResult, Executor, Family, RowStream, Statement, StreamExecutor,
91};
92pub use row::{Column, FromRow, Row};
93pub use transaction::{
94    Access, Atomic, Begin, BeginExt, BeginWith, BeginWithExt, ExecHook, ExecLoader, Isolation,
95    RawConnection, SqliteBegin, Transaction, TxConflict, TxConflictError, TxOptions,
96};
97
98// For `bind_newtype!` expansion only.
99#[doc(hidden)]
100pub use keelson_core as __core;