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}