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}