Skip to main content

drizzle_postgres/builder/
insert.rs

1#[cfg(not(feature = "std"))]
2use crate::prelude::*;
3use crate::traits::PostgresTable;
4use crate::values::PostgresValue;
5use core::fmt::Debug;
6use core::marker::PhantomData;
7use drizzle_core::{ConflictTarget, NamedConstraint, SQL, ToSQL, Token};
8
9// Import the ExecutableState trait
10use super::ExecutableState;
11
12//------------------------------------------------------------------------------
13// Type State Markers
14//------------------------------------------------------------------------------
15
16/// Marker for the initial state of `InsertBuilder`.
17#[derive(Debug, Clone, Copy, Default)]
18pub struct InsertInitial;
19
20/// Marker for the state after VALUES are set.
21#[derive(Debug, Clone, Copy, Default)]
22pub struct InsertValuesSet;
23
24/// Marker for the state after RETURNING clause is added.
25#[derive(Debug, Clone, Copy, Default)]
26pub struct InsertReturningSet;
27
28/// Marker for the state after ON CONFLICT is set.
29#[derive(Debug, Clone, Copy, Default)]
30pub struct InsertOnConflictSet;
31
32/// Marker for the state after DO UPDATE SET (before optional WHERE).
33#[derive(Debug, Clone, Copy, Default)]
34pub struct InsertDoUpdateSet;
35
36// Mark states that can execute insert queries
37impl ExecutableState for InsertValuesSet {}
38impl ExecutableState for InsertReturningSet {}
39impl ExecutableState for InsertOnConflictSet {}
40impl ExecutableState for InsertDoUpdateSet {}
41
42//------------------------------------------------------------------------------
43// OnConflictBuilder
44//------------------------------------------------------------------------------
45
46/// Internal: how the conflict target was specified.
47#[derive(Debug, Clone)]
48enum ConflictTargetKind<'a> {
49    /// ON CONFLICT (col1, col2)
50    Columns(Box<SQL<'a, PostgresValue<'a>>>),
51    /// ON CONFLICT ON CONSTRAINT "`constraint_name`"
52    Constraint(&'static str),
53}
54
55/// Intermediate builder for typed ON CONFLICT clause construction (`PostgreSQL`).
56///
57/// Created by [`InsertBuilder::on_conflict()`] or
58/// [`InsertBuilder::on_conflict_on_constraint()`].
59/// Call [`do_nothing()`](Self::do_nothing) or [`do_update()`](Self::do_update)
60/// to complete the clause.
61#[derive(Debug, Clone)]
62pub struct OnConflictBuilder<'a, S, T> {
63    sql: SQL<'a, PostgresValue<'a>>,
64    target: ConflictTargetKind<'a>,
65    target_where: Option<SQL<'a, PostgresValue<'a>>>,
66    schema: PhantomData<S>,
67    table: PhantomData<T>,
68}
69
70impl<'a, S, T> OnConflictBuilder<'a, S, T> {
71    /// Adds a WHERE clause to the conflict target for partial index matching.
72    ///
73    /// Generates: `ON CONFLICT (col) WHERE condition DO ...`
74    ///
75    /// Note: WHERE is only meaningful for column-based targets, not for
76    /// `ON CONFLICT ON CONSTRAINT` targets.
77    #[must_use]
78    pub fn r#where<E>(mut self, condition: E) -> Self
79    where
80        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
81        E::SQLType: drizzle_core::types::BooleanLike,
82    {
83        self.target_where = Some(condition.to_sql());
84        self
85    }
86
87    /// Splits into (base insert SQL, conflict target SQL prefix).
88    fn into_parts(self) -> (SQL<'a, PostgresValue<'a>>, SQL<'a, PostgresValue<'a>>) {
89        let target = match self.target {
90            ConflictTargetKind::Columns(cols) => {
91                let mut t = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
92                    .append(*cols)
93                    .push(Token::RPAREN);
94                if let Some(tw) = self.target_where {
95                    t = t.push(Token::WHERE).append(tw);
96                }
97                t
98            }
99            ConflictTargetKind::Constraint(name) => {
100                SQL::from_iter([Token::ON, Token::CONFLICT, Token::ON, Token::CONSTRAINT])
101                    .append(SQL::ident(name))
102            }
103        };
104        (self.sql, target)
105    }
106
107    /// Resolves the conflict by doing nothing (ignoring the conflicting row).
108    ///
109    /// Generates: `ON CONFLICT (col1, col2) DO NOTHING`
110    #[must_use]
111    pub fn do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
112        let (sql, target) = self.into_parts();
113        InsertBuilder {
114            sql: sql.append(target.push(Token::DO).push(Token::NOTHING)),
115            schema: PhantomData,
116            state: PhantomData,
117            table: PhantomData,
118            marker: PhantomData,
119            row: PhantomData,
120            grouped: PhantomData,
121        }
122    }
123
124    /// Resolves the conflict by updating the existing row.
125    ///
126    /// The `set` parameter accepts any `ToSQL` value, typically an `UpdateModel`
127    /// which generates the SET clause assignments. Use `EXCLUDED.column` to
128    /// reference the proposed insert values.
129    ///
130    /// Generates: `ON CONFLICT (col1, col2) DO UPDATE SET ...`
131    ///
132    /// Chain `.r#where(condition)` to add a conditional update filter.
133    pub fn do_update(
134        self,
135        set: impl ToSQL<'a, PostgresValue<'a>>,
136    ) -> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
137        let (sql, target) = self.into_parts();
138        let conflict = target
139            .push(Token::DO)
140            .push(Token::UPDATE)
141            .push(Token::SET)
142            .append(set.to_sql());
143        InsertBuilder {
144            sql: sql.append(conflict),
145            schema: PhantomData,
146            state: PhantomData,
147            table: PhantomData,
148            marker: PhantomData,
149            row: PhantomData,
150            grouped: PhantomData,
151        }
152    }
153}
154
155//------------------------------------------------------------------------------
156// InsertBuilder Definition
157//------------------------------------------------------------------------------
158
159/// Builds an INSERT query specifically for `PostgreSQL`.
160///
161/// Provides a type-safe, fluent API for constructing INSERT statements
162/// with support for typed conflict resolution, batch inserts, and returning clauses.
163///
164/// ## Type Parameters
165///
166/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
167/// - `State`: The current builder state, enforcing proper query construction order
168/// - `Table`: The table being inserted into
169pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
170    super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
171
172type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
173    <Columns as drizzle_core::IntoSelectTarget>::Marker,
174    drizzle_core::Cons<Table, drizzle_core::Nil>,
175>;
176
177type ReturningRow<Table, Columns> =
178    <<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
179
180type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
181    'a,
182    S,
183    InsertReturningSet,
184    T,
185    ReturningMarker<T, Columns>,
186    ReturningRow<T, Columns>,
187>;
188
189//------------------------------------------------------------------------------
190// Initial State Implementation
191//------------------------------------------------------------------------------
192
193impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
194where
195    Table: PostgresTable<'a>,
196{
197    /// Specifies a single row to insert. Shorthand for `.values([row])`.
198    #[inline]
199    pub fn value<T>(
200        self,
201        value: Table::Insert<T>,
202    ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table> {
203        self.values([value])
204    }
205
206    /// Specifies multiple rows to insert.
207    #[inline]
208    pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
209    where
210        I: IntoIterator<Item = Table::Insert<T>>,
211    {
212        let sql = crate::helpers::values::<'a, Table, T>(values);
213        InsertBuilder {
214            sql: self.sql.append(sql),
215            schema: PhantomData,
216            state: PhantomData,
217            table: PhantomData,
218            marker: PhantomData,
219            row: PhantomData,
220            grouped: PhantomData,
221        }
222    }
223}
224
225//------------------------------------------------------------------------------
226// Post-VALUES Implementation
227//------------------------------------------------------------------------------
228
229impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
230    /// Begins a typed ON CONFLICT clause targeting specific columns.
231    ///
232    /// The target must implement `ConflictTarget<T>`, which is auto-generated for
233    /// primary key columns, unique columns, and unique indexes.
234    ///
235    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// # extern crate self as drizzle;
241    /// # mod _drizzle {
242    /// #     pub mod core { pub use drizzle_core::*; }
243    /// #     pub mod error { pub use drizzle_core::error::*; }
244    /// #     pub mod types { pub use drizzle_types::*; }
245    /// #     pub mod migrations { pub use drizzle_migrations::*; }
246    /// #     pub use drizzle_types::Dialect;
247    /// #     pub use drizzle_types as ddl;
248    /// #     pub mod postgres {
249    /// #         pub mod values { pub use drizzle_postgres::values::*; }
250    /// #         pub mod traits { pub use drizzle_postgres::traits::*; }
251    /// #         pub mod common { pub use drizzle_postgres::common::*; }
252    /// #         pub mod attrs { pub use drizzle_postgres::attrs::*; }
253    /// #         pub mod builder { pub use drizzle_postgres::builder::*; }
254    /// #         pub mod helpers { pub use drizzle_postgres::helpers::*; }
255    /// #         pub mod expr { pub use drizzle_postgres::expr::*; }
256    /// #         pub mod types { pub use drizzle_postgres::types::*; }
257    /// #         pub struct Row;
258    /// #         impl Row {
259    /// #             pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
260    /// #             pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
261    /// #         }
262    /// #         pub mod prelude {
263    /// #             pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
264    /// #             pub use drizzle_postgres::attrs::*;
265    /// #             pub use drizzle_postgres::common::PostgresSchemaType;
266    /// #             pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
267    /// #             pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
268    /// #             pub use drizzle_core::*;
269    /// #         }
270    /// #     }
271    /// # }
272    /// # pub use _drizzle::*;
273    /// # pub use const_format;
274    /// fn main() {
275    /// use drizzle::postgres::prelude::*;
276    /// use drizzle::postgres::builder::QueryBuilder;
277    ///
278    /// #[PostgresTable(name = "users")]
279    /// struct User {
280    ///     #[column(serial, primary)]
281    ///     id: i32,
282    ///     name: String,
283    ///     #[column(unique)]
284    ///     email: Option<String>,
285    /// }
286    ///
287    /// #[PostgresIndex(unique)]
288    /// struct UserEmailIdx(User::email);
289    ///
290    /// #[derive(PostgresSchema)]
291    /// struct Schema {
292    ///     user: User,
293    ///     user_email_idx: UserEmailIdx,
294    /// }
295    ///
296    /// let builder = QueryBuilder::new::<Schema>();
297    /// let schema = Schema::new();
298    /// let user = schema.user;
299    ///
300    /// // Target a specific column
301    /// builder.insert(user).values([InsertUser::new("Alice")])
302    ///     .on_conflict(user.id).do_nothing();
303    ///
304    /// // Target with DO UPDATE using EXCLUDED
305    /// builder.insert(user).values([InsertUser::new("Alice")])
306    ///     .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
307    ///
308    /// // Target a unique index
309    /// builder.insert(user).values([InsertUser::new("Alice")])
310    ///     .on_conflict(schema.user_email_idx).do_nothing();
311    /// }
312    /// ```
313    pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
314        let columns = target.conflict_columns();
315        let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
316        OnConflictBuilder {
317            sql: self.sql,
318            target: ConflictTargetKind::Columns(Box::new(target_sql)),
319            target_where: None,
320            schema: PhantomData,
321            table: PhantomData,
322        }
323    }
324
325    /// Begins a typed ON CONFLICT ON CONSTRAINT clause (PostgreSQL-only).
326    ///
327    /// The target must implement `NamedConstraint<T>`, which is auto-generated
328    /// for unique indexes.
329    ///
330    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
331    ///
332    /// # Examples
333    ///
334    /// ```rust
335    /// # extern crate self as drizzle;
336    /// # mod _drizzle {
337    /// #     pub mod core { pub use drizzle_core::*; }
338    /// #     pub mod error { pub use drizzle_core::error::*; }
339    /// #     pub mod types { pub use drizzle_types::*; }
340    /// #     pub mod migrations { pub use drizzle_migrations::*; }
341    /// #     pub use drizzle_types::Dialect;
342    /// #     pub use drizzle_types as ddl;
343    /// #     pub mod postgres {
344    /// #         pub mod values { pub use drizzle_postgres::values::*; }
345    /// #         pub mod traits { pub use drizzle_postgres::traits::*; }
346    /// #         pub mod common { pub use drizzle_postgres::common::*; }
347    /// #         pub mod attrs { pub use drizzle_postgres::attrs::*; }
348    /// #         pub mod builder { pub use drizzle_postgres::builder::*; }
349    /// #         pub mod helpers { pub use drizzle_postgres::helpers::*; }
350    /// #         pub mod expr { pub use drizzle_postgres::expr::*; }
351    /// #         pub mod types { pub use drizzle_postgres::types::*; }
352    /// #         pub struct Row;
353    /// #         impl Row {
354    /// #             pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
355    /// #             pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
356    /// #         }
357    /// #         pub mod prelude {
358    /// #             pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
359    /// #             pub use drizzle_postgres::attrs::*;
360    /// #             pub use drizzle_postgres::common::PostgresSchemaType;
361    /// #             pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
362    /// #             pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
363    /// #             pub use drizzle_core::*;
364    /// #         }
365    /// #     }
366    /// # }
367    /// # pub use _drizzle::*;
368    /// # pub use const_format;
369    /// fn main() {
370    /// use drizzle::postgres::prelude::*;
371    /// use drizzle::postgres::builder::QueryBuilder;
372    ///
373    /// #[PostgresTable(name = "users")]
374    /// struct User {
375    ///     #[column(serial, primary)]
376    ///     id: i32,
377    ///     name: String,
378    ///     #[column(unique)]
379    ///     email: Option<String>,
380    /// }
381    ///
382    /// #[PostgresIndex(unique)]
383    /// struct UserEmailIdx(User::email);
384    ///
385    /// #[derive(PostgresSchema)]
386    /// struct Schema {
387    ///     user: User,
388    ///     user_email_idx: UserEmailIdx,
389    /// }
390    ///
391    /// let builder = QueryBuilder::new::<Schema>();
392    /// let schema = Schema::new();
393    ///
394    /// builder.insert(schema.user).values([InsertUser::new("Alice")])
395    ///     .on_conflict_on_constraint(schema.user_email_idx).do_nothing();
396    /// }
397    /// ```
398    pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
399        self,
400        target: C,
401    ) -> OnConflictBuilder<'a, S, T> {
402        OnConflictBuilder {
403            sql: self.sql,
404            target: ConflictTargetKind::Constraint(target.constraint_name()),
405            target_where: None,
406            schema: PhantomData,
407            table: PhantomData,
408        }
409    }
410
411    /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
412    ///
413    /// This matches any constraint violation.
414    #[must_use]
415    pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
416        let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
417        InsertBuilder {
418            sql: self.sql.append(conflict_sql),
419            schema: PhantomData,
420            state: PhantomData,
421            table: PhantomData,
422            marker: PhantomData,
423            row: PhantomData,
424            grouped: PhantomData,
425        }
426    }
427
428    /// Adds a RETURNING clause and transitions to `ReturningSet` state
429    #[inline]
430    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
431    where
432        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
433        Columns::Marker: drizzle_core::ResolveRow<T>,
434    {
435        let returning_sql = crate::helpers::returning(columns);
436        InsertBuilder {
437            sql: self.sql.append(returning_sql),
438            schema: PhantomData,
439            state: PhantomData,
440            table: PhantomData,
441            marker: PhantomData,
442            row: PhantomData,
443            grouped: PhantomData,
444        }
445    }
446}
447
448//------------------------------------------------------------------------------
449// Post-ON CONFLICT Implementation
450//------------------------------------------------------------------------------
451
452impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
453    /// Adds a RETURNING clause after ON CONFLICT
454    #[inline]
455    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
456    where
457        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
458        Columns::Marker: drizzle_core::ResolveRow<T>,
459    {
460        let returning_sql = crate::helpers::returning(columns);
461        InsertBuilder {
462            sql: self.sql.append(returning_sql),
463            schema: PhantomData,
464            state: PhantomData,
465            table: PhantomData,
466            marker: PhantomData,
467            row: PhantomData,
468            grouped: PhantomData,
469        }
470    }
471}
472
473//------------------------------------------------------------------------------
474// Post-DO UPDATE SET Implementation
475//------------------------------------------------------------------------------
476
477impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
478    /// Adds a WHERE clause to the DO UPDATE SET clause.
479    ///
480    /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
481    pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
482    where
483        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
484        E::SQLType: drizzle_core::types::BooleanLike,
485    {
486        let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
487        InsertBuilder {
488            sql,
489            schema: PhantomData,
490            state: PhantomData,
491            table: PhantomData,
492            marker: PhantomData,
493            row: PhantomData,
494            grouped: PhantomData,
495        }
496    }
497
498    /// Adds a RETURNING clause after DO UPDATE SET
499    #[inline]
500    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
501    where
502        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
503        Columns::Marker: drizzle_core::ResolveRow<T>,
504    {
505        let returning_sql = crate::helpers::returning(columns);
506        InsertBuilder {
507            sql: self.sql.append(returning_sql),
508            schema: PhantomData,
509            state: PhantomData,
510            table: PhantomData,
511            marker: PhantomData,
512            row: PhantomData,
513            grouped: PhantomData,
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521    use drizzle_core::{SQL, ToSQL};
522
523    #[test]
524    fn test_insert_builder_creation() {
525        let builder = InsertBuilder::<(), InsertInitial, ()> {
526            sql: SQL::raw("INSERT INTO test"),
527            schema: PhantomData,
528            state: PhantomData,
529            table: PhantomData,
530            marker: PhantomData,
531            row: PhantomData,
532            grouped: PhantomData,
533        };
534
535        assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
536    }
537}