drizzle_postgres/builder/
insert.rs1use crate::ToPostgresSQL;
2use crate::traits::PostgresTable;
3use crate::values::PostgresValue;
4use drizzle_core::SQL;
5use std::fmt::Debug;
6use std::marker::PhantomData;
7
8use super::ExecutableState;
10
11#[derive(Debug, Clone, Copy, Default)]
17pub struct InsertInitial;
18
19#[derive(Debug, Clone, Copy, Default)]
21pub struct InsertValuesSet;
22
23#[derive(Debug, Clone, Copy, Default)]
25pub struct InsertReturningSet;
26
27#[derive(Debug, Clone, Copy, Default)]
29pub struct InsertOnConflictSet;
30
31impl InsertInitial {
33 #[inline]
34 pub const fn new() -> Self {
35 Self
36 }
37}
38impl InsertValuesSet {
39 #[inline]
40 pub const fn new() -> Self {
41 Self
42 }
43}
44impl InsertReturningSet {
45 #[inline]
46 pub const fn new() -> Self {
47 Self
48 }
49}
50impl InsertOnConflictSet {
51 #[inline]
52 pub const fn new() -> Self {
53 Self
54 }
55}
56
57#[derive(Debug, Clone)]
59pub enum ConflictTarget<'a> {
60 Columns(SQL<'a, PostgresValue<'a>>),
62 ColumnsWhere {
65 columns: SQL<'a, PostgresValue<'a>>,
66 where_clause: SQL<'a, PostgresValue<'a>>,
67 },
68 Constraint(String),
70}
71
72#[derive(Debug, Clone)]
74pub enum Conflict<'a> {
75 DoNothing {
77 target: Option<ConflictTarget<'a>>,
79 },
80 DoUpdate {
82 target: ConflictTarget<'a>,
84 set: SQL<'a, PostgresValue<'a>>,
86 where_clause: Option<SQL<'a, PostgresValue<'a>>>,
89 },
90}
91
92impl<'a> Default for Conflict<'a> {
93 fn default() -> Self {
94 Self::DoNothing { target: None }
95 }
96}
97
98impl<'a> Conflict<'a> {
99 pub fn do_nothing_on_columns<T>(columns: T) -> Self
101 where
102 T: ToPostgresSQL<'a>,
103 {
104 Conflict::DoNothing {
105 target: Some(ConflictTarget::Columns(columns.to_sql())),
106 }
107 }
108
109 pub fn do_nothing_on_constraint(constraint_name: String) -> Self {
111 Conflict::DoNothing {
112 target: Some(ConflictTarget::Constraint(constraint_name)),
113 }
114 }
115
116 pub fn do_update(
118 target: ConflictTarget<'a>,
119 set: SQL<'a, PostgresValue<'a>>,
120 where_clause: Option<SQL<'a, PostgresValue<'a>>>,
121 ) -> Self {
122 Conflict::DoUpdate {
123 target,
124 set,
125 where_clause,
126 }
127 }
128}
129
130impl<'a> ConflictTarget<'a> {
131 pub fn columns<T>(columns: T) -> Self
133 where
134 T: ToPostgresSQL<'a>,
135 {
136 ConflictTarget::Columns(columns.to_sql())
137 }
138
139 pub fn columns_where<T>(columns: T, where_clause: SQL<'a, PostgresValue<'a>>) -> Self
141 where
142 T: ToPostgresSQL<'a>,
143 {
144 ConflictTarget::ColumnsWhere {
145 columns: columns.to_sql(),
146 where_clause,
147 }
148 }
149
150 pub fn constraint(constraint_name: String) -> Self {
152 ConflictTarget::Constraint(constraint_name)
153 }
154}
155
156impl ExecutableState for InsertValuesSet {}
158impl ExecutableState for InsertReturningSet {}
159impl ExecutableState for InsertOnConflictSet {}
160
161pub type InsertBuilder<'a, Schema, State, Table> = super::QueryBuilder<'a, Schema, State, Table>;
167
168impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
173where
174 Table: PostgresTable<'a>,
175{
176 #[inline]
178 pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
179 where
180 I: IntoIterator<Item = Table::Insert<T>>,
181 {
182 let sql = crate::helpers::values::<'a, Table, T>(values);
183 InsertBuilder {
184 sql: self.sql.append(sql),
185 schema: PhantomData,
186 state: PhantomData,
187 table: PhantomData,
188 }
189 }
190}
191
192impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
197 pub fn on_conflict(
199 self,
200 conflict: Conflict<'a>,
201 ) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
202 let conflict_sql = match conflict {
203 Conflict::DoNothing { target } => {
204 let mut sql = SQL::raw("ON CONFLICT");
205
206 if let Some(target) = target {
207 sql = sql.append(Self::build_conflict_target(target));
208 }
209
210 sql.append(SQL::raw(" DO NOTHING"))
211 }
212 Conflict::DoUpdate {
213 target,
214 set,
215 where_clause,
216 } => {
217 let mut sql = SQL::raw("ON CONFLICT")
218 .append(Self::build_conflict_target(target))
219 .append(SQL::raw(" DO UPDATE SET "))
220 .append(set);
221
222 if let Some(where_clause) = where_clause {
224 sql = sql.append(SQL::raw(" WHERE ")).append(where_clause);
225 }
226
227 sql
228 }
229 };
230
231 InsertBuilder {
232 sql: self.sql.append(conflict_sql),
233 schema: PhantomData,
234 state: PhantomData,
235 table: PhantomData,
236 }
237 }
238
239 fn build_conflict_target(target: ConflictTarget<'a>) -> SQL<'a, PostgresValue<'a>> {
241 match target {
242 ConflictTarget::Columns(columns) => {
243 SQL::raw(" (").append(columns).append(SQL::raw(")"))
244 }
245 ConflictTarget::ColumnsWhere {
246 columns,
247 where_clause,
248 } => SQL::raw(" (")
249 .append(columns)
250 .append(SQL::raw(") WHERE "))
251 .append(where_clause),
252 ConflictTarget::Constraint(constraint_name) => {
253 SQL::raw(" ON CONSTRAINT ").append(SQL::raw(constraint_name))
254 }
255 }
256 }
257
258 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
260 self.on_conflict(Conflict::default())
261 }
262
263 pub fn on_conflict_do_nothing_on<C>(
265 self,
266 columns: C,
267 ) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
268 where
269 C: ToPostgresSQL<'a>,
270 {
271 self.on_conflict(Conflict::do_nothing_on_columns(columns))
272 }
273
274 #[inline]
276 pub fn returning(
277 self,
278 columns: impl ToPostgresSQL<'a>,
279 ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
280 let returning_sql = crate::helpers::returning(columns);
281 InsertBuilder {
282 sql: self.sql.append(returning_sql),
283 schema: PhantomData,
284 state: PhantomData,
285 table: PhantomData,
286 }
287 }
288}
289
290impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
295 #[inline]
297 pub fn returning(
298 self,
299 columns: impl ToPostgresSQL<'a>,
300 ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
301 let returning_sql = crate::helpers::returning(columns);
302 InsertBuilder {
303 sql: self.sql.append(returning_sql),
304 schema: PhantomData,
305 state: PhantomData,
306 table: PhantomData,
307 }
308 }
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314 use drizzle_core::{SQL, ToSQL};
315
316 #[test]
317 fn test_insert_builder_creation() {
318 let builder = InsertBuilder::<(), InsertInitial, ()> {
319 sql: SQL::raw("INSERT INTO test"),
320 schema: PhantomData,
321 state: PhantomData,
322 table: PhantomData,
323 };
324
325 assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
326 }
327
328 #[test]
329 fn test_conflict_types() {
330 let do_nothing = Conflict::DoNothing { target: None };
331 let do_update = Conflict::DoUpdate {
332 target: ConflictTarget::Columns(SQL::raw("id")),
333 set: SQL::raw("name = EXCLUDED.name"),
334 where_clause: None,
335 };
336
337 match do_nothing {
338 Conflict::DoNothing { .. } => (),
339 _ => panic!("Expected DoNothing"),
340 }
341
342 match do_update {
343 Conflict::DoUpdate { .. } => (),
344 _ => panic!("Expected DoUpdate"),
345 }
346 }
347}