drizzle_sqlite/builder/
insert.rs

1use crate::traits::SQLiteTable;
2use crate::values::SQLiteValue;
3use drizzle_core::{SQL, SQLModel, ToSQL, Token};
4use std::fmt::Debug;
5use std::marker::PhantomData;
6
7// Import the ExecutableState trait
8use super::ExecutableState;
9
10//------------------------------------------------------------------------------
11// Type State Markers
12//------------------------------------------------------------------------------
13
14/// Marker for the initial state of InsertBuilder.
15#[derive(Debug, Clone, Copy, Default)]
16pub struct InsertInitial;
17
18/// Marker for the state after VALUES are set.
19#[derive(Debug, Clone, Copy, Default)]
20pub struct InsertValuesSet;
21
22/// Marker for the state after RETURNING clause is added.
23#[derive(Debug, Clone, Copy, Default)]
24pub struct InsertReturningSet;
25
26/// Marker for the state after ON CONFLICT is set.
27#[derive(Debug, Clone, Copy, Default)]
28pub struct InsertOnConflictSet;
29
30// Const constructors for insert marker types
31impl InsertInitial {
32    #[inline]
33    pub const fn new() -> Self {
34        Self
35    }
36}
37impl InsertValuesSet {
38    #[inline]
39    pub const fn new() -> Self {
40        Self
41    }
42}
43impl InsertReturningSet {
44    #[inline]
45    pub const fn new() -> Self {
46        Self
47    }
48}
49impl InsertOnConflictSet {
50    #[inline]
51    pub const fn new() -> Self {
52        Self
53    }
54}
55
56/// Conflict resolution strategies
57#[derive(Debug, Clone)]
58pub enum Conflict<
59    'a,
60    T: IntoIterator<Item: ToSQL<'a, SQLiteValue<'a>>> = Vec<SQL<'a, SQLiteValue<'a>>>,
61> {
62    /// Do nothing on conflict - ON CONFLICT DO NOTHING
63    Ignore {
64        /// Optional target columns to specify which constraint triggers the conflict
65        target: Option<T>,
66    },
67    /// Update on conflict - ON CONFLICT DO UPDATE
68    Update {
69        /// Target columns that trigger the conflict
70        target: T,
71        /// SET clause for what to update
72        set: Box<SQL<'a, SQLiteValue<'a>>>,
73        /// Optional WHERE clause for the conflict target (partial indexes)
74        /// This goes after the target: ON CONFLICT (col) WHERE condition
75        target_where: Box<Option<SQL<'a, SQLiteValue<'a>>>>,
76        /// Optional WHERE clause for the update (conditional updates)
77        /// This goes after the SET: DO UPDATE SET col = val WHERE condition
78        set_where: Box<Option<SQL<'a, SQLiteValue<'a>>>>,
79    },
80}
81
82impl<'a> Default for Conflict<'a> {
83    fn default() -> Self {
84        Self::Ignore { target: None }
85    }
86}
87
88impl<'a, T> Conflict<'a, T>
89where
90    T: IntoIterator<Item: ToSQL<'a, SQLiteValue<'a>>>,
91{
92    pub fn update<S, TW, SW>(
93        target: T,
94        set: S,
95        target_where: Option<TW>,
96        set_where: Option<SW>,
97    ) -> Self
98    where
99        S: ToSQL<'a, SQLiteValue<'a>>,
100        TW: ToSQL<'a, SQLiteValue<'a>>,
101        SW: ToSQL<'a, SQLiteValue<'a>>,
102    {
103        Conflict::Update {
104            target,
105            set: Box::new(set.to_sql()),
106            target_where: Box::new(target_where.map(|w| w.to_sql())),
107            set_where: Box::new(set_where.map(|w| w.to_sql())),
108        }
109    }
110}
111
112// Mark states that can execute insert queries
113impl ExecutableState for InsertValuesSet {}
114impl ExecutableState for InsertReturningSet {}
115impl ExecutableState for InsertOnConflictSet {}
116
117//------------------------------------------------------------------------------
118// InsertBuilder Definition
119//------------------------------------------------------------------------------
120
121/// Builds an INSERT query specifically for SQLite.
122///
123/// `InsertBuilder` provides a type-safe, fluent API for constructing INSERT statements
124/// with support for conflict resolution, batch inserts, and returning clauses.
125///
126/// ## Type Parameters
127///
128/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
129/// - `State`: The current builder state, enforcing proper query construction order
130/// - `Table`: The table being inserted into
131///
132/// ## Query Building Flow
133///
134/// 1. Start with `QueryBuilder::insert(table)` to specify the target table
135/// 2. Add `values()` to specify what data to insert
136/// 3. Optionally add conflict resolution with `on_conflict()`
137/// 4. Optionally add a `returning()` clause
138///
139/// ## Basic Usage
140///
141/// ```rust
142/// # mod drizzle {
143/// #     pub mod core { pub use drizzle_core::*; }
144/// #     pub mod error { pub use drizzle_core::error::*; }
145/// #     pub mod types { pub use drizzle_types::*; }
146/// #     pub mod migrations { pub use drizzle_migrations::*; }
147/// #     pub use drizzle_types::Dialect;
148/// #     pub use drizzle_types as ddl;
149/// #     pub mod sqlite {
150/// #             pub use drizzle_sqlite::{*, attrs::*};
151/// #         pub mod prelude {
152/// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
153/// #             pub use drizzle_sqlite::{*, attrs::*};
154/// #             pub use drizzle_core::*;
155/// #         }
156/// #     }
157/// # }
158/// use drizzle::sqlite::prelude::*;
159/// use drizzle::sqlite::builder::QueryBuilder;
160///
161/// #[SQLiteTable(name = "users")]
162/// struct User {
163///     #[column(primary)]
164///     id: i32,
165///     name: String,
166///     email: Option<String>,
167/// }
168///
169/// #[derive(SQLiteSchema)]
170/// struct Schema {
171///     user: User,
172/// }
173///
174/// let builder = QueryBuilder::new::<Schema>();
175/// let Schema { user } = Schema::new();
176///
177/// // Basic INSERT
178/// let query = builder
179///     .insert(user)
180///     .values([InsertUser::new("Alice")]);
181/// assert_eq!(query.to_sql().sql(), r#"INSERT INTO "users" ("name") VALUES (?)"#);
182///
183/// // Batch INSERT
184/// let query = builder
185///     .insert(user)
186///     .values([
187///         InsertUser::new("Alice").with_email("alice@example.com"),
188///         InsertUser::new("Bob").with_email("bob@example.com"),
189///     ]);
190/// ```
191///
192/// ## Conflict Resolution
193///
194/// SQLite supports various conflict resolution strategies:
195///
196/// ```rust,no_run
197/// # mod drizzle {
198/// #     pub mod core { pub use drizzle_core::*; }
199/// #     pub mod error { pub use drizzle_core::error::*; }
200/// #     pub mod types { pub use drizzle_types::*; }
201/// #     pub mod migrations { pub use drizzle_migrations::*; }
202/// #     pub use drizzle_types::Dialect;
203/// #     pub use drizzle_types as ddl;
204/// #     pub mod sqlite {
205/// #             pub use drizzle_sqlite::{*, attrs::*};
206/// #         pub mod prelude {
207/// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
208/// #             pub use drizzle_sqlite::{*, attrs::*};
209/// #             pub use drizzle_core::*;
210/// #         }
211/// #     }
212/// # }
213/// # use drizzle::sqlite::prelude::*;
214/// # use drizzle::sqlite::builder::{QueryBuilder, insert::Conflict};
215/// # #[SQLiteTable(name = "users")] struct User { #[column(primary)] id: i32, name: String }
216/// # #[derive(SQLiteSchema)] struct Schema { user: User }
217/// # let builder = QueryBuilder::new::<Schema>();
218/// # let Schema { user } = Schema::new();
219/// // Ignore conflicts (ON CONFLICT DO NOTHING)
220/// let query = builder
221///     .insert(user)
222///     .values([InsertUser::new("Alice")])
223///     .on_conflict(Conflict::default());
224/// ```
225pub type InsertBuilder<'a, Schema, State, Table> = super::QueryBuilder<'a, Schema, State, Table>;
226
227//------------------------------------------------------------------------------
228// Initial State Implementation
229//------------------------------------------------------------------------------
230
231impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
232where
233    Table: SQLiteTable<'a>,
234{
235    /// Specifies the values to insert into the table.
236    ///
237    /// This method accepts an iterable of insert value objects generated by the
238    /// SQLiteTable macro (e.g., `InsertUser`). You can insert single values or
239    /// multiple values for batch operations.
240    ///
241    /// # Examples
242    ///
243    /// ```rust
244    /// # mod drizzle {
245    /// #     pub mod core { pub use drizzle_core::*; }
246    /// #     pub mod error { pub use drizzle_core::error::*; }
247    /// #     pub mod types { pub use drizzle_types::*; }
248    /// #     pub mod migrations { pub use drizzle_migrations::*; }
249    /// #     pub use drizzle_types::Dialect;
250    /// #     pub use drizzle_types as ddl;
251    /// #     pub mod sqlite {
252    /// #         pub use drizzle_sqlite::*;
253    /// #         pub mod prelude {
254    /// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
255    /// #             pub use drizzle_sqlite::{*, attrs::*};
256    /// #             pub use drizzle_core::*;
257    /// #         }
258    /// #     }
259    /// # }
260    /// # use drizzle::sqlite::prelude::*;
261    /// # use drizzle::sqlite::builder::QueryBuilder;
262    /// # #[SQLiteTable(name = "users")] struct User { #[column(primary)] id: i32, name: String, email: Option<String> }
263    /// # #[derive(SQLiteSchema)] struct Schema { user: User }
264    /// # let builder = QueryBuilder::new::<Schema>();
265    /// # let Schema { user } = Schema::new();
266    /// // Single insert
267    /// let query = builder
268    ///     .insert(user)
269    ///     .values([InsertUser::new("Alice")]);
270    /// assert_eq!(query.to_sql().sql(), r#"INSERT INTO "users" ("name") VALUES (?)"#);
271    ///
272    /// // Batch insert (all values must have the same fields set)
273    /// let query = builder
274    ///     .insert(user)
275    ///     .values([
276    ///         InsertUser::new("Alice").with_email("alice@example.com"),
277    ///         InsertUser::new("Bob").with_email("bob@example.com"),
278    ///     ]);
279    /// assert_eq!(
280    ///     query.to_sql().sql(),
281    ///     r#"INSERT INTO "users" ("name", "email") VALUES (?, ?), (?, ?)"#
282    /// );
283    /// ```
284    #[inline]
285    pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
286    where
287        I: IntoIterator<Item = Table::Insert<T>>,
288        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
289    {
290        let sql = crate::helpers::values::<'a, Table, T>(values);
291        InsertBuilder {
292            sql: self.sql.append(sql),
293            schema: PhantomData,
294            state: PhantomData,
295            table: PhantomData,
296        }
297    }
298}
299
300//------------------------------------------------------------------------------
301// Post-VALUES Implementation
302//------------------------------------------------------------------------------
303
304impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
305    /// Adds conflict resolution to handle constraint violations.
306    ///
307    /// SQLite supports various conflict resolution strategies when inserting data
308    /// that would violate unique constraints or primary keys. This method allows
309    /// you to specify how to handle such conflicts.
310    ///
311    /// # Examples
312    ///
313    /// ```rust
314    /// # mod drizzle {
315    /// #     pub mod core { pub use drizzle_core::*; }
316    /// #     pub mod error { pub use drizzle_core::error::*; }
317    /// #     pub mod types { pub use drizzle_types::*; }
318    /// #     pub mod migrations { pub use drizzle_migrations::*; }
319    /// #     pub use drizzle_types::Dialect;
320    /// #     pub use drizzle_types as ddl;
321    /// #     pub mod sqlite {
322    /// #         pub use drizzle_sqlite::*;
323    /// #         pub mod prelude {
324    /// #             pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
325    /// #             pub use drizzle_sqlite::{*, attrs::*};
326    /// #             pub use drizzle_core::*;
327    /// #         }
328    /// #     }
329    /// # }
330    /// # use drizzle::sqlite::prelude::*;
331    /// # use drizzle::sqlite::builder::{QueryBuilder, insert::Conflict};
332    /// # #[SQLiteTable(name = "users")] struct User { #[column(primary)] id: i32, name: String, email: Option<String> }
333    /// # #[derive(SQLiteSchema)] struct Schema { user: User }
334    /// # let builder = QueryBuilder::new::<Schema>();
335    /// # let Schema { user } = Schema::new();
336    /// // Ignore conflicts (do nothing)
337    /// let query = builder
338    ///     .insert(user)
339    ///     .values([InsertUser::new("Alice")])
340    ///     .on_conflict(Conflict::default());
341    /// assert_eq!(
342    ///     query.to_sql().sql(),
343    ///     r#"INSERT INTO "users" ("name") VALUES (?) ON CONFLICT DO NOTHING"#
344    /// );
345    /// ```
346    pub fn on_conflict<TI>(
347        self,
348        conflict: Conflict<'a, TI>,
349    ) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
350    where
351        TI: IntoIterator,
352        TI::Item: ToSQL<'a, SQLiteValue<'a>>,
353    {
354        let conflict_sql = match conflict {
355            Conflict::Ignore { target } => {
356                if let Some(target_iter) = target {
357                    let cols = SQL::join(
358                        target_iter.into_iter().map(|item| item.to_sql()),
359                        Token::COMMA,
360                    );
361                    SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
362                        .append(cols)
363                        .push(Token::RPAREN)
364                        .push(Token::DO)
365                        .push(Token::NOTHING)
366                } else {
367                    SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING])
368                }
369            }
370            Conflict::Update {
371                target,
372                set,
373                target_where,
374                set_where,
375            } => {
376                let target_cols =
377                    SQL::join(target.into_iter().map(|item| item.to_sql()), Token::COMMA);
378                let mut sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
379                    .append(target_cols)
380                    .push(Token::RPAREN);
381
382                // Add target WHERE clause (for partial indexes)
383                if let Some(target_where) = *target_where {
384                    sql = sql.push(Token::WHERE).append(target_where);
385                }
386
387                sql = sql
388                    .push(Token::DO)
389                    .push(Token::UPDATE)
390                    .push(Token::SET)
391                    .append(*set);
392
393                // Add set WHERE clause (for conditional updates)
394                if let Some(set_where) = *set_where {
395                    sql = sql.push(Token::WHERE).append(set_where);
396                }
397
398                sql
399            }
400        };
401
402        InsertBuilder {
403            sql: self.sql.append(conflict_sql),
404            schema: PhantomData,
405            state: PhantomData,
406            table: PhantomData,
407        }
408    }
409
410    /// Adds a RETURNING clause and transitions to ReturningSet state
411    #[inline]
412    pub fn returning(
413        self,
414        columns: impl ToSQL<'a, SQLiteValue<'a>>,
415    ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
416        let returning_sql = crate::helpers::returning(columns);
417        InsertBuilder {
418            sql: self.sql.append(returning_sql),
419            schema: PhantomData,
420            state: PhantomData,
421            table: PhantomData,
422        }
423    }
424}
425
426//------------------------------------------------------------------------------
427// Post-ON CONFLICT Implementation
428//------------------------------------------------------------------------------
429
430impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
431    /// Adds a RETURNING clause after ON CONFLICT
432    #[inline]
433    pub fn returning(
434        self,
435        columns: impl ToSQL<'a, SQLiteValue<'a>>,
436    ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
437        let returning_sql = crate::helpers::returning(columns);
438        InsertBuilder {
439            sql: self.sql.append(returning_sql),
440            schema: PhantomData,
441            state: PhantomData,
442            table: PhantomData,
443        }
444    }
445}