use std::future::Future;
use vantage_core::Result;
use vantage_types::{EmptyEntity, Entity, InvariantValue, Record};
use vantage_vista::Transaction;
use super::invariants::enforce_invariants;
use crate::table::{
AfterCx, AfterFn, BeforeCx, BeforeDeleteFn, BeforeFn, DeleteAllCx, DeleteAllFn, DeleteCx,
HookReturn, Phase, Table,
};
use crate::traits::table_source::TableSource;
use crate::write_context::WriteContext;
impl<T: TableSource, E: Entity<T::Value>> Table<T, E>
where
T::Value: InvariantValue,
{
pub async fn prepare_insert(&self, record: &Record<T::Value>) -> Result<Record<T::Value>> {
self.require_confined_writes("insert")?;
let mut record = record.clone();
run_before(
self.before_insert_hooks(),
&mut record,
None,
self.as_entity_erased(),
)
.await?;
self.strip_imported_columns(&mut record);
enforce_invariants(&mut record, self.invariants())?;
Ok(record)
}
pub async fn finish_insert(&self, id: &T::Id, record: &Record<T::Value>) -> Result<()> {
run_after(
self.after_insert_hooks(),
id,
record,
None,
self.as_entity_erased(),
)
.await
}
}
pub(crate) struct UpdateOf<'a, T: TableSource> {
pub id: &'a T::Id,
pub stored: Option<&'a Record<T::Value>>,
pub patch: bool,
}
pub(crate) async fn run_before<T: TableSource>(
hooks: &[(Phase, BeforeFn<T>)],
record: &mut Record<T::Value>,
update: Option<UpdateOf<'_, T>>,
table: &Table<T, EmptyEntity>,
) -> Result<()> {
if hooks.is_empty() {
return Ok(());
}
let context = WriteContext::current();
for (_phase, hook) in hooks {
hook(BeforeCx {
record: &mut *record,
id: update.as_ref().map(|u| u.id),
stored: update.as_ref().and_then(|u| u.stored),
patch: update.as_ref().is_some_and(|u| u.patch),
context: context.as_ref(),
table,
})
.await?;
}
Ok(())
}
pub(crate) async fn run_after<T: TableSource>(
hooks: &[AfterFn<T>],
id: &T::Id,
record: &Record<T::Value>,
before: Option<&Record<T::Value>>,
table: &Table<T, EmptyEntity>,
) -> Result<()> {
if hooks.is_empty() {
return Ok(());
}
let Some(tx) = table.data_source().transaction() else {
return run_after_now(hooks, id, record, before, table).await;
};
let hooks = hooks.to_vec();
let (id, record, before) = (id.clone(), record.clone(), before.cloned());
after_commit(&tx, table, move |table| async move {
run_after_now(&hooks, &id, &record, before.as_ref(), &table).await
});
Ok(())
}
pub(crate) async fn run_after_delete_all<T: TableSource>(
hooks: &[DeleteAllFn<T>],
table: &Table<T, EmptyEntity>,
) -> Result<()> {
if hooks.is_empty() {
return Ok(());
}
let Some(tx) = table.data_source().transaction() else {
return run_delete_all(hooks, table).await;
};
let hooks = hooks.to_vec();
after_commit(&tx, table, move |table| async move {
run_delete_all(&hooks, &table).await
});
Ok(())
}
fn after_commit<T, F, Fut>(tx: &Transaction, table: &Table<T, EmptyEntity>, work: F)
where
T: TableSource,
F: FnOnce(Table<T, EmptyEntity>) -> Fut + Send + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
let mut outside = table.with_data_source(table.data_source().without_transaction());
outside.lock = false;
let context = WriteContext::current();
tx.defer(Box::new(move || {
Box::pin(async move {
match context {
Some(context) => context.scope(work(outside)).await,
None => work(outside).await,
}
})
}));
}
async fn run_after_now<T: TableSource>(
hooks: &[AfterFn<T>],
id: &T::Id,
record: &Record<T::Value>,
before: Option<&Record<T::Value>>,
table: &Table<T, EmptyEntity>,
) -> Result<()> {
let context = WriteContext::current();
for hook in hooks {
hook(AfterCx {
id,
record,
before,
context: context.as_ref(),
table,
})
.await?;
}
Ok(())
}
pub(crate) async fn run_delete_all<T: TableSource>(
hooks: &[DeleteAllFn<T>],
table: &Table<T, EmptyEntity>,
) -> Result<()> {
if hooks.is_empty() {
return Ok(());
}
let context = WriteContext::current();
for hook in hooks {
hook(DeleteAllCx {
context: context.as_ref(),
table,
})
.await?;
}
Ok(())
}
pub(crate) async fn run_before_delete<T: TableSource>(
hooks: &[BeforeDeleteFn<T>],
id: &T::Id,
record: &Record<T::Value>,
table: &Table<T, EmptyEntity>,
) -> Result<HookReturn> {
let context = WriteContext::current();
for hook in hooks {
let cx = DeleteCx {
id,
record,
context: context.as_ref(),
table,
};
if let HookReturn::Handled = hook(cx).await? {
return Ok(HookReturn::Handled);
}
}
Ok(HookReturn::Proceed)
}