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}