drizzle_postgres/builder/insert.rs
1#[cfg(not(feature = "std"))]
2use crate::prelude::*;
3use crate::traits::PostgresTable;
4use crate::values::PostgresValue;
5use core::fmt::Debug;
6use core::marker::PhantomData;
7use drizzle_core::{ConflictTarget, NamedConstraint, SQL, ToSQL, Token};
8
9// Import the ExecutableState trait
10use super::ExecutableState;
11
12//------------------------------------------------------------------------------
13// Type State Markers
14//------------------------------------------------------------------------------
15
16/// Marker for the initial state of `InsertBuilder`.
17#[derive(Debug, Clone, Copy, Default)]
18pub struct InsertInitial;
19
20/// Marker for the state after VALUES are set.
21#[derive(Debug, Clone, Copy, Default)]
22pub struct InsertValuesSet;
23
24/// Marker for the state after RETURNING clause is added.
25#[derive(Debug, Clone, Copy, Default)]
26pub struct InsertReturningSet;
27
28/// Marker for the state after ON CONFLICT is set.
29#[derive(Debug, Clone, Copy, Default)]
30pub struct InsertOnConflictSet;
31
32/// Marker for the state after DO UPDATE SET (before optional WHERE).
33#[derive(Debug, Clone, Copy, Default)]
34pub struct InsertDoUpdateSet;
35
36// Mark states that can execute insert queries
37impl ExecutableState for InsertValuesSet {}
38impl ExecutableState for InsertReturningSet {}
39impl ExecutableState for InsertOnConflictSet {}
40impl ExecutableState for InsertDoUpdateSet {}
41
42//------------------------------------------------------------------------------
43// OnConflictBuilder
44//------------------------------------------------------------------------------
45
46/// Internal: how the conflict target was specified.
47#[derive(Debug, Clone)]
48enum ConflictTargetKind<'a> {
49 /// ON CONFLICT (col1, col2)
50 Columns(Box<SQL<'a, PostgresValue<'a>>>),
51 /// ON CONFLICT ON CONSTRAINT "`constraint_name`"
52 Constraint(&'static str),
53}
54
55/// Intermediate builder for typed ON CONFLICT clause construction (`PostgreSQL`).
56///
57/// Created by [`InsertBuilder::on_conflict()`] or
58/// [`InsertBuilder::on_conflict_on_constraint()`].
59/// Call [`do_nothing()`](Self::do_nothing) or [`do_update()`](Self::do_update)
60/// to complete the clause.
61#[derive(Debug, Clone)]
62pub struct OnConflictBuilder<'a, S, T> {
63 sql: SQL<'a, PostgresValue<'a>>,
64 target: ConflictTargetKind<'a>,
65 target_where: Option<SQL<'a, PostgresValue<'a>>>,
66 schema: PhantomData<S>,
67 table: PhantomData<T>,
68}
69
70impl<'a, S, T> OnConflictBuilder<'a, S, T> {
71 /// Adds a WHERE clause to the conflict target for partial index matching.
72 ///
73 /// Generates: `ON CONFLICT (col) WHERE condition DO ...`
74 ///
75 /// Note: WHERE is only meaningful for column-based targets, not for
76 /// `ON CONFLICT ON CONSTRAINT` targets.
77 #[must_use]
78 pub fn r#where<E>(mut self, condition: E) -> Self
79 where
80 E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
81 E::SQLType: drizzle_core::types::BooleanLike,
82 {
83 self.target_where = Some(condition.to_sql());
84 self
85 }
86
87 /// Splits into (base insert SQL, conflict target SQL prefix).
88 fn into_parts(self) -> (SQL<'a, PostgresValue<'a>>, SQL<'a, PostgresValue<'a>>) {
89 let target = match self.target {
90 ConflictTargetKind::Columns(cols) => {
91 let mut t = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
92 .append(*cols)
93 .push(Token::RPAREN);
94 if let Some(tw) = self.target_where {
95 t = t.push(Token::WHERE).append(tw);
96 }
97 t
98 }
99 ConflictTargetKind::Constraint(name) => {
100 SQL::from_iter([Token::ON, Token::CONFLICT, Token::ON, Token::CONSTRAINT])
101 .append(SQL::ident(name))
102 }
103 };
104 (self.sql, target)
105 }
106
107 /// Resolves the conflict by doing nothing (ignoring the conflicting row).
108 ///
109 /// Generates: `ON CONFLICT (col1, col2) DO NOTHING`
110 #[must_use]
111 pub fn do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
112 let (sql, target) = self.into_parts();
113 InsertBuilder {
114 sql: sql.append(target.push(Token::DO).push(Token::NOTHING)),
115 schema: PhantomData,
116 state: PhantomData,
117 table: PhantomData,
118 marker: PhantomData,
119 row: PhantomData,
120 grouped: PhantomData,
121 }
122 }
123
124 /// Resolves the conflict by updating the existing row.
125 ///
126 /// The `set` parameter accepts any `ToSQL` value, typically an `UpdateModel`
127 /// which generates the SET clause assignments. Use `EXCLUDED.column` to
128 /// reference the proposed insert values.
129 ///
130 /// Generates: `ON CONFLICT (col1, col2) DO UPDATE SET ...`
131 ///
132 /// Chain `.r#where(condition)` to add a conditional update filter.
133 pub fn do_update(
134 self,
135 set: impl ToSQL<'a, PostgresValue<'a>>,
136 ) -> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
137 let (sql, target) = self.into_parts();
138 let conflict = target
139 .push(Token::DO)
140 .push(Token::UPDATE)
141 .push(Token::SET)
142 .append(set.to_sql());
143 InsertBuilder {
144 sql: sql.append(conflict),
145 schema: PhantomData,
146 state: PhantomData,
147 table: PhantomData,
148 marker: PhantomData,
149 row: PhantomData,
150 grouped: PhantomData,
151 }
152 }
153}
154
155//------------------------------------------------------------------------------
156// InsertBuilder Definition
157//------------------------------------------------------------------------------
158
159/// Builds an INSERT query specifically for `PostgreSQL`.
160///
161/// Provides a type-safe, fluent API for constructing INSERT statements
162/// with support for typed conflict resolution, batch inserts, and returning clauses.
163///
164/// ## Type Parameters
165///
166/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
167/// - `State`: The current builder state, enforcing proper query construction order
168/// - `Table`: The table being inserted into
169pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
170 super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
171
172type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
173 <Columns as drizzle_core::IntoSelectTarget>::Marker,
174 drizzle_core::Cons<Table, drizzle_core::Nil>,
175>;
176
177type ReturningRow<Table, Columns> =
178 <<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
179
180type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
181 'a,
182 S,
183 InsertReturningSet,
184 T,
185 ReturningMarker<T, Columns>,
186 ReturningRow<T, Columns>,
187>;
188
189//------------------------------------------------------------------------------
190// Initial State Implementation
191//------------------------------------------------------------------------------
192
193impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
194where
195 Table: PostgresTable<'a>,
196{
197 /// Specifies a single row to insert. Shorthand for `.values([row])`.
198 #[inline]
199 pub fn value<T>(
200 self,
201 value: Table::Insert<T>,
202 ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table> {
203 self.values([value])
204 }
205
206 /// Specifies multiple rows to insert.
207 #[inline]
208 pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
209 where
210 I: IntoIterator<Item = Table::Insert<T>>,
211 {
212 let sql = crate::helpers::values::<'a, Table, T>(values);
213 InsertBuilder {
214 sql: self.sql.append(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, PostgresValue<'a>>,
231 {
232 InsertBuilder {
233 sql: self.sql.append(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 specific columns.
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 postgres {
268 /// # pub mod values { pub use drizzle_postgres::values::*; }
269 /// # pub mod traits { pub use drizzle_postgres::traits::*; }
270 /// # pub mod common { pub use drizzle_postgres::common::*; }
271 /// # pub mod attrs { pub use drizzle_postgres::attrs::*; }
272 /// # pub mod builder { pub use drizzle_postgres::builder::*; }
273 /// # pub mod helpers { pub use drizzle_postgres::helpers::*; }
274 /// # pub mod expr { pub use drizzle_postgres::expr::*; }
275 /// # pub mod types { pub use drizzle_postgres::types::*; }
276 /// # pub struct Row;
277 /// # impl Row {
278 /// # pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
279 /// # pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
280 /// # }
281 /// # pub mod prelude {
282 /// # pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
283 /// # pub use drizzle_postgres::attrs::*;
284 /// # pub use drizzle_postgres::common::PostgresSchemaType;
285 /// # pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
286 /// # pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
287 /// # pub use drizzle_core::*;
288 /// # }
289 /// # }
290 /// # }
291 /// # pub use _drizzle::*;
292 /// # pub use const_format;
293 /// fn main() {
294 /// use drizzle::postgres::prelude::*;
295 /// use drizzle::postgres::builder::QueryBuilder;
296 ///
297 /// #[PostgresTable(name = "users")]
298 /// struct User {
299 /// #[column(serial, primary)]
300 /// id: i32,
301 /// name: String,
302 /// #[column(unique)]
303 /// email: Option<String>,
304 /// }
305 ///
306 /// #[PostgresIndex(unique)]
307 /// struct UserEmailIdx(User::email);
308 ///
309 /// #[derive(PostgresSchema)]
310 /// struct Schema {
311 /// user: User,
312 /// user_email_idx: UserEmailIdx,
313 /// }
314 ///
315 /// let builder = QueryBuilder::new::<Schema>();
316 /// let schema = Schema::new();
317 /// let user = schema.user;
318 ///
319 /// // Target a specific column
320 /// builder.insert(user).values([InsertUser::new("Alice")])
321 /// .on_conflict(user.id).do_nothing();
322 ///
323 /// // Target with DO UPDATE using EXCLUDED
324 /// builder.insert(user).values([InsertUser::new("Alice")])
325 /// .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
326 ///
327 /// // Target a unique index
328 /// builder.insert(user).values([InsertUser::new("Alice")])
329 /// .on_conflict(schema.user_email_idx).do_nothing();
330 /// }
331 /// ```
332 pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
333 let columns = target.conflict_columns();
334 let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
335 OnConflictBuilder {
336 sql: self.sql,
337 target: ConflictTargetKind::Columns(Box::new(target_sql)),
338 target_where: None,
339 schema: PhantomData,
340 table: PhantomData,
341 }
342 }
343
344 /// Begins a typed ON CONFLICT ON CONSTRAINT clause (PostgreSQL-only).
345 ///
346 /// The target must implement `NamedConstraint<T>`, which is auto-generated
347 /// for unique indexes.
348 ///
349 /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
350 ///
351 /// # Examples
352 ///
353 /// ```rust
354 /// # extern crate self as drizzle;
355 /// # mod _drizzle {
356 /// # pub mod core { pub use drizzle_core::*; }
357 /// # pub mod error { pub use drizzle_core::error::*; }
358 /// # pub mod types { pub use drizzle_types::*; }
359 /// # pub mod migrations { pub use drizzle_migrations::*; }
360 /// # pub use drizzle_types::Dialect;
361 /// # pub use drizzle_types as ddl;
362 /// # pub mod postgres {
363 /// # pub mod values { pub use drizzle_postgres::values::*; }
364 /// # pub mod traits { pub use drizzle_postgres::traits::*; }
365 /// # pub mod common { pub use drizzle_postgres::common::*; }
366 /// # pub mod attrs { pub use drizzle_postgres::attrs::*; }
367 /// # pub mod builder { pub use drizzle_postgres::builder::*; }
368 /// # pub mod helpers { pub use drizzle_postgres::helpers::*; }
369 /// # pub mod expr { pub use drizzle_postgres::expr::*; }
370 /// # pub mod types { pub use drizzle_postgres::types::*; }
371 /// # pub struct Row;
372 /// # impl Row {
373 /// # pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
374 /// # pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
375 /// # }
376 /// # pub mod prelude {
377 /// # pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
378 /// # pub use drizzle_postgres::attrs::*;
379 /// # pub use drizzle_postgres::common::PostgresSchemaType;
380 /// # pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
381 /// # pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
382 /// # pub use drizzle_core::*;
383 /// # }
384 /// # }
385 /// # }
386 /// # pub use _drizzle::*;
387 /// # pub use const_format;
388 /// fn main() {
389 /// use drizzle::postgres::prelude::*;
390 /// use drizzle::postgres::builder::QueryBuilder;
391 ///
392 /// #[PostgresTable(name = "users")]
393 /// struct User {
394 /// #[column(serial, primary)]
395 /// id: i32,
396 /// name: String,
397 /// #[column(unique)]
398 /// email: Option<String>,
399 /// }
400 ///
401 /// #[PostgresIndex(unique)]
402 /// struct UserEmailIdx(User::email);
403 ///
404 /// #[derive(PostgresSchema)]
405 /// struct Schema {
406 /// user: User,
407 /// user_email_idx: UserEmailIdx,
408 /// }
409 ///
410 /// let builder = QueryBuilder::new::<Schema>();
411 /// let schema = Schema::new();
412 ///
413 /// builder.insert(schema.user).values([InsertUser::new("Alice")])
414 /// .on_conflict_on_constraint(schema.user_email_idx).do_nothing();
415 /// }
416 /// ```
417 pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
418 self,
419 target: C,
420 ) -> OnConflictBuilder<'a, S, T> {
421 OnConflictBuilder {
422 sql: self.sql,
423 target: ConflictTargetKind::Constraint(target.constraint_name()),
424 target_where: None,
425 schema: PhantomData,
426 table: PhantomData,
427 }
428 }
429
430 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
431 ///
432 /// This matches any constraint violation.
433 #[must_use]
434 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
435 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
436 InsertBuilder {
437 sql: self.sql.append(conflict_sql),
438 schema: PhantomData,
439 state: PhantomData,
440 table: PhantomData,
441 marker: PhantomData,
442 row: PhantomData,
443 grouped: PhantomData,
444 }
445 }
446
447 /// Adds a RETURNING clause and transitions to `ReturningSet` state
448 #[inline]
449 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
450 where
451 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
452 Columns::Marker: drizzle_core::ResolveRow<T>,
453 {
454 let returning_sql = crate::helpers::returning(columns);
455 InsertBuilder {
456 sql: self.sql.append(returning_sql),
457 schema: PhantomData,
458 state: PhantomData,
459 table: PhantomData,
460 marker: PhantomData,
461 row: PhantomData,
462 grouped: PhantomData,
463 }
464 }
465}
466
467//------------------------------------------------------------------------------
468// Post-ON CONFLICT Implementation
469//------------------------------------------------------------------------------
470
471impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
472 /// Adds a RETURNING clause after ON CONFLICT
473 #[inline]
474 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
475 where
476 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
477 Columns::Marker: drizzle_core::ResolveRow<T>,
478 {
479 let returning_sql = crate::helpers::returning(columns);
480 InsertBuilder {
481 sql: self.sql.append(returning_sql),
482 schema: PhantomData,
483 state: PhantomData,
484 table: PhantomData,
485 marker: PhantomData,
486 row: PhantomData,
487 grouped: PhantomData,
488 }
489 }
490}
491
492//------------------------------------------------------------------------------
493// Post-DO UPDATE SET Implementation
494//------------------------------------------------------------------------------
495
496impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
497 /// Adds a WHERE clause to the DO UPDATE SET clause.
498 ///
499 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
500 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
501 where
502 E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
503 E::SQLType: drizzle_core::types::BooleanLike,
504 {
505 let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
506 InsertBuilder {
507 sql,
508 schema: PhantomData,
509 state: PhantomData,
510 table: PhantomData,
511 marker: PhantomData,
512 row: PhantomData,
513 grouped: PhantomData,
514 }
515 }
516
517 /// Adds a RETURNING clause after DO UPDATE SET
518 #[inline]
519 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
520 where
521 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
522 Columns::Marker: drizzle_core::ResolveRow<T>,
523 {
524 let returning_sql = crate::helpers::returning(columns);
525 InsertBuilder {
526 sql: self.sql.append(returning_sql),
527 schema: PhantomData,
528 state: PhantomData,
529 table: PhantomData,
530 marker: PhantomData,
531 row: PhantomData,
532 grouped: PhantomData,
533 }
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540 use drizzle_core::{SQL, ToSQL};
541
542 #[test]
543 fn test_insert_builder_creation() {
544 let builder = InsertBuilder::<(), InsertInitial, ()> {
545 sql: SQL::raw("INSERT INTO test"),
546 schema: PhantomData,
547 state: PhantomData,
548 table: PhantomData,
549 marker: PhantomData,
550 row: PhantomData,
551 grouped: PhantomData,
552 };
553
554 assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
555 }
556}