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 /// Inserts rows produced by a SELECT query without an explicit column list.
225 ///
226 /// The SELECT output must provide every table column in declaration order.
227 #[inline]
228 pub fn select<Q>(self, query: Q) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
229 where
230 Q: ToSQL<'a, SQLiteValue<'a>>,
231 {
232 InsertBuilder {
233 sql: append_sql(self.sql, query.to_sql()),
234 schema: PhantomData,
235 state: PhantomData,
236 table: PhantomData,
237 marker: PhantomData,
238 row: PhantomData,
239 grouped: PhantomData,
240 }
241 }
242}
243
244//------------------------------------------------------------------------------
245// Post-VALUES Implementation
246//------------------------------------------------------------------------------
247
248impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
249 /// Begins a typed ON CONFLICT clause targeting a specific constraint.
250 ///
251 /// The target must implement `ConflictTarget<T>`, which is auto-generated for
252 /// primary key columns, unique columns, and unique indexes.
253 ///
254 /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
255 ///
256 /// # Examples
257 ///
258 /// ```rust
259 /// # extern crate self as drizzle;
260 /// # mod _drizzle {
261 /// # pub mod core { pub use drizzle_core::*; }
262 /// # pub mod error { pub use drizzle_core::error::*; }
263 /// # pub mod types { pub use drizzle_types::*; }
264 /// # pub mod migrations { pub use drizzle_migrations::*; }
265 /// # pub use drizzle_types::Dialect;
266 /// # pub use drizzle_types as ddl;
267 /// # pub mod sqlite {
268 /// # pub use drizzle_sqlite::*;
269 /// # #[cfg(feature = "rusqlite")]
270 /// # pub mod rusqlite { pub use ::rusqlite::{Error, Result, Row, types}; }
271 /// # #[cfg(feature = "libsql")]
272 /// # pub mod libsql { pub use ::libsql::{Row, Value}; }
273 /// # #[cfg(feature = "turso")]
274 /// # pub mod turso { pub use ::turso::{Error, IntoValue, Result, Row, Value}; }
275 /// # pub mod prelude {
276 /// # pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
277 /// # pub use drizzle_sqlite::{*, attrs::*};
278 /// # pub use drizzle_core::*;
279 /// # }
280 /// # }
281 /// # }
282 /// # pub use _drizzle::*;
283 /// # pub use const_format;
284 /// fn main() {
285 /// use drizzle::sqlite::prelude::*;
286 /// use drizzle::sqlite::builder::QueryBuilder;
287 ///
288 /// #[SQLiteTable(name = "users")]
289 /// struct User {
290 /// #[column(primary)]
291 /// id: i32,
292 /// name: String,
293 /// #[column(unique)]
294 /// email: Option<String>,
295 /// }
296 ///
297 /// #[derive(SQLiteSchema)]
298 /// struct Schema {
299 /// user: User,
300 /// }
301 ///
302 /// let builder = QueryBuilder::new::<Schema>();
303 /// let schema = Schema::new();
304 /// let user = schema.user;
305 ///
306 /// // Target a specific column (requires PK or unique constraint)
307 /// builder.insert(user).values([InsertUser::new("Alice")])
308 /// .on_conflict(user.id).do_nothing();
309 ///
310 /// // Target with DO UPDATE
311 /// builder.insert(user).values([InsertUser::new("Alice")])
312 /// .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
313 /// }
314 /// ```
315 pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
316 let columns = target.conflict_columns();
317 let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
318 OnConflictBuilder {
319 sql: self.sql,
320 target_sql,
321 target_where: None,
322 schema: PhantomData,
323 table: PhantomData,
324 }
325 }
326
327 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
328 ///
329 /// This matches any constraint violation.
330 #[must_use]
331 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
332 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
333 InsertBuilder {
334 sql: append_sql(self.sql, conflict_sql),
335 schema: PhantomData,
336 state: PhantomData,
337 table: PhantomData,
338 marker: PhantomData,
339 row: PhantomData,
340 grouped: PhantomData,
341 }
342 }
343
344 /// Adds a RETURNING clause and transitions to `ReturningSet` state
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-ON CONFLICT Implementation
366//------------------------------------------------------------------------------
367
368impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
369 /// Adds a RETURNING clause after ON CONFLICT
370 #[inline]
371 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
372 where
373 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
374 Columns::Marker: drizzle_core::ResolveRow<T>,
375 {
376 let returning_sql = crate::helpers::returning(columns);
377 InsertBuilder {
378 sql: append_sql(self.sql, returning_sql),
379 schema: PhantomData,
380 state: PhantomData,
381 table: PhantomData,
382 marker: PhantomData,
383 row: PhantomData,
384 grouped: PhantomData,
385 }
386 }
387}
388
389//------------------------------------------------------------------------------
390// Post-DO UPDATE SET Implementation
391//------------------------------------------------------------------------------
392
393impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
394 /// Adds a WHERE clause to the DO UPDATE SET clause.
395 ///
396 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
397 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
398 where
399 E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
400 E::SQLType: drizzle_core::types::BooleanLike,
401 {
402 let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
403 InsertBuilder {
404 sql,
405 schema: PhantomData,
406 state: PhantomData,
407 table: PhantomData,
408 marker: PhantomData,
409 row: PhantomData,
410 grouped: PhantomData,
411 }
412 }
413
414 /// Adds a RETURNING clause after DO UPDATE SET
415 #[inline]
416 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
417 where
418 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
419 Columns::Marker: drizzle_core::ResolveRow<T>,
420 {
421 let returning_sql = crate::helpers::returning(columns);
422 InsertBuilder {
423 sql: append_sql(self.sql, returning_sql),
424 schema: PhantomData,
425 state: PhantomData,
426 table: PhantomData,
427 marker: PhantomData,
428 row: PhantomData,
429 grouped: PhantomData,
430 }
431 }
432}