Skip to main content

drizzle_sqlite/builder/
insert.rs

1use crate::traits::SQLiteTable;
2use crate::values::SQLiteValue;
3use core::fmt::Debug;
4use core::marker::PhantomData;
5use drizzle_core::{ConflictTarget, SQL, SQLModel, ToSQL, Token};
6
7// Import the ExecutableState trait
8use super::ExecutableState;
9
10#[inline]
11fn append_sql<'a>(
12    mut base: SQL<'a, SQLiteValue<'a>>,
13    fragment: SQL<'a, SQLiteValue<'a>>,
14) -> SQL<'a, SQLiteValue<'a>> {
15    base.append_mut(fragment);
16    base
17}
18
19//------------------------------------------------------------------------------
20// Type State Markers
21//------------------------------------------------------------------------------
22
23/// Marker for the initial state of `InsertBuilder`.
24#[derive(Debug, Clone, Copy, Default)]
25pub struct InsertInitial;
26
27/// Marker for the state after VALUES are set.
28#[derive(Debug, Clone, Copy, Default)]
29pub struct InsertValuesSet;
30
31/// Marker for the state after RETURNING clause is added.
32#[derive(Debug, Clone, Copy, Default)]
33pub struct InsertReturningSet;
34
35/// Marker for the state after ON CONFLICT is set.
36#[derive(Debug, Clone, Copy, Default)]
37pub struct InsertOnConflictSet;
38
39/// Marker for the state after DO UPDATE SET (before optional WHERE).
40#[derive(Debug, Clone, Copy, Default)]
41pub struct InsertDoUpdateSet;
42
43// Mark states that can execute insert queries
44impl ExecutableState for InsertValuesSet {}
45impl ExecutableState for InsertReturningSet {}
46impl ExecutableState for InsertOnConflictSet {}
47impl ExecutableState for InsertDoUpdateSet {}
48
49//------------------------------------------------------------------------------
50// OnConflictBuilder
51//------------------------------------------------------------------------------
52
53/// Intermediate builder for typed ON CONFLICT clause construction.
54///
55/// Created by [`InsertBuilder::on_conflict()`]. Call [`do_nothing()`](Self::do_nothing)
56/// or [`do_update()`](Self::do_update) to complete the clause.
57#[derive(Debug, Clone)]
58pub struct OnConflictBuilder<'a, S, T> {
59    sql: SQL<'a, SQLiteValue<'a>>,
60    target_sql: SQL<'a, SQLiteValue<'a>>,
61    target_where: Option<SQL<'a, SQLiteValue<'a>>>,
62    schema: PhantomData<S>,
63    table: PhantomData<T>,
64}
65
66impl<'a, S, T> OnConflictBuilder<'a, S, T> {
67    /// Adds a WHERE clause to the conflict target for partial index matching.
68    ///
69    /// Generates: `ON CONFLICT (col) WHERE condition DO ...`
70    #[must_use]
71    pub fn r#where<E>(mut self, condition: E) -> Self
72    where
73        E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
74        E::SQLType: drizzle_core::types::BooleanLike,
75    {
76        self.target_where = Some(condition.to_sql());
77        self
78    }
79
80    /// Splits into (base insert SQL, conflict target SQL prefix).
81    fn into_parts(self) -> (SQL<'a, SQLiteValue<'a>>, SQL<'a, SQLiteValue<'a>>) {
82        let mut target = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
83            .append(self.target_sql)
84            .push(Token::RPAREN);
85        if let Some(tw) = self.target_where {
86            target = target.push(Token::WHERE).append(tw);
87        }
88        (self.sql, target)
89    }
90
91    /// Resolves the conflict by doing nothing (ignoring the conflicting row).
92    ///
93    /// Generates: `ON CONFLICT (col1, col2) DO NOTHING`
94    #[must_use]
95    pub fn do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
96        let (sql, target) = self.into_parts();
97        InsertBuilder {
98            sql: append_sql(sql, target.push(Token::DO).push(Token::NOTHING)),
99            schema: PhantomData,
100            state: PhantomData,
101            table: PhantomData,
102            marker: PhantomData,
103            row: PhantomData,
104            grouped: PhantomData,
105        }
106    }
107
108    /// Resolves the conflict by updating the existing row.
109    ///
110    /// The `set` parameter accepts any `ToSQL` value, typically an `UpdateModel`
111    /// which generates the SET clause assignments.
112    ///
113    /// Generates: `ON CONFLICT (col1, col2) DO UPDATE SET ...`
114    ///
115    /// Chain `.r#where(condition)` to add a conditional update filter.
116    pub fn do_update(
117        self,
118        set: impl ToSQL<'a, SQLiteValue<'a>>,
119    ) -> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
120        let (sql, target) = self.into_parts();
121        let conflict = target
122            .push(Token::DO)
123            .push(Token::UPDATE)
124            .push(Token::SET)
125            .append(set.to_sql());
126        InsertBuilder {
127            sql: append_sql(sql, conflict),
128            schema: PhantomData,
129            state: PhantomData,
130            table: PhantomData,
131            marker: PhantomData,
132            row: PhantomData,
133            grouped: PhantomData,
134        }
135    }
136}
137
138//------------------------------------------------------------------------------
139// InsertBuilder Definition
140//------------------------------------------------------------------------------
141
142/// Builds an INSERT query specifically for `SQLite`.
143///
144/// Provides a type-safe, fluent API for constructing INSERT statements
145/// with support for typed conflict resolution, batch inserts, and returning clauses.
146///
147/// ## Type Parameters
148///
149/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
150/// - `State`: The current builder state, enforcing proper query construction order
151/// - `Table`: The table being inserted into
152///
153/// ## Query Building Flow
154///
155/// 1. Start with `QueryBuilder::insert(table)` to specify the target table
156/// 2. Add `values()` to specify what data to insert
157/// 3. Optionally add conflict resolution with `on_conflict(target).do_nothing()` or `.do_update(set)`
158/// 4. Optionally add a `returning()` clause
159pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
160    super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
161
162type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
163    <Columns as drizzle_core::IntoSelectTarget>::Marker,
164    drizzle_core::Cons<Table, drizzle_core::Nil>,
165>;
166
167type ReturningRow<Table, Columns> =
168    <<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
169
170type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
171    'a,
172    S,
173    InsertReturningSet,
174    T,
175    ReturningMarker<T, Columns>,
176    ReturningRow<T, Columns>,
177>;
178
179//------------------------------------------------------------------------------
180// Initial State Implementation
181//------------------------------------------------------------------------------
182
183impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
184where
185    Table: SQLiteTable<'a>,
186{
187    /// Specifies a single row to insert into the table.
188    ///
189    /// Accepts an insert value object generated by the `SQLiteTable` macro
190    /// (e.g., `InsertUser`).
191    #[inline]
192    pub fn value<T>(
193        self,
194        value: Table::Insert<T>,
195    ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
196    where
197        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
198    {
199        self.values([value])
200    }
201
202    /// Specifies the values to insert into the table.
203    ///
204    /// Accepts an iterable of insert value objects generated by the
205    /// `SQLiteTable` macro (e.g., `InsertUser`).
206    #[inline]
207    pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
208    where
209        I: IntoIterator<Item = Table::Insert<T>>,
210        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
211    {
212        let sql = crate::helpers::values::<'a, Table, T>(values);
213        InsertBuilder {
214            sql: append_sql(self.sql, 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, SQLiteValue<'a>>,
231    {
232        InsertBuilder {
233            sql: append_sql(self.sql, 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 a specific constraint.
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 sqlite {
268    /// #         pub use drizzle_sqlite::*;
269    /// #         #[cfg(feature = "rusqlite")]
270    /// #         pub mod rusqlite { pub use ::rusqlite::{Error, Result, Row, types}; }
271    /// #         #[cfg(feature = "libsql")]
272    /// #         pub mod libsql { pub use ::libsql::{Row, Value}; }
273    /// #         #[cfg(feature = "turso")]
274    /// #         pub mod turso { pub use ::turso::{Error, IntoValue, Result, Row, Value}; }
275    /// #         pub mod prelude {
276    /// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
277    /// #             pub use drizzle_sqlite::{*, attrs::*};
278    /// #             pub use drizzle_core::*;
279    /// #         }
280    /// #     }
281    /// # }
282    /// # pub use _drizzle::*;
283    /// # pub use const_format;
284    /// fn main() {
285    /// use drizzle::sqlite::prelude::*;
286    /// use drizzle::sqlite::builder::QueryBuilder;
287    ///
288    /// #[SQLiteTable(name = "users")]
289    /// struct User {
290    ///     #[column(primary)]
291    ///     id: i32,
292    ///     name: String,
293    ///     #[column(unique)]
294    ///     email: Option<String>,
295    /// }
296    ///
297    /// #[derive(SQLiteSchema)]
298    /// struct Schema {
299    ///     user: User,
300    /// }
301    ///
302    /// let builder = QueryBuilder::new::<Schema>();
303    /// let schema = Schema::new();
304    /// let user = schema.user;
305    ///
306    /// // Target a specific column (requires PK or unique constraint)
307    /// builder.insert(user).values([InsertUser::new("Alice")])
308    ///     .on_conflict(user.id).do_nothing();
309    ///
310    /// // Target with DO UPDATE
311    /// builder.insert(user).values([InsertUser::new("Alice")])
312    ///     .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
313    /// }
314    /// ```
315    pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
316        let columns = target.conflict_columns();
317        let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
318        OnConflictBuilder {
319            sql: self.sql,
320            target_sql,
321            target_where: None,
322            schema: PhantomData,
323            table: PhantomData,
324        }
325    }
326
327    /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
328    ///
329    /// This matches any constraint violation.
330    #[must_use]
331    pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
332        let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
333        InsertBuilder {
334            sql: append_sql(self.sql, conflict_sql),
335            schema: PhantomData,
336            state: PhantomData,
337            table: PhantomData,
338            marker: PhantomData,
339            row: PhantomData,
340            grouped: PhantomData,
341        }
342    }
343
344    /// Adds a RETURNING clause and transitions to `ReturningSet` state
345    #[inline]
346    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
347    where
348        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
349        Columns::Marker: drizzle_core::ResolveRow<T>,
350    {
351        let returning_sql = crate::helpers::returning(columns);
352        InsertBuilder {
353            sql: append_sql(self.sql, returning_sql),
354            schema: PhantomData,
355            state: PhantomData,
356            table: PhantomData,
357            marker: PhantomData,
358            row: PhantomData,
359            grouped: PhantomData,
360        }
361    }
362}
363
364//------------------------------------------------------------------------------
365// Post-ON CONFLICT Implementation
366//------------------------------------------------------------------------------
367
368impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
369    /// Adds a RETURNING clause after ON CONFLICT
370    #[inline]
371    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
372    where
373        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
374        Columns::Marker: drizzle_core::ResolveRow<T>,
375    {
376        let returning_sql = crate::helpers::returning(columns);
377        InsertBuilder {
378            sql: append_sql(self.sql, returning_sql),
379            schema: PhantomData,
380            state: PhantomData,
381            table: PhantomData,
382            marker: PhantomData,
383            row: PhantomData,
384            grouped: PhantomData,
385        }
386    }
387}
388
389//------------------------------------------------------------------------------
390// Post-DO UPDATE SET Implementation
391//------------------------------------------------------------------------------
392
393impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
394    /// Adds a WHERE clause to the DO UPDATE SET clause.
395    ///
396    /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
397    pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
398    where
399        E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
400        E::SQLType: drizzle_core::types::BooleanLike,
401    {
402        let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
403        InsertBuilder {
404            sql,
405            schema: PhantomData,
406            state: PhantomData,
407            table: PhantomData,
408            marker: PhantomData,
409            row: PhantomData,
410            grouped: PhantomData,
411        }
412    }
413
414    /// Adds a RETURNING clause after DO UPDATE SET
415    #[inline]
416    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
417    where
418        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
419        Columns::Marker: drizzle_core::ResolveRow<T>,
420    {
421        let returning_sql = crate::helpers::returning(columns);
422        InsertBuilder {
423            sql: append_sql(self.sql, returning_sql),
424            schema: PhantomData,
425            state: PhantomData,
426            table: PhantomData,
427            marker: PhantomData,
428            row: PhantomData,
429            grouped: PhantomData,
430        }
431    }
432}