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>;
14}
15
16#[derive(Debug, Clone)]
18pub struct ConflictColumnsTarget<'a, V: SQLParam> {
19 columns: SQL<'a, V>,
20}
21
22impl<'a, V: SQLParam> ConflictColumnsTarget<'a, V> {
23 #[inline]
24 #[must_use]
25 pub fn new(columns: SQL<'a, V>) -> Self {
26 Self { columns }
27 }
28}
29
30impl<'a, V: SQLParam> ConflictTargetSql<'a, V> for ConflictColumnsTarget<'a, V> {
31 fn into_target_sql(self, target_where: Option<SQL<'a, V>>) -> SQL<'a, V> {
32 let mut target = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
33 .append(self.columns)
34 .push(Token::RPAREN);
35 if let Some(target_where) = target_where {
36 target = target.push(Token::WHERE).append(target_where);
37 }
38 target
39 }
40}
41
42#[derive(Debug, Clone)]
44pub enum PostgresConflictTarget<'a, V: SQLParam> {
45 Columns(Box<ConflictColumnsTarget<'a, V>>),
46 Constraint(&'static str),
47}
48
49impl<'a, V: SQLParam> PostgresConflictTarget<'a, V> {
50 #[inline]
51 #[must_use]
52 pub fn columns(columns: SQL<'a, V>) -> Self {
53 Self::Columns(Box::new(ConflictColumnsTarget::new(columns)))
54 }
55
56 #[inline]
57 #[must_use]
58 pub const fn constraint(name: &'static str) -> Self {
59 Self::Constraint(name)
60 }
61}
62
63impl<'a, V: SQLParam> ConflictTargetSql<'a, V> for PostgresConflictTarget<'a, V> {
64 fn into_target_sql(self, target_where: Option<SQL<'a, V>>) -> SQL<'a, V> {
65 match self {
66 Self::Columns(columns) => (*columns).into_target_sql(target_where),
67 Self::Constraint(name) => {
68 SQL::from_iter([Token::ON, Token::CONFLICT, Token::ON, Token::CONSTRAINT])
69 .append(SQL::ident(name))
70 }
71 }
72 }
73}
74
75pub trait OnConflictOutput<'a, V: SQLParam, Schema, Table> {
77 type OnConflictSet;
78 type DoUpdateSet;
79
80 fn on_conflict(sql: SQL<'a, V>) -> Self::OnConflictSet;
81 fn do_update(sql: SQL<'a, V>) -> Self::DoUpdateSet;
82}
83
84#[derive(Debug, Clone)]
86pub struct OnConflictBuilder<'a, V, Schema, Table, Target, Output>
87where
88 V: SQLParam,
89{
90 sql: SQL<'a, V>,
91 target: Target,
92 target_where: Option<SQL<'a, V>>,
93 schema: PhantomData<Schema>,
94 table: PhantomData<Table>,
95 output: PhantomData<Output>,
96}
97
98impl<'a, V, Schema, Table, Target, Output> OnConflictBuilder<'a, V, Schema, Table, Target, Output>
99where
100 V: SQLParam,
101 Target: ConflictTargetSql<'a, V>,
102 Output: OnConflictOutput<'a, V, Schema, Table>,
103{
104 #[inline]
105 #[must_use]
106 pub fn new(sql: SQL<'a, V>, target: Target) -> Self {
107 Self {
108 sql,
109 target,
110 target_where: None,
111 schema: PhantomData,
112 table: PhantomData,
113 output: PhantomData,
114 }
115 }
116
117 #[must_use]
119 pub fn r#where<E>(mut self, condition: E) -> Self
120 where
121 E: Expr<'a, V>,
122 E::SQLType: BooleanLike,
123 {
124 self.target_where = Some(condition.into_expr_sql());
125 self
126 }
127
128 fn into_parts(self) -> (SQL<'a, V>, SQL<'a, V>) {
129 (self.sql, self.target.into_target_sql(self.target_where))
130 }
131
132 #[must_use]
134 pub fn do_nothing(self) -> Output::OnConflictSet {
135 let (sql, target) = self.into_parts();
136 Output::on_conflict(sql.append(target.push(Token::DO).push(Token::NOTHING)))
137 }
138
139 pub fn do_update(self, set: impl ToSQL<'a, V>) -> Output::DoUpdateSet {
141 let (sql, target) = self.into_parts();
142 let conflict = target
143 .push(Token::DO)
144 .push(Token::UPDATE)
145 .push(Token::SET)
146 .append(set.into_sql());
147 Output::do_update(sql.append(conflict))
148 }
149}