use crate::prelude::Database;
use crate::traits::{InsertableRepository, Model, UpdatableRepository};
use crate::utils::{BatchOperator, DEFAULT_BATCH_SIZE};
use sqlx::Executor;
#[diagnostic::on_unimplemented(
note = "Type `{Self}` does not implement the `SaveRepository<{M}>` trait",
label = "this type cannot automatically save `{M}` records",
message = "`{Self}` must implement both `InsertableRepository<{M}>` and `UpdatableRepository<{M}>` to gain `SaveRepository<{M}>` capabilities"
)]
#[async_trait::async_trait]
pub trait SaveRepository<M: Model>: InsertableRepository<M> + UpdatableRepository<M> {
#[inline]
#[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "save", err))]
#[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "save"))]
async fn save_with_executor<'c, E>(&self, tx: E, model: M) -> crate::Result<M>
where
M: 'async_trait,
E: Executor<'c, Database = Database> + Send,
{
if model.get_id().is_none() {
<Self as InsertableRepository<M>>::insert_with_executor(self, tx, model).await
} else {
<Self as UpdatableRepository<M>>::update_with_executor(self, tx, model).await
}
}
#[inline]
#[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "save", err))]
#[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "save"))]
async fn save_ref_with_executor<'c, E>(&self, tx: E, model: &M) -> crate::Result<()>
where
M: 'async_trait,
E: Executor<'c, Database = Database> + Send,
{
if model.get_id().is_none() {
<Self as InsertableRepository<M>>::insert_ref_with_executor(self, tx, model).await
} else {
<Self as UpdatableRepository<M>>::update_ref_with_executor(self, tx, model).await
}
}
#[inline(always)]
async fn save(&self, model: M) -> crate::Result<M>
where
M: 'async_trait,
{
self.save_with_executor(self.pool(), model).await
}
#[inline(always)]
async fn save_ref(&self, model: &M) -> crate::Result<()>
where
M: 'async_trait,
{
self.save_ref_with_executor(self.pool(), model).await
}
#[inline]
async fn save_all<I>(&self, models: I) -> crate::Result<()>
where
I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
{
<Self as SaveRepository<M>>::save_batch::<DEFAULT_BATCH_SIZE, I>(self, models).await
}
#[cfg_attr(feature = "log_err", tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "save_batch", err))]
#[cfg_attr(not(feature = "log_err"), tracing::instrument(skip_all, level = "debug", parent = &(Self::repository_span()), name = "save_batch"))]
async fn save_batch<const N: usize, I>(&self, models: I) -> crate::Result<()>
where
I: IntoIterator<Item = M> + Send + 'async_trait,
I::IntoIter: Send,
M: 'async_trait,
{
let span = tracing::Span::current();
span.record("BATCH_SIZE", N);
let op = BatchOperator::<M, N>::execute_batch(models, |batch| async {
let mut update = Vec::new();
let mut insert = Vec::new();
for model in batch {
if model.get_id().is_some() {
update.push(model);
} else {
insert.push(model);
}
}
match (update.is_empty(), insert.is_empty()) {
(false, false) => {
futures::try_join!(
<Self as UpdatableRepository<M>>::update_batch::<N, Vec<M>>(self, update),
<Self as InsertableRepository<M>>::insert_batch::<N, Vec<M>>(self, insert)
)?;
}
(false, true) => {
<Self as UpdatableRepository<M>>::update_batch::<N, Vec<M>>(self, update)
.await?;
}
(true, false) => {
<Self as InsertableRepository<M>>::insert_batch::<N, Vec<M>>(self, insert)
.await?;
}
(true, true) => {}
}
Ok(())
});
op.await
}
}
#[async_trait::async_trait]
impl<M: Model, T: InsertableRepository<M> + UpdatableRepository<M>> SaveRepository<M> for T {}