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 109)
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    }
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 198)
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}
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§