Skip to main content

drizzle_core/builder/
conflict.rs

1//! Shared `ON CONFLICT` builder machinery.
2
3use core::marker::PhantomData;
4
5use crate::expr::Expr;
6use crate::prelude::Box;
7use crate::sql::{SQL, Token};
8use crate::traits::{SQLParam, ToSQL};
9use crate::types::BooleanLike;
10
11/// A conflict target that renders as `ON CONFLICT ...`.
12pub trait ConflictTargetSql<'a, V: SQLParam> {
13    /// Renders the target, followed by `WHERE target_where` when given.
14    fn into_target_sql(self, target_where: Option<SQL<'a, V>>) -> SQL<'a, V>;
15}
16
17/// Shared column-list conflict target: `ON CONFLICT (col1, col2)`.
18#[derive(Debug, Clone)]
19pub struct ConflictColumnsTarget<'a, V: SQLParam> {
20    columns: SQL<'a, V>,
21}
22
23impl<'a, V: SQLParam> ConflictColumnsTarget<'a, V> {
24    /// Creates a target from a comma-separated column list.
25    #[inline]
26    #[must_use]
27    pub fn new(columns: SQL<'a, V>) -> Self {
28        Self { columns }
29    }
30}
31
32impl<'a, V: SQLParam> ConflictTargetSql<'a, V> for ConflictColumnsTarget<'a, V> {
33    fn into_target_sql(self, target_where: Option<SQL<'a, V>>) -> SQL<'a, V> {
34        let mut target = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
35            .append(self.columns)
36            .push(Token::RPAREN);
37        if let Some(target_where) = target_where {
38            target = target.push(Token::WHERE).append(target_where);
39        }
40        target
41    }
42}
43
44/// PostgreSQL conflict target, including `ON CONSTRAINT`.
45#[derive(Debug, Clone)]
46pub enum PostgresConflictTarget<'a, V: SQLParam> {
47    /// `ON CONFLICT (col1, col2)`.
48    Columns(Box<ConflictColumnsTarget<'a, V>>),
49    /// `ON CONFLICT ON CONSTRAINT "name"`. Takes no `WHERE`.
50    Constraint(&'static str),
51}
52
53impl<'a, V: SQLParam> PostgresConflictTarget<'a, V> {
54    /// Creates a column-list target.
55    #[inline]
56    #[must_use]
57    pub fn columns(columns: SQL<'a, V>) -> Self {
58        Self::Columns(Box::new(ConflictColumnsTarget::new(columns)))
59    }
60
61    /// Creates a named-constraint target.
62    #[inline]
63    #[must_use]
64    pub const fn constraint(name: &'static str) -> Self {
65        Self::Constraint(name)
66    }
67}
68
69impl<'a, V: SQLParam> ConflictTargetSql<'a, V> for PostgresConflictTarget<'a, V> {
70    fn into_target_sql(self, target_where: Option<SQL<'a, V>>) -> SQL<'a, V> {
71        match self {
72            Self::Columns(columns) => (*columns).into_target_sql(target_where),
73            Self::Constraint(name) => {
74                SQL::from_iter([Token::ON, Token::CONFLICT, Token::ON, Token::CONSTRAINT])
75                    .append(SQL::ident(name))
76            }
77        }
78    }
79}
80
81/// Builds the dialect's insert builder after an `ON CONFLICT` clause.
82pub trait OnConflictOutput<'a, V: SQLParam, Schema, Table> {
83    /// Builder returned by `.do_nothing()`.
84    type OnConflictSet;
85    /// Builder returned by `.do_update(...)`.
86    type DoUpdateSet;
87
88    /// Wraps the SQL so far in [`Self::OnConflictSet`].
89    fn on_conflict(sql: SQL<'a, V>) -> Self::OnConflictSet;
90    /// Wraps the SQL so far in [`Self::DoUpdateSet`].
91    fn do_update(sql: SQL<'a, V>) -> Self::DoUpdateSet;
92}
93
94/// Builder for the `ON CONFLICT` clause of an INSERT, returned by the
95/// dialect's `.on_conflict(...)`.
96///
97/// Finish it with [`do_nothing`](Self::do_nothing) or
98/// [`do_update`](Self::do_update).
99#[derive(Debug, Clone)]
100pub struct OnConflictBuilder<'a, V, Schema, Table, Target, Output>
101where
102    V: SQLParam,
103{
104    sql: SQL<'a, V>,
105    target: Target,
106    target_where: Option<SQL<'a, V>>,
107    schema: PhantomData<Schema>,
108    table: PhantomData<Table>,
109    output: PhantomData<Output>,
110}
111
112impl<'a, V, Schema, Table, Target, Output> OnConflictBuilder<'a, V, Schema, Table, Target, Output>
113where
114    V: SQLParam,
115    Target: ConflictTargetSql<'a, V>,
116    Output: OnConflictOutput<'a, V, Schema, Table>,
117{
118    /// Starts a conflict clause for the INSERT `sql` with the given target.
119    #[inline]
120    #[must_use]
121    pub fn new(sql: SQL<'a, V>, target: Target) -> Self {
122        Self {
123            sql,
124            target,
125            target_where: None,
126            schema: PhantomData,
127            table: PhantomData,
128            output: PhantomData,
129        }
130    }
131
132    /// Seeds a partial-index predicate supplied by a typed conflict target.
133    #[doc(hidden)]
134    #[must_use]
135    pub fn with_target_where_sql(mut self, target_where: Option<SQL<'a, V>>) -> Self {
136        self.target_where = target_where;
137        self
138    }
139
140    /// Adds a `WHERE` to the conflict target, to match a partial unique
141    /// index.
142    ///
143    /// A typed partial-index target already supplies its declared predicate.
144    /// Calling this replaces it, so the new predicate must still identify the
145    /// same unique index.
146    ///
147    /// # Compile-time checks
148    ///
149    /// The condition must be boolean and may only read columns of the
150    /// inserted table; reading another table fails with "is not in this
151    /// query's FROM/JOIN scope".
152    #[must_use]
153    pub fn r#where<E, ScopeProof>(mut self, condition: E) -> Self
154    where
155        E: Expr<'a, V>,
156        E::Sources: crate::scope::SourcesIn<crate::Cons<Table, crate::Nil>, ScopeProof>,
157        E::SQLType: BooleanLike,
158    {
159        self.target_where = Some(condition.into_expr_sql());
160        self
161    }
162
163    fn into_parts(self) -> (SQL<'a, V>, SQL<'a, V>) {
164        (self.sql, self.target.into_target_sql(self.target_where))
165    }
166
167    /// Finishes the clause with `DO NOTHING`.
168    #[must_use]
169    pub fn do_nothing(self) -> Output::OnConflictSet {
170        let (sql, target) = self.into_parts();
171        Output::on_conflict(sql.append(target.push(Token::DO).push(Token::NOTHING)))
172    }
173
174    /// Finishes the clause with `DO UPDATE SET <set>`, updating the
175    /// existing row.
176    pub fn do_update(self, set: impl ToSQL<'a, V>) -> Output::DoUpdateSet {
177        let (sql, target) = self.into_parts();
178        let conflict = target
179            .push(Token::DO)
180            .push(Token::UPDATE)
181            .push(Token::SET)
182            .append(set.into_sql());
183        Output::do_update(sql.append(conflict))
184    }
185}