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