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}