sqlx_utils/traits/repository/select/
mod.rs

1//! Trait for adding select capabilities to a repository
2
3mod_def! {!export
4    pub(crate) mod filter;
5}
6
7use crate::mod_def;
8use crate::prelude::Database;
9use crate::traits::{Model, Repository};
10use crate::types::QueryAs;
11use crate::utils::tracing_debug_log;
12use sqlx::{Database as DatabaseTrait, Executor, FromRow};
13
14/// Trait for repositories that can retrieve records from the database.
15///
16/// The `SelectRepository` trait extends the base [`Repository`] trait with methods
17/// for querying and retrieving records. It defines a standard interface for fetching
18/// both individual records by ID and collections of records.
19///
20/// # Type Parameters
21///
22/// * `M` - The model type that this repository retrieves. Must implement the [`Model`] trait
23///   and [`FromRow`] for the database's row type.
24///
25/// # Required Methods
26///
27/// Implementing repositories must define:
28///
29/// * [`get_all_query`](SelectRepository::get_all_query) - Creates a query to retrieve all records
30/// * [`get_by_id_query`](SelectRepository::get_by_id_query) - Creates a query to retrieve a record by ID
31///
32/// # Provided Methods
33///
34/// These methods are automatically provided based on the required methods:
35///
36/// * [`get_all_with_executor`](SelectRepository::get_all_with_executor) - Execute the get_all query with a custom executor
37/// * [`get_all`](SelectRepository::get_all) - Retrieve all records using the repository's pool
38/// * [`get_by_id_with_executor`](SelectRepository::get_by_id_with_executor) - Execute the get_by_id query with a custom executor
39/// * [`get_by_id`](SelectRepository::get_by_id) - Retrieve a record by ID using the repository's pool
40///
41/// # Examples
42///
43/// Basic implementation:
44/// ```rust
45/// use sqlx_utils::prelude::QueryAs;
46/// use sqlx_utils::traits::{Model, Repository, SelectRepository};
47/// # use sqlx_utils::types::Pool;
48/// # #[derive(sqlx::FromRow)]
49/// # struct User { id: i32, name: String }
50/// # impl Model for User {
51/// #     type Id = i32;
52/// #     fn get_id(&self) -> Option<Self::Id> { Some(self.id) }
53/// # }
54/// # struct UserRepository { pool: Pool }
55/// # impl Repository<User> for UserRepository {
56/// #     fn pool(&self) -> &Pool { &self.pool }
57/// # }
58///
59/// impl SelectRepository<User> for UserRepository {
60///     fn get_all_query(&self) -> QueryAs<User> {
61///         sqlx::query_as("SELECT * FROM users")
62///     }
63///
64///     fn get_by_id_query(&self, id: impl Into<i32>) -> QueryAs<User> {
65///         let id = id.into();
66///         sqlx::query_as("SELECT * FROM users WHERE id = $1")
67///             .bind(id)
68///     }
69/// }
70///
71/// // Usage
72/// # async fn example(repo: &UserRepository) -> sqlx_utils::Result<()> {
73/// // Get a single user
74/// let user = repo.get_by_id(1).await?;
75///
76/// // Get all users
77/// let all_users = repo.get_all().await?;
78/// # Ok(())
79/// # }
80/// ```
81///
82/// # Implementation Notes
83///
84/// 1. When implementing this trait, you only need to define:
85///    - [`get_all_query`](SelectRepository::get_all_query)
86///    - [`get_by_id_query`](SelectRepository::get_by_id_query)
87/// 2. The execution methods are provided automatically based on these query methods
88/// 3. Consider implementing pagination for [`get_all`](SelectRepository::get_all) if the table may contain a large
89///    number of records
90/// 4. Use parameter binding to prevent SQL injection
91/// 5. Consider caching strategies for frequently accessed data
92/// 6. The trait supports using custom executors (like transactions) via the `_with_executor` methods
93#[diagnostic::on_unimplemented(
94    note = "Type `{Self}` does not implement the `SelectRepository<{M}>` trait",
95    label = "this type does not implement `SelectRepository` for model type `{M}`",
96    message = "`{Self}` must implement `SelectRepository<{M}>` to query for `{M}` records",
97    note = "Model `{M}` must implement `FromRow` for the database's row type. If you're seeing lifetime issues, ensure the model and repository properly handle the `'r` lifetime."
98)]
99pub trait SelectRepository<M: Model>: Repository<M>
100where
101    M: Model + for<'r> FromRow<'r, <Database as DatabaseTrait>::Row> + Send + Unpin,
102{
103    /// Creates a query to retrieve all records of this model type from the database.
104    ///
105    /// This is a required method that implementors must define. It creates a query
106    /// that will select all records of the model type, without executing it.
107    ///
108    /// # Returns
109    ///
110    /// * [`QueryAs<M>`] - A prepared query that maps rows to the model type `M`
111    ///
112    /// # Warning
113    ///
114    /// Be cautious with this method on large tables as it could consume significant
115    /// memory and impact database performance. Consider implementing pagination or
116    /// adding WHERE clauses to limit the result set.
117    ///
118    /// # Implementation Example
119    ///
120    /// ```rust,ignore
121    /// fn get_all_query(&self) -> QueryAs<User> {
122    ///     sqlx::query_as("SELECT * FROM users")
123    /// }
124    /// ```
125    ///
126    /// ```rust,ignore
127    /// fn get_all_query(&self) -> QueryAs<User> {
128    ///     sqlx::query_as!(User, "SELECT * FROM users")
129    /// }
130    /// ```
131    fn get_all_query(&self) -> QueryAs<M>;
132
133    /// Creates a query to retrieve a single model instance by its ID.
134    ///
135    /// This is a required method that implementors must define. It creates a query
136    /// that will select a record with the specified ID, without executing it.
137    ///
138    /// # Parameters
139    ///
140    /// * `id` - Any value that can be converted into the model's ID type
141    ///
142    /// # Returns
143    ///
144    /// * [`QueryAs<M>`] - A prepared query that maps rows to the model type `M`
145    ///
146    /// # Implementation Example
147    ///
148    /// ```rust,ignore
149    /// fn get_by_id_query(&self, id: impl Into<User::Id>) -> QueryAs<User> {
150    ///     let id = id.into();
151    ///     sqlx::query_as("SELECT * FROM users WHERE id = $1")
152    ///         .bind(id)
153    /// }
154    /// ```
155    ///
156    /// ```rust,ignore
157    /// fn get_by_id_query(&self, id: impl Into<User::Id>) -> QueryAs<User> {
158    ///     let id = id.into();
159    ///     sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
160    /// }
161    /// ```
162    fn get_by_id_query(&self, id: impl Into<M::Id>) -> QueryAs<M>;
163
164    tracing_debug_log! {
165        [skip_all, Self::repository_span(), "get_all",]
166        /// Executes the `get_all` query with a custom executor.
167        ///
168        /// This method is automatically provided based on your implementation of
169        /// [`get_all_query`](SelectRepository::get_all_query). It allows you to execute
170        /// the query using a custom executor, such as a transactions.
171        ///
172        /// # Type Parameters
173        ///
174        /// * `E` - The executor type, such as a transactions or connection pool
175        ///
176        /// # Parameters
177        ///
178        /// * `tx` - The executor to use for the query
179        ///
180        /// # Returns
181        ///
182        /// * [`crate::Result<Vec<M>>`] - A Result containing a vector of all models if successful
183        ///
184        /// # Example
185        ///
186        /// ```rust,ignore
187        /// async fn get_users_in_transaction<'a>(&self, tx: &mut Transaction<'a, Database>) -> Result<Vec<User>> {
188        ///     self.get_all_with_executor(&mut *tx).await
189        /// }
190        /// ```
191        ///
192        /// # Warning
193        ///
194        /// Be cautious with this method on large tables as it could consume significant
195        /// memory and impact database performance. Consider implementing pagination instead.
196        #[inline(always)]
197        async fn get_all_with_executor<E>(
198            &self,
199            tx: E,
200        ) -> crate::Result<Vec<M>>
201        where
202            E: for<'c> Executor<'c, Database = Database>,
203        {
204            self.get_all_query().fetch_all(tx).await.map_err(Into::into)
205        }
206    }
207
208    /// Retrieves all records of this model type from the database.
209    ///
210    /// This method is automatically provided and simply calls [`get_all_with_executor`](SelectRepository::get_all_with_executor)
211    /// with the repository's connection pool. It executes the query from
212    /// [`get_all_query`](SelectRepository::get_all_query).
213    ///
214    /// # Returns
215    ///
216    /// * [`crate::Result<Vec<M>>`] - A Result containing a vector of all models if successful
217    ///
218    /// # Warning
219    ///
220    /// Be cautious with this method on large tables as it could consume significant
221    /// memory and impact database performance. Consider implementing pagination instead.
222    #[inline(always)]
223    async fn get_all(&self) -> crate::Result<Vec<M>> {
224        self.get_all_with_executor(self.pool()).await
225    }
226
227    tracing_debug_log! {
228        [skip_all, Self::repository_span(), "get_by_id",]
229        /// Executes the `get_by_id` query with a custom executor.
230        ///
231        /// This method is automatically provided based on your implementation of
232        /// [`get_by_id_query`](SelectRepository::get_by_id_query). It allows you to execute
233        /// the query using a custom executor, such as a transactions.
234        ///
235        /// # Type Parameters
236        ///
237        /// * `E` - The executor type, such as a transactions or connection pool
238        ///
239        /// # Parameters
240        ///
241        /// * `tx` - The executor to use for the query
242        /// * `id` - Any value that can be converted into the model's ID type
243        ///
244        /// # Returns
245        ///
246        /// * [`crate::Result<Option<M>>`] - A Result containing either:
247        ///   - `Some(model)` if a record was found
248        ///   - `None` if no record exists with the given ID
249        ///
250        /// # Example
251        ///
252        /// ```rust,ignore
253        /// async fn get_user_in_transaction<'a>(&self, tx: &mut Transaction<'a, Database>, id: i32) -> Result<Option<User>> {
254        ///     self.get_by_id_with_executor(&mut *tx, id).await
255        /// }
256        /// ```
257        #[inline(always)]
258        async fn get_by_id_with_executor<E>(
259            &self,
260            tx: E,
261            id: impl Into<M::Id>
262        ) -> crate::Result<Option<M>>
263        where
264            E: for<'c> Executor<'c, Database = Database>,
265        {
266            self.get_by_id_query(id).fetch_optional(tx).await.map_err(Into::into)
267        }
268    }
269
270    /// Retrieves a single model instance by its ID.
271    ///
272    /// This method is automatically provided and simply calls [`get_by_id_with_executor`](SelectRepository::get_by_id_with_executor)
273    /// with the repository's connection pool. It executes the query from
274    /// [`get_by_id_query`](SelectRepository::get_by_id_query).
275    ///
276    /// # Parameters
277    ///
278    /// * `id` - Any value that can be converted into the model's ID type
279    ///
280    /// # Returns
281    ///
282    /// * [`crate::Result<Option<M>>`] - A Result containing either:
283    ///   - `Some(model)` if a record was found
284    ///   - `None` if no record exists with the given ID
285    async fn get_by_id(&self, id: impl Into<M::Id>) -> crate::Result<Option<M>> {
286        self.get_by_id_with_executor(self.pool(), id).await
287    }
288}