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