Skip to main content

rainier_database/
connection.rs

1//! The connection port — [`Connection`] and [`Database`].
2//!
3//! Rainier ORM's `Executor` trait is deliberately **not** `dyn`-safe: it uses
4//! `async fn` in trait and carries no `Send + Sync` bound, so the same generic
5//! code can run inside a single-threaded Cloudflare Worker over a `!Send` D1
6//! binding. That is the right call for the ORM, and it means a framework
7//! cannot put an `Arc<dyn Executor>` in its container.
8//!
9//! [`Connection`] is Rainier's `dyn`-safe mirror of that port, and [`Database`]
10//! is the shared handle the container holds.
11//!
12//! ## Two ways to read rows
13//!
14//! `Connection` exposes fetching twice, and **both futures are `Send`**:
15//!
16//! | | Yields | For |
17//! |---|---|---|
18//! | [`fetch`](Connection::fetch) | [`OwnedRow`] — a `Send` snapshot | the repository layer, which decodes later |
19//! | [`fetch_raw`](Connection::fetch_raw) | the driver's own `Box<dyn Row>` | [`Database`]'s `Executor` impl, and so Rainier ORM's `repo::` |
20//!
21//! A future's **output** need not be `Send` for the future to be `Send` — the
22//! value is moved out on the final poll rather than held across a suspension —
23//! so `fetch_raw` can hand back `!Send` rows from a `Send` future. What the
24//! caller must not do is hold those rows across a *further* await; decoding
25//! them immediately, as `Entity::from_row` does, is the whole pattern.
26//!
27//! `fetch` exists on top of that because the repository decodes into an entity
28//! some way after the query, and an [`OwnedRow`] can be carried there safely.
29//!
30//! ## `repo::` on the request path
31//!
32//! Rainier ORM used to hold a `sea_query` statement across its awaits, which
33//! made every `repo::` future `!Send` and unusable from a handler the server
34//! will `tokio::spawn`. That is fixed upstream (the statement is now built and
35//! dropped in a scope that ends before the await), so
36//! `repo::`/[`Query`](rainier_orm::Query) work directly against a [`Database`].
37//!
38//! [`statement`](crate::statement) predates the fix and still backs the
39//! repository. It is no longer a workaround — it is how the repository renders
40//! SQL — but the constraint that forced it is gone, and the
41//! `the_orms_own_futures_are_send…` test in this crate's root asserts as much.
42
43use std::sync::Arc;
44
45use rainier_orm::sea_query::Value;
46use rainier_orm::{Dialect, ExecOutcome, Executor, Row, ShardRoute};
47use rainier_support::{BoxFuture, Error, Result};
48
49use crate::row::{ColumnRequest, OwnedRow};
50use crate::statement::Prepared;
51
52/// A `dyn`-safe database backend.
53///
54/// Implemented for concrete Rainier ORM executors by
55/// [`bind_executor!`](crate::bind_executor).
56pub trait Connection: Send + Sync + 'static {
57    /// The SQL dialect this backend speaks.
58    fn dialect(&self) -> Dialect;
59
60    /// The shard family this connector routes within, or `None` for a single
61    /// database.
62    fn shard_family(&self) -> Option<String> {
63        None
64    }
65
66    /// Mint a shard-encoded primary key for a row owned by `shard_key`.
67    fn allocate_id(&self, shard_key: u64) -> Option<u64> {
68        let _ = shard_key;
69        None
70    }
71
72    /// Run a query, copying the named columns out of each row.
73    ///
74    /// `Send`, which is what makes it usable from a request handler.
75    fn fetch<'a>(
76        &'a self,
77        route: ShardRoute,
78        sql: &'a str,
79        params: Vec<Value>,
80        columns: Vec<ColumnRequest>,
81    ) -> BoxFuture<'a, Result<Vec<OwnedRow>>>;
82
83    /// Run a write and report its outcome. `Send`.
84    fn execute<'a>(
85        &'a self,
86        route: ShardRoute,
87        sql: &'a str,
88        params: Vec<Value>,
89    ) -> BoxFuture<'a, Result<Vec<ExecOutcome>>>;
90
91    /// Run a query and hand back the driver's own rows.
92    ///
93    /// The future is `Send` even though `Vec<Box<dyn Row>>` is not — a future's
94    /// **output** need not be `Send` for the future to be, because the value is
95    /// moved out on the final poll rather than held across a suspension. That
96    /// is what lets [`Database`]'s `Executor` impl, and therefore Rainier ORM'
97    /// own `repo::` API, be used from a request handler.
98    ///
99    /// The rows themselves still cannot cross a thread, so decode them before
100    /// the next `await` — which is exactly what `Entity::from_row` does.
101    fn fetch_raw<'a>(
102        &'a self,
103        route: ShardRoute,
104        sql: &'a str,
105        params: Vec<Value>,
106    ) -> BoxFuture<'a, Result<Vec<Box<dyn Row>>>>;
107}
108
109/// Writes the [`Connection`] impl for a concrete Rainier ORM `Executor`.
110///
111/// ```ignore
112/// struct MyExecutor { /* … */ }
113/// impl rainier_orm::Executor for MyExecutor { /* … */ }
114///
115/// rainier_database::bind_executor!(MyExecutor);
116/// ```
117///
118/// Two constraints, both worth knowing before you reach for this:
119///
120/// 1. **The type must be concrete.** Rust cannot prove a generic `E: Executor`
121///    has `Send` futures — that needs return-type notation, still unstable —
122///    whereas for a named type the auto traits leak out of the `async fn`
123///    bodies and the compiler works it out.
124/// 2. **The type must be yours.** [`Connection`] is defined here, so the
125///    orphan rule allows the impl only in this crate or in the crate defining
126///    the executor. An application therefore cannot bind an executor it did
127///    not write; the ones Rainier ORM ships are bound below, behind the same
128///    feature flags Rainier ORM uses for them.
129#[macro_export]
130macro_rules! bind_executor {
131    ($executor:ty) => {
132        impl $crate::Connection for $executor {
133            fn dialect(&self) -> $crate::reexport::Dialect {
134                $crate::reexport::Executor::dialect(self)
135            }
136
137            fn shard_family(&self) -> ::std::option::Option<::std::string::String> {
138                $crate::reexport::Executor::shard_family(self)
139                    .map(::std::string::ToString::to_string)
140            }
141
142            fn allocate_id(&self, shard_key: u64) -> ::std::option::Option<u64> {
143                $crate::reexport::Executor::allocate_id(self, shard_key)
144            }
145
146            fn fetch<'a>(
147                &'a self,
148                route: $crate::reexport::ShardRoute,
149                sql: &'a str,
150                params: ::std::vec::Vec<$crate::reexport::Value>,
151                columns: ::std::vec::Vec<$crate::reexport::ColumnRequest>,
152            ) -> $crate::reexport::BoxFuture<
153                'a,
154                $crate::reexport::Result<::std::vec::Vec<$crate::reexport::OwnedRow>>,
155            > {
156                ::std::boxed::Box::pin(async move {
157                    // The driver rows are created *after* the await and never
158                    // held across one, so this future stays `Send` even though
159                    // `dyn Row` is not.
160                    let rows =
161                        $crate::reexport::Executor::fetch_all_routed(self, route, sql, params)
162                            .await
163                            .map_err($crate::reexport::Error::from)?;
164
165                    let mut owned = ::std::vec::Vec::with_capacity(rows.len());
166                    for row in &rows {
167                        owned.push(
168                            $crate::reexport::OwnedRow::snapshot(row.as_ref(), &columns)
169                                .map_err($crate::reexport::Error::from)?,
170                        );
171                    }
172                    ::std::result::Result::Ok(owned)
173                })
174            }
175
176            fn execute<'a>(
177                &'a self,
178                route: $crate::reexport::ShardRoute,
179                sql: &'a str,
180                params: ::std::vec::Vec<$crate::reexport::Value>,
181            ) -> $crate::reexport::BoxFuture<
182                'a,
183                $crate::reexport::Result<::std::vec::Vec<$crate::reexport::ExecOutcome>>,
184            > {
185                ::std::boxed::Box::pin(async move {
186                    let outcome =
187                        $crate::reexport::Executor::execute_routed(self, route, sql, params)
188                            .await
189                            .map_err($crate::reexport::Error::from)?;
190                    ::std::result::Result::Ok(::std::vec![outcome])
191                })
192            }
193
194            fn fetch_raw<'a>(
195                &'a self,
196                route: $crate::reexport::ShardRoute,
197                sql: &'a str,
198                params: ::std::vec::Vec<$crate::reexport::Value>,
199            ) -> $crate::reexport::BoxFuture<
200                'a,
201                $crate::reexport::Result<
202                    ::std::vec::Vec<::std::boxed::Box<dyn $crate::reexport::Row>>,
203                >,
204            > {
205                ::std::boxed::Box::pin(async move {
206                    $crate::reexport::Executor::fetch_all_routed(self, route, sql, params)
207                        .await
208                        .map_err($crate::reexport::Error::from)
209                })
210            }
211        }
212    };
213}
214
215// The executors Rainier ORM ships, bound here because the orphan rule puts
216// them out of reach of an application crate. Feature-gated to match
217// Rainier ORM's own gating, so the wasm-safe default stays wasm-safe.
218#[cfg(feature = "sea-orm-executor")]
219crate::bind_executor!(rainier_drivers::sql::SeaOrmExecutor);
220
221/// A database handle: a shared [`Connection`], cheap to clone.
222///
223/// Lives as a container singleton and is cloned into repositories, jobs and
224/// console commands freely.
225#[derive(Clone)]
226pub struct Database {
227    connection: Arc<dyn Connection>,
228}
229
230impl Database {
231    /// Wrap a concrete connection.
232    pub fn new(connection: impl Connection) -> Self {
233        Self { connection: Arc::new(connection) }
234    }
235
236    /// Wrap an already-shared connection.
237    pub fn from_arc(connection: Arc<dyn Connection>) -> Self {
238        Self { connection }
239    }
240
241    /// The underlying connection.
242    pub fn connection(&self) -> &Arc<dyn Connection> {
243        &self.connection
244    }
245
246    /// The dialect the backend speaks.
247    pub fn dialect(&self) -> Dialect {
248        self.connection.dialect()
249    }
250
251    /// Whether the backend is a sharded fleet.
252    pub fn is_sharded(&self) -> bool {
253        self.connection.shard_family().is_some()
254    }
255
256    /// The shard family, if any.
257    pub fn shard_family(&self) -> Option<String> {
258        self.connection.shard_family()
259    }
260
261    // --- the Send-safe query path ------------------------------------------
262
263    /// Run a prepared query, reading back the named columns.
264    pub async fn fetch(
265        &self,
266        prepared: Prepared,
267        columns: Vec<ColumnRequest>,
268    ) -> Result<Vec<OwnedRow>> {
269        self.connection.fetch(prepared.route, &prepared.sql, prepared.params, columns).await
270    }
271
272    /// Run a prepared query and decode every row into `E`.
273    pub async fn fetch_all<E: rainier_orm::Entity>(&self, prepared: Prepared) -> Result<Vec<E>> {
274        let rows = self.fetch(prepared, ColumnRequest::for_entity::<E>()).await?;
275        rows.iter().map(|row| E::from_row(row).map_err(Error::from)).collect()
276    }
277
278    /// Run a prepared query and decode the first row into `E`.
279    pub async fn fetch_one<E: rainier_orm::Entity>(&self, prepared: Prepared) -> Result<Option<E>> {
280        let rows = self.fetch(prepared, ColumnRequest::for_entity::<E>()).await?;
281        match rows.first() {
282            Some(row) => Ok(Some(E::from_row(row).map_err(Error::from)?)),
283            None => Ok(None),
284        }
285    }
286
287    /// Run a prepared `COUNT(*) AS cnt` and read the total.
288    pub async fn fetch_count(&self, prepared: Prepared) -> Result<u64> {
289        let columns = vec![ColumnRequest::new("cnt", rainier_orm::ColumnType::BigInt)];
290        let rows = self.fetch(prepared, columns).await?;
291
292        let count = match rows.first() {
293            Some(row) => row.get_i64("cnt").map_err(Error::from)?.unwrap_or(0),
294            None => 0,
295        };
296        // A negative count is impossible, but the column is signed.
297        Ok(count.max(0) as u64)
298    }
299
300    /// Run a prepared write.
301    pub async fn execute(&self, prepared: Prepared) -> Result<ExecOutcome> {
302        let outcomes =
303            self.connection.execute(prepared.route, &prepared.sql, prepared.params).await?;
304        Ok(outcomes.into_iter().next().unwrap_or_default())
305    }
306
307    /// Run a raw statement with no bindings — for DDL.
308    pub async fn statement(&self, sql: &str) -> Result<ExecOutcome> {
309        let outcomes = self.connection.execute(ShardRoute::Global, sql, Vec::new()).await?;
310        Ok(outcomes.into_iter().next().unwrap_or_default())
311    }
312}
313
314impl std::fmt::Debug for Database {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        f.debug_struct("Database")
317            .field("dialect", &self.connection.dialect())
318            .field("shard_family", &self.connection.shard_family())
319            .finish()
320    }
321}
322
323/// Implementing `Executor` is what keeps Rainier ORM's whole surface available.
324///
325/// `repo::insert`, `repo::query::<E>()`, `Cursor` and `Migrator::run` are all
326/// generic over `X: Executor`, so they accept a `Database` directly. Their
327/// futures are `!Send` (see the module docs), so use them from a console
328/// command, a migration, or a Worker — not from a request handler, which needs
329/// the `Send` path above.
330impl Executor for Database {
331    fn dialect(&self) -> Dialect {
332        self.connection.dialect()
333    }
334
335    fn shard_family(&self) -> Option<&str> {
336        // The `dyn`-safe port returns an owned `String` — a borrow would tie
337        // the trait object's lifetime into the signature — so there is nothing
338        // to hand back a reference to. `is_sharded` still answers correctly,
339        // and `Database::shard_family` returns the owned name.
340        None
341    }
342
343    fn is_sharded(&self) -> bool {
344        self.connection.shard_family().is_some()
345    }
346
347    fn allocate_id(&self, shard_key: u64) -> Option<u64> {
348        self.connection.allocate_id(shard_key)
349    }
350
351    async fn fetch_all(
352        &self,
353        sql: &str,
354        params: Vec<Value>,
355    ) -> rainier_orm::Result<Vec<Box<dyn Row>>> {
356        self.connection.fetch_raw(ShardRoute::Global, sql, params).await.map_err(into_anyhow)
357    }
358
359    async fn execute(&self, sql: &str, params: Vec<Value>) -> rainier_orm::Result<ExecOutcome> {
360        let outcomes =
361            self.connection.execute(ShardRoute::Global, sql, params).await.map_err(into_anyhow)?;
362        Ok(outcomes.into_iter().next().unwrap_or_default())
363    }
364
365    async fn fetch_all_routed(
366        &self,
367        route: ShardRoute,
368        sql: &str,
369        params: Vec<Value>,
370    ) -> rainier_orm::Result<Vec<Box<dyn Row>>> {
371        self.connection.fetch_raw(route, sql, params).await.map_err(into_anyhow)
372    }
373
374    async fn execute_routed(
375        &self,
376        route: ShardRoute,
377        sql: &str,
378        params: Vec<Value>,
379    ) -> rainier_orm::Result<ExecOutcome> {
380        let outcomes = self.connection.execute(route, sql, params).await.map_err(into_anyhow)?;
381        Ok(outcomes.into_iter().next().unwrap_or_default())
382    }
383}
384
385/// Convert back into the ORM's error type at the boundary.
386fn into_anyhow(error: Error) -> rainier_orm::Error {
387    anyhow::anyhow!("{error}")
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393    use crate::statement;
394    use crate::testing::{fake_database, MemoryConnection};
395
396    #[derive(rainier_orm::Entity, Clone, Debug, PartialEq)]
397    #[orm(table = "posts")]
398    struct Post {
399        #[orm(pk, auto_increment)]
400        id: u64,
401        title: String,
402    }
403
404    fn row(id: u64, title: &str) -> OwnedRow {
405        OwnedRow::new().with("id", id).with("title", title)
406    }
407
408    #[test]
409    fn the_send_query_path_is_actually_send() {
410        // The property the whole module exists to guarantee. If this stops
411        // holding, every handler that touches the database breaks with a far
412        // more confusing error than this one.
413        fn assert_send<T: Send>(_: T) {}
414
415        let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite));
416        assert_send(async move {
417            let prepared = statement::select_all::<Post>(Dialect::Sqlite);
418            let _: Vec<Post> = db.fetch_all(prepared).await.unwrap();
419        });
420    }
421
422    #[test]
423    fn a_database_reports_its_backend() {
424        let db = Database::new(MemoryConnection::new(Dialect::Postgres));
425        assert_eq!(db.dialect(), Dialect::Postgres);
426        assert!(!db.is_sharded());
427        assert_eq!(db.shard_family(), None);
428    }
429
430    #[tokio::test]
431    async fn fetch_all_decodes_entities() {
432        let (db, _) = fake_database(
433            MemoryConnection::new(Dialect::Sqlite).returning([row(1, "a"), row(2, "b")]),
434        );
435
436        let posts: Vec<Post> =
437            db.fetch_all(statement::select_all::<Post>(Dialect::Sqlite)).await.unwrap();
438
439        assert_eq!(posts.len(), 2);
440        assert_eq!(posts[1], Post { id: 2, title: "b".into() });
441    }
442
443    #[tokio::test]
444    async fn fetch_one_returns_none_for_no_rows() {
445        let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite));
446        let found: Option<Post> = db
447            .fetch_one(statement::select_by_pk::<Post>(Dialect::Sqlite, 1_i64.into()))
448            .await
449            .unwrap();
450        assert!(found.is_none());
451    }
452
453    #[tokio::test]
454    async fn fetch_count_reads_the_cnt_column() {
455        let (db, _) = fake_database(
456            MemoryConnection::new(Dialect::Sqlite).returning([OwnedRow::new().with("cnt", 42_i64)]),
457        );
458
459        let criteria = crate::Criteria::new();
460        let prepared = statement::count_matching::<Post>(Dialect::Sqlite, &criteria);
461        assert_eq!(db.fetch_count(prepared).await.unwrap(), 42);
462    }
463
464    #[tokio::test]
465    async fn fetch_count_is_zero_when_nothing_comes_back() {
466        let (db, _) = fake_database(MemoryConnection::new(Dialect::Sqlite));
467        let criteria = crate::Criteria::new();
468        let prepared = statement::count_matching::<Post>(Dialect::Sqlite, &criteria);
469        assert_eq!(db.fetch_count(prepared).await.unwrap(), 0);
470    }
471
472    #[tokio::test]
473    async fn statements_and_bindings_reach_the_connection() {
474        let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
475
476        db.execute(statement::delete_by_pk::<Post>(Dialect::Sqlite, 3_i64.into())).await.unwrap();
477
478        let sql = connection.last_statement().unwrap();
479        assert!(sql.starts_with("DELETE FROM"), "{sql}");
480        assert_eq!(connection.bindings()[0].len(), 1);
481    }
482
483    #[tokio::test]
484    async fn ddl_runs_through_statement() {
485        let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
486        db.statement("CREATE TABLE posts (id INTEGER)").await.unwrap();
487        assert_eq!(connection.last_statement().as_deref(), Some("CREATE TABLE posts (id INTEGER)"));
488    }
489
490    #[tokio::test]
491    async fn an_error_survives_the_round_trip() {
492        let (db, _) =
493            fake_database(MemoryConnection::new(Dialect::Sqlite).failing("table is locked"));
494
495        let err = db.statement("SELECT 1").await.unwrap_err();
496        assert!(err.message().contains("table is locked"), "{}", err.message());
497    }
498
499    #[tokio::test]
500    async fn the_orm_surface_is_still_reachable_through_the_executor_impl() {
501        // Not `Send`, but valid outside the request path — a console command,
502        // a migration, or a Worker.
503        let (db, connection) = fake_database(MemoryConnection::new(Dialect::Sqlite));
504
505        let outcome = Executor::execute(&db, "DELETE FROM posts", vec![]).await.unwrap();
506        assert_eq!(outcome.rows_affected, 1);
507        assert_eq!(connection.last_statement().as_deref(), Some("DELETE FROM posts"));
508    }
509
510    #[test]
511    fn cloning_shares_one_connection() {
512        let db = Database::new(MemoryConnection::new(Dialect::MySql));
513        assert!(Arc::ptr_eq(db.connection(), db.clone().connection()));
514    }
515}