Skip to main content

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}