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_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
249 OnConflictBuilder::new(self.sql, PostgresConflictTarget::columns(target_sql))
250 }
251
252 /// Begins a typed ON CONFLICT ON CONSTRAINT clause (PostgreSQL-only).
253 ///
254 /// The target must implement `NamedConstraint<T>`, which is auto-generated
255 /// for unique indexes.
256 ///
257 /// Returns an [`OnConflictBuilder`] to specify `do_nothing()` or `do_update()`.
258 ///
259 /// # Examples
260 ///
261 /// ```rust
262 /// # extern crate self as drizzle;
263 /// # mod _drizzle {
264 /// # pub mod core { pub use drizzle_core::*; }
265 /// # pub mod error { pub use drizzle_core::error::*; }
266 /// # pub mod types { pub use drizzle_types::*; }
267 /// # pub mod migrations { pub use drizzle_migrations::*; }
268 /// # pub use drizzle_types::Dialect;
269 /// # pub use drizzle_types as ddl;
270 /// # pub mod postgres {
271 /// # pub mod values { pub use drizzle_postgres::values::*; }
272 /// # pub mod traits { pub use drizzle_postgres::traits::*; }
273 /// # pub mod common { pub use drizzle_postgres::common::*; }
274 /// # pub mod attrs { pub use drizzle_postgres::attrs::*; }
275 /// # pub mod builder { pub use drizzle_postgres::builder::*; }
276 /// # pub mod helpers { pub use drizzle_postgres::helpers::*; }
277 /// # pub mod expr { pub use drizzle_postgres::expr::*; }
278 /// # pub mod types { pub use drizzle_postgres::types::*; }
279 /// # pub struct Row;
280 /// # impl Row {
281 /// # pub fn get<'a, I, T>(&'a self, _: I) -> T { unimplemented!() }
282 /// # pub fn try_get<'a, I, T>(&'a self, _: I) -> Result<T, Box<dyn std::error::Error + Sync + Send>> { unimplemented!() }
283 /// # }
284 /// # pub mod prelude {
285 /// # pub use drizzle_macros::{PostgresTable, PostgresSchema, PostgresIndex};
286 /// # pub use drizzle_postgres::attrs::*;
287 /// # pub use drizzle_postgres::common::PostgresSchemaType;
288 /// # pub use drizzle_postgres::traits::{PostgresColumn, PostgresTable};
289 /// # pub use drizzle_postgres::values::{PostgresInsertValue, PostgresUpdateValue, PostgresValue};
290 /// # pub use drizzle_core::*;
291 /// # }
292 /// # }
293 /// # }
294 /// # pub use _drizzle::*;
295 /// # pub use const_format;
296 /// fn main() {
297 /// use drizzle::postgres::prelude::*;
298 /// use drizzle::postgres::builder::QueryBuilder;
299 ///
300 /// #[PostgresTable(name = "users")]
301 /// struct User {
302 /// #[column(serial, primary)]
303 /// id: i32,
304 /// name: String,
305 /// #[column(unique)]
306 /// email: Option<String>,
307 /// }
308 ///
309 /// #[PostgresIndex(unique)]
310 /// struct UserEmailIdx(User::email);
311 ///
312 /// #[derive(PostgresSchema)]
313 /// struct Schema {
314 /// user: User,
315 /// user_email_idx: UserEmailIdx,
316 /// }
317 ///
318 /// let builder = QueryBuilder::new::<Schema>();
319 /// let schema = Schema::new();
320 ///
321 /// builder.insert(schema.user).values([InsertUser::new("Alice")])
322 /// .on_conflict_on_constraint(schema.user_email_idx).do_nothing();
323 /// }
324 /// ```
325 pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
326 self,
327 target: C,
328 ) -> OnConflictBuilder<'a, S, T> {
329 OnConflictBuilder::new(
330 self.sql,
331 PostgresConflictTarget::constraint(target.constraint_name()),
332 )
333 }
334
335 /// Shorthand for `ON CONFLICT DO NOTHING` without specifying a target.
336 ///
337 /// This matches any constraint violation.
338 #[must_use]
339 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
340 let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
341 InsertBuilder {
342 sql: self.sql.append(conflict_sql),
343 schema: PhantomData,
344 state: PhantomData,
345 table: PhantomData,
346 marker: PhantomData,
347 row: PhantomData,
348 grouped: PhantomData,
349 }
350 }
351
352 /// Adds a RETURNING clause and transitions to `ReturningSet` state
353 #[inline]
354 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
355 where
356 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
357 Columns::Marker: drizzle_core::ResolveRow<T>,
358 {
359 let returning_sql = crate::helpers::returning(columns);
360 InsertBuilder {
361 sql: self.sql.append(returning_sql),
362 schema: PhantomData,
363 state: PhantomData,
364 table: PhantomData,
365 marker: PhantomData,
366 row: PhantomData,
367 grouped: PhantomData,
368 }
369 }
370}
371
372//------------------------------------------------------------------------------
373// Post-ON CONFLICT Implementation
374//------------------------------------------------------------------------------
375
376impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
377 /// Adds a RETURNING clause after ON CONFLICT
378 #[inline]
379 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
380 where
381 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
382 Columns::Marker: drizzle_core::ResolveRow<T>,
383 {
384 let returning_sql = crate::helpers::returning(columns);
385 InsertBuilder {
386 sql: self.sql.append(returning_sql),
387 schema: PhantomData,
388 state: PhantomData,
389 table: PhantomData,
390 marker: PhantomData,
391 row: PhantomData,
392 grouped: PhantomData,
393 }
394 }
395}
396
397//------------------------------------------------------------------------------
398// Post-DO UPDATE SET Implementation
399//------------------------------------------------------------------------------
400
401impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
402 /// Adds a WHERE clause to the DO UPDATE SET clause.
403 ///
404 /// Generates: `ON CONFLICT (col) DO UPDATE SET ... WHERE condition`
405 pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
406 where
407 E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
408 E::SQLType: drizzle_core::types::BooleanLike,
409 {
410 let sql = self
411 .sql
412 .push(Token::WHERE)
413 .append(condition.into_expr_sql());
414 InsertBuilder {
415 sql,
416 schema: PhantomData,
417 state: PhantomData,
418 table: PhantomData,
419 marker: PhantomData,
420 row: PhantomData,
421 grouped: PhantomData,
422 }
423 }
424
425 /// Adds a RETURNING clause after DO UPDATE SET
426 #[inline]
427 pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
428 where
429 Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
430 Columns::Marker: drizzle_core::ResolveRow<T>,
431 {
432 let returning_sql = crate::helpers::returning(columns);
433 InsertBuilder {
434 sql: self.sql.append(returning_sql),
435 schema: PhantomData,
436 state: PhantomData,
437 table: PhantomData,
438 marker: PhantomData,
439 row: PhantomData,
440 grouped: PhantomData,
441 }
442 }
443}
444
445#[cfg(test)]
446mod tests {
447 use super::*;
448 use drizzle_core::{SQL, ToSQL};
449
450 #[test]
451 fn test_insert_builder_creation() {
452 let builder = InsertBuilder::<(), InsertInitial, ()> {
453 sql: SQL::raw("INSERT INTO test"),
454 schema: PhantomData,
455 state: PhantomData,
456 table: PhantomData,
457 marker: PhantomData,
458 row: PhantomData,
459 grouped: PhantomData,
460 };
461
462 assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
463 }
464}