sqlx_utils/traits/repository/
update.rs

1//! Trait for adding update capabilities to a repository
2
3use crate::prelude::Database;
4use crate::traits::{Model, Repository};
5use crate::types::Query;
6use crate::utils::{BatchOperator, DEFAULT_BATCH_SIZE};
7use sqlx::Executor;
8
9/// Trait for repositories that can update existing records in the database.
10///
11/// The `UpdatableRepository` trait extends the base [`Repository`] trait with methods
12/// for updating existing records. It provides standardized ways to update both individual
13/// models and batches of models, optimizing database interactions while maintaining data
14/// integrity.
15///
16/// # Type Parameters
17///
18/// * `M` - The model type that this repository updates. Must implement the [`Model`] trait.
19///
20/// # Examples
21///
22/// Basic implementation:
23/// ```rust
24/// # use sqlx_utils::traits::{Model, Repository, UpdatableRepository};
25/// # use sqlx_utils::types::{Pool, Query};
26/// # struct User { id: i32, name: String }
27/// # impl Model for User {
28/// #     type Id = i32;
29/// #     fn get_id(&self) -> Option<Self::Id> { Some(self.id) }
30/// # }
31/// # struct UserRepository { pool: Pool }
32/// # impl Repository<User> for UserRepository {
33/// #     fn pool(&self) -> &Pool { &self.pool }
34/// # }
35///
36/// impl UpdatableRepository<User> for UserRepository {
37///     fn update_query(user: &User) -> Query<'_> {
38///         sqlx::query("UPDATE users SET name = $1 WHERE id = $2")
39///             .bind(&user.name)
40///             .bind(user.id)
41///     }
42/// }
43///
44/// // Usage
45/// # async fn example(repo: &UserRepository, user: &User) -> sqlx_utils::Result<()> {
46/// // Update a single user
47/// repo.update_ref(user).await?;
48///
49/// // Update multiple users
50/// let users = vec![
51///     User { id: 1, name: String::from("Updated Alice") },
52///     User { id: 2, name: String::from("Updated Bob") }
53/// ];
54/// repo.update_many(users).await?;
55/// # Ok(())
56/// # }
57/// ```
58///
59/// Using the macro for simpler implementation:
60/// ```rust
61/// # use sqlx_utils::{repository, repository_update};
62/// # use sqlx_utils::traits::Model;
63/// # use sqlx_utils::types::Query;
64/// # struct User { id: i32, name: String }
65/// # impl Model for User {
66/// #     type Id = i32;
67/// #     fn get_id(&self) -> Option<Self::Id> { Some(self.id) }
68/// # }
69///
70/// repository! {
71///     UserRepository<User>;
72///
73///     // if you need to override any method other than `Repository::pool` they will go here
74/// }
75///
76/// repository_update! {
77///     UserRepository<User>;
78///
79///     update_query(user) {
80///         sqlx::query("UPDATE users SET name = $1 WHERE id = $2")
81///             .bind(&user.name)
82///             .bind(user.id)
83///     }
84/// }
85/// ```
86///
87/// # Implementation Notes
88///
89/// 1. Required method: [`update_query`](UpdatableRepository::update_query) - Defines how a model is translated into an UPDATE statement
90/// 2. Provided methods:
91///    - [`update_with_executor`](UpdatableRepository::update_with_executor) - updates a single model using any [`Executor`]
92///    - [`update`](UpdatableRepository::update) - Updates a single model
93///    - [`update_many`](UpdatableRepository::update_many) - Updates multiple models using the default batch size
94///    - [`update_batch`](UpdatableRepository::update_batch) - Updates multiple models with a custom batch size
95/// 3. All batch operations use transactions to ensure data consistency
96/// 4. All methods assume the model has a valid ID (typically checked by the `Model::get_id` method)
97/// 5. Performance is optimized through batching and connection pooling
98#[diagnostic::on_unimplemented(
99    note = "Type `{Self}` does not implement the `UpdatableRepository<{M}>` trait",
100    label = "this type does not implement `UpdatableRepository` for model type `{M}`",
101    message = "`{Self}` must implement `UpdatableRepository<{M}>` to update `{M}` records"
102)]
103#[async_trait::async_trait]
104pub trait UpdatableRepository<M: Model>: Repository<M> {
105    /// Creates a SQL query to update an existing model in the database.
106    ///
107    /// This method constructs an UPDATE statement that will modify an existing database record
108    /// to match the current state of the model. It should use the model's ID to identify
109    /// the record to update and include all relevant fields in the SET clause.
110    ///
111    /// # Parameters
112    ///
113    /// * `model` - A reference to the model instance containing updated values
114    ///
115    /// # Returns
116    ///
117    /// * [`Query`] - A prepared SQL UPDATE query
118    ///
119    /// # Implementation Notes
120    ///
121    /// The query should:
122    /// 1. Include a WHERE clause matching the model's ID
123    /// 2. Only update columns that can be modified
124    /// 3. Preserve any timestamp or audit fields as required
125    fn update_query(model: &M) -> Query<'_>;
126
127    /// Executes an update operation for a single model instance.
128    ///
129    /// This method takes the query generated by [`update_query`](Self::update_query) and executes it against the [`Executor`] `tx`.
130    /// It's a higher-level wrapper that handles the actual database interaction, providing a simpler
131    /// interface for updating records.
132    ///
133    /// # Parameters
134    ///
135    /// * `tx` - The executor to use for the query
136    /// * `model` - A reference to the model instance to update
137    ///
138    /// # Returns
139    ///
140    /// * [`crate::Result<()>`](crate::Result) - Success if the update was executed, or an error if the operation failed
141    ///
142    /// # Implementation Details
143    ///
144    /// The method:
145    /// 1. Gets the update query from [`update_query`](Self::update_query)
146    /// 2. Executes it using the connection pool
147    /// 3. Handles any potential database errors
148    #[inline(always)]
149    #[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "update", err))]
150    #[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "update"))]
151    async fn update_with_executor<'c, E>(&self, tx: E, model: M) -> crate::Result<M>
152    where
153        M: 'async_trait,
154        E: Executor<'c, Database = Database> + Send,
155    {
156        Self::update_query(&model).execute(tx).await?;
157        Ok(model)
158    }
159
160    /// Executes an update operation for a single model instance.
161    ///
162    /// This method takes the query generated by [`update_query`](Self::update_query) and executes it against the [`Executor`] `tx`.
163    /// It's a higher-level wrapper that handles the actual database interaction, providing a simpler
164    /// interface for updating records.
165    ///
166    /// # Parameters
167    ///
168    /// * `tx` - The executor to use for the query
169    /// * `model` - A reference to the model instance to update
170    ///
171    /// # Returns
172    ///
173    /// * [`crate::Result<()>`](crate::Result) - Success if the update was executed, or an error if the operation failed
174    ///
175    /// # Implementation Details
176    ///
177    /// The method:
178    /// 1. Gets the update query from [`update_query`](Self::update_query)
179    /// 2. Executes it using the connection pool
180    /// 3. Handles any potential database errors
181    #[inline(always)]
182    #[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "update", err))]
183    #[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "update"))]
184    async fn update_ref_with_executor<'c, E>(&self, tx: E, model: &M) -> crate::Result<()>
185    where
186        M: 'async_trait,
187        E: Executor<'c, Database = Database> + Send,
188    {
189        Self::update_query(model).execute(tx).await?;
190        Ok(())
191    }
192
193    /// Executes an update operation for a single model instance.
194    ///
195    /// This method takes the query generated by [`update_query`](Self::update_query) and executes it against the database.
196    /// It's a higher-level wrapper that handles the actual database interaction, providing a simpler
197    /// interface for updating records.
198    ///
199    /// # Parameters
200    ///
201    /// * `model` - A reference to the model instance to update
202    ///
203    /// # Returns
204    ///
205    /// * [`crate::Result<()>`](crate::Result) - Success if the update was executed, or an error if the operation failed
206    ///
207    /// # Implementation Details
208    ///
209    /// The method:
210    /// 1. Gets the update query from [`update_query`](Self::update_query)
211    /// 2. Executes it using the connection pool
212    /// 3. Handles any potential database errors
213    #[inline(always)]
214    async fn update(&self, model: M) -> crate::Result<M>
215    where
216        M: 'async_trait,
217    {
218        self.update_with_executor(self.pool(), model).await
219    }
220
221    /// Executes an update operation for a single model instance.
222    ///
223    /// This method takes the query generated by [`update_query`](Self::update_query) and executes it against the database.
224    /// It's a higher-level wrapper that handles the actual database interaction, providing a simpler
225    /// interface for updating records.
226    ///
227    /// # Parameters
228    ///
229    /// * `model` - A reference to the model instance to update
230    ///
231    /// # Returns
232    ///
233    /// * [`crate::Result<()>`](crate::Result) - Success if the update was executed, or an error if the operation failed
234    ///
235    /// # Implementation Details
236    ///
237    /// The method:
238    /// 1. Gets the update query from [`update_query`](Self::update_query)
239    /// 2. Executes it using the connection pool
240    /// 3. Handles any potential database errors
241    #[inline(always)]
242    async fn update_ref(&self, model: &M) -> crate::Result<()>
243    where
244        M: 'async_trait,
245    {
246        self.update_ref_with_executor(self.pool(), model).await
247    }
248
249    /// Updates multiple models using the default batch size.
250    ///
251    /// This is a convenience wrapper around [`update_batch`](Self::update_batch) that uses [`DEFAULT_BATCH_SIZE`].
252    /// It simplifies bulk update operations when the default batch size is suitable.
253    ///
254    /// # Parameters
255    ///
256    /// * `models` - An iterator yielding model instances to update
257    ///
258    /// # Returns
259    ///
260    /// * [`crate::Result<()>`](crate::Result) - Success if all updates were executed, or an error if any operation failed
261    #[inline(always)]
262    async fn update_many<I>(&self, models: I) -> crate::Result<()>
263    where
264        I: IntoIterator<Item = M> + Send + 'async_trait,
265        I::IntoIter: Send,
266    {
267        self.update_batch::<DEFAULT_BATCH_SIZE, I>(models).await
268    }
269
270    /// Performs a batched update operation with a specified batch size.
271    ///
272    /// Similar to [`insert_batch`](crate::traits::InsertableRepository::insert_batch), this method uses [`BatchOperator`] to efficiently process
273    /// large numbers of updates in chunks, preventing memory overflow and maintaining
274    /// optimal database performance.
275    ///
276    /// # Type Parameters
277    ///
278    /// * `N` - The size of each batch to process
279    ///
280    /// # Parameters
281    ///
282    /// * `models` - An iterator yielding model instances to update
283    ///
284    /// # Returns
285    ///
286    /// * [`crate::Result<()>`](crate::Result) - Success if all batches were processed, or an error if any operation failed
287    ///
288    /// # Performance Considerations
289    ///
290    /// Consider batch size carefully:
291    /// - Too small: More overhead from multiple transactions
292    /// - Too large: Higher memory usage and longer transactions times
293    #[inline(always)]
294    #[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "update_batch", err))]
295    #[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "update_batch"))]
296    async fn update_batch<const N: usize, I>(&self, models: I) -> crate::Result<()>
297    where
298        I: IntoIterator<Item = M> + Send + 'async_trait,
299        I::IntoIter: Send,
300    {
301        let span = tracing::Span::current();
302        span.record("BATCH_SIZE", N);
303
304        BatchOperator::<M, N>::execute_query(models, self.pool(), Self::update_query).await
305    }
306}