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(
93        target: T,
94        set: SQL<'a, SQLiteValue<'a>>,
95        target_where: Option<SQL<'a, SQLiteValue<'a>>>,
96        set_where: Option<SQL<'a, SQLiteValue<'a>>>,
97    ) -> Self {
98        Conflict::Update {
99            target,
100            set: Box::new(set),
101            target_where: Box::new(target_where),
102            set_where: Box::new(set_where),
103        }
104    }
105}
106
107// Mark states that can execute insert queries
108impl ExecutableState for InsertValuesSet {}
109impl ExecutableState for InsertReturningSet {}
110impl ExecutableState for InsertOnConflictSet {}
111
112//------------------------------------------------------------------------------
113// InsertBuilder Definition
114//------------------------------------------------------------------------------
115
116/// Builds an INSERT query specifically for SQLite.
117///
118/// `InsertBuilder` provides a type-safe, fluent API for constructing INSERT statements
119/// with support for conflict resolution, batch inserts, and returning clauses.
120///
121/// ## Type Parameters
122///
123/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
124/// - `State`: The current builder state, enforcing proper query construction order
125/// - `Table`: The table being inserted into
126///
127/// ## Query Building Flow
128///
129/// 1. Start with `QueryBuilder::insert(table)` to specify the target table
130/// 2. Add `values()` to specify what data to insert
131/// 3. Optionally add conflict resolution with `on_conflict()`
132/// 4. Optionally add a `returning()` clause
133///
134/// ## Basic Usage
135///
136/// ```rust
137/// use drizzle_sqlite::builder::QueryBuilder;
138/// use drizzle_macros::{SQLiteTable, SQLiteSchema};
139/// use drizzle_core::ToSQL;
140///
141/// #[SQLiteTable(name = "users")]
142/// struct User {
143///     #[integer(primary)]
144///     id: i32,
145///     #[text]
146///     name: String,
147///     #[text]
148///     email: Option<String>,
149/// }
150///
151/// #[derive(SQLiteSchema)]
152/// struct Schema {
153///     user: User,
154/// }
155///
156/// let builder = QueryBuilder::new::<Schema>();
157/// let Schema { user } = Schema::new();
158///
159/// // Basic INSERT
160/// let query = builder
161///     .insert(user)
162///     .values([InsertUser::new("Alice")]);
163/// assert_eq!(query.to_sql().sql(), r#"INSERT INTO "users" (name) VALUES (?)"#);
164///
165/// // Batch INSERT
166/// let query = builder
167///     .insert(user)
168///     .values([
169///         InsertUser::new("Alice").with_email("alice@example.com"),
170///         InsertUser::new("Bob").with_email("bob@example.com"),
171///     ]);
172/// ```
173///
174/// ## Conflict Resolution
175///
176/// SQLite supports various conflict resolution strategies:
177///
178/// ```rust
179/// # use drizzle_sqlite::builder::{QueryBuilder, insert::Conflict};
180/// # use drizzle_macros::{SQLiteTable, SQLiteSchema};
181/// # use drizzle_core::ToSQL;
182/// # #[SQLiteTable(name = "users")] struct User { #[integer(primary)] id: i32, #[text] name: String }
183/// # #[derive(SQLiteSchema)] struct Schema { user: User }
184/// # let builder = QueryBuilder::new::<Schema>();
185/// # let Schema { user } = Schema::new();
186/// // Ignore conflicts (ON CONFLICT DO NOTHING)
187/// let query = builder
188///     .insert(user)
189///     .values([InsertUser::new("Alice")])
190///     .on_conflict(Conflict::default());
191/// ```
192pub type InsertBuilder<'a, Schema, State, Table> = super::QueryBuilder<'a, Schema, State, Table>;
193
194//------------------------------------------------------------------------------
195// Initial State Implementation
196//------------------------------------------------------------------------------
197
198impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
199where
200    Table: SQLiteTable<'a>,
201{
202    /// Specifies the values to insert into the table.
203    ///
204    /// This method accepts an iterable of insert value objects generated by the
205    /// SQLiteTable macro (e.g., `InsertUser`). You can insert single values or
206    /// multiple values for batch operations.
207    ///
208    /// # Examples
209    ///
210    /// ```rust
211    /// # use drizzle_sqlite::builder::QueryBuilder;
212    /// # use drizzle_macros::{SQLiteTable, SQLiteSchema};
213    /// # use drizzle_core::ToSQL;
214    /// # #[SQLiteTable(name = "users")] struct User { #[integer(primary)] id: i32, #[text] name: String, #[text] email: Option<String> }
215    /// # #[derive(SQLiteSchema)] struct Schema { user: User }
216    /// # let builder = QueryBuilder::new::<Schema>();
217    /// # let Schema { user } = Schema::new();
218    /// // Single insert
219    /// let query = builder
220    ///     .insert(user)
221    ///     .values([InsertUser::new("Alice")]);
222    /// assert_eq!(query.to_sql().sql(), r#"INSERT INTO "users" (name) VALUES (?)"#);
223    ///
224    /// // Batch insert (all values must have the same fields set)
225    /// let query = builder
226    ///     .insert(user)
227    ///     .values([
228    ///         InsertUser::new("Alice").with_email("alice@example.com"),
229    ///         InsertUser::new("Bob").with_email("bob@example.com"),
230    ///     ]);
231    /// assert_eq!(
232    ///     query.to_sql().sql(),
233    ///     r#"INSERT INTO "users" (name, email) VALUES (?, ?), (?, ?)"#
234    /// );
235    /// ```
236    #[inline]
237    pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
238    where
239        I: IntoIterator<Item = Table::Insert<T>>,
240        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
241    {
242        let sql = crate::helpers::values::<'a, Table, T>(values);
243        InsertBuilder {
244            sql: self.sql.append(sql),
245            schema: PhantomData,
246            state: PhantomData,
247            table: PhantomData,
248        }
249    }
250}
251
252//------------------------------------------------------------------------------
253// Post-VALUES Implementation
254//------------------------------------------------------------------------------
255
256impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
257    /// Adds conflict resolution to handle constraint violations.
258    ///
259    /// SQLite supports various conflict resolution strategies when inserting data
260    /// that would violate unique constraints or primary keys. This method allows
261    /// you to specify how to handle such conflicts.
262    ///
263    /// # Examples
264    ///
265    /// ```rust
266    /// # use drizzle_sqlite::builder::{QueryBuilder, insert::Conflict};
267    /// # use drizzle_macros::{SQLiteTable, SQLiteSchema};
268    /// # use drizzle_core::ToSQL;
269    /// # #[SQLiteTable(name = "users")] struct User { #[integer(primary)] id: i32, #[text] name: String, #[text] email: Option<String> }
270    /// # #[derive(SQLiteSchema)] struct Schema { user: User }
271    /// # let builder = QueryBuilder::new::<Schema>();
272    /// # let Schema { user } = Schema::new();
273    /// // Ignore conflicts (do nothing)
274    /// let query = builder
275    ///     .insert(user)
276    ///     .values([InsertUser::new("Alice")])
277    ///     .on_conflict(Conflict::default());
278    /// assert_eq!(
279    ///     query.to_sql().sql(),
280    ///     r#"INSERT INTO "users" (name) VALUES (?) ON CONFLICT DO NOTHING"#
281    /// );
282    /// ```
283    pub fn on_conflict<TI>(
284        self,
285        conflict: Conflict<'a, TI>,
286    ) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
287    where
288        TI: IntoIterator,
289        TI::Item: ToSQL<'a, SQLiteValue<'a>>,
290    {
291        let conflict_sql = match conflict {
292            Conflict::Ignore { target } => {
293                if let Some(target_iter) = target {
294                    let cols = SQL::join(
295                        target_iter.into_iter().map(|item| item.to_sql()),
296                        Token::COMMA,
297                    );
298                    SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
299                        .append(cols)
300                        .push(Token::RPAREN)
301                        .push(Token::DO)
302                        .push(Token::NOTHING)
303                } else {
304                    SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING])
305                }
306            }
307            Conflict::Update {
308                target,
309                set,
310                target_where,
311                set_where,
312            } => {
313                let target_cols =
314                    SQL::join(target.into_iter().map(|item| item.to_sql()), Token::COMMA);
315                let mut sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
316                    .append(target_cols)
317                    .push(Token::RPAREN);
318
319                // Add target WHERE clause (for partial indexes)
320                if let Some(target_where) = *target_where {
321                    sql = sql.push(Token::WHERE).append(target_where);
322                }
323
324                sql = sql
325                    .push(Token::DO)
326                    .push(Token::UPDATE)
327                    .push(Token::SET)
328                    .append(*set);
329
330                // Add set WHERE clause (for conditional updates)
331                if let Some(set_where) = *set_where {
332                    sql = sql.push(Token::WHERE).append(set_where);
333                }
334
335                sql
336            }
337        };
338
339        InsertBuilder {
340            sql: self.sql.append(conflict_sql),
341            schema: PhantomData,
342            state: PhantomData,
343            table: PhantomData,
344        }
345    }
346
347    /// Adds a RETURNING clause and transitions to ReturningSet state
348    #[inline]
349    pub fn returning(
350        self,
351        columns: impl ToSQL<'a, SQLiteValue<'a>>,
352    ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
353        let returning_sql = crate::helpers::returning(columns);
354        InsertBuilder {
355            sql: self.sql.append(returning_sql),
356            schema: PhantomData,
357            state: PhantomData,
358            table: PhantomData,
359        }
360    }
361}
362
363//------------------------------------------------------------------------------
364// Post-ON CONFLICT Implementation
365//------------------------------------------------------------------------------
366
367impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
368    /// Adds a RETURNING clause after ON CONFLICT
369    #[inline]
370    pub fn returning(
371        self,
372        columns: impl ToSQL<'a, SQLiteValue<'a>>,
373    ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
374        let returning_sql = crate::helpers::returning(columns);
375        InsertBuilder {
376            sql: self.sql.append(returning_sql),
377            schema: PhantomData,
378            state: PhantomData,
379            table: PhantomData,
380        }
381    }
382}