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?
98 pub async fn save_with_context(
99 &self,
100 model: User,
101 context: UserContext,
102 ) -> Result<User, DbError> {
103 self.with_transaction(move |mut tx| async move {
104 let res = match context {
105 UserContext::System => self
106 .save_with_executor(&mut *tx, model)
107 .await
108 .map_err(Into::into),
109 UserContext::UnAuthenticated => Err(DbError::NotAllowed),
110 };
111
112 (res, tx)
113 })
114 .await
115 }
116
117 pub async fn save_with_tx<'a, 'b>(&'a self, model: User) -> Result<Vec<User>, DbError> {
118 self.transaction_sequential::<'a, 'b>([
119 move |mut tx: Transaction<'b, Database>| async move {
120 let res = self.save_with_executor(&mut *tx, model).await;
121
122 (res, tx)
123 },
124 ])
125 .await
126 .map_err(Into::into)
127 }
128
129 pub async fn save_with_rx_concurrent<'a, 'b>(
130 &'a self,
131 model: User,
132 ) -> Result<Vec<User>, DbError>
133 where
134 'b: 'a,
135 {
136 self.transaction_concurrent::<'a, 'b>([
137 |tx: Arc<parking_lot::Mutex<Transaction<'b, Database>>>| async move {
138 let mut tx = match tx.try_lock_arc() {
139 Some(tx) => tx,
140 None => return Err(Error::MutexLockError),
141 };
142
143 let res = USER_REPO.save_with_executor(&mut **tx, model).await;
144
145 ArcMutexGuard::<parking_lot::RawMutex, Transaction<'b, sqlx::Any>>::unlock_fair(tx);
146
147 res
148 },
149 ])
150 .await
151 .map_err(Into::into)
152 }
153
154 pub async fn try_save_with_tx<'a, 'b>(
155 &'a self,
156 model: User,
157 ) -> Result<Vec<User>, Vec<DbError>> {
158 self.try_transaction::<'a, 'b>([move |mut tx: Transaction<'b, Database>| async move {
159 let res = self.save_with_executor(&mut *tx, model).await;
160
161 (res, tx)
162 }])
163 .await
164 .map_err(|errors| errors.into_iter().map(Into::into).collect())
165 }
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?
175async fn main() {
176 install_default_drivers();
177
178 initialize_db_pool(
179 PoolOptions::new()
180 .max_connections(21)
181 .min_connections(5)
182 .idle_timeout(Duration::from_secs(60 * 10))
183 .max_lifetime(Duration::from_secs(60 * 60 * 24))
184 .acquire_timeout(Duration::from_secs(20))
185 .connect(&DATABASE_URL)
186 .await
187 .expect("Failed to connect to database"),
188 );
189
190 let user = User {
191 id: 1,
192 name: String::new(),
193 };
194
195 USER_REPO.save_ref(&user).await.unwrap();
196
197 USER_REPO.save_in_transaction(user.clone()).await.unwrap();
198
199 USER_REPO
200 .save_with_context(user.clone(), UserContext::System)
201 .await
202 .unwrap();
203
204 USER_REPO.with_transaction(action).await.unwrap();
205
206 USER_REPO
207 .delete_by_values_in_transaction("id", [1, 2, 3, 11, 22])
208 .await
209 .unwrap();
210}
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.