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