cratestack_sqlx/query/write/
upsert_do_nothing.rs1use cratestack_core::{CratestackContext, CratestackError};
7
8use crate::audit::{RunInTxOutcome, dispatch_audit_sink};
9use crate::{
10 ConflictTarget, ModelDescriptor, SqlxRuntime, UpsertModelInput, cratestack_error_from_sqlx,
11 sqlx,
12};
13
14use super::upsert_do_nothing_exec::run_upsert_do_nothing_in_tx;
15use super::upsert_outcome::UpsertOutcome;
16
17#[derive(Debug, Clone)]
18pub struct UpsertRecordDoNothing<'a, M: 'static, PK: 'static, I> {
19 pub(crate) runtime: &'a SqlxRuntime,
20 pub(crate) descriptor: &'static ModelDescriptor<M, PK>,
21 pub(crate) input: I,
22 pub(crate) conflict_target: ConflictTarget,
23}
24
25impl<'a, M: 'static, PK: 'static, I> UpsertRecordDoNothing<'a, M, PK, I>
26where
27 I: UpsertModelInput<M>,
28{
29 pub fn on_conflict(mut self, target: ConflictTarget) -> Self {
33 self.conflict_target = target;
34 self
35 }
36
37 pub fn preview_sql(&self) -> String {
42 let values = self.input.sql_values();
43 let placeholders = (1..=values.len())
44 .map(|index| format!("${index}"))
45 .collect::<Vec<_>>()
46 .join(", ");
47 let columns = values
48 .iter()
49 .map(|value| value.column)
50 .collect::<Vec<_>>()
51 .join(", ");
52 let conflict_tuple = match self.conflict_target {
53 ConflictTarget::PrimaryKey => self.descriptor.primary_key.to_owned(),
54 ConflictTarget::Columns(cols) => cols.join(", "),
55 };
56
57 format!(
58 "INSERT INTO {table} ({columns}) VALUES ({placeholders}) \
59 ON CONFLICT ({conflict_tuple}) DO NOTHING \
60 RETURNING {projection}",
61 table = self.descriptor.table_name,
62 projection = self.descriptor.select_projection(),
63 )
64 }
65
66 pub async fn run(self, ctx: &CratestackContext) -> Result<UpsertOutcome<M>, CratestackError>
67 where
68 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
69 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
70 {
71 let runtime = self.runtime;
72 let mut tx = runtime
73 .pool()
74 .begin()
75 .await
76 .map_err(cratestack_error_from_sqlx)?;
77 let (outcome, emits_event, audit_event) = run_upsert_do_nothing_in_tx(
78 &mut tx,
79 runtime,
80 self.descriptor,
81 self.input,
82 self.conflict_target,
83 ctx,
84 )
85 .await?;
86 tx.commit().await.map_err(cratestack_error_from_sqlx)?;
87 if emits_event {
88 let _ = runtime.drain_event_outbox().await;
89 }
90 if let Some(event) = &audit_event {
91 dispatch_audit_sink(runtime, std::slice::from_ref(event)).await;
92 }
93 Ok(outcome)
94 }
95
96 pub async fn run_in_tx<'tx>(
104 self,
105 tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
106 ctx: &CratestackContext,
107 ) -> Result<RunInTxOutcome<UpsertOutcome<M>>, CratestackError>
108 where
109 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
110 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
111 {
112 let (outcome, _emits_event, audit_event) = run_upsert_do_nothing_in_tx(
113 tx,
114 self.runtime,
115 self.descriptor,
116 self.input,
117 self.conflict_target,
118 ctx,
119 )
120 .await?;
121 Ok(RunInTxOutcome::new(
122 outcome,
123 audit_event.into_iter().collect(),
124 ))
125 }
126}