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 /// # #[cfg(feature = "rusqlite")]
251 /// # pub mod rusqlite { pub use ::rusqlite::{Error, Result, Row, types}; }
252 /// # #[cfg(feature = "libsql")]
253 /// # pub mod libsql { pub use ::libsql::{Row, Value}; }
254 /// # #[cfg(feature = "turso")]
255 /// # pub mod turso { pub use ::turso::{Error, IntoValue, Result, Row, Value}; }
256 /// # pub mod prelude {
257 /// # pub use drizzle_macros::{SQLiteTable, SQLiteSchema};
258 /// # pub use drizzle_sqlite::{*, attrs::*};
259 /// # pub use drizzle_core::*;
260 /// # }
261 /// # }
262 /// # }
263 /// # pub use _drizzle::*;
264 /// # pub use const_format;
265 /// fn main() {
266 /// use drizzle::sqlite::prelude::*;
267 /// use drizzle::sqlite::builder::QueryBuilder;
268 ///
269 /// #[SQLiteTable(name = "users")]
270 /// struct User {
271 /// #[column(primary)]
272 /// id: i32,
273 /// name: String,
274 /// #[column(unique)]
275 /// email: Option<String>,
276 /// }
277 ///
278 /// #[derive(SQLiteSchema)]
279 /// struct Schema {
280 /// user: User,
281 /// }
282 ///
283 /// let builder = QueryBuilder::new::<Schema>();
284 /// let schema = Schema::new();
285 /// let user = schema.user;
286 ///
287 /// // Target a specific column (requires PK or unique constraint)
288 /// builder.insert(user).values([InsertUser::new("Alice")])
289 /// .on_conflict(user.id).do_nothing();
290 ///
291 /// // Target with DO UPDATE
292 /// builder.insert(user).values([InsertUser::new("Alice")])
293 /// .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
294 /// }
295 /// ```
296 pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
297 let columns = target.conflict_columns();
298 let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
299 OnConflictBuilder {
300 sql: self.sql,
301 target_sql,
302 target_where: None,
303 schema: PhantomData,
304 table: PhantomData,
305 }
306 }
307
308 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
309 ///
310 /// This matches any constraint violation.
311 #[must_use]
312 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
313 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
314 InsertBuilder {
315 sql: append_sql(self.sql, conflict_sql),
316 schema: PhantomData,
317 state: PhantomData,
318 table: PhantomData,
319 marker: PhantomData,
320 row: PhantomData,
321 grouped: PhantomData,
322 }
323 }
324
325 /// Adds a RETURNING clause and transitions to `ReturningSet` state
326 #[inline]
327 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
328 where
329 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
330 Columns::Marker: drizzle_core::ResolveRow<T>,
331 {
332 let returning_sql = crate::helpers::returning(columns);
333 InsertBuilder {
334 sql: append_sql(self.sql, returning_sql),
335 schema: PhantomData,
336 state: PhantomData,
337 table: PhantomData,
338 marker: PhantomData,
339 row: PhantomData,
340 grouped: PhantomData,
341 }
342 }
343}
344
345//------------------------------------------------------------------------------
346// Post-ON CONFLICT Implementation
347//------------------------------------------------------------------------------
348
349impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
350 /// Adds a RETURNING clause after ON CONFLICT
351 #[inline]
352 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
353 where
354 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
355 Columns::Marker: drizzle_core::ResolveRow<T>,
356 {
357 let returning_sql = crate::helpers::returning(columns);
358 InsertBuilder {
359 sql: append_sql(self.sql, returning_sql),
360 schema: PhantomData,
361 state: PhantomData,
362 table: PhantomData,
363 marker: PhantomData,
364 row: PhantomData,
365 grouped: PhantomData,
366 }
367 }
368}
369
370//------------------------------------------------------------------------------
371// Post-DO UPDATE SET Implementation
372//------------------------------------------------------------------------------
373
374impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
375 /// Adds a WHERE clause to the DO UPDATE SET clause.
376 ///
377 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
378 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
379 where
380 E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
381 E::SQLType: drizzle_core::types::BooleanLike,
382 {
383 let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
384 InsertBuilder {
385 sql,
386 schema: PhantomData,
387 state: PhantomData,
388 table: PhantomData,
389 marker: PhantomData,
390 row: PhantomData,
391 grouped: PhantomData,
392 }
393 }
394
395 /// Adds a RETURNING clause after DO UPDATE SET
396 #[inline]
397 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
398 where
399 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
400 Columns::Marker: drizzle_core::ResolveRow<T>,
401 {
402 let returning_sql = crate::helpers::returning(columns);
403 InsertBuilder {
404 sql: append_sql(self.sql, returning_sql),
405 schema: PhantomData,
406 state: PhantomData,
407 table: PhantomData,
408 marker: PhantomData,
409 row: PhantomData,
410 grouped: PhantomData,
411 }
412 }
413}