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::{ConflictTarget, SQL, SQLModel, 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.
22///
23/// Created by [`InsertBuilder::on_conflict()`]. Call [`do_nothing()`](Self::do_nothing)
24/// or [`do_update()`](Self::do_update) to complete the clause.
25pub type OnConflictBuilder<'a, S, T> = CoreOnConflictBuilder<
26    'a,
27    SQLiteValue<'a>,
28    S,
29    T,
30    ConflictColumnsTarget<'a, SQLiteValue<'a>>,
31    SQLiteOnConflictOutput,
32>;
33
34#[doc(hidden)]
35#[derive(Debug, Clone, Copy, Default)]
36pub struct SQLiteOnConflictOutput;
37
38impl<'a, S, T> OnConflictOutput<'a, SQLiteValue<'a>, S, T> for SQLiteOnConflictOutput {
39    type OnConflictSet = InsertBuilder<'a, S, InsertOnConflictSet, T>;
40    type DoUpdateSet = InsertBuilder<'a, S, InsertDoUpdateSet, T>;
41
42    fn on_conflict(sql: SQL<'a, SQLiteValue<'a>>) -> Self::OnConflictSet {
43        InsertBuilder {
44            sql,
45            schema: PhantomData,
46            state: PhantomData,
47            table: PhantomData,
48            marker: PhantomData,
49            row: PhantomData,
50            grouped: PhantomData,
51        }
52    }
53
54    fn do_update(sql: SQL<'a, SQLiteValue<'a>>) -> Self::DoUpdateSet {
55        InsertBuilder {
56            sql,
57            schema: PhantomData,
58            state: PhantomData,
59            table: PhantomData,
60            marker: PhantomData,
61            row: PhantomData,
62            grouped: PhantomData,
63        }
64    }
65}
66
67//------------------------------------------------------------------------------
68// InsertBuilder Definition
69//------------------------------------------------------------------------------
70
71/// Builds an INSERT query specifically for `SQLite`.
72///
73/// Provides a type-safe, fluent API for constructing INSERT statements
74/// with support for typed conflict resolution, batch inserts, and returning clauses.
75///
76/// ## Type Parameters
77///
78/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
79/// - `State`: The current builder state, enforcing proper query construction order
80/// - `Table`: The table being inserted into
81///
82/// ## Query Building Flow
83///
84/// 1. Start with `QueryBuilder::insert(table)` to specify the target table
85/// 2. Add `values()` to specify what data to insert
86/// 3. Optionally add conflict resolution with `on_conflict(target).do_nothing()` or `.do_update(set)`
87/// 4. Optionally add a `returning()` clause
88pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
89    super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
90
91type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
92    <Columns as drizzle_core::IntoSelectTarget>::Marker,
93    drizzle_core::Cons<Table, drizzle_core::Nil>,
94>;
95
96type ReturningRow<Table, Columns> =
97    <<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
98
99type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
100    'a,
101    S,
102    InsertReturningSet,
103    T,
104    ReturningMarker<T, Columns>,
105    ReturningRow<T, Columns>,
106>;
107
108//------------------------------------------------------------------------------
109// Initial State Implementation
110//------------------------------------------------------------------------------
111
112impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
113where
114    Table: SQLiteTable<'a>,
115{
116    /// Specifies a single row to insert into the table.
117    ///
118    /// Accepts an insert value object generated by the `SQLiteTable` macro
119    /// (e.g., `InsertUser`).
120    #[inline]
121    pub fn value<T>(
122        self,
123        value: Table::Insert<T>,
124    ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
125    where
126        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
127    {
128        self.values([value])
129    }
130
131    /// Specifies the values to insert into the table.
132    ///
133    /// Accepts an iterable of insert value objects generated by the
134    /// `SQLiteTable` macro (e.g., `InsertUser`).
135    #[inline]
136    pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
137    where
138        I: IntoIterator<Item = Table::Insert<T>>,
139        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
140    {
141        let sql = crate::helpers::values::<'a, Table, T>(values);
142        InsertBuilder {
143            sql: self.sql.append(sql),
144            schema: PhantomData,
145            state: PhantomData,
146            table: PhantomData,
147            marker: PhantomData,
148            row: PhantomData,
149            grouped: PhantomData,
150        }
151    }
152
153    /// Inserts rows produced by a SELECT query without an explicit column list.
154    ///
155    /// The SELECT output must provide every table column in declaration order.
156    #[inline]
157    pub fn select<Q>(self, query: Q) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
158    where
159        Q: ToSQL<'a, SQLiteValue<'a>>,
160    {
161        InsertBuilder {
162            sql: self.sql.append(query.into_sql()),
163            schema: PhantomData,
164            state: PhantomData,
165            table: PhantomData,
166            marker: PhantomData,
167            row: PhantomData,
168            grouped: PhantomData,
169        }
170    }
171}
172
173//------------------------------------------------------------------------------
174// Post-VALUES Implementation
175//------------------------------------------------------------------------------
176
177impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
178    /// Begins a typed ON CONFLICT clause targeting a specific constraint.
179    ///
180    /// The target must implement `ConflictTarget<T>`, which is auto-generated for
181    /// primary key columns, unique columns, and unique indexes.
182    ///
183    /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
184    ///
185    /// # Examples
186    ///
187    /// ```rust
188    /// # extern crate self as drizzle;
189    /// # mod _drizzle {
190    /// #     pub mod core { pub use drizzle_core::*; }
191    /// #     pub mod error { pub use drizzle_core::error::*; }
192    /// #     pub mod types { pub use drizzle_types::*; }
193    /// #     pub mod migrations { pub use drizzle_migrations::*; }
194    /// #     pub use drizzle_types::Dialect;
195    /// #     pub use drizzle_types as ddl;
196    /// #     pub mod sqlite {
197    /// #         pub use drizzle_sqlite::*;
198    /// #         #[cfg(feature = "rusqlite")]
199    /// #         pub mod rusqlite { pub use ::rusqlite::{Error, Result, Row, types}; }
200    /// #         #[cfg(feature = "libsql")]
201    /// #         pub mod libsql { pub use ::libsql::{Row, Value}; }
202    /// #         #[cfg(feature = "turso")]
203    /// #         pub mod turso { pub use ::turso::{Error, IntoValue, Result, Row, Value}; }
204    /// #         pub mod prelude {
205    /// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
206    /// #             pub use drizzle_sqlite::{*, attrs::*};
207    /// #             pub use drizzle_core::*;
208    /// #         }
209    /// #     }
210    /// # }
211    /// # pub use _drizzle::*;
212    /// # pub use const_format;
213    /// fn main() {
214    /// use drizzle::sqlite::prelude::*;
215    /// use drizzle::sqlite::builder::QueryBuilder;
216    ///
217    /// #[SQLiteTable(name = "users")]
218    /// struct User {
219    ///     #[column(primary)]
220    ///     id: i32,
221    ///     name: String,
222    ///     #[column(unique)]
223    ///     email: Option<String>,
224    /// }
225    ///
226    /// #[derive(SQLiteSchema)]
227    /// struct Schema {
228    ///     user: User,
229    /// }
230    ///
231    /// let builder = QueryBuilder::new::<Schema>();
232    /// let schema = Schema::new();
233    /// let user = schema.user;
234    ///
235    /// // Target a specific column (requires PK or unique constraint)
236    /// builder.insert(user).values([InsertUser::new("Alice")])
237    ///     .on_conflict(user.id).do_nothing();
238    ///
239    /// // Target with DO UPDATE
240    /// builder.insert(user).values([InsertUser::new("Alice")])
241    ///     .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
242    /// }
243    /// ```
244    pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
245        let columns = target.conflict_columns();
246        let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
247        OnConflictBuilder::new(self.sql, ConflictColumnsTarget::new(target_sql))
248    }
249
250    /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
251    ///
252    /// This matches any constraint violation.
253    #[must_use]
254    pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
255        let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
256        InsertBuilder {
257            sql: self.sql.append(conflict_sql),
258            schema: PhantomData,
259            state: PhantomData,
260            table: PhantomData,
261            marker: PhantomData,
262            row: PhantomData,
263            grouped: PhantomData,
264        }
265    }
266
267    /// Adds a RETURNING clause and transitions to `ReturningSet` state
268    #[inline]
269    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
270    where
271        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
272        Columns::Marker: drizzle_core::ResolveRow<T>,
273    {
274        let returning_sql = crate::helpers::returning(columns);
275        InsertBuilder {
276            sql: self.sql.append(returning_sql),
277            schema: PhantomData,
278            state: PhantomData,
279            table: PhantomData,
280            marker: PhantomData,
281            row: PhantomData,
282            grouped: PhantomData,
283        }
284    }
285}
286
287//------------------------------------------------------------------------------
288// Post-ON CONFLICT Implementation
289//------------------------------------------------------------------------------
290
291impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
292    /// Adds a RETURNING clause after ON CONFLICT
293    #[inline]
294    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
295    where
296        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
297        Columns::Marker: drizzle_core::ResolveRow<T>,
298    {
299        let returning_sql = crate::helpers::returning(columns);
300        InsertBuilder {
301            sql: self.sql.append(returning_sql),
302            schema: PhantomData,
303            state: PhantomData,
304            table: PhantomData,
305            marker: PhantomData,
306            row: PhantomData,
307            grouped: PhantomData,
308        }
309    }
310}
311
312//------------------------------------------------------------------------------
313// Post-DO UPDATE SET Implementation
314//------------------------------------------------------------------------------
315
316impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
317    /// Adds a WHERE clause to the DO UPDATE SET clause.
318    ///
319    /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
320    pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
321    where
322        E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
323        E::SQLType: drizzle_core::types::BooleanLike,
324    {
325        let sql = self
326            .sql
327            .push(Token::WHERE)
328            .append(condition.into_expr_sql());
329        InsertBuilder {
330            sql,
331            schema: PhantomData,
332            state: PhantomData,
333            table: PhantomData,
334            marker: PhantomData,
335            row: PhantomData,
336            grouped: PhantomData,
337        }
338    }
339
340    /// Adds a RETURNING clause after DO UPDATE SET
341    #[inline]
342    pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
343    where
344        Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
345        Columns::Marker: drizzle_core::ResolveRow<T>,
346    {
347        let returning_sql = crate::helpers::returning(columns);
348        InsertBuilder {
349            sql: self.sql.append(returning_sql),
350            schema: PhantomData,
351            state: PhantomData,
352            table: PhantomData,
353            marker: PhantomData,
354            row: PhantomData,
355            grouped: PhantomData,
356        }
357    }
358}