1use crate::parse;
8use sqlparser::ast::Statement;
9
10struct JoinClause {
13 table: String,
14 on: String,
15 join_type: &'static str, }
17
18struct OrderByClause {
19 expr: String,
20 ascending: bool,
21}
22
23pub struct QueryBuilder {
45 select_cols: Vec<String>,
46 from_table: Option<String>,
47 joins: Vec<JoinClause>,
48 wheres: Vec<String>,
49 group_by: Vec<String>,
50 having: Option<String>,
51 order_bys: Vec<OrderByClause>,
52 limit_val: Option<u64>,
53 offset_val: Option<u64>,
54 distinct: bool,
55}
56
57impl QueryBuilder {
58 pub fn select(columns: &[&str]) -> Self {
62 Self {
63 select_cols: columns.iter().map(|c| c.to_string()).collect(),
64 from_table: None,
65 joins: Vec::new(),
66 wheres: Vec::new(),
67 group_by: Vec::new(),
68 having: None,
69 order_bys: Vec::new(),
70 limit_val: None,
71 offset_val: None,
72 distinct: false,
73 }
74 }
75
76 pub fn select_all() -> Self {
78 Self::select(&["*"])
79 }
80
81 pub fn distinct(mut self) -> Self {
85 self.distinct = true;
86 self
87 }
88
89 pub fn from(mut self, table: &str) -> Self {
91 self.from_table = Some(table.to_string());
92 self
93 }
94
95 pub fn join(mut self, table: &str, on: &str) -> Self {
97 self.joins.push(JoinClause {
98 table: table.to_string(),
99 on: on.to_string(),
100 join_type: "INNER",
101 });
102 self
103 }
104
105 pub fn left_join(mut self, table: &str, on: &str) -> Self {
107 self.joins.push(JoinClause {
108 table: table.to_string(),
109 on: on.to_string(),
110 join_type: "LEFT",
111 });
112 self
113 }
114
115 pub fn right_join(mut self, table: &str, on: &str) -> Self {
117 self.joins.push(JoinClause {
118 table: table.to_string(),
119 on: on.to_string(),
120 join_type: "RIGHT",
121 });
122 self
123 }
124
125 pub fn where_clause(mut self, condition: &str) -> Self {
127 self.wheres.push(condition.to_string());
128 self
129 }
130
131 pub fn group_by(mut self, expr: &str) -> Self {
133 self.group_by.push(expr.to_string());
134 self
135 }
136
137 pub fn having(mut self, condition: &str) -> Self {
139 self.having = Some(condition.to_string());
140 self
141 }
142
143 pub fn order_by(mut self, expr: &str, ascending: bool) -> Self {
145 self.order_bys.push(OrderByClause {
146 expr: expr.to_string(),
147 ascending,
148 });
149 self
150 }
151
152 pub fn limit(mut self, n: u64) -> Self {
154 self.limit_val = Some(n);
155 self
156 }
157
158 pub fn offset(mut self, n: u64) -> Self {
160 self.offset_val = Some(n);
161 self
162 }
163
164 pub fn build(self) -> Result<String, String> {
170 let from = self
171 .from_table
172 .ok_or_else(|| "QueryBuilder: no FROM table set".to_string())?;
173
174 let mut parts: Vec<String> = Vec::new();
175
176 let cols = if self.select_cols.is_empty() {
178 "*".to_string()
179 } else {
180 self.select_cols.join(", ")
181 };
182 if self.distinct {
183 parts.push(format!("SELECT DISTINCT {cols}"));
184 } else {
185 parts.push(format!("SELECT {cols}"));
186 }
187
188 parts.push(format!("FROM {from}"));
190
191 for j in &self.joins {
193 parts.push(format!("{} JOIN {} ON {}", j.join_type, j.table, j.on));
194 }
195
196 if !self.wheres.is_empty() {
198 parts.push(format!("WHERE {}", self.wheres.join(" AND ")));
199 }
200
201 if !self.group_by.is_empty() {
203 parts.push(format!("GROUP BY {}", self.group_by.join(", ")));
204 }
205
206 if let Some(h) = &self.having {
208 parts.push(format!("HAVING {h}"));
209 }
210
211 if !self.order_bys.is_empty() {
213 let order_exprs: Vec<String> = self
214 .order_bys
215 .iter()
216 .map(|o| {
217 let dir = if o.ascending { "ASC" } else { "DESC" };
218 format!("{} {dir}", o.expr)
219 })
220 .collect();
221 parts.push(format!("ORDER BY {}", order_exprs.join(", ")));
222 }
223
224 if let Some(n) = self.limit_val {
226 parts.push(format!("LIMIT {n}"));
227 }
228
229 if let Some(n) = self.offset_val {
231 parts.push(format!("OFFSET {n}"));
232 }
233
234 Ok(parts.join("\n"))
235 }
236
237 pub fn build_and_parse(self) -> Result<Vec<Statement>, String> {
241 let sql = self.build()?;
242 parse(&sql).map_err(|e| e.to_string())
243 }
244
245 pub fn build_ref(&self) -> Result<String, String> {
250 let cloned = QueryBuilder {
251 select_cols: self.select_cols.clone(),
252 from_table: self.from_table.clone(),
253 joins: self
254 .joins
255 .iter()
256 .map(|j| JoinClause {
257 table: j.table.clone(),
258 on: j.on.clone(),
259 join_type: j.join_type,
260 })
261 .collect(),
262 wheres: self.wheres.clone(),
263 group_by: self.group_by.clone(),
264 having: self.having.clone(),
265 order_bys: self
266 .order_bys
267 .iter()
268 .map(|o| OrderByClause {
269 expr: o.expr.clone(),
270 ascending: o.ascending,
271 })
272 .collect(),
273 limit_val: self.limit_val,
274 offset_val: self.offset_val,
275 distinct: self.distinct,
276 };
277 cloned.build()
278 }
279
280 pub fn insert(table: &str, columns: &[&str], values: &[&str]) -> String {
293 let cols = columns.join(", ");
294 let vals = values.join(", ");
295 format!("INSERT INTO {table} ({cols}) VALUES ({vals})")
296 }
297
298 pub fn update(table: &str, sets: &[(&str, &str)], where_clause: Option<&str>) -> String {
314 let set_exprs: Vec<String> = sets.iter().map(|(k, v)| format!("{k} = {v}")).collect();
315 let mut sql = format!("UPDATE {table} SET {}", set_exprs.join(", "));
316 if let Some(w) = where_clause {
317 sql.push_str(&format!(" WHERE {w}"));
318 }
319 sql
320 }
321
322 pub fn delete(table: &str, where_clause: Option<&str>) -> String {
333 let mut sql = format!("DELETE FROM {table}");
334 if let Some(w) = where_clause {
335 sql.push_str(&format!(" WHERE {w}"));
336 }
337 sql
338 }
339}
340
341#[cfg(test)]
344mod tests {
345 use super::QueryBuilder;
346
347 #[test]
348 fn test_simple_select() {
349 let sql = QueryBuilder::select(&["id", "name"])
350 .from("users")
351 .build()
352 .expect("build");
353 assert_eq!(sql, "SELECT id, name\nFROM users");
354 }
355
356 #[test]
357 fn test_select_with_where() {
358 let sql = QueryBuilder::select(&["id", "name"])
359 .from("users")
360 .where_clause("active = TRUE")
361 .build()
362 .expect("build");
363 assert!(sql.contains("WHERE active = TRUE"), "got: {sql}");
364 }
365
366 #[test]
367 fn test_multiple_where_anded() {
368 let sql = QueryBuilder::select(&["*"])
369 .from("t")
370 .where_clause("a = 1")
371 .where_clause("b = 2")
372 .build()
373 .expect("build");
374 assert!(sql.contains("WHERE a = 1 AND b = 2"), "got: {sql}");
375 }
376
377 #[test]
378 fn test_join() {
379 let sql = QueryBuilder::select(&["u.id", "o.id"])
380 .from("users u")
381 .join("orders o", "u.id = o.user_id")
382 .build()
383 .expect("build");
384 assert!(
385 sql.contains("INNER JOIN orders o ON u.id = o.user_id"),
386 "got: {sql}"
387 );
388 }
389
390 #[test]
391 fn test_left_join() {
392 let sql = QueryBuilder::select(&["u.id"])
393 .from("users u")
394 .left_join("orders o", "u.id = o.user_id")
395 .build()
396 .expect("build");
397 assert!(
398 sql.contains("LEFT JOIN orders o ON u.id = o.user_id"),
399 "got: {sql}"
400 );
401 }
402
403 #[test]
404 fn test_order_limit_offset() {
405 let sql = QueryBuilder::select(&["id"])
406 .from("users")
407 .order_by("name", true)
408 .order_by("id", false)
409 .limit(10)
410 .offset(20)
411 .build()
412 .expect("build");
413 assert!(sql.contains("ORDER BY name ASC, id DESC"), "got: {sql}");
414 assert!(sql.contains("LIMIT 10"), "got: {sql}");
415 assert!(sql.contains("OFFSET 20"), "got: {sql}");
416 }
417
418 #[test]
419 fn test_group_by_having() {
420 let sql = QueryBuilder::select(&["dept", "COUNT(*)"])
421 .from("employees")
422 .group_by("dept")
423 .having("COUNT(*) > 5")
424 .build()
425 .expect("build");
426 assert!(sql.contains("GROUP BY dept"), "got: {sql}");
427 assert!(sql.contains("HAVING COUNT(*) > 5"), "got: {sql}");
428 }
429
430 #[test]
431 fn test_distinct() {
432 let sql = QueryBuilder::select(&["email"])
433 .distinct()
434 .from("users")
435 .build()
436 .expect("build");
437 assert!(sql.starts_with("SELECT DISTINCT email"), "got: {sql}");
438 }
439
440 #[test]
441 fn test_insert() {
442 let sql = QueryBuilder::insert("users", &["id", "name"], &["1", "'Alice'"]);
443 assert_eq!(sql, "INSERT INTO users (id, name) VALUES (1, 'Alice')");
444 }
445
446 #[test]
447 fn test_update() {
448 let sql = QueryBuilder::update(
449 "users",
450 &[("name", "'Bob'"), ("active", "FALSE")],
451 Some("id = 42"),
452 );
453 assert!(sql.starts_with("UPDATE users SET"), "got: {sql}");
454 assert!(sql.contains("name = 'Bob'"), "got: {sql}");
455 assert!(sql.contains("active = FALSE"), "got: {sql}");
456 assert!(sql.contains("WHERE id = 42"), "got: {sql}");
457 }
458
459 #[test]
460 fn test_delete_with_where() {
461 let sql = QueryBuilder::delete("users", Some("id = 99"));
462 assert_eq!(sql, "DELETE FROM users WHERE id = 99");
463 }
464
465 #[test]
466 fn test_build_and_parse() {
467 let result = QueryBuilder::select(&["u.id", "u.name", "o.total"])
468 .from("users u")
469 .join("orders o", "u.id = o.user_id")
470 .where_clause("u.active = TRUE")
471 .order_by("u.name", true)
472 .limit(50)
473 .build_and_parse();
474 assert!(result.is_ok(), "build_and_parse failed: {:?}", result.err());
475 let stmts = result.expect("stmts");
476 assert_eq!(stmts.len(), 1);
477 }
478
479 #[test]
480 fn test_build_no_from_errors() {
481 let result = QueryBuilder::select_all().build();
482 assert!(result.is_err(), "expected Err when no FROM is set");
483 }
484
485 #[test]
486 fn test_select_all() {
487 let sql = QueryBuilder::select_all()
488 .from("products")
489 .build()
490 .expect("build");
491 assert!(sql.starts_with("SELECT *"), "got: {sql}");
492 }
493
494 #[test]
495 fn test_build_ref_matches_build() {
496 let qb = QueryBuilder::select(&["id", "name"])
497 .from("users")
498 .where_clause("active = TRUE")
499 .limit(10);
500 let via_ref = qb.build_ref().expect("build_ref");
501 let via_owned = QueryBuilder::select(&["id", "name"])
502 .from("users")
503 .where_clause("active = TRUE")
504 .limit(10)
505 .build()
506 .expect("build");
507 assert_eq!(via_ref, via_owned);
508 }
509}