Skip to main content

drizzle_sqlite/builder/
insert.rs

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