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