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_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
249        OnConflictBuilder::new(self.sql, PostgresConflictTarget::columns(target_sql))
250    }
251
252    /// Begins a typed ON CONFLICT ON CONSTRAINT clause (PostgreSQL-only).
253    ///
254    /// The target must implement `NamedConstraint<T>`, which is auto-generated
255    /// for unique indexes.
256    ///
257    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
258    ///
259    /// # Examples
260    ///
261    /// ```rust
262    /// # extern crate self as drizzle;
263    /// # mod _drizzle {
264    /// #     pub mod core { pub use drizzle_core::*; }
265    /// #     pub mod error { pub use drizzle_core::error::*; }
266    /// #     pub mod types { pub use drizzle_types::*; }
267    /// #     pub mod migrations { pub use drizzle_migrations::*; }
268    /// #     pub use drizzle_types::Dialect;
269    /// #     pub use drizzle_types as ddl;
270    /// #     pub mod postgres {
271    /// #         pub mod values { pub use drizzle_postgres::values::*; }
272    /// #         pub mod traits { pub use drizzle_postgres::traits::*; }
273    /// #         pub mod common { pub use drizzle_postgres::common::*; }
274    /// #         pub mod attrs { pub use drizzle_postgres::attrs::*; }
275    /// #         pub mod builder { pub use drizzle_postgres::builder::*; }
276    /// #         pub mod helpers { pub use drizzle_postgres::helpers::*; }
277    /// #         pub mod expr { pub use drizzle_postgres::expr::*; }
278    /// #         pub mod types { pub use drizzle_postgres::types::*; }
279    /// #         pub struct Row;
280    /// #         impl Row {
281    /// #             pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
282    /// #             pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
283    /// #         }
284    /// #         pub mod prelude {
285    /// #             pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
286    /// #             pub use drizzle_postgres::attrs::*;
287    /// #             pub use drizzle_postgres::common::PostgresSchemaType;
288    /// #             pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
289    /// #             pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
290    /// #             pub use drizzle_core::*;
291    /// #         }
292    /// #     }
293    /// # }
294    /// # pub use _drizzle::*;
295    /// # pub use const_format;
296    /// fn main() {
297    /// use drizzle::postgres::prelude::*;
298    /// use drizzle::postgres::builder::QueryBuilder;
299    ///
300    /// #[PostgresTable(name = "users")]
301    /// struct User {
302    ///     #[column(serial, primary)]
303    ///     id: i32,
304    ///     name: String,
305    ///     #[column(unique)]
306    ///     email: Option<String>,
307    /// }
308    ///
309    /// #[PostgresIndex(unique)]
310    /// struct UserEmailIdx(User::email);
311    ///
312    /// #[derive(PostgresSchema)]
313    /// struct Schema {
314    ///     user: User,
315    ///     user_email_idx: UserEmailIdx,
316    /// }
317    ///
318    /// let builder = QueryBuilder::new::<Schema>();
319    /// let schema = Schema::new();
320    ///
321    /// builder.insert(schema.user).values([InsertUser::new("Alice")])
322    ///     .on_conflict_on_constraint(schema.user_email_idx).do_nothing();
323    /// }
324    /// ```
325    pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
326        self,
327        target: C,
328    ) -> OnConflictBuilder<'a, S, T> {
329        OnConflictBuilder::new(
330            self.sql,
331            PostgresConflictTarget::constraint(target.constraint_name()),
332        )
333    }
334
335    /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
336    ///
337    /// This matches any constraint violation.
338    #[must_use]
339    pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
340        let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
341        InsertBuilder {
342            sql: self.sql.append(conflict_sql),
343            schema: PhantomData,
344            state: PhantomData,
345            table: PhantomData,
346            marker: PhantomData,
347            row: PhantomData,
348            grouped: PhantomData,
349        }
350    }
351
352    /// Adds a RETURNING clause and transitions to `ReturningSet` state
353    #[inline]
354    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
355    where
356        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
357        Columns::Marker: drizzle_core::ResolveRow<T>,
358    {
359        let returning_sql = crate::helpers::returning(columns);
360        InsertBuilder {
361            sql: self.sql.append(returning_sql),
362            schema: PhantomData,
363            state: PhantomData,
364            table: PhantomData,
365            marker: PhantomData,
366            row: PhantomData,
367            grouped: PhantomData,
368        }
369    }
370}
371
372//------------------------------------------------------------------------------
373// Post-ON CONFLICT Implementation
374//------------------------------------------------------------------------------
375
376impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
377    /// Adds a RETURNING clause after ON CONFLICT
378    #[inline]
379    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
380    where
381        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
382        Columns::Marker: drizzle_core::ResolveRow<T>,
383    {
384        let returning_sql = crate::helpers::returning(columns);
385        InsertBuilder {
386            sql: self.sql.append(returning_sql),
387            schema: PhantomData,
388            state: PhantomData,
389            table: PhantomData,
390            marker: PhantomData,
391            row: PhantomData,
392            grouped: PhantomData,
393        }
394    }
395}
396
397//------------------------------------------------------------------------------
398// Post-DO UPDATE SET Implementation
399//------------------------------------------------------------------------------
400
401impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
402    /// Adds a WHERE clause to the DO UPDATE SET clause.
403    ///
404    /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
405    pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
406    where
407        E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
408        E::SQLType: drizzle_core::types::BooleanLike,
409    {
410        let sql = self
411            .sql
412            .push(Token::WHERE)
413            .append(condition.into_expr_sql());
414        InsertBuilder {
415            sql,
416            schema: PhantomData,
417            state: PhantomData,
418            table: PhantomData,
419            marker: PhantomData,
420            row: PhantomData,
421            grouped: PhantomData,
422        }
423    }
424
425    /// Adds a RETURNING clause after DO UPDATE SET
426    #[inline]
427    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
428    where
429        Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
430        Columns::Marker: drizzle_core::ResolveRow<T>,
431    {
432        let returning_sql = crate::helpers::returning(columns);
433        InsertBuilder {
434            sql: self.sql.append(returning_sql),
435            schema: PhantomData,
436            state: PhantomData,
437            table: PhantomData,
438            marker: PhantomData,
439            row: PhantomData,
440            grouped: PhantomData,
441        }
442    }
443}
444
445#[cfg(test)]
446mod tests {
447    use super::*;
448    use drizzle_core::{SQL, ToSQL};
449
450    #[test]
451    fn test_insert_builder_creation() {
452        let builder = InsertBuilder::<(), InsertInitial, ()> {
453            sql: SQL::raw("INSERT INTO test"),
454            schema: PhantomData,
455            state: PhantomData,
456            table: PhantomData,
457            marker: PhantomData,
458            row: PhantomData,
459            grouped: PhantomData,
460        };
461
462        assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
463    }
464}