drizzle_sqlite/builder/insert.rs
1use crate::traits::SQLiteTable;
2use crate::values::SQLiteValue;
3use core::fmt::Debug;
4use core::marker::PhantomData;
5use drizzle_core::{ConflictTarget, SQL, SQLModel, ToSQL, Token};
6
7// Import the ExecutableState trait
8use super::ExecutableState;
9
10#[inline]
11fn append_sql<'a>(
12 mut base: SQL<'a, SQLiteValue<'a>>,
13 fragment: SQL<'a, SQLiteValue<'a>>,
14) -> SQL<'a, SQLiteValue<'a>> {
15 base.append_mut(fragment);
16 base
17}
18
19//------------------------------------------------------------------------------
20// Type State Markers
21//------------------------------------------------------------------------------
22
23/// Marker for the initial state of `InsertBuilder`.
24#[derive(Debug, Clone, Copy, Default)]
25pub struct InsertInitial;
26
27/// Marker for the state after VALUES are set.
28#[derive(Debug, Clone, Copy, Default)]
29pub struct InsertValuesSet;
30
31/// Marker for the state after RETURNING clause is added.
32#[derive(Debug, Clone, Copy, Default)]
33pub struct InsertReturningSet;
34
35/// Marker for the state after ON CONFLICT is set.
36#[derive(Debug, Clone, Copy, Default)]
37pub struct InsertOnConflictSet;
38
39/// Marker for the state after DO UPDATE SET (before optional WHERE).
40#[derive(Debug, Clone, Copy, Default)]
41pub struct InsertDoUpdateSet;
42
43// Mark states that can execute insert queries
44impl ExecutableState for InsertValuesSet {}
45impl ExecutableState for InsertReturningSet {}
46impl ExecutableState for InsertOnConflictSet {}
47impl ExecutableState for InsertDoUpdateSet {}
48
49//------------------------------------------------------------------------------
50// OnConflictBuilder
51//------------------------------------------------------------------------------
52
53/// Intermediate builder for typed ON CONFLICT clause construction.
54///
55/// Created by [`InsertBuilder::on_conflict()`]. Call [`do_nothing()`](Self::do_nothing)
56/// or [`do_update()`](Self::do_update) to complete the clause.
57#[derive(Debug, Clone)]
58pub struct OnConflictBuilder<'a, S, T> {
59 sql: SQL<'a, SQLiteValue<'a>>,
60 target_sql: SQL<'a, SQLiteValue<'a>>,
61 target_where: Option<SQL<'a, SQLiteValue<'a>>>,
62 schema: PhantomData<S>,
63 table: PhantomData<T>,
64}
65
66impl<'a, S, T> OnConflictBuilder<'a, S, T> {
67 /// Adds a WHERE clause to the conflict target for partial index matching.
68 ///
69 /// Generates: `ON CONFLICT (col) WHERE condition DO ...`
70 #[must_use]
71 pub fn r#where<E>(mut self, condition: E) -> Self
72 where
73 E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
74 E::SQLType: drizzle_core::types::BooleanLike,
75 {
76 self.target_where = Some(condition.to_sql());
77 self
78 }
79
80 /// Splits into (base insert SQL, conflict target SQL prefix).
81 fn into_parts(self) -> (SQL<'a, SQLiteValue<'a>>, SQL<'a, SQLiteValue<'a>>) {
82 let mut target = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
83 .append(self.target_sql)
84 .push(Token::RPAREN);
85 if let Some(tw) = self.target_where {
86 target = target.push(Token::WHERE).append(tw);
87 }
88 (self.sql, target)
89 }
90
91 /// Resolves the conflict by doing nothing (ignoring the conflicting row).
92 ///
93 /// Generates: `ON CONFLICT (col1, col2) DO NOTHING`
94 #[must_use]
95 pub fn do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
96 let (sql, target) = self.into_parts();
97 InsertBuilder {
98 sql: append_sql(sql, target.push(Token::DO).push(Token::NOTHING)),
99 schema: PhantomData,
100 state: PhantomData,
101 table: PhantomData,
102 marker: PhantomData,
103 row: PhantomData,
104 grouped: PhantomData,
105 }
106 }
107
108 /// Resolves the conflict by updating the existing row.
109 ///
110 /// The `set` parameter accepts any `ToSQL` value, typically an `UpdateModel`
111 /// which generates the SET clause assignments.
112 ///
113 /// Generates: `ON CONFLICT (col1, col2) DO UPDATE SET ...`
114 ///
115 /// Chain `.r#where(condition)` to add a conditional update filter.
116 pub fn do_update(
117 self,
118 set: impl ToSQL<'a, SQLiteValue<'a>>,
119 ) -> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
120 let (sql, target) = self.into_parts();
121 let conflict = target
122 .push(Token::DO)
123 .push(Token::UPDATE)
124 .push(Token::SET)
125 .append(set.to_sql());
126 InsertBuilder {
127 sql: append_sql(sql, conflict),
128 schema: PhantomData,
129 state: PhantomData,
130 table: PhantomData,
131 marker: PhantomData,
132 row: PhantomData,
133 grouped: PhantomData,
134 }
135 }
136}
137
138//------------------------------------------------------------------------------
139// InsertBuilder Definition
140//------------------------------------------------------------------------------
141
142/// Builds an INSERT query specifically for `SQLite`.
143///
144/// Provides a type-safe, fluent API for constructing INSERT statements
145/// with support for typed conflict resolution, batch inserts, and returning clauses.
146///
147/// ## Type Parameters
148///
149/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
150/// - `State`: The current builder state, enforcing proper query construction order
151/// - `Table`: The table being inserted into
152///
153/// ## Query Building Flow
154///
155/// 1. Start with `QueryBuilder::insert(table)` to specify the target table
156/// 2. Add `values()` to specify what data to insert
157/// 3. Optionally add conflict resolution with `on_conflict(target).do_nothing()` or `.do_update(set)`
158/// 4. Optionally add a `returning()` clause
159pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
160 super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
161
162type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
163 <Columns as drizzle_core::IntoSelectTarget>::Marker,
164 drizzle_core::Cons<Table, drizzle_core::Nil>,
165>;
166
167type ReturningRow<Table, Columns> =
168 <<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
169
170type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
171 'a,
172 S,
173 InsertReturningSet,
174 T,
175 ReturningMarker<T, Columns>,
176 ReturningRow<T, Columns>,
177>;
178
179//------------------------------------------------------------------------------
180// Initial State Implementation
181//------------------------------------------------------------------------------
182
183impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
184where
185 Table: SQLiteTable<'a>,
186{
187 /// Specifies a single row to insert into the table.
188 ///
189 /// Accepts an insert value object generated by the `SQLiteTable` macro
190 /// (e.g., `InsertUser`).
191 #[inline]
192 pub fn value<T>(
193 self,
194 value: Table::Insert<T>,
195 ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
196 where
197 Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
198 {
199 self.values([value])
200 }
201
202 /// Specifies the values to insert into the table.
203 ///
204 /// Accepts an iterable of insert value objects generated by the
205 /// `SQLiteTable` macro (e.g., `InsertUser`).
206 #[inline]
207 pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
208 where
209 I: IntoIterator<Item = Table::Insert<T>>,
210 Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
211 {
212 let sql = crate::helpers::values::<'a, Table, T>(values);
213 InsertBuilder {
214 sql: append_sql(self.sql, sql),
215 schema: PhantomData,
216 state: PhantomData,
217 table: PhantomData,
218 marker: PhantomData,
219 row: PhantomData,
220 grouped: PhantomData,
221 }
222 }
223}
224
225//------------------------------------------------------------------------------
226// Post-VALUES Implementation
227//------------------------------------------------------------------------------
228
229impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
230 /// Begins a typed ON CONFLICT clause targeting a specific constraint.
231 ///
232 /// The target must implement `ConflictTarget<T>`, which is auto-generated for
233 /// primary key columns, unique columns, and unique indexes.
234 ///
235 /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
236 ///
237 /// # Examples
238 ///
239 /// ```rust
240 /// # extern crate self as drizzle;
241 /// # mod _drizzle {
242 /// # pub mod core { pub use drizzle_core::*; }
243 /// # pub mod error { pub use drizzle_core::error::*; }
244 /// # pub mod types { pub use drizzle_types::*; }
245 /// # pub mod migrations { pub use drizzle_migrations::*; }
246 /// # pub use drizzle_types::Dialect;
247 /// # pub use drizzle_types as ddl;
248 /// # pub mod sqlite {
249 /// # pub use drizzle_sqlite::*;
250 /// # pub mod prelude {
251 /// # pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
252 /// # pub use drizzle_sqlite::{*, attrs::*};
253 /// # pub use drizzle_core::*;
254 /// # }
255 /// # }
256 /// # }
257 /// # pub use _drizzle::*;
258 /// # pub use const_format;
259 /// fn main() {
260 /// use drizzle::sqlite::prelude::*;
261 /// use drizzle::sqlite::builder::QueryBuilder;
262 ///
263 /// #[SQLiteTable(name = "users")]
264 /// struct User {
265 /// #[column(primary)]
266 /// id: i32,
267 /// name: String,
268 /// #[column(unique)]
269 /// email: Option<String>,
270 /// }
271 ///
272 /// #[derive(SQLiteSchema)]
273 /// struct Schema {
274 /// user: User,
275 /// }
276 ///
277 /// let builder = QueryBuilder::new::<Schema>();
278 /// let schema = Schema::new();
279 /// let user = schema.user;
280 ///
281 /// // Target a specific column (requires PK or unique constraint)
282 /// builder.insert(user).values([InsertUser::new("Alice")])
283 /// .on_conflict(user.id).do_nothing();
284 ///
285 /// // Target with DO UPDATE
286 /// builder.insert(user).values([InsertUser::new("Alice")])
287 /// .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
288 /// }
289 /// ```
290 pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
291 let columns = target.conflict_columns();
292 let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
293 OnConflictBuilder {
294 sql: self.sql,
295 target_sql,
296 target_where: None,
297 schema: PhantomData,
298 table: PhantomData,
299 }
300 }
301
302 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
303 ///
304 /// This matches any constraint violation.
305 #[must_use]
306 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
307 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
308 InsertBuilder {
309 sql: append_sql(self.sql, conflict_sql),
310 schema: PhantomData,
311 state: PhantomData,
312 table: PhantomData,
313 marker: PhantomData,
314 row: PhantomData,
315 grouped: PhantomData,
316 }
317 }
318
319 /// Adds a RETURNING clause and transitions to `ReturningSet` state
320 #[inline]
321 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
322 where
323 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
324 Columns::Marker: drizzle_core::ResolveRow<T>,
325 {
326 let returning_sql = crate::helpers::returning(columns);
327 InsertBuilder {
328 sql: append_sql(self.sql, returning_sql),
329 schema: PhantomData,
330 state: PhantomData,
331 table: PhantomData,
332 marker: PhantomData,
333 row: PhantomData,
334 grouped: PhantomData,
335 }
336 }
337}
338
339//------------------------------------------------------------------------------
340// Post-ON CONFLICT Implementation
341//------------------------------------------------------------------------------
342
343impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
344 /// Adds a RETURNING clause after ON CONFLICT
345 #[inline]
346 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
347 where
348 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
349 Columns::Marker: drizzle_core::ResolveRow<T>,
350 {
351 let returning_sql = crate::helpers::returning(columns);
352 InsertBuilder {
353 sql: append_sql(self.sql, returning_sql),
354 schema: PhantomData,
355 state: PhantomData,
356 table: PhantomData,
357 marker: PhantomData,
358 row: PhantomData,
359 grouped: PhantomData,
360 }
361 }
362}
363
364//------------------------------------------------------------------------------
365// Post-DO UPDATE SET Implementation
366//------------------------------------------------------------------------------
367
368impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
369 /// Adds a WHERE clause to the DO UPDATE SET clause.
370 ///
371 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
372 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
373 where
374 E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
375 E::SQLType: drizzle_core::types::BooleanLike,
376 {
377 let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
378 InsertBuilder {
379 sql,
380 schema: PhantomData,
381 state: PhantomData,
382 table: PhantomData,
383 marker: PhantomData,
384 row: PhantomData,
385 grouped: PhantomData,
386 }
387 }
388
389 /// Adds a RETURNING clause after DO UPDATE SET
390 #[inline]
391 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
392 where
393 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
394 Columns::Marker: drizzle_core::ResolveRow<T>,
395 {
396 let returning_sql = crate::helpers::returning(columns);
397 InsertBuilder {
398 sql: append_sql(self.sql, returning_sql),
399 schema: PhantomData,
400 state: PhantomData,
401 table: PhantomData,
402 marker: PhantomData,
403 row: PhantomData,
404 grouped: PhantomData,
405 }
406 }
407}