rorm_db/executor.rs
1//! This module defines a wrapper for sqlx's Executor
2//!
3//! Unlike sqlx's Executor which provides several separate methods for different querying strategies,
4//! our [`Executor`] has a single method which is generic using the [`QueryStrategy`] trait.
5
6use std::future::Future;
7
8use futures::future::BoxFuture;
9use rorm_sql::value::Value;
10use rorm_sql::DBImpl;
11
12use crate::transaction::{Transaction, TransactionGuard};
13use crate::{internal, Database, Error};
14
15/// [`QueryStrategy`] returning nothing
16///
17/// `type Result<'result> = impl Future<Output = Result<(), Error>>`
18pub struct Nothing;
19impl QueryStrategy for Nothing {}
20
21/// [`QueryStrategy`] returning how many rows have been affected by the query
22///
23/// `type Result<'result> = impl Future<Output = Result<u64, Error>>`
24pub struct AffectedRows;
25impl QueryStrategy for AffectedRows {}
26
27/// [`QueryStrategy`] returning a single row
28///
29/// `type Result<'result> = impl Future<Output = Result<Row, Error>>`
30pub struct One;
31impl QueryStrategy for One {}
32
33/// [`QueryStrategy`] returning an optional row
34///
35/// `type Result<'result> = impl Future<Output = Result<Option<Row>, Error>>`
36pub struct Optional;
37impl QueryStrategy for Optional {}
38
39/// [`QueryStrategy`] returning a vector of rows
40///
41/// `type Result<'result> = impl Future<Output = Result<Vec<Row>, Error>>`
42pub struct All;
43impl QueryStrategy for All {}
44
45/// [`QueryStrategy`] returning a stream of rows
46///
47/// `type Result<'result> = impl Stream<Item = Result<Row, Error>>`
48pub struct Stream;
49impl QueryStrategy for Stream {}
50
51/// Define how a query is send to and results retrieved from the database.
52///
53/// This trait is implemented on the following unit structs:
54/// - [`Nothing`] retrieves nothing
55/// - [`Optional`] retrieves an optional row
56/// - [`One`] retrieves a single row
57/// - [`Stream`] retrieves many rows in a stream
58/// - [`All`] retrieves many rows in a vector
59/// - [`AffectedRows`] returns the number of rows affected by the query
60///
61/// This trait has an associated `Result<'result>` type which is returned by [`Executor::execute`].
62/// To avoid boxing, these types are quite big.
63///
64/// Each of those unit structs' docs (follow links above) contains an easy to read `impl Trait` version of the actual types.
65pub trait QueryStrategy: QueryStrategyResult + internal::executor::QueryStrategyImpl {}
66
67/// Helper trait to make the `Result<'result>` public,
68/// while keeping [`QueryStrategyImpl`](internal::executor::QueryStrategyImpl) itself private
69#[doc(hidden)]
70pub trait QueryStrategyResult {
71 type Result<'result>;
72}
73
74/// Some kind of database connection which can execute queries
75///
76/// This trait is implemented by the database connection itself as well as transactions.
77pub trait Executor<'executor> {
78 /// Execute a query
79 ///
80 /// ```skipped
81 /// db.execute::<All>("SELECT * FROM foo;".to_string(), vec![]);
82 /// ```
83 fn execute<'data, 'result, Q>(
84 self,
85 query: String,
86 values: Vec<Value<'data>>,
87 ) -> Q::Result<'result>
88 where
89 'executor: 'result,
90 'data: 'result,
91 Q: QueryStrategy;
92
93 /// Get the executor's sql dialect.
94 fn dialect(&self) -> DBImpl;
95
96 /// A future producing a [`TransactionGuard`] returned by [`ensure_transaction`](Executor::ensure_transaction)
97 type EnsureTransactionFuture: Future<Output = Result<TransactionGuard<'executor>, Error>> + Send;
98
99 /// Ensure a piece of code is run inside a transaction using a [`TransactionGuard`].
100 ///
101 /// In generic code an [`Executor`] might and might not be a [`&mut Transaction`].
102 /// But sometimes you'd want to ensure your code is run inside a transaction
103 /// (for example [bulk inserts](crate::database::insert_bulk)).
104 ///
105 /// This method solves this by producing a type which is either an owned or borrowed Transaction
106 /// depending on the [`Executor`] it is called on.
107 fn ensure_transaction(self)
108 -> BoxFuture<'executor, Result<TransactionGuard<'executor>, Error>>;
109}
110
111/// Choose whether to use transactions or not at runtime
112///
113/// Like a `Box<dyn Executor<'executor>>`
114pub enum DynamicExecutor<'executor> {
115 /// Use a default database connection
116 Database(&'executor Database),
117 /// Use a transaction
118 Transaction(&'executor mut Transaction),
119}
120impl<'executor> Executor<'executor> for DynamicExecutor<'executor> {
121 fn execute<'data, 'result, Q>(
122 self,
123 query: String,
124 values: Vec<Value<'data>>,
125 ) -> Q::Result<'result>
126 where
127 'executor: 'result,
128 'data: 'result,
129 Q: QueryStrategy,
130 {
131 match self {
132 DynamicExecutor::Database(db) => db.execute::<Q>(query, values),
133 DynamicExecutor::Transaction(tr) => tr.execute::<Q>(query, values),
134 }
135 }
136
137 fn dialect(&self) -> DBImpl {
138 match self {
139 DynamicExecutor::Database(db) => db.dialect(),
140 DynamicExecutor::Transaction(tr) => tr.dialect(),
141 }
142 }
143
144 type EnsureTransactionFuture = BoxFuture<'executor, Result<TransactionGuard<'executor>, Error>>;
145
146 fn ensure_transaction(self) -> Self::EnsureTransactionFuture {
147 match self {
148 DynamicExecutor::Database(db) => db.ensure_transaction(),
149 DynamicExecutor::Transaction(tr) => Box::pin(tr.ensure_transaction()),
150 }
151 }
152}