sqlx_utils/traits/repository/delete.rs
1//! Trait for adding delete capabilities to a repository
2
3use crate::prelude::{Database, SqlFilter};
4use crate::traits::{Model, Repository};
5use crate::types::Query;
6use crate::utils::{BatchOperator, DEFAULT_BATCH_SIZE};
7use sqlx::{Executor, QueryBuilder};
8
9/// Trait for repositories that can delete records from the database.
10///
11/// The `DeleteRepository` trait extends the base [`Repository`] trait with methods
12/// for deleting records. It provides standardized ways to delete both individual
13/// records and batches of records, optimizing database interactions while maintaining
14/// data integrity.
15///
16/// # Type Parameters
17///
18/// * `M` - The model type that this repository deletes. Must implement the [`Model`] trait.
19///
20/// # Examples
21///
22/// Basic implementation:
23/// ```rust
24/// # use sqlx_utils::prelude::SqlFilter;
25/// use sqlx_utils::traits::{Model, Repository, DeleteRepository};
26/// # use sqlx_utils::types::{Pool, Query};
27/// # struct User { id: i32, name: String }
28/// # impl Model for User {
29/// # type Id = i32;
30/// # fn get_id(&self) -> Option<Self::Id> { Some(self.id) }
31/// # }
32/// # struct UserRepository { pool: Pool }
33/// # impl Repository<User> for UserRepository {
34/// # fn pool(&self) -> &Pool { &self.pool }
35/// # }
36///
37/// impl DeleteRepository<User> for UserRepository {
38/// fn delete_by_id_query(id: &i32) -> Query<'_> {
39/// sqlx::query("DELETE FROM users WHERE id = $1")
40/// .bind(id)
41/// }
42///
43/// fn delete_by_filter_query<'args>(filter: impl SqlFilter<'args>) -> Query<'args> {
44/// let mut builder = sqlx::query_builder::QueryBuilder::new("DELETE FROM users");
45///
46/// if filter.should_apply_filter() {
47/// builder.push("WHERE ");
48/// filter.apply_filter(&mut builder);
49/// }
50///
51/// builder.build()
52/// }
53/// }
54///
55/// // Usage
56/// # async fn example(repo: &UserRepository) -> sqlx_utils::Result<()> {
57/// // Delete a single user
58/// repo.delete_by_id(1).await?;
59///
60/// // Delete multiple users
61/// let ids = vec![1, 2, 3];
62/// repo.delete_many_by_id(ids).await?;
63/// # Ok(())
64/// # }
65/// ```
66///
67/// Using the macro for simpler implementation:
68/// ```rust
69/// # use sqlx_utils::{repository, repository_delete};
70/// # use sqlx_utils::traits::Model;
71/// # use sqlx_utils::types::Query;
72/// # struct User { id: i32, name: String }
73/// # impl Model for User {
74/// # type Id = i32;
75/// # fn get_id(&self) -> Option<Self::Id> { Some(self.id) }
76/// # }
77///
78/// repository! {
79/// UserRepository<User>;
80///
81/// // if you need to override any method other than `Repository::pool` they will go here
82/// }
83///
84/// repository_delete! {
85/// UserRepository<User>;
86///
87/// delete_by_id_query(id) {
88/// sqlx::query("DELETE FROM users WHERE id = $1")
89/// .bind(id)
90/// }
91/// }
92/// ```
93///
94/// # Implementation Notes
95///
96/// 1. Required method: [`delete_by_id_query`](DeleteRepository::delete_by_id_query) - Defines how to create a deletion query for a model ID
97/// 2. Provided methods:
98/// - [`delete_by_id`](DeleteRepository::delete_by_id) - Deletes a single record by ID
99/// - [`delete_many_by_id`](DeleteRepository::delete_many_by_id) - Deletes multiple records by id using the default batch size
100/// - [`delete_batch_by_id`](DeleteRepository::delete_batch_by_id) - Deletes multiple records by id with a custom batch size
101/// 3. All batch operations use transactions to ensure data consistency
102/// 4. Performance is optimized through batching and connection pooling
103/// 5. Consider implementing soft deletes if required by your application
104#[diagnostic::on_unimplemented(
105 note = "Type `{Self}` does not implement the `DeleteRepository<{M}>` trait",
106 label = "this type does not implement `DeleteRepository` for model type `{M}`",
107 message = "`{Self}` must implement `DeleteRepository<{M}>` to delete `{M}` records"
108)]
109#[async_trait::async_trait]
110pub trait DeleteRepository<M: Model>: Repository<M> {
111 /// Creates a SQL query to delete a record by its ID.
112 ///
113 /// This method generates a DELETE statement that will remove exactly one record
114 /// from the database based on its primary key. It's designed to be both safe
115 /// and efficient by using parameterized queries.
116 ///
117 /// # Parameters
118 ///
119 /// * `id` - A reference to the ID of the record to delete
120 ///
121 /// # Returns
122 ///
123 /// * [`Query`] - A prepared SQL DELETE query
124 ///
125 /// # Implementation Notes
126 ///
127 /// Consider:
128 /// 1. Handling soft deletes if required
129 /// 2. Checking foreign key constraints
130 /// 3. Implementing cascading deletes if needed
131 fn delete_by_id_query(id: &M::Id) -> Query<'_>;
132
133 /// Creates a SQL query to delete a record by a given [`SqlFilter`].
134 ///
135 /// This method generates a DELETE statement that will remove all records that match a given [`SqlFilter`]. It's designed to be both safe
136 /// and efficient by using parameterized queries. The query can do a soft delete or a complete remove of it,
137 /// that detail is up to the implementor, the rest of the Trait expects the query to not return anything however and the query should reflect that.
138 ///
139 /// # Parameters
140 ///
141 /// * `filter` - The filter used when generating the query.
142 ///
143 /// # Returns
144 ///
145 /// * [`Query`] - A prepared SQL query to DELETE records or soft delete them
146 ///
147 /// # Implementation Notes
148 ///
149 /// Consider:
150 /// 1. Handling soft deletes if required
151 /// 2. Checking foreign key constraints
152 /// 3. Implementing cascading deletes if needed
153 /// 4. If called via the default implementation of [`delete_by_filter_with_executor`](Self::delete_by_filter_with_executor)
154 /// the filter will be guaranteed to be applied.
155 fn delete_by_filter_query<'args>(
156 filter: impl SqlFilter<'args>,
157 ) -> QueryBuilder<'args, Database>;
158
159 /// Removes a single record from the database by its identifier.
160 ///
161 /// This method executes the deletion query generated by [`delete_by_id_query`](Self::delete_by_id_query) and uses the [`Executor`] `tx` for doing it. It provides
162 /// a simple interface for removing individual records while handling all necessary database
163 /// interactions and error management.
164 ///
165 /// # Parameters
166 ///
167 /// * `tx` - The executor to use for the query
168 /// * `id` - Any value that can be converted into the model's ID type
169 ///
170 /// # Returns
171 ///
172 /// * [`crate::Result<()>`](crate::Result) - Success if the deletion was executed, or an error if the operation failed
173 ///
174 /// # Example
175 ///
176 /// ```ignore
177 /// async fn remove_user(repo: &UserRepository, user_id: i32) -> crate::Result<()> {
178 /// repo.delete_by_id_with_executor(repo.pool(), user_id).await
179 /// }
180 /// ```
181 #[inline(always)]
182 #[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "delete_by_id", err))]
183 #[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "delete_by_id"))]
184 async fn delete_by_id_with_executor<'c, E>(
185 &self,
186 tx: E,
187 id: impl Into<M::Id> + Send,
188 ) -> crate::Result<()>
189 where
190 'c: 'async_trait,
191 E: Executor<'c, Database = Database> + Send,
192 {
193 Self::delete_by_id_query(&id.into()).execute(tx).await?;
194 Ok(())
195 }
196
197 /// Removes a single record from the database by its identifier.
198 ///
199 /// This method executes the deletion query generated by [`delete_by_id_query`](Self::delete_by_id_query). It provides
200 /// a simple interface for removing individual records while handling all necessary database
201 /// interactions and error management.
202 ///
203 /// # Parameters
204 ///
205 /// * `id` - Any value that can be converted into the model's ID type
206 ///
207 /// # Returns
208 ///
209 /// * [`crate::Result<()>`](crate::Result) - Success if the deletion was executed, or an error if the operation failed
210 ///
211 /// # Example
212 ///
213 /// ```no_compile
214 /// async fn remove_user(repo: &UserRepository, user_id: i32) -> crate::Result<()> {
215 /// repo.delete_by_id(user_id).await
216 /// }
217 /// ```
218 #[inline(always)]
219 async fn delete_by_id(&self, id: impl Into<M::Id> + Send) -> crate::Result<()> {
220 self.delete_by_id_with_executor(self.pool(), id).await
221 }
222
223 /// Deletes multiple records using the default batch size.
224 ///
225 /// This is a convenience wrapper around [`delete_batch`](Self::delete_batch_by_id) that uses [`DEFAULT_BATCH_SIZE`].
226 /// It provides a simpler interface for bulk deletions when the default batch size
227 /// is appropriate.
228 ///
229 /// # Parameters
230 ///
231 /// * `ids` - An iterator yielding IDs of records to delete
232 ///
233 /// # Returns
234 ///
235 /// * [`crate::Result<()>`](crate::Result) - Success if all deletions were executed, or an error if any operation failed
236 #[inline(always)]
237 async fn delete_many_by_id<I>(&self, ids: I) -> crate::Result<()>
238 where
239 I: IntoIterator<Item = M::Id> + Send,
240 I::IntoIter: Send,
241 {
242 <Self as DeleteRepository<M>>::delete_batch_by_id::<DEFAULT_BATCH_SIZE, I>(self, ids).await
243 }
244
245 /// Performs a batched deletion operation with a specified batch size.
246 ///
247 /// Similar to other batch operations, this method uses [`BatchOperator`] to efficiently
248 /// process large numbers of deletions in chunks, maintaining optimal performance
249 /// and preventing resource exhaustion.
250 ///
251 /// # Type Parameters
252 ///
253 /// * `N` - The size of each batch to process
254 ///
255 /// # Parameters
256 ///
257 /// * `ids` - An iterator yielding IDs of records to delete
258 ///
259 /// # Returns
260 ///
261 /// * [`crate::Result<()>`](crate::Result) - Success if all batches were processed, or an error if any operation failed
262 ///
263 /// # Implementation Details
264 ///
265 /// The method:
266 /// 1. Chunks the input IDs into batches of size N
267 /// 2. Processes each batch in a transactions using [`delete_query_by_id`](Self::delete_by_id_query)
268 /// 3. Maintains ACID properties within each batch
269 ///
270 /// # Performance Considerations
271 ///
272 /// Consider batch size carefully:
273 /// - Too small: More overhead from multiple transactions
274 /// - Too large: Higher memory usage and longer transactions times
275 #[inline(always)]
276 #[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "delete_batch_by_id", err))]
277 #[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "delete_batch_by_id"))]
278 async fn delete_batch_by_id<const N: usize, I>(&self, ids: I) -> crate::Result<()>
279 where
280 I: IntoIterator<Item = M::Id> + Send,
281 I::IntoIter: Send,
282 {
283 let span = tracing::Span::current();
284 span.record("BATCH_SIZE", N);
285
286 BatchOperator::<M::Id, N>::execute_query(ids, self.pool(), Self::delete_by_id_query).await
287 }
288
289 /// Removes records from the database by a filter.
290 ///
291 /// This method executes the deletion query generated by [`delete_by_filter_query`](Self::delete_by_filter_query)
292 /// and uses the [`Executor`] `tx` for doing it. It provides a simple interface for removing records with a filter
293 /// while handling all necessary database interactions and error management.
294 ///
295 /// # Parameters
296 ///
297 /// * `tx` - The executor to use for the query
298 /// * `filter` - A [`SqlFilter`] to define what records should be deleted
299 ///
300 /// # Returns
301 ///
302 /// * [`crate::Result<()>`](crate::Result) - Success if the deletion was executed, or an error if the operation failed
303 ///
304 /// # Example
305 ///
306 /// ```ignore
307 /// async fn remove_user(repo: &UserRepository, filter: impl SqlFilter<'_>) -> crate::Result<()> {
308 /// repo.delete_by_filter_with_executor(repo.pool(), filter).await
309 /// }
310 /// ```
311 #[inline(always)]
312 #[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "delete_by_filter", err))]
313 #[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "delete_by_filter"))]
314 async fn delete_by_filter_with_executor<'c, E>(
315 &self,
316 tx: E,
317 filter: impl SqlFilter<'_> + Send,
318 ) -> crate::Result<()>
319 where
320 E: Executor<'c, Database = Database> + Send,
321 {
322 if !filter.should_apply_filter() {
323 return Err(crate::Error::Repository {
324 message: "Can not Delete from table with a empty filter.".into(),
325 });
326 }
327
328 Self::delete_by_filter_query(filter)
329 .build()
330 .execute(tx)
331 .await?;
332 Ok(())
333 }
334
335 /// Removes records from the database by a filter.
336 ///
337 /// This method executes the deletion query generated by [`delete_by_filter_query`](Self::delete_by_filter_query)
338 /// and uses the [`Repository::pool`] as a [`Executor`]. It provides a simple interface for removing records with a filter
339 /// while handling all necessary database interactions and error management.
340 ///
341 /// # Parameters
342 ///
343 /// * `filter` - A [`SqlFilter`] to define what records should be deleted
344 ///
345 /// # Returns
346 ///
347 /// * [`crate::Result<()>`](crate::Result) - Success if the deletion was executed, or an error if the operation failed
348 ///
349 /// # Example
350 ///
351 /// ```no_compile
352 /// async fn remove_user(repo: &UserRepository, filter: impl SqlFilter<'_>) -> crate::Result<()> {
353 /// repo.delete_by_filter(filter).await
354 /// }
355 /// ```
356 #[inline(always)]
357 async fn delete_by_filter(&self, filter: impl SqlFilter<'_> + Send) -> crate::Result<()> {
358 self.delete_by_filter_with_executor(self.pool(), filter)
359 .await
360 }
361}