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