Skip to main content

drizzle_postgres/builder/
insert.rs

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