pub trait SaveRepository<M: Model>: InsertableRepository<M> + UpdatableRepository<M> {
// Provided methods
fn save_with_executor<'c, 'life0, 'async_trait, E>(
&'life0 self,
tx: E,
model: M,
) -> Pin<Box<dyn Future<Output = Result<M>> + Send + 'async_trait>>
where M: 'async_trait,
E: Executor<'c, Database = Database> + Send + 'async_trait,
Self: Sync + 'async_trait,
'c: 'async_trait,
'life0: 'async_trait { ... }
fn save_ref_with_executor<'c, 'life0, 'life1, 'async_trait, E>(
&'life0 self,
tx: E,
model: &'life1 M,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where M: 'async_trait,
E: Executor<'c, Database = Database> + Send + 'async_trait,
Self: Sync + 'async_trait,
'c: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn save<'life0, 'async_trait>(
&'life0 self,
model: M,
) -> Pin<Box<dyn Future<Output = Result<M>> + Send + 'async_trait>>
where M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait { ... }
fn save_ref<'life0, 'life1, 'async_trait>(
&'life0 self,
model: &'life1 M,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait { ... }
fn save_all<'life0, 'async_trait, I>(
&'life0 self,
models: I,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
Self: Sync + 'async_trait,
'life0: 'async_trait { ... }
fn save_batch<'life0, 'async_trait, const N: usize, I>(
&'life0 self,
models: I,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
where I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait { ... }
}
Expand description
Trait for repositories that can intelligently save records by either inserting or updating them.
The SaveRepository
trait combines InsertableRepository
and UpdatableRepository
to provide a higher-level interface for persisting models. It automatically determines
whether to insert or update a record based on whether the model has an ID, allowing
for simpler code when the operation type doesn’t matter to the caller.
§Type Parameters
M
- The model type that this repository saves. Must implement theModel
trait.
§Examples
The trait is automatically implemented for any repository that implements both
InsertableRepository
and UpdatableRepository
:
impl InsertableRepository<User> for UserRepository {
fn insert_query(user: &User) -> Query<'_> {
sqlx::query("INSERT INTO users (name) VALUES ($1)")
.bind(&user.name)
}
}
impl UpdatableRepository<User> for UserRepository {
fn update_query(user: &User) -> Query<'_> {
sqlx::query("UPDATE users SET name = $1 WHERE id = $2")
.bind(&user.name)
.bind(user.id.unwrap())
}
}
// SaveRepository is automatically implemented
// Usage
// Create a new user (no ID)
let new_user = User { id: None, name: String::from("Alice") };
repo.save(new_user).await?; // Will insert
// Update an existing user (has ID)
let existing_user = User { id: Some(1), name: String::from("Updated Alice") };
repo.save_ref(&existing_user).await?; // Will update
// Save a mixed batch of new and existing users
let users = vec![
User { id: None, name: String::from("Bob") }, // Will insert
User { id: Some(2), name: String::from("Charlie") } // Will update
];
repo.save_all(users).await?; // Automatically sorts and batches operations
§Implementation Notes
- This trait is automatically implemented for any type that implements both
InsertableRepository
andUpdatableRepository
- The
save
method checksModel::get_id()
to determine whether to insert or update - The batch methods intelligently sort models into separate insert and update operations
- Where possible, insert and update operations within a batch are executed concurrently for optimal performance
Provided Methods§
Sourcefn save_with_executor<'c, 'life0, 'async_trait, E>(
&'life0 self,
tx: E,
model: M,
) -> Pin<Box<dyn Future<Output = Result<M>> + Send + 'async_trait>>
fn save_with_executor<'c, 'life0, 'async_trait, E>( &'life0 self, tx: E, model: M, ) -> Pin<Box<dyn Future<Output = Result<M>> + Send + 'async_trait>>
Intelligently persists a model instance by either inserting or updating using the Executor
tx
.
This method determines the appropriate operation based on whether the model has an ID:
- If the model has no ID, it performs an insertion
- If the model has an ID, it performs an update
§Parameters
tx
- The executor to use for the querymodel
- A reference to the model instance to save
§Returns
crate::Result<()>
- Success if the operation was executed, or an error if it failed
§Example
async fn save_user(repo: &UserRepository, user: &User) -> crate::Result<()> {
repo.save(user).await // Will insert or update based on user.id
}
Examples found in repository?
101 pub async fn save_with_context(
102 &self,
103 model: User,
104 context: UserContext,
105 ) -> Result<User, DbError> {
106 self.with_transaction(move |mut tx| async move {
107 let res = match context {
108 UserContext::System => self
109 .save_with_executor(&mut *tx, model)
110 .await
111 .map_err(Into::into),
112 UserContext::UnAuthenticated => Err(DbError::NotAllowed),
113 };
114
115 (res, tx)
116 })
117 .await
118 }
119
120 pub async fn save_with_tx<'a, 'b>(&'a self, model: User) -> Result<Vec<User>, DbError> {
121 self.transaction_sequential::<'a, 'b>([
122 move |mut tx: Transaction<'b, Database>| async move {
123 let res = self.save_with_executor(&mut *tx, model).await;
124
125 (res, tx)
126 },
127 ])
128 .await
129 .map_err(Into::into)
130 }
131
132 pub async fn save_with_rx_concurrent<'a, 'b>(
133 &'a self,
134 model: User,
135 ) -> Result<Vec<User>, DbError>
136 where
137 'b: 'a,
138 {
139 self.transaction_concurrent::<'a, 'b>([
140 |tx: Arc<parking_lot::Mutex<Transaction<'b, Database>>>| async move {
141 let mut tx = match tx.try_lock_arc() {
142 Some(tx) => tx,
143 None => return Err(Error::MutexLockError),
144 };
145
146 let res = USER_REPO.save_with_executor(&mut **tx, model).await;
147
148 ArcMutexGuard::<parking_lot::RawMutex, Transaction<'b, sqlx::Any>>::unlock_fair(tx);
149
150 res
151 },
152 ])
153 .await
154 .map_err(Into::into)
155 }
156
157 pub async fn try_save_with_tx<'a, 'b>(
158 &'a self,
159 model: User,
160 ) -> Result<Vec<User>, Vec<DbError>> {
161 self.try_transaction::<'a, 'b>([move |mut tx: Transaction<'b, Database>| async move {
162 let res = self.save_with_executor(&mut *tx, model).await;
163
164 (res, tx)
165 }])
166 .await
167 .map_err(|errors| errors.into_iter().map(Into::into).collect())
168 }
Sourcefn save_ref_with_executor<'c, 'life0, 'life1, 'async_trait, E>(
&'life0 self,
tx: E,
model: &'life1 M,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
fn save_ref_with_executor<'c, 'life0, 'life1, 'async_trait, E>( &'life0 self, tx: E, model: &'life1 M, ) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>
Intelligently persists a model instance by either inserting or updating using the Executor
tx
.
This method determines the appropriate operation based on whether the model has an ID:
- If the model has no ID, it performs an insertion
- If the model has an ID, it performs an update
§Parameters
tx
- The executor to use for the querymodel
- A reference to the model instance to save
§Returns
crate::Result<()>
- Success if the operation was executed, or an error if it failed
§Example
async fn save_user(repo: &UserRepository, user: &User) -> crate::Result<()> {
repo.save(user).await // Will insert or update based on user.id
}
Sourcefn save<'life0, 'async_trait>(
&'life0 self,
model: M,
) -> Pin<Box<dyn Future<Output = Result<M>> + Send + 'async_trait>>where
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
fn save<'life0, 'async_trait>(
&'life0 self,
model: M,
) -> Pin<Box<dyn Future<Output = Result<M>> + Send + 'async_trait>>where
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
Intelligently persists a model instance by either inserting or updating.
This method determines the appropriate operation based on whether the model has an ID:
- If the model has no ID, it performs an insertion
- If the model has an ID, it performs an update
§Parameters
model
- A reference to the model instance to save
§Returns
crate::Result<()>
- Success if the operation was executed, or an error if it failed
§Example
async fn save_user(repo: &UserRepository, user: &User) -> crate::Result<()> {
repo.save(user).await // Will insert or update based on user.id
}
Sourcefn save_ref<'life0, 'life1, 'async_trait>(
&'life0 self,
model: &'life1 M,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn save_ref<'life0, 'life1, 'async_trait>(
&'life0 self,
model: &'life1 M,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Intelligently persists a model instance by either inserting or updating.
This method determines the appropriate operation based on whether the model has an ID:
- If the model has no ID, it performs an insertion
- If the model has an ID, it performs an update
§Parameters
model
- A reference to the model instance to save
§Returns
crate::Result<()>
- Success if the operation was executed, or an error if it failed
§Example
async fn save_user(repo: &UserRepository, user: &User) -> crate::Result<()> {
repo.save(user).await // Will insert or update based on user.id
}
Examples found in repository?
178async fn main() {
179 install_default_drivers();
180
181 initialize_db_pool(
182 PoolOptions::new()
183 .max_connections(21)
184 .min_connections(5)
185 .idle_timeout(Duration::from_secs(60 * 10))
186 .max_lifetime(Duration::from_secs(60 * 60 * 24))
187 .acquire_timeout(Duration::from_secs(20))
188 .connect(&DATABASE_URL)
189 .await
190 .expect("Failed to connect to database"),
191 );
192
193 let user = User {
194 id: 1,
195 name: String::new(),
196 };
197
198 USER_REPO.save_ref(&user).await.unwrap();
199
200 USER_REPO.save_in_transaction(user.clone()).await.unwrap();
201
202 USER_REPO
203 .save_with_context(user.clone(), UserContext::System)
204 .await
205 .unwrap();
206
207 USER_REPO.with_transaction(action).await.unwrap();
208
209 USER_REPO
210 .delete_by_values_in_transaction("id", [1, 2, 3, 11, 22])
211 .await
212 .unwrap();
213
214 USER_REPO
215 .get_one_by_filter(UserFilter::new("name").id(1))
216 .await
217 .unwrap();
218}
Sourcefn save_all<'life0, 'async_trait, I>(
&'life0 self,
models: I,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
Self: Sync + 'async_trait,
'life0: 'async_trait,
fn save_all<'life0, 'async_trait, I>(
&'life0 self,
models: I,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
Self: Sync + 'async_trait,
'life0: 'async_trait,
Saves multiple models using the default batch size.
This is a convenience wrapper around save_batch
that uses DEFAULT_BATCH_SIZE
.
It provides a simpler interface for bulk save operations when the default batch
size is appropriate.
§Parameters
models
- An iterator yielding model instances to save
§Returns
crate::Result<()>
- Success if all operations were executed, or an error if any failed
Sourcefn save_batch<'life0, 'async_trait, const N: usize, I>(
&'life0 self,
models: I,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
fn save_batch<'life0, 'async_trait, const N: usize, I>(
&'life0 self,
models: I,
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'async_trait>>where
I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
M: 'async_trait,
Self: Sync + 'async_trait,
'life0: 'async_trait,
Performs an intelligent batched save operation with a specified batch size.
This is the most sophisticated batch operation, efficiently handling both insertions and updates in the same operation. It sorts models based on whether they need insertion or update, then processes them optimally.
§Type Parameters
N
- The size of each batch to process
§Parameters
models
- An iterator yielding model instances to save
§Returns
crate::Result<()>
- Success if all batches were processed, or an error if any operation failed
§Implementation Details
The method:
- Splits each batch into models requiring insertion vs update
- Processes insertions and updates concurrently when possible
- Handles empty cases efficiently
- Maintains transactional integrity within each batch
§Performance Features
- Concurrent processing of inserts and updates
- Efficient batch size management
- Smart handling of empty cases
- Transaction management for data consistency
§Performance Considerations
Consider batch size carefully:
- Too small: More overhead from multiple transactions
- Too large: Higher memory usage and longer transactions times
Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.