drizzle_postgres/builder/
insert.rs1use crate::traits::PostgresTable;
2use crate::values::PostgresValue;
3use drizzle_core::{SQL, ToSQL};
4use std::fmt::Debug;
5use std::marker::PhantomData;
6
7use super::ExecutableState;
9
10#[derive(Debug, Clone, Copy, Default)]
16pub struct InsertInitial;
17
18#[derive(Debug, Clone, Copy, Default)]
20pub struct InsertValuesSet;
21
22#[derive(Debug, Clone, Copy, Default)]
24pub struct InsertReturningSet;
25
26#[derive(Debug, Clone, Copy, Default)]
28pub struct InsertOnConflictSet;
29
30impl InsertInitial {
32 #[inline]
33 pub const fn new() -> Self {
34 Self
35 }
36}
37impl InsertValuesSet {
38 #[inline]
39 pub const fn new() -> Self {
40 Self
41 }
42}
43impl InsertReturningSet {
44 #[inline]
45 pub const fn new() -> Self {
46 Self
47 }
48}
49impl InsertOnConflictSet {
50 #[inline]
51 pub const fn new() -> Self {
52 Self
53 }
54}
55
56#[derive(Debug, Clone)]
58#[allow(clippy::large_enum_variant)]
59pub enum ConflictTarget<'a> {
60 Columns(SQL<'a, PostgresValue<'a>>),
62 ColumnsWhere {
65 columns: SQL<'a, PostgresValue<'a>>,
66 where_clause: SQL<'a, PostgresValue<'a>>,
67 },
68 Constraint(String),
70}
71
72#[derive(Debug, Clone)]
74#[allow(clippy::large_enum_variant)]
75pub enum Conflict<'a> {
76 DoNothing {
78 target: Option<ConflictTarget<'a>>,
80 },
81 DoUpdate {
83 target: ConflictTarget<'a>,
85 set: SQL<'a, PostgresValue<'a>>,
87 where_clause: Option<SQL<'a, PostgresValue<'a>>>,
90 },
91}
92
93impl<'a> Default for Conflict<'a> {
94 fn default() -> Self {
95 Self::DoNothing { target: None }
96 }
97}
98
99impl<'a> Conflict<'a> {
100 pub fn do_nothing_on_columns<T>(columns: T) -> Self
102 where
103 T: ToSQL<'a, PostgresValue<'a>>,
104 {
105 Conflict::DoNothing {
106 target: Some(ConflictTarget::Columns(columns.to_sql())),
107 }
108 }
109
110 pub fn do_nothing_on_constraint(constraint_name: String) -> Self {
112 Conflict::DoNothing {
113 target: Some(ConflictTarget::Constraint(constraint_name)),
114 }
115 }
116
117 pub fn do_update<S, W>(target: ConflictTarget<'a>, set: S, where_clause: Option<W>) -> Self
119 where
120 S: ToSQL<'a, PostgresValue<'a>>,
121 W: ToSQL<'a, PostgresValue<'a>>,
122 {
123 Conflict::DoUpdate {
124 target,
125 set: set.to_sql(),
126 where_clause: where_clause.map(|w| w.to_sql()),
127 }
128 }
129}
130
131impl<'a> ConflictTarget<'a> {
132 pub fn columns<T>(columns: T) -> Self
134 where
135 T: ToSQL<'a, PostgresValue<'a>>,
136 {
137 ConflictTarget::Columns(columns.to_sql())
138 }
139
140 pub fn columns_where<T, W>(columns: T, where_clause: W) -> Self
142 where
143 T: ToSQL<'a, PostgresValue<'a>>,
144 W: ToSQL<'a, PostgresValue<'a>>,
145 {
146 ConflictTarget::ColumnsWhere {
147 columns: columns.to_sql(),
148 where_clause: where_clause.to_sql(),
149 }
150 }
151
152 pub fn constraint(constraint_name: String) -> Self {
154 ConflictTarget::Constraint(constraint_name)
155 }
156}
157
158impl ExecutableState for InsertValuesSet {}
160impl ExecutableState for InsertReturningSet {}
161impl ExecutableState for InsertOnConflictSet {}
162
163pub type InsertBuilder<'a, Schema, State, Table> = super::QueryBuilder<'a, Schema, State, Table>;
169
170impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
175where
176 Table: PostgresTable<'a>,
177{
178 #[inline]
180 pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
181 where
182 I: IntoIterator<Item = Table::Insert<T>>,
183 {
184 let sql = crate::helpers::values::<'a, Table, T>(values);
185 InsertBuilder {
186 sql: self.sql.append(sql),
187 schema: PhantomData,
188 state: PhantomData,
189 table: PhantomData,
190 }
191 }
192}
193
194impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
199 pub fn on_conflict(
201 self,
202 conflict: Conflict<'a>,
203 ) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
204 let conflict_sql = match conflict {
205 Conflict::DoNothing { target } => {
206 let mut sql = SQL::raw("ON CONFLICT");
207
208 if let Some(target) = target {
209 sql = sql.append(Self::build_conflict_target(target));
210 }
211
212 sql.append(SQL::raw(" DO NOTHING"))
213 }
214 Conflict::DoUpdate {
215 target,
216 set,
217 where_clause,
218 } => {
219 let mut sql = SQL::raw("ON CONFLICT")
220 .append(Self::build_conflict_target(target))
221 .append(SQL::raw(" DO UPDATE SET "))
222 .append(set);
223
224 if let Some(where_clause) = where_clause {
226 sql = sql.append(SQL::raw(" WHERE ")).append(where_clause);
227 }
228
229 sql
230 }
231 };
232
233 InsertBuilder {
234 sql: self.sql.append(conflict_sql),
235 schema: PhantomData,
236 state: PhantomData,
237 table: PhantomData,
238 }
239 }
240
241 fn build_conflict_target(target: ConflictTarget<'a>) -> SQL<'a, PostgresValue<'a>> {
243 match target {
244 ConflictTarget::Columns(columns) => {
245 SQL::raw(" (").append(columns).append(SQL::raw(")"))
246 }
247 ConflictTarget::ColumnsWhere {
248 columns,
249 where_clause,
250 } => SQL::raw(" (")
251 .append(columns)
252 .append(SQL::raw(") WHERE "))
253 .append(where_clause),
254 ConflictTarget::Constraint(constraint_name) => {
255 SQL::raw(" ON CONSTRAINT ").append(SQL::raw(constraint_name))
256 }
257 }
258 }
259
260 pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
262 self.on_conflict(Conflict::default())
263 }
264
265 pub fn on_conflict_do_nothing_on<C>(
267 self,
268 columns: C,
269 ) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
270 where
271 C: ToSQL<'a, PostgresValue<'a>>,
272 {
273 self.on_conflict(Conflict::do_nothing_on_columns(columns))
274 }
275
276 #[inline]
278 pub fn returning(
279 self,
280 columns: impl ToSQL<'a, PostgresValue<'a>>,
281 ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
282 let returning_sql = crate::helpers::returning(columns);
283 InsertBuilder {
284 sql: self.sql.append(returning_sql),
285 schema: PhantomData,
286 state: PhantomData,
287 table: PhantomData,
288 }
289 }
290}
291
292impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
297 #[inline]
299 pub fn returning(
300 self,
301 columns: impl ToSQL<'a, PostgresValue<'a>>,
302 ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
303 let returning_sql = crate::helpers::returning(columns);
304 InsertBuilder {
305 sql: self.sql.append(returning_sql),
306 schema: PhantomData,
307 state: PhantomData,
308 table: PhantomData,
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use drizzle_core::{SQL, ToSQL};
317
318 #[test]
319 fn test_insert_builder_creation() {
320 let builder = InsertBuilder::<(), InsertInitial, ()> {
321 sql: SQL::raw("INSERT INTO test"),
322 schema: PhantomData,
323 state: PhantomData,
324 table: PhantomData,
325 };
326
327 assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
328 }
329
330 #[test]
331 fn test_conflict_types() {
332 let do_nothing = Conflict::DoNothing { target: None };
333 let do_update = Conflict::DoUpdate {
334 target: ConflictTarget::Columns(SQL::raw("id")),
335 set: SQL::raw("name = EXCLUDED.name"),
336 where_clause: None,
337 };
338
339 match do_nothing {
340 Conflict::DoNothing { .. } => (),
341 _ => panic!("Expected DoNothing"),
342 }
343
344 match do_update {
345 Conflict::DoUpdate { .. } => (),
346 _ => panic!("Expected DoUpdate"),
347 }
348 }
349}