drizzle_postgres/builder/insert.rs
1use crate::traits::PostgresTable;
2use crate::values::PostgresValue;
3use core::marker::PhantomData;
4use drizzle_core::builder::{
5 OnConflictBuilder as CoreOnConflictBuilder, OnConflictOutput, PostgresConflictTarget,
6};
7use drizzle_core::{
8 ConflictTarget, InsertSelectCompatible, InsertSelectTable, InsertTargetColumns,
9 NamedConstraint, PartialInsertSelectCompatible, SQL, ToSQL, Token,
10};
11
12use super::select::{CompletedSelect, IntoSelectQuery};
13
14//------------------------------------------------------------------------------
15// Type State Markers
16//------------------------------------------------------------------------------
17
18pub use drizzle_core::builder::{
19 InsertColumnsSet, InsertDoUpdateSet, InsertInitial, InsertOnConflictSet, InsertReturningSet,
20 InsertValuesSet,
21};
22
23//------------------------------------------------------------------------------
24// OnConflictBuilder
25//------------------------------------------------------------------------------
26
27/// Intermediate builder for typed ON CONFLICT clause construction (`PostgreSQL`).
28///
29/// Created by [`InsertBuilder::on_conflict()`] or
30/// [`InsertBuilder::on_conflict_on_constraint()`].
31/// Call [`do_nothing()`](Self::do_nothing) or [`do_update()`](Self::do_update)
32/// to complete the clause.
33pub type OnConflictBuilder<'a, S, T> = CoreOnConflictBuilder<
34 'a,
35 PostgresValue<'a>,
36 S,
37 T,
38 PostgresConflictTarget<'a, PostgresValue<'a>>,
39 PostgresOnConflictOutput,
40>;
41
42#[doc(hidden)]
43#[derive(Debug, Clone, Copy, Default)]
44pub struct PostgresOnConflictOutput;
45
46impl<'a, S, T> OnConflictOutput<'a, PostgresValue<'a>, S, T> for PostgresOnConflictOutput {
47 type OnConflictSet = InsertBuilder<'a, S, InsertOnConflictSet, T>;
48 type DoUpdateSet = InsertBuilder<'a, S, InsertDoUpdateSet, T>;
49
50 fn on_conflict(sql: SQL<'a, PostgresValue<'a>>) -> Self::OnConflictSet {
51 InsertBuilder {
52 sql,
53 schema: PhantomData,
54 state: PhantomData,
55 table: PhantomData,
56 marker: PhantomData,
57 row: PhantomData,
58 grouped: PhantomData,
59 }
60 }
61
62 fn do_update(sql: SQL<'a, PostgresValue<'a>>) -> Self::DoUpdateSet {
63 InsertBuilder {
64 sql,
65 schema: PhantomData,
66 state: PhantomData,
67 table: PhantomData,
68 marker: PhantomData,
69 row: PhantomData,
70 grouped: PhantomData,
71 }
72 }
73}
74
75//------------------------------------------------------------------------------
76// InsertBuilder Definition
77//------------------------------------------------------------------------------
78
79/// Builds an INSERT query specifically for `PostgreSQL`.
80///
81/// Provides a type-safe, fluent API for constructing INSERT statements
82/// with support for typed conflict resolution, batch inserts, and returning clauses.
83///
84/// ## Type Parameters
85///
86/// - `Schema`: The database schema type, ensuring only valid tables can be referenced
87/// - `State`: The current builder state, enforcing proper query construction order
88/// - `Table`: The table being inserted into
89pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
90 super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
91
92type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
93 <Columns as drizzle_core::IntoSelectTarget>::Marker,
94 drizzle_core::Cons<Table, drizzle_core::Nil>,
95>;
96
97type ReturningRow<Table, Columns> =
98 <<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
99
100type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
101 'a,
102 S,
103 InsertReturningSet,
104 T,
105 ReturningMarker<T, Columns>,
106 ReturningRow<T, Columns>,
107>;
108
109//------------------------------------------------------------------------------
110// Initial State Implementation
111//------------------------------------------------------------------------------
112
113impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
114where
115 Table: PostgresTable<'a>,
116{
117 /// Specifies a single row to insert. Shorthand for `.values([row])`.
118 #[inline]
119 pub fn value<T>(
120 self,
121 value: Table::Insert<T>,
122 ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table> {
123 self.values([value])
124 }
125
126 /// Specifies multiple rows to insert.
127 #[inline]
128 pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
129 where
130 I: IntoIterator<Item = Table::Insert<T>>,
131 {
132 let sql = crate::helpers::values::<'a, Table, T>(values);
133 InsertBuilder {
134 sql: self.sql.append(sql),
135 schema: PhantomData,
136 state: PhantomData,
137 table: PhantomData,
138 marker: PhantomData,
139 row: PhantomData,
140 grouped: PhantomData,
141 }
142 }
143
144 /// Chooses an explicit ordered target-column list for an INSERT SELECT.
145 #[inline]
146 pub fn columns<Columns>(
147 self,
148 columns: Columns,
149 ) -> InsertBuilder<'a, Schema, InsertColumnsSet<Columns::Columns>, Table>
150 where
151 Columns: InsertTargetColumns<'a, PostgresValue<'a>, Table>,
152 {
153 InsertBuilder {
154 sql: self.sql.append(columns.into_target_columns_sql()),
155 schema: PhantomData,
156 state: PhantomData,
157 table: PhantomData,
158 marker: PhantomData,
159 row: PhantomData,
160 grouped: PhantomData,
161 }
162 }
163
164 /// Inserts a checked SELECT into every insertable table column.
165 #[inline]
166 pub fn select<Q, R, ScopeProof, AggProof>(
167 self,
168 query: Q,
169 ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
170 where
171 Table: InsertSelectTable,
172 Q: IntoSelectQuery<'a, Schema, R>,
173 Q::Marker: InsertSelectCompatible<'a, PostgresValue<'a>, Table, R>
174 + drizzle_core::InsertSourceInScope<ScopeProof>
175 + drizzle_core::MarkerAggValidFor<Q::Grouped, AggProof>,
176 {
177 let select = query.into_select_query().into_select_sql();
178 InsertBuilder {
179 sql: self
180 .sql
181 .append(Table::insert_columns_sql::<PostgresValue<'a>>())
182 .append(select),
183 schema: PhantomData,
184 state: PhantomData,
185 table: PhantomData,
186 marker: PhantomData,
187 row: PhantomData,
188 grouped: PhantomData,
189 }
190 }
191
192 /// Inserts an unchecked raw SELECT without a target list.
193 ///
194 /// This opts out of projection shape, type, nullability, source-scope, and
195 /// aggregate validation.
196 #[inline]
197 pub fn select_raw<Q>(self, query: Q) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
198 where
199 Q: ToSQL<'a, PostgresValue<'a>>,
200 {
201 InsertBuilder {
202 sql: self.sql.append(query.into_sql()),
203 schema: PhantomData,
204 state: PhantomData,
205 table: PhantomData,
206 marker: PhantomData,
207 row: PhantomData,
208 grouped: PhantomData,
209 }
210 }
211}
212
213impl<'a, Schema, Table, Targets> InsertBuilder<'a, Schema, InsertColumnsSet<Targets>, Table>
214where
215 Table: PostgresTable<'a> + InsertSelectTable,
216{
217 /// Inserts a checked SELECT into the chosen target columns.
218 #[inline]
219 pub fn select<Q, R, RequiredProof, ScopeProof, AggProof>(
220 self,
221 query: Q,
222 ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
223 where
224 Targets: drizzle_core::IncludesRequired<Table::RequiredColumns, RequiredProof>,
225 Q: IntoSelectQuery<'a, Schema, R>,
226 Q::Marker: PartialInsertSelectCompatible<'a, PostgresValue<'a>, Targets>
227 + drizzle_core::InsertSourceInScope<ScopeProof>
228 + drizzle_core::MarkerAggValidFor<Q::Grouped, AggProof>,
229 {
230 let select = query.into_select_query().into_select_sql();
231 InsertBuilder {
232 sql: self.sql.append(select),
233 schema: PhantomData,
234 state: PhantomData,
235 table: PhantomData,
236 marker: PhantomData,
237 row: PhantomData,
238 grouped: PhantomData,
239 }
240 }
241
242 /// Inserts an unchecked raw SELECT into the chosen target columns.
243 ///
244 /// This opts out of projection shape, type, nullability, source-scope, and
245 /// aggregate validation.
246 #[inline]
247 pub fn select_raw<Q, RequiredProof>(
248 self,
249 query: Q,
250 ) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
251 where
252 Targets: drizzle_core::IncludesRequired<Table::RequiredColumns, RequiredProof>,
253 Q: ToSQL<'a, PostgresValue<'a>>,
254 {
255 InsertBuilder {
256 sql: self.sql.append(query.into_sql()),
257 schema: PhantomData,
258 state: PhantomData,
259 table: PhantomData,
260 marker: PhantomData,
261 row: PhantomData,
262 grouped: PhantomData,
263 }
264 }
265}
266
267//------------------------------------------------------------------------------
268// Post-VALUES Implementation
269//------------------------------------------------------------------------------
270
271impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
272 /// Begins a typed ON CONFLICT clause targeting specific columns.
273 ///
274 /// The target must implement `ConflictTarget<T>`, which is auto-generated for
275 /// primary key columns, unique columns, and unique indexes.
276 ///
277 /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
278 ///
279 /// # Examples
280 ///
281 /// ```rust
282 /// # extern crate self as drizzle;
283 /// # mod _drizzle {
284 /// # pub mod core { pub use drizzle_core::*; }
285 /// # pub mod error { pub use drizzle_core::error::*; }
286 /// # pub mod types { pub use drizzle_types::*; }
287 /// # pub mod migrations { pub use drizzle_migrations::*; }
288 /// # pub use drizzle_types::Dialect;
289 /// # pub use drizzle_types as ddl;
290 /// # pub mod postgres {
291 /// # pub mod values { pub use drizzle_postgres::values::*; }
292 /// # pub mod traits { pub use drizzle_postgres::traits::*; }
293 /// # pub mod common { pub use drizzle_postgres::common::*; }
294 /// # pub mod attrs { pub use drizzle_postgres::attrs::*; }
295 /// # pub mod builder { pub use drizzle_postgres::builder::*; }
296 /// # pub mod helpers { pub use drizzle_postgres::helpers::*; }
297 /// # pub mod expr { pub use drizzle_postgres::expr::*; }
298 /// # pub mod types { pub use drizzle_postgres::types::*; }
299 /// # #[cfg(feature = "aws-data-api")]
300 /// # pub mod aws_data_api { pub use drizzle_postgres::aws_data_api::*; }
301 /// # pub struct Row;
302 /// # impl Row {
303 /// # pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
304 /// # pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
305 /// # }
306 /// # pub mod prelude {
307 /// # pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
308 /// # pub use drizzle_postgres::attrs::*;
309 /// # pub use drizzle_postgres::common::PostgresSchemaType;
310 /// # pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
311 /// # pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
312 /// # pub use drizzle_core::*;
313 /// # }
314 /// # }
315 /// # }
316 /// # pub use _drizzle::*;
317 /// # pub use const_format;
318 /// fn main() {
319 /// use drizzle::postgres::prelude::*;
320 /// use drizzle::postgres::builder::QueryBuilder;
321 ///
322 /// #[PostgresTable(name = "users")]
323 /// struct User {
324 /// #[column(serial, primary)]
325 /// id: i32,
326 /// name: String,
327 /// #[column(unique)]
328 /// email: Option<String>,
329 /// }
330 ///
331 /// #[PostgresIndex(unique)]
332 /// struct UserEmailIdx(User::email);
333 ///
334 /// #[derive(PostgresSchema)]
335 /// struct Schema {
336 /// user: User,
337 /// user_email_idx: UserEmailIdx,
338 /// }
339 ///
340 /// let builder = QueryBuilder::new::<Schema>();
341 /// let schema = Schema::new();
342 /// let user = schema.user;
343 ///
344 /// // Target a specific column
345 /// builder.insert(user).values([InsertUser::new("Alice")])
346 /// .on_conflict(user.id).do_nothing();
347 ///
348 /// // Target with DO UPDATE using EXCLUDED
349 /// builder.insert(user).values([InsertUser::new("Alice")])
350 /// .on_conflict(user.email).do_update(UpdateUser::default().with_name("updated"));
351 ///
352 /// // Target a unique index
353 /// builder.insert(user).values([InsertUser::new("Alice")])
354 /// .on_conflict(schema.user_email_idx).do_nothing();
355 /// }
356 /// ```
357 pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
358 let columns = target.conflict_columns();
359 let target_where = target.conflict_where_clause().map(SQL::raw);
360 let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
361 OnConflictBuilder::new(self.sql, PostgresConflictTarget::columns(target_sql))
362 .with_target_where_sql(target_where)
363 }
364
365 /// Begins a typed ON CONFLICT ON CONSTRAINT clause (PostgreSQL-only).
366 ///
367 /// The target must implement `NamedConstraint<T>`, which is auto-generated
368 /// for unique columns and named unique constraints. Standalone indexes are
369 /// conflict targets, but PostgreSQL does not accept them after
370 /// `ON CONFLICT ON CONSTRAINT`.
371 ///
372 /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
373 ///
374 /// # Examples
375 ///
376 /// ```rust
377 /// # extern crate self as drizzle;
378 /// # mod _drizzle {
379 /// # pub mod core { pub use drizzle_core::*; }
380 /// # pub mod error { pub use drizzle_core::error::*; }
381 /// # pub mod types { pub use drizzle_types::*; }
382 /// # pub mod migrations { pub use drizzle_migrations::*; }
383 /// # pub use drizzle_types::Dialect;
384 /// # pub use drizzle_types as ddl;
385 /// # pub mod postgres {
386 /// # pub mod values { pub use drizzle_postgres::values::*; }
387 /// # pub mod traits { pub use drizzle_postgres::traits::*; }
388 /// # pub mod common { pub use drizzle_postgres::common::*; }
389 /// # pub mod attrs { pub use drizzle_postgres::attrs::*; }
390 /// # pub mod builder { pub use drizzle_postgres::builder::*; }
391 /// # pub mod helpers { pub use drizzle_postgres::helpers::*; }
392 /// # pub mod expr { pub use drizzle_postgres::expr::*; }
393 /// # pub mod types { pub use drizzle_postgres::types::*; }
394 /// # #[cfg(feature = "aws-data-api")]
395 /// # pub mod aws_data_api { pub use drizzle_postgres::aws_data_api::*; }
396 /// # pub struct Row;
397 /// # impl Row {
398 /// # pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
399 /// # pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
400 /// # }
401 /// # pub mod prelude {
402 /// # pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
403 /// # pub use drizzle_postgres::attrs::*;
404 /// # pub use drizzle_postgres::common::PostgresSchemaType;
405 /// # pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
406 /// # pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
407 /// # pub use drizzle_core::*;
408 /// # }
409 /// # }
410 /// # }
411 /// # pub use _drizzle::*;
412 /// # pub use const_format;
413 /// fn main() {
414 /// use drizzle::postgres::prelude::*;
415 /// use drizzle::postgres::builder::QueryBuilder;
416 ///
417 /// #[PostgresTable(name = "users")]
418 /// struct User {
419 /// #[column(serial, primary)]
420 /// id: i32,
421 /// name: String,
422 /// #[column(unique)]
423 /// email: Option<String>,
424 /// }
425 ///
426 /// #[derive(PostgresSchema)]
427 /// struct Schema {
428 /// user: User,
429 /// }
430 ///
431 /// let builder = QueryBuilder::new::<Schema>();
432 /// let schema = Schema::new();
433 ///
434 /// let user = schema.user;
435 /// builder.insert(user).values([InsertUser::new("Alice")])
436 /// .on_conflict_on_constraint(user.email).do_nothing();
437 /// }
438 /// ```
439 pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
440 self,
441 target: C,
442 ) -> OnConflictBuilder<'a, S, T> {
443 OnConflictBuilder::new(
444 self.sql,
445 PostgresConflictTarget::constraint(target.constraint_name()),
446 )
447 }
448
449 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
450 ///
451 /// This matches any constraint violation.
452 #[must_use]
453 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
454 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
455 InsertBuilder {
456 sql: self.sql.append(conflict_sql),
457 schema: PhantomData,
458 state: PhantomData,
459 table: PhantomData,
460 marker: PhantomData,
461 row: PhantomData,
462 grouped: PhantomData,
463 }
464 }
465
466 /// Adds a RETURNING clause and transitions to `ReturningSet` state
467 #[inline]
468 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
469 where
470 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
471 Columns::Marker: drizzle_core::ResolveRow<T>,
472 {
473 let returning_sql = crate::helpers::returning(columns);
474 InsertBuilder {
475 sql: self.sql.append(returning_sql),
476 schema: PhantomData,
477 state: PhantomData,
478 table: PhantomData,
479 marker: PhantomData,
480 row: PhantomData,
481 grouped: PhantomData,
482 }
483 }
484}
485
486//------------------------------------------------------------------------------
487// Post-ON CONFLICT Implementation
488//------------------------------------------------------------------------------
489
490impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
491 /// Adds a RETURNING clause after ON CONFLICT
492 #[inline]
493 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
494 where
495 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
496 Columns::Marker: drizzle_core::ResolveRow<T>,
497 {
498 let returning_sql = crate::helpers::returning(columns);
499 InsertBuilder {
500 sql: self.sql.append(returning_sql),
501 schema: PhantomData,
502 state: PhantomData,
503 table: PhantomData,
504 marker: PhantomData,
505 row: PhantomData,
506 grouped: PhantomData,
507 }
508 }
509}
510
511//------------------------------------------------------------------------------
512// Post-DO UPDATE SET Implementation
513//------------------------------------------------------------------------------
514
515impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
516 /// Adds a WHERE clause to the DO UPDATE SET clause.
517 ///
518 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
519 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
520 where
521 E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
522 E::SQLType: drizzle_core::types::BooleanLike,
523 {
524 let sql = self
525 .sql
526 .push(Token::WHERE)
527 .append(condition.into_expr_sql());
528 InsertBuilder {
529 sql,
530 schema: PhantomData,
531 state: PhantomData,
532 table: PhantomData,
533 marker: PhantomData,
534 row: PhantomData,
535 grouped: PhantomData,
536 }
537 }
538
539 /// Adds a RETURNING clause after DO UPDATE SET
540 #[inline]
541 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
542 where
543 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
544 Columns::Marker: drizzle_core::ResolveRow<T>,
545 {
546 let returning_sql = crate::helpers::returning(columns);
547 InsertBuilder {
548 sql: self.sql.append(returning_sql),
549 schema: PhantomData,
550 state: PhantomData,
551 table: PhantomData,
552 marker: PhantomData,
553 row: PhantomData,
554 grouped: PhantomData,
555 }
556 }
557}
558
559#[cfg(test)]
560mod tests {
561 use super::*;
562 use drizzle_core::{SQL, ToSQL};
563
564 #[test]
565 fn test_insert_builder_creation() {
566 let builder = InsertBuilder::<(), InsertInitial, ()> {
567 sql: SQL::raw("INSERT INTO test"),
568 schema: PhantomData,
569 state: PhantomData,
570 table: PhantomData,
571 marker: PhantomData,
572 row: PhantomData,
573 grouped: PhantomData,
574 };
575
576 assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
577 }
578}