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
225//------------------------------------------------------------------------------
226// Post-VALUES Implementation
227//------------------------------------------------------------------------------
228
229impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
230    /// Begins a typed ON CONFLICT clause targeting a specific constraint.
231    ///
232    /// The target must implement `ConflictTarget<T>`, which is auto-generated for
233    /// primary key columns, unique columns, and unique indexes.
234    ///
235    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// # extern crate self as drizzle;
241    /// # mod _drizzle {
242    /// #     pub mod core { pub use drizzle_core::*; }
243    /// #     pub mod error { pub use drizzle_core::error::*; }
244    /// #     pub mod types { pub use drizzle_types::*; }
245    /// #     pub mod migrations { pub use drizzle_migrations::*; }
246    /// #     pub use drizzle_types::Dialect;
247    /// #     pub use drizzle_types as ddl;
248    /// #     pub mod sqlite {
249    /// #         pub use drizzle_sqlite::*;
250    /// #         #[cfg(feature = "rusqlite")]
251    /// #         pub mod rusqlite { pub use ::rusqlite::{Error, Result, Row, types}; }
252    /// #         #[cfg(feature = "libsql")]
253    /// #         pub mod libsql { pub use ::libsql::{Row, Value}; }
254    /// #         #[cfg(feature = "turso")]
255    /// #         pub mod turso { pub use ::turso::{Error, IntoValue, Result, Row, Value}; }
256    /// #         pub mod prelude {
257    /// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
258    /// #             pub use drizzle_sqlite::{*, attrs::*};
259    /// #             pub use drizzle_core::*;
260    /// #         }
261    /// #     }
262    /// # }
263    /// # pub use _drizzle::*;
264    /// # pub use const_format;
265    /// fn main() {
266    /// use drizzle::sqlite::prelude::*;
267    /// use drizzle::sqlite::builder::QueryBuilder;
268    ///
269    /// #[SQLiteTable(name = "users")]
270    /// struct User {
271    ///     #[column(primary)]
272    ///     id: i32,
273    ///     name: String,
274    ///     #[column(unique)]
275    ///     email: Option<String>,
276    /// }
277    ///
278    /// #[derive(SQLiteSchema)]
279    /// struct Schema {
280    ///     user: User,
281    /// }
282    ///
283    /// let builder = QueryBuilder::new::<Schema>();
284    /// let schema = Schema::new();
285    /// let user = schema.user;
286    ///
287    /// // Target a specific column (requires PK or unique constraint)
288    /// builder.insert(user).values([InsertUser::new("Alice")])
289    ///     .on_conflict(user.id).do_nothing();
290    ///
291    /// // Target with DO UPDATE
292    /// builder.insert(user).values([InsertUser::new("Alice")])
293    ///     .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
294    /// }
295    /// ```
296    pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
297        let columns = target.conflict_columns();
298        let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
299        OnConflictBuilder {
300            sql: self.sql,
301            target_sql,
302            target_where: None,
303            schema: PhantomData,
304            table: PhantomData,
305        }
306    }
307
308    /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
309    ///
310    /// This matches any constraint violation.
311    #[must_use]
312    pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
313        let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
314        InsertBuilder {
315            sql: append_sql(self.sql, conflict_sql),
316            schema: PhantomData,
317            state: PhantomData,
318            table: PhantomData,
319            marker: PhantomData,
320            row: PhantomData,
321            grouped: PhantomData,
322        }
323    }
324
325    /// Adds a RETURNING clause and transitions to `ReturningSet` state
326    #[inline]
327    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
328    where
329        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
330        Columns::Marker: drizzle_core::ResolveRow<T>,
331    {
332        let returning_sql = crate::helpers::returning(columns);
333        InsertBuilder {
334            sql: append_sql(self.sql, returning_sql),
335            schema: PhantomData,
336            state: PhantomData,
337            table: PhantomData,
338            marker: PhantomData,
339            row: PhantomData,
340            grouped: PhantomData,
341        }
342    }
343}
344
345//------------------------------------------------------------------------------
346// Post-ON CONFLICT Implementation
347//------------------------------------------------------------------------------
348
349impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
350    /// Adds a RETURNING clause after ON CONFLICT
351    #[inline]
352    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
353    where
354        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
355        Columns::Marker: drizzle_core::ResolveRow<T>,
356    {
357        let returning_sql = crate::helpers::returning(columns);
358        InsertBuilder {
359            sql: append_sql(self.sql, returning_sql),
360            schema: PhantomData,
361            state: PhantomData,
362            table: PhantomData,
363            marker: PhantomData,
364            row: PhantomData,
365            grouped: PhantomData,
366        }
367    }
368}
369
370//------------------------------------------------------------------------------
371// Post-DO UPDATE SET Implementation
372//------------------------------------------------------------------------------
373
374impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
375    /// Adds a WHERE clause to the DO UPDATE SET clause.
376    ///
377    /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
378    pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
379    where
380        E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
381        E::SQLType: drizzle_core::types::BooleanLike,
382    {
383        let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
384        InsertBuilder {
385            sql,
386            schema: PhantomData,
387            state: PhantomData,
388            table: PhantomData,
389            marker: PhantomData,
390            row: PhantomData,
391            grouped: PhantomData,
392        }
393    }
394
395    /// Adds a RETURNING clause after DO UPDATE SET
396    #[inline]
397    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
398    where
399        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
400        Columns::Marker: drizzle_core::ResolveRow<T>,
401    {
402        let returning_sql = crate::helpers::returning(columns);
403        InsertBuilder {
404            sql: append_sql(self.sql, returning_sql),
405            schema: PhantomData,
406            state: PhantomData,
407            table: PhantomData,
408            marker: PhantomData,
409            row: PhantomData,
410            grouped: PhantomData,
411        }
412    }
413}