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 rorm_sql::value::Value;
9use rorm_sql::DBImpl;
10use tracing::debug;
11
12use crate::futures_util::BoxFuture;
13use crate::transaction::{Transaction, TransactionGuard};
14use crate::{internal, Database, Error};
15
16/// [`QueryStrategy`] returning nothing
17///
18/// `type Result<'result> = impl Future<Output = Result<(), Error>>`
19pub struct Nothing;
20
21impl QueryStrategy for Nothing {}
22
23/// [`QueryStrategy`] returning how many rows have been affected by the query
24///
25/// `type Result<'result> = impl Future<Output = Result<u64, Error>>`
26pub struct AffectedRows;
27
28impl QueryStrategy for AffectedRows {}
29
30/// [`QueryStrategy`] returning a single row
31///
32/// `type Result<'result> = impl Future<Output = Result<Row, Error>>`
33pub struct One;
34
35impl QueryStrategy for One {}
36
37/// [`QueryStrategy`] returning an optional row
38///
39/// `type Result<'result> = impl Future<Output = Result<Option<Row>, Error>>`
40pub struct Optional;
41
42impl QueryStrategy for Optional {}
43
44/// [`QueryStrategy`] returning a vector of rows
45///
46/// `type Result<'result> = impl Future<Output = Result<Vec<Row>, Error>>`
47pub struct All;
48
49impl QueryStrategy for All {}
50
51/// [`QueryStrategy`] returning a stream of rows
52///
53/// `type Result<'result> = impl Stream<Item = Result<Row, Error>>`
54pub struct Stream;
55
56impl QueryStrategy for Stream {}
57
58/// Define how a query is sent to and results retrieved from the database.
59///
60/// This trait is implemented on the following unit structs:
61/// - [`Nothing`] retrieves nothing
62/// - [`Optional`] retrieves an optional row
63/// - [`One`] retrieves a single row
64/// - [`Stream`] retrieves many rows in a stream
65/// - [`All`] retrieves many rows in a vector
66/// - [`AffectedRows`] returns the number of rows affected by the query
67///
68/// This trait has an associated `Result<'result>` type which is returned by [`Executor::execute`].
69/// To avoid boxing, these types are quite big.
70///
71/// Each of those unit structs' docs (follow links above) contains an easy to read `impl Trait` version of the actual types.
72pub trait QueryStrategy: QueryStrategyResult + internal::executor::QueryStrategyImpl {}
73
74/// Helper trait to make the `Result<'result>` public,
75/// while keeping [`QueryStrategyImpl`](internal::executor::QueryStrategyImpl) itself private
76#[doc(hidden)]
77pub trait QueryStrategyResult {
78 type Result<'result>;
79}
80
81/// Some kind of database connection which can execute queries
82///
83/// This trait is implemented by the database connection itself as well as transactions.
84///
85/// # Object Safety
86/// This trait is **not** object safe.
87/// However, there only exist two implementors,
88/// which were combined into the [`DynamicExecutor`] enum.
89pub trait Executor<'executor> {
90 /// Executes a raw SQL query
91 ///
92 /// The query is executed as prepared statement.
93 /// To bind parameter, use ? as placeholder in SQLite and MySQL
94 /// and $1, $2, $n in Postgres.
95 ///
96 /// The generic `Q` is used to "select" what the database is supposed to respond with.
97 /// See [`QueryStrategy`] for a list of available options.
98 ///
99 /// ```skipped
100 /// db.execute::<All>("SELECT * FROM foo;".to_string(), vec![]);
101 /// ```
102 fn execute<'data, 'result, Q>(
103 self,
104 query: String,
105 values: Vec<Value<'data>>,
106 ) -> Q::Result<'result>
107 where
108 'executor: 'result,
109 'data: 'result,
110 Q: QueryStrategy;
111
112 /// Get the executor's sql dialect.
113 fn dialect(&self) -> DBImpl;
114
115 /// Convenience method to convert into a "`dyn Executor`"
116 fn into_dyn(self) -> DynamicExecutor<'executor>;
117
118 /// A future producing a [`TransactionGuard`] returned by [`ensure_transaction`](Executor::ensure_transaction)
119 type EnsureTransactionFuture: Future<Output = Result<TransactionGuard<'executor>, Error>> + Send;
120
121 /// Ensure a piece of code is run inside a transaction using a [`TransactionGuard`].
122 ///
123 /// In generic code an [`Executor`] might and might not be a `&mut Transaction`.
124 /// But sometimes you'd want to ensure your code is run inside a transaction
125 /// (for example [bulk inserts](crate::database::insert_bulk)).
126 ///
127 /// This method solves this by producing a type which is either an owned or borrowed Transaction
128 /// depending on the [`Executor`] it is called on.
129 fn ensure_transaction(self)
130 -> BoxFuture<'executor, Result<TransactionGuard<'executor>, Error>>;
131}
132
133/// Choose whether to use transactions or not at runtime
134///
135/// Like a `Box<dyn Executor<'executor>>`
136pub enum DynamicExecutor<'executor> {
137 /// Use a default database connection
138 Database(&'executor Database),
139 /// Use a transaction
140 Transaction(&'executor mut Transaction),
141}
142
143impl<'executor> Executor<'executor> for DynamicExecutor<'executor> {
144 fn execute<'data, 'result, Q>(
145 self,
146 query: String,
147 values: Vec<Value<'data>>,
148 ) -> Q::Result<'result>
149 where
150 'executor: 'result,
151 'data: 'result,
152 Q: QueryStrategy,
153 {
154 debug!(
155 target: "rorm_db::executor",
156 sql = query,
157 values.len = values.len(),
158 "Executing statement"
159 );
160 match self {
161 DynamicExecutor::Database(db) => db.execute::<Q>(query, values),
162 DynamicExecutor::Transaction(tr) => tr.execute::<Q>(query, values),
163 }
164 }
165
166 fn dialect(&self) -> DBImpl {
167 match self {
168 DynamicExecutor::Database(db) => db.dialect(),
169 DynamicExecutor::Transaction(tr) => tr.dialect(),
170 }
171 }
172
173 fn into_dyn(self) -> DynamicExecutor<'executor> {
174 self
175 }
176
177 type EnsureTransactionFuture = BoxFuture<'executor, Result<TransactionGuard<'executor>, Error>>;
178
179 fn ensure_transaction(self) -> Self::EnsureTransactionFuture {
180 match self {
181 DynamicExecutor::Database(db) => db.ensure_transaction(),
182 DynamicExecutor::Transaction(tr) => Box::pin(tr.ensure_transaction()),
183 }
184 }
185}