cratestack_sqlx/query/write/
delete_many.rs1use cratestack_core::{BatchSummary, CratestackContext, CratestackError};
10
11use crate::audit::{RunInTxOutcome, dispatch_audit_sink};
12use crate::{FilterExpr, ModelDescriptor, SqlxRuntime, cratestack_error_from_sqlx, sqlx};
13
14use super::delete_many_exec::run_delete_many_in_tx;
15
16#[derive(Debug, Clone)]
17pub struct DeleteMany<'a, M: 'static, PK: 'static> {
18 pub(crate) runtime: &'a SqlxRuntime,
19 pub(crate) descriptor: &'static ModelDescriptor<M, PK>,
20 pub(crate) filters: Vec<FilterExpr>,
21}
22
23impl<'a, M: 'static, PK: 'static> DeleteMany<'a, M, PK> {
24 pub fn where_(mut self, filter: crate::Filter) -> Self {
25 self.filters.push(FilterExpr::from(filter));
26 self
27 }
28
29 pub fn where_expr(mut self, filter: FilterExpr) -> Self {
30 self.filters.push(filter);
31 self
32 }
33
34 pub fn where_any(mut self, filters: impl IntoIterator<Item = FilterExpr>) -> Self {
35 self.filters.push(FilterExpr::any(filters));
36 self
37 }
38
39 pub fn where_optional<F>(mut self, filter: Option<F>) -> Self
41 where
42 F: Into<FilterExpr>,
43 {
44 if let Some(filter) = filter {
45 self.filters.push(filter.into());
46 }
47 self
48 }
49
50 pub fn preview_sql(&self) -> String {
54 let mut sql = match self.descriptor.soft_delete_column {
55 Some(col) => {
56 let mut s = format!("UPDATE {} SET {col} = NOW()", self.descriptor.table_name);
57 if let Some(version_col) = self.descriptor.version_column {
58 s.push_str(&format!(", {version_col} = {version_col} + 1"));
59 }
60 s.push_str(&format!(" WHERE {col} IS NULL AND "));
61 s
62 }
63 None => format!("DELETE FROM {} WHERE ", self.descriptor.table_name),
64 };
65 sql.push_str("<filters> AND <delete_policy> RETURNING ");
66 sql.push_str(&self.descriptor.select_projection());
67 sql
68 }
69
70 pub async fn run(self, ctx: &CratestackContext) -> Result<BatchSummary, CratestackError>
71 where
72 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
73 {
74 if let Some(bound) = self.runtime.bound() {
78 let emits = self
79 .descriptor
80 .emits(cratestack_core::ModelEventKind::Deleted);
81 let outcome = crate::bound::in_bound_savepoint!(bound, |sp| self.run_in_tx(sp, ctx))?;
82 return Ok(bound.settle(outcome, emits));
83 }
84 let runtime = self.runtime;
85 let descriptor = self.descriptor;
86 let mut tx = runtime
87 .pool()
88 .begin()
89 .await
90 .map_err(cratestack_error_from_sqlx)?;
91 let (summary, emits_event, audit_events) =
92 run_delete_many_in_tx(&mut tx, runtime, descriptor, &self.filters, ctx).await?;
93 tx.commit().await.map_err(cratestack_error_from_sqlx)?;
94 if emits_event {
95 let _ = runtime.drain_event_outbox().await;
96 }
97 dispatch_audit_sink(runtime, &audit_events).await;
98 Ok(summary)
99 }
100
101 pub async fn run_in_tx<'tx>(
106 self,
107 tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
108 ctx: &CratestackContext,
109 ) -> Result<RunInTxOutcome<BatchSummary>, CratestackError>
110 where
111 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
112 {
113 let (summary, _emits_event, audit_events) =
114 run_delete_many_in_tx(tx, self.runtime, self.descriptor, &self.filters, ctx).await?;
115 Ok(RunInTxOutcome::new(summary, audit_events))
116 }
117}