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}