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