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_where = target.conflict_where_clause().map(SQL::raw);
247 let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
248 OnConflictBuilder::new(self.sql, ConflictColumnsTarget::new(target_sql))
249 .with_target_where_sql(target_where)
250 }
251
252 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
253 ///
254 /// This matches any constraint violation.
255 #[must_use]
256 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
257 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
258 InsertBuilder {
259 sql: self.sql.append(conflict_sql),
260 schema: PhantomData,
261 state: PhantomData,
262 table: PhantomData,
263 marker: PhantomData,
264 row: PhantomData,
265 grouped: PhantomData,
266 }
267 }
268
269 /// Adds a RETURNING clause and transitions to `ReturningSet` state
270 #[inline]
271 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
272 where
273 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
274 Columns::Marker: drizzle_core::ResolveRow<T>,
275 {
276 let returning_sql = crate::helpers::returning(columns);
277 InsertBuilder {
278 sql: self.sql.append(returning_sql),
279 schema: PhantomData,
280 state: PhantomData,
281 table: PhantomData,
282 marker: PhantomData,
283 row: PhantomData,
284 grouped: PhantomData,
285 }
286 }
287}
288
289//------------------------------------------------------------------------------
290// Post-ON CONFLICT Implementation
291//------------------------------------------------------------------------------
292
293impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
294 /// Adds a RETURNING clause after ON CONFLICT
295 #[inline]
296 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
297 where
298 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
299 Columns::Marker: drizzle_core::ResolveRow<T>,
300 {
301 let returning_sql = crate::helpers::returning(columns);
302 InsertBuilder {
303 sql: self.sql.append(returning_sql),
304 schema: PhantomData,
305 state: PhantomData,
306 table: PhantomData,
307 marker: PhantomData,
308 row: PhantomData,
309 grouped: PhantomData,
310 }
311 }
312}
313
314//------------------------------------------------------------------------------
315// Post-DO UPDATE SET Implementation
316//------------------------------------------------------------------------------
317
318impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
319 /// Adds a WHERE clause to the DO UPDATE SET clause.
320 ///
321 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
322 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
323 where
324 E: drizzle_core::expr::Expr<'a, SQLiteValue<'a>>,
325 E::SQLType: drizzle_core::types::BooleanLike,
326 {
327 let sql = self
328 .sql
329 .push(Token::WHERE)
330 .append(condition.into_expr_sql());
331 InsertBuilder {
332 sql,
333 schema: PhantomData,
334 state: PhantomData,
335 table: PhantomData,
336 marker: PhantomData,
337 row: PhantomData,
338 grouped: PhantomData,
339 }
340 }
341
342 /// Adds a RETURNING clause after DO UPDATE SET
343 #[inline]
344 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
345 where
346 Columns: ToSQL<'a, SQLiteValue<'a>> + drizzle_core::IntoSelectTarget,
347 Columns::Marker: drizzle_core::ResolveRow<T>,
348 {
349 let returning_sql = crate::helpers::returning(columns);
350 InsertBuilder {
351 sql: self.sql.append(returning_sql),
352 schema: PhantomData,
353 state: PhantomData,
354 table: PhantomData,
355 marker: PhantomData,
356 row: PhantomData,
357 grouped: PhantomData,
358 }
359 }
360}