Trait SaveRepository

Source
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 the Model 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

  1. This trait is automatically implemented for any type that implements both InsertableRepository and UpdatableRepository
  2. The save method checks Model::get_id() to determine whether to insert or update
  3. The batch methods intelligently sort models into separate insert and update operations
  4. Where possible, insert and update operations within a batch are executed concurrently for optimal performance

Provided Methods§

Source

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,

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 query
  • 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?
examples/basic_repo.rs (line 106)
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    }
Source

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,

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 query
  • 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
}
Source

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
}
Source

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?
examples/basic_repo.rs (line 195)
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}
Source

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
Source

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:

  1. Splits each batch into models requiring insertion vs update
  2. Processes insertions and updates concurrently when possible
  3. Handles empty cases efficiently
  4. 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.

Implementors§