1use 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
11pub trait ConflictTargetSql<'a, V: SQLParam> {
13 fn into_target_sql(self, target_where: Option<SQL<'a, V>>) -> SQL<'a, V>;
15}
16
17#[derive(Debug, Clone)]
19pub struct ConflictColumnsTarget<'a, V: SQLParam> {
20 columns: SQL<'a, V>,
21}
22
23impl<'a, V: SQLParam> ConflictColumnsTarget<'a, V> {
24 #[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#[derive(Debug, Clone)]
46pub enum PostgresConflictTarget<'a, V: SQLParam> {
47 Columns(Box<ConflictColumnsTarget<'a, V>>),
49 Constraint(&'static str),
51}
52
53impl<'a, V: SQLParam> PostgresConflictTarget<'a, V> {
54 #[inline]
56 #[must_use]
57 pub fn columns(columns: SQL<'a, V>) -> Self {
58 Self::Columns(Box::new(ConflictColumnsTarget::new(columns)))
59 }
60
61 #[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
81pub trait OnConflictOutput<'a, V: SQLParam, Schema, Table> {
83 type OnConflictSet;
85 type DoUpdateSet;
87
88 fn on_conflict(sql: SQL<'a, V>) -> Self::OnConflictSet;
90 fn do_update(sql: SQL<'a, V>) -> Self::DoUpdateSet;
92}
93
94#[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 #[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 #[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 #[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 #[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 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}