1use crate::{
4 builder::SqlFragment,
5 identifier::{escape_ident, from_qi, QualifiedIdentifier},
6 param::SqlParam,
7};
8
9#[derive(Clone, Debug, Default)]
11pub struct InsertBuilder {
12 table: Option<SqlFragment>,
13 columns: Vec<String>,
14 values: Vec<Vec<SqlFragment>>,
15 on_conflict: Option<OnConflict>,
16 returning: Vec<SqlFragment>,
17}
18
19#[derive(Clone, Debug)]
20pub enum OnConflict {
21 DoNothing,
22 DoUpdate {
23 columns: Vec<String>,
24 set: Vec<(String, SqlFragment)>,
25 where_clause: Option<SqlFragment>,
26 },
27}
28
29impl InsertBuilder {
30 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn into_table(mut self, qi: &QualifiedIdentifier) -> Self {
37 self.table = Some(SqlFragment::raw(from_qi(qi)));
38 self
39 }
40
41 pub fn columns(mut self, cols: Vec<String>) -> Self {
43 self.columns = cols;
44 self
45 }
46
47 pub fn values(mut self, vals: Vec<SqlParam>) -> Self {
49 let row: Vec<SqlFragment> = vals
50 .into_iter()
51 .map(|v| {
52 let mut frag = SqlFragment::new();
53 frag.push_param(v);
54 frag
55 })
56 .collect();
57 self.values.push(row);
58 self
59 }
60
61 pub fn values_raw(mut self, vals: Vec<SqlFragment>) -> Self {
63 self.values.push(vals);
64 self
65 }
66
67 pub fn on_conflict_do_nothing(mut self) -> Self {
69 self.on_conflict = Some(OnConflict::DoNothing);
70 self
71 }
72
73 pub fn on_conflict_do_update(
75 mut self,
76 conflict_columns: Vec<String>,
77 set: Vec<(String, SqlFragment)>,
78 ) -> Self {
79 self.on_conflict = Some(OnConflict::DoUpdate {
80 columns: conflict_columns,
81 set,
82 where_clause: None,
83 });
84 self
85 }
86
87 pub fn returning(mut self, column: &str) -> Self {
89 self.returning.push(SqlFragment::raw(escape_ident(column)));
90 self
91 }
92
93 pub fn returning_all(mut self) -> Self {
95 self.returning.push(SqlFragment::raw("*"));
96 self
97 }
98
99 pub fn build(self) -> SqlFragment {
101 let mut result = SqlFragment::new();
102
103 result.push("INSERT INTO ");
104
105 if let Some(table) = self.table {
106 result.append(table);
107 }
108
109 if !self.columns.is_empty() {
111 result.push(" (");
112 for (i, col) in self.columns.iter().enumerate() {
113 if i > 0 {
114 result.push(", ");
115 }
116 result.push(&escape_ident(col));
117 }
118 result.push(")");
119 }
120
121 if !self.values.is_empty() {
123 result.push(" VALUES ");
124 for (i, row) in self.values.into_iter().enumerate() {
125 if i > 0 {
126 result.push(", ");
127 }
128 result.push("(");
129 for (j, val) in row.into_iter().enumerate() {
130 if j > 0 {
131 result.push(", ");
132 }
133 result.append(val);
134 }
135 result.push(")");
136 }
137 } else {
138 result.push(" DEFAULT VALUES");
139 }
140
141 if let Some(conflict) = self.on_conflict {
143 match conflict {
144 OnConflict::DoNothing => {
145 result.push(" ON CONFLICT DO NOTHING");
146 }
147 OnConflict::DoUpdate {
148 columns,
149 set,
150 where_clause,
151 } => {
152 result.push(" ON CONFLICT (");
153 for (i, col) in columns.iter().enumerate() {
154 if i > 0 {
155 result.push(", ");
156 }
157 result.push(&escape_ident(col));
158 }
159 result.push(") DO UPDATE SET ");
160 for (i, (col, val)) in set.into_iter().enumerate() {
161 if i > 0 {
162 result.push(", ");
163 }
164 result.push(&escape_ident(&col));
165 result.push(" = ");
166 result.append(val);
167 }
168 if let Some(where_sql) = where_clause {
169 result.push(" WHERE ");
170 result.append(where_sql);
171 }
172 }
173 }
174 }
175
176 if !self.returning.is_empty() {
178 result.push(" RETURNING ");
179 for (i, ret) in self.returning.into_iter().enumerate() {
180 if i > 0 {
181 result.push(", ");
182 }
183 result.append(ret);
184 }
185 }
186
187 result
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn test_simple_insert() {
197 let qi = QualifiedIdentifier::new("public", "users");
198 let sql = InsertBuilder::new()
199 .into_table(&qi)
200 .columns(vec!["name".into(), "email".into()])
201 .values(vec![
202 SqlParam::text("John"),
203 SqlParam::text("john@example.com"),
204 ])
205 .build();
206
207 assert!(sql.sql().contains("INSERT INTO"));
208 assert!(sql.sql().contains("VALUES"));
209 assert_eq!(sql.params().len(), 2);
210 }
211
212 #[test]
213 fn test_insert_returning() {
214 let qi = QualifiedIdentifier::unqualified("users");
215 let sql = InsertBuilder::new()
216 .into_table(&qi)
217 .columns(vec!["name".into()])
218 .values(vec![SqlParam::text("John")])
219 .returning("id")
220 .build();
221
222 assert!(sql.sql().contains("RETURNING"));
223 }
224
225 #[test]
226 fn test_insert_on_conflict_nothing() {
227 let qi = QualifiedIdentifier::unqualified("users");
228 let sql = InsertBuilder::new()
229 .into_table(&qi)
230 .columns(vec!["email".into()])
231 .values(vec![SqlParam::text("john@example.com")])
232 .on_conflict_do_nothing()
233 .build();
234
235 assert!(sql.sql().contains("ON CONFLICT DO NOTHING"));
236 }
237
238 #[test]
239 fn test_insert_upsert() {
240 let qi = QualifiedIdentifier::unqualified("users");
241 let mut name_val = SqlFragment::new();
242 name_val.push("EXCLUDED.\"name\"");
243
244 let sql = InsertBuilder::new()
245 .into_table(&qi)
246 .columns(vec!["id".into(), "name".into()])
247 .values(vec![SqlParam::Int(1), SqlParam::text("John")])
248 .on_conflict_do_update(vec!["id".into()], vec![("name".into(), name_val)])
249 .build();
250
251 assert!(sql.sql().contains("ON CONFLICT"));
252 assert!(sql.sql().contains("DO UPDATE SET"));
253 }
254}