Skip to main content

cratestack_sqlx/delegate/
scoped_upsert.rs

1//! `.bind(ctx)`-scoped upsert wrappers, split out of `scoped_writes.rs`
2//! (200-LoC ceiling) once `.do_nothing()` (cratestack#487) added a
3//! second wrapper type alongside `ScopedUpsertRecord`.
4
5use cratestack_core::{CratestackContext, CratestackError};
6
7use crate::audit::RunInTxOutcome;
8use crate::{UpsertModelInput, UpsertOutcome, UpsertRecord, UpsertRecordDoNothing, sqlx};
9
10#[derive(Debug, Clone)]
11pub struct ScopedUpsertRecord<'a, M: 'static, PK: 'static, I> {
12    request: UpsertRecord<'a, M, PK, I>,
13    ctx: CratestackContext,
14}
15
16impl<'a, M: 'static, PK: 'static, I> ScopedUpsertRecord<'a, M, PK, I> {
17    pub(super) fn new(request: UpsertRecord<'a, M, PK, I>, ctx: CratestackContext) -> Self {
18        Self { request, ctx }
19    }
20}
21
22impl<'a, M: 'static, PK: 'static, I> ScopedUpsertRecord<'a, M, PK, I>
23where
24    I: UpsertModelInput<M>,
25{
26    /// See [`UpsertRecord::on_conflict`].
27    pub fn on_conflict(mut self, target: cratestack_sql::ConflictTarget) -> Self {
28        self.request = self.request.on_conflict(target);
29        self
30    }
31
32    /// See [`UpsertRecord::do_nothing`].
33    pub fn do_nothing(self) -> ScopedUpsertRecordDoNothing<'a, M, PK, I> {
34        ScopedUpsertRecordDoNothing {
35            request: self.request.do_nothing(),
36            ctx: self.ctx,
37        }
38    }
39
40    pub fn preview_sql(&self) -> String {
41        self.request.preview_sql()
42    }
43
44    pub async fn run(self) -> Result<M, CratestackError>
45    where
46        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
47        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
48    {
49        self.request.run(&self.ctx).await
50    }
51
52    pub async fn run_in_tx<'tx>(
53        self,
54        tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
55    ) -> Result<RunInTxOutcome<M>, CratestackError>
56    where
57        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
58        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
59    {
60        self.request.run_in_tx(tx, &self.ctx).await
61    }
62}
63
64/// `.upsert(..).do_nothing()` bound to a `CratestackContext` via `.bind(ctx)`.
65/// See [`UpsertRecordDoNothing`] for the run-time semantics; this is
66/// purely a `ctx`-carrying wrapper, same relationship as
67/// `ScopedUpsertRecord` has to `UpsertRecord`.
68#[derive(Debug, Clone)]
69pub struct ScopedUpsertRecordDoNothing<'a, M: 'static, PK: 'static, I> {
70    request: UpsertRecordDoNothing<'a, M, PK, I>,
71    ctx: CratestackContext,
72}
73
74impl<'a, M: 'static, PK: 'static, I> ScopedUpsertRecordDoNothing<'a, M, PK, I>
75where
76    I: UpsertModelInput<M>,
77{
78    /// See [`UpsertRecordDoNothing::on_conflict`].
79    pub fn on_conflict(mut self, target: cratestack_sql::ConflictTarget) -> Self {
80        self.request = self.request.on_conflict(target);
81        self
82    }
83
84    pub fn preview_sql(&self) -> String {
85        self.request.preview_sql()
86    }
87
88    pub async fn run(self) -> Result<UpsertOutcome<M>, CratestackError>
89    where
90        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
91        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
92    {
93        self.request.run(&self.ctx).await
94    }
95
96    pub async fn run_in_tx<'tx>(
97        self,
98        tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
99    ) -> Result<RunInTxOutcome<UpsertOutcome<M>>, CratestackError>
100    where
101        for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow> + serde::Serialize,
102        PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
103    {
104        self.request.run_in_tx(tx, &self.ctx).await
105    }
106}