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    /// Inserts rows produced by a SELECT query without an explicit column list.
225    ///
226    /// The SELECT output must provide every table column in declaration order.
227    #[inline]
228    pub fn select<Q>(self, query: Q) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
229    where
230        Q: ToSQL<'a, PostgresValue<'a>>,
231    {
232        InsertBuilder {
233            sql: self.sql.append(query.to_sql()),
234            schema: PhantomData,
235            state: PhantomData,
236            table: PhantomData,
237            marker: PhantomData,
238            row: PhantomData,
239            grouped: PhantomData,
240        }
241    }
242}
243
244//------------------------------------------------------------------------------
245// Post-VALUES Implementation
246//------------------------------------------------------------------------------
247
248impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
249    /// Begins a typed ON CONFLICT clause targeting specific columns.
250    ///
251    /// The target must implement `ConflictTarget<T>`, which is auto-generated for
252    /// primary key columns, unique columns, and unique indexes.
253    ///
254    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
255    ///
256    /// # Examples
257    ///
258    /// ```rust
259    /// # extern crate self as drizzle;
260    /// # mod _drizzle {
261    /// #     pub mod core { pub use drizzle_core::*; }
262    /// #     pub mod error { pub use drizzle_core::error::*; }
263    /// #     pub mod types { pub use drizzle_types::*; }
264    /// #     pub mod migrations { pub use drizzle_migrations::*; }
265    /// #     pub use drizzle_types::Dialect;
266    /// #     pub use drizzle_types as ddl;
267    /// #     pub mod postgres {
268    /// #         pub mod values { pub use drizzle_postgres::values::*; }
269    /// #         pub mod traits { pub use drizzle_postgres::traits::*; }
270    /// #         pub mod common { pub use drizzle_postgres::common::*; }
271    /// #         pub mod attrs { pub use drizzle_postgres::attrs::*; }
272    /// #         pub mod builder { pub use drizzle_postgres::builder::*; }
273    /// #         pub mod helpers { pub use drizzle_postgres::helpers::*; }
274    /// #         pub mod expr { pub use drizzle_postgres::expr::*; }
275    /// #         pub mod types { pub use drizzle_postgres::types::*; }
276    /// #         pub struct Row;
277    /// #         impl Row {
278    /// #             pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
279    /// #             pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
280    /// #         }
281    /// #         pub mod prelude {
282    /// #             pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
283    /// #             pub use drizzle_postgres::attrs::*;
284    /// #             pub use drizzle_postgres::common::PostgresSchemaType;
285    /// #             pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
286    /// #             pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
287    /// #             pub use drizzle_core::*;
288    /// #         }
289    /// #     }
290    /// # }
291    /// # pub use _drizzle::*;
292    /// # pub use const_format;
293    /// fn main() {
294    /// use drizzle::postgres::prelude::*;
295    /// use drizzle::postgres::builder::QueryBuilder;
296    ///
297    /// #[PostgresTable(name = "users")]
298    /// struct User {
299    ///     #[column(serial, primary)]
300    ///     id: i32,
301    ///     name: String,
302    ///     #[column(unique)]
303    ///     email: Option<String>,
304    /// }
305    ///
306    /// #[PostgresIndex(unique)]
307    /// struct UserEmailIdx(User::email);
308    ///
309    /// #[derive(PostgresSchema)]
310    /// struct Schema {
311    ///     user: User,
312    ///     user_email_idx: UserEmailIdx,
313    /// }
314    ///
315    /// let builder = QueryBuilder::new::<Schema>();
316    /// let schema = Schema::new();
317    /// let user = schema.user;
318    ///
319    /// // Target a specific column
320    /// builder.insert(user).values([InsertUser::new("Alice")])
321    ///     .on_conflict(user.id).do_nothing();
322    ///
323    /// // Target with DO UPDATE using EXCLUDED
324    /// builder.insert(user).values([InsertUser::new("Alice")])
325    ///     .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
326    ///
327    /// // Target a unique index
328    /// builder.insert(user).values([InsertUser::new("Alice")])
329    ///     .on_conflict(schema.user_email_idx).do_nothing();
330    /// }
331    /// ```
332    pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
333        let columns = target.conflict_columns();
334        let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
335        OnConflictBuilder {
336            sql: self.sql,
337            target: ConflictTargetKind::Columns(Box::new(target_sql)),
338            target_where: None,
339            schema: PhantomData,
340            table: PhantomData,
341        }
342    }
343
344    /// Begins a typed ON CONFLICT ON CONSTRAINT clause (PostgreSQL-only).
345    ///
346    /// The target must implement `NamedConstraint<T>`, which is auto-generated
347    /// for unique indexes.
348    ///
349    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
350    ///
351    /// # Examples
352    ///
353    /// ```rust
354    /// # extern crate self as drizzle;
355    /// # mod _drizzle {
356    /// #     pub mod core { pub use drizzle_core::*; }
357    /// #     pub mod error { pub use drizzle_core::error::*; }
358    /// #     pub mod types { pub use drizzle_types::*; }
359    /// #     pub mod migrations { pub use drizzle_migrations::*; }
360    /// #     pub use drizzle_types::Dialect;
361    /// #     pub use drizzle_types as ddl;
362    /// #     pub mod postgres {
363    /// #         pub mod values { pub use drizzle_postgres::values::*; }
364    /// #         pub mod traits { pub use drizzle_postgres::traits::*; }
365    /// #         pub mod common { pub use drizzle_postgres::common::*; }
366    /// #         pub mod attrs { pub use drizzle_postgres::attrs::*; }
367    /// #         pub mod builder { pub use drizzle_postgres::builder::*; }
368    /// #         pub mod helpers { pub use drizzle_postgres::helpers::*; }
369    /// #         pub mod expr { pub use drizzle_postgres::expr::*; }
370    /// #         pub mod types { pub use drizzle_postgres::types::*; }
371    /// #         pub struct Row;
372    /// #         impl Row {
373    /// #             pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
374    /// #             pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
375    /// #         }
376    /// #         pub mod prelude {
377    /// #             pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
378    /// #             pub use drizzle_postgres::attrs::*;
379    /// #             pub use drizzle_postgres::common::PostgresSchemaType;
380    /// #             pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
381    /// #             pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
382    /// #             pub use drizzle_core::*;
383    /// #         }
384    /// #     }
385    /// # }
386    /// # pub use _drizzle::*;
387    /// # pub use const_format;
388    /// fn main() {
389    /// use drizzle::postgres::prelude::*;
390    /// use drizzle::postgres::builder::QueryBuilder;
391    ///
392    /// #[PostgresTable(name = "users")]
393    /// struct User {
394    ///     #[column(serial, primary)]
395    ///     id: i32,
396    ///     name: String,
397    ///     #[column(unique)]
398    ///     email: Option<String>,
399    /// }
400    ///
401    /// #[PostgresIndex(unique)]
402    /// struct UserEmailIdx(User::email);
403    ///
404    /// #[derive(PostgresSchema)]
405    /// struct Schema {
406    ///     user: User,
407    ///     user_email_idx: UserEmailIdx,
408    /// }
409    ///
410    /// let builder = QueryBuilder::new::<Schema>();
411    /// let schema = Schema::new();
412    ///
413    /// builder.insert(schema.user).values([InsertUser::new("Alice")])
414    ///     .on_conflict_on_constraint(schema.user_email_idx).do_nothing();
415    /// }
416    /// ```
417    pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
418        self,
419        target: C,
420    ) -> OnConflictBuilder<'a, S, T> {
421        OnConflictBuilder {
422            sql: self.sql,
423            target: ConflictTargetKind::Constraint(target.constraint_name()),
424            target_where: None,
425            schema: PhantomData,
426            table: PhantomData,
427        }
428    }
429
430    /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
431    ///
432    /// This matches any constraint violation.
433    #[must_use]
434    pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
435        let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
436        InsertBuilder {
437            sql: self.sql.append(conflict_sql),
438            schema: PhantomData,
439            state: PhantomData,
440            table: PhantomData,
441            marker: PhantomData,
442            row: PhantomData,
443            grouped: PhantomData,
444        }
445    }
446
447    /// Adds a RETURNING clause and transitions to `ReturningSet` state
448    #[inline]
449    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
450    where
451        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
452        Columns::Marker: drizzle_core::ResolveRow<T>,
453    {
454        let returning_sql = crate::helpers::returning(columns);
455        InsertBuilder {
456            sql: self.sql.append(returning_sql),
457            schema: PhantomData,
458            state: PhantomData,
459            table: PhantomData,
460            marker: PhantomData,
461            row: PhantomData,
462            grouped: PhantomData,
463        }
464    }
465}
466
467//------------------------------------------------------------------------------
468// Post-ON CONFLICT Implementation
469//------------------------------------------------------------------------------
470
471impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
472    /// Adds a RETURNING clause after ON CONFLICT
473    #[inline]
474    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
475    where
476        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
477        Columns::Marker: drizzle_core::ResolveRow<T>,
478    {
479        let returning_sql = crate::helpers::returning(columns);
480        InsertBuilder {
481            sql: self.sql.append(returning_sql),
482            schema: PhantomData,
483            state: PhantomData,
484            table: PhantomData,
485            marker: PhantomData,
486            row: PhantomData,
487            grouped: PhantomData,
488        }
489    }
490}
491
492//------------------------------------------------------------------------------
493// Post-DO UPDATE SET Implementation
494//------------------------------------------------------------------------------
495
496impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
497    /// Adds a WHERE clause to the DO UPDATE SET clause.
498    ///
499    /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
500    pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
501    where
502        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
503        E::SQLType: drizzle_core::types::BooleanLike,
504    {
505        let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
506        InsertBuilder {
507            sql,
508            schema: PhantomData,
509            state: PhantomData,
510            table: PhantomData,
511            marker: PhantomData,
512            row: PhantomData,
513            grouped: PhantomData,
514        }
515    }
516
517    /// Adds a RETURNING clause after DO UPDATE SET
518    #[inline]
519    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
520    where
521        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
522        Columns::Marker: drizzle_core::ResolveRow<T>,
523    {
524        let returning_sql = crate::helpers::returning(columns);
525        InsertBuilder {
526            sql: self.sql.append(returning_sql),
527            schema: PhantomData,
528            state: PhantomData,
529            table: PhantomData,
530            marker: PhantomData,
531            row: PhantomData,
532            grouped: PhantomData,
533        }
534    }
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540    use drizzle_core::{SQL, ToSQL};
541
542    #[test]
543    fn test_insert_builder_creation() {
544        let builder = InsertBuilder::<(), InsertInitial, ()> {
545            sql: SQL::raw("INSERT INTO test"),
546            schema: PhantomData,
547            state: PhantomData,
548            table: PhantomData,
549            marker: PhantomData,
550            row: PhantomData,
551            grouped: PhantomData,
552        };
553
554        assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
555    }
556}