1use crate::builder::{
9 ConflictAction, Cte, Having, Join, JoinCond, JoinKind, Lock, LockStrength, LockWait, Method,
10 Order, QueryBuilder, SelectExpr,
11};
12use crate::dialect::{Dialect, UpsertStyle};
13use crate::ident::escape_identifier;
14use crate::value::Value;
15use crate::where_::{Conj, Predicate};
16
17struct Ctx {
24 sql: String,
25 binds: Vec<Value>,
26 quote: char,
27}
28
29impl Ctx {
30 fn placeholder<D: Dialect>(&mut self, val: Value) {
32 self.binds.push(val);
33 D::write_placeholder(&mut self.sql, self.binds.len());
34 }
35
36 fn esc(&self, ident: &str) -> String {
44 escape_identifier(ident, self.quote)
45 }
46
47 fn qualify(&self, db: Option<&str>, table: &str) -> String {
50 match db {
51 Some(d) => format!("{}.{}", self.esc(d), self.esc(table)),
52 None => self.esc(table),
53 }
54 }
55}
56
57pub fn compile<D: Dialect>(qb: &QueryBuilder<D>) -> (String, Vec<Value>) {
59 let mut ctx = Ctx {
60 sql: String::new(),
61 binds: Vec::new(),
62 quote: D::quote_char(),
63 };
64 compile_into::<D>(&mut ctx, qb);
65 (ctx.sql, ctx.binds)
66}
67
68fn compile_into<D: Dialect>(ctx: &mut Ctx, qb: &QueryBuilder<D>) {
72 let table = ctx.qualify(qb.db.as_deref(), &qb.table);
73
74 match qb.method {
75 Method::Select => {
76 write_ctes::<D>(ctx, &qb.ctes);
78 if !qb.distinct_on.is_empty() {
79 if !D::supports_distinct_on() {
80 panic!("DISTINCT ON requires PostgreSQL");
81 }
82 ctx.sql.push_str("SELECT DISTINCT ON (");
83 let cols: Vec<String> = qb.distinct_on.iter().map(|c| ctx.esc(c)).collect();
84 ctx.sql.push_str(&cols.join(", "));
85 ctx.sql.push_str(") ");
86 } else if qb.distinct {
87 ctx.sql.push_str("SELECT DISTINCT ");
88 } else {
89 ctx.sql.push_str("SELECT ");
90 }
91 write_select_list::<D>(ctx, qb);
92 ctx.sql.push_str(" FROM ");
93 ctx.sql.push_str(&table);
94 write_joins::<D>(ctx, &qb.joins, qb.db.as_deref());
95 write_wheres::<D>(ctx, &qb.wheres);
96 write_group_by(ctx, &qb.groups, qb.group_by_raw.as_ref());
97 write_having::<D>(ctx, &qb.havings);
98 write_order_by(ctx, &qb.orders, qb.order_by_raw.as_ref());
99 write_limit_offset::<D>(ctx, qb.limit, qb.offset);
100 write_unions::<D>(ctx, &qb.unions);
101 write_lock::<D>(ctx, qb.lock.as_ref());
102 }
103 Method::Insert => {
104 if qb.set.is_empty() && qb.insert_rows.is_empty() {
105 panic!("insert() requires at least one column");
106 }
107
108 let mut single_rows: Vec<&(String, Value)> = qb.set.iter().collect();
112 single_rows.sort_by(|a, b| a.0.cmp(&b.0));
113 let sorted_cols: Vec<&str> = if !qb.insert_rows.is_empty() {
114 let mut cols: Vec<&str> =
115 qb.insert_rows[0].iter().map(|(k, _)| k.as_str()).collect();
116 cols.sort_unstable();
117 cols
118 } else {
119 single_rows.iter().map(|(k, _)| k.as_str()).collect()
120 };
121
122 let mysql_ignore = D::upsert_style() == UpsertStyle::OnDuplicateKey
125 && matches!(
126 qb.on_conflict.as_ref().map(|c| c.action),
127 Some(ConflictAction::DoNothing)
128 );
129 if mysql_ignore {
130 ctx.sql.push_str("INSERT IGNORE INTO ");
131 } else {
132 ctx.sql.push_str("INSERT INTO ");
133 }
134 ctx.sql.push_str(&table);
135 ctx.sql.push_str(" (");
136 let cols: Vec<String> = sorted_cols.iter().map(|k| ctx.esc(k)).collect();
137 ctx.sql.push_str(&cols.join(", "));
138 ctx.sql.push_str(") VALUES ");
139
140 if !qb.insert_rows.is_empty() {
141 for (ri, row) in qb.insert_rows.iter().enumerate() {
144 if ri > 0 {
145 ctx.sql.push_str(", ");
146 }
147 ctx.sql.push('(');
148 for (ci, col) in sorted_cols.iter().enumerate() {
149 if ci > 0 {
150 ctx.sql.push_str(", ");
151 }
152 let v = row
153 .iter()
154 .find(|(k, _)| k == col)
155 .map(|(_, v)| v.clone())
156 .unwrap_or(Value::Null);
157 ctx.placeholder::<D>(v);
158 }
159 ctx.sql.push(')');
160 }
161 } else {
162 ctx.sql.push('(');
165 for (i, (_, v)) in single_rows.iter().enumerate() {
166 if i > 0 {
167 ctx.sql.push_str(", ");
168 }
169 ctx.placeholder::<D>(v.clone());
170 }
171 ctx.sql.push(')');
172 }
173
174 if !mysql_ignore {
175 if let Some(oc) = &qb.on_conflict {
176 write_on_conflict::<D>(ctx, oc, &sorted_cols);
177 }
178 }
179 write_returning::<D>(ctx, &qb.returning);
180 }
181 Method::Update => {
182 if qb.set.is_empty() {
183 panic!("update() requires at least one column");
184 }
185 let mut rows: Vec<&(String, Value)> = qb.set.iter().collect();
186 rows.sort_by(|a, b| a.0.cmp(&b.0));
187 ctx.sql.push_str("UPDATE ");
188 ctx.sql.push_str(&table);
189 ctx.sql.push_str(" SET ");
190 for (i, (k, v)) in rows.iter().enumerate() {
191 if i > 0 {
192 ctx.sql.push_str(", ");
193 }
194 let col = ctx.esc(k);
195 ctx.sql.push_str(&col);
196 ctx.sql.push_str(" = ");
197 ctx.placeholder::<D>(v.clone());
198 }
199 write_wheres::<D>(ctx, &qb.wheres);
200 write_returning::<D>(ctx, &qb.returning);
201 }
202 Method::Delete => {
203 ctx.sql.push_str("DELETE FROM ");
204 ctx.sql.push_str(&table);
205 write_wheres::<D>(ctx, &qb.wheres);
206 write_returning::<D>(ctx, &qb.returning);
207 }
208 }
209}
210
211fn write_on_conflict<D: Dialect>(
215 ctx: &mut Ctx,
216 oc: &crate::builder::OnConflict,
217 inserted: &[&str],
218) {
219 match D::upsert_style() {
220 UpsertStyle::OnDuplicateKey => {
221 ctx.sql.push_str(" ON DUPLICATE KEY UPDATE ");
224 let sets: Vec<String> = inserted
225 .iter()
226 .map(|c| {
227 let e = ctx.esc(c);
228 format!("{e} = VALUES({e})")
229 })
230 .collect();
231 ctx.sql.push_str(&sets.join(", "));
232 }
233 UpsertStyle::OnConflict => {
234 let targets = &oc.targets;
235 let set_cols: Vec<&&str> = inserted
237 .iter()
238 .filter(|c| !targets.iter().any(|t| t == **c))
239 .collect();
240 let do_update = matches!(oc.action, ConflictAction::Merge)
241 && !targets.is_empty()
242 && !set_cols.is_empty();
243
244 ctx.sql.push_str(" ON CONFLICT");
245 if !targets.is_empty() {
246 ctx.sql.push_str(" (");
247 let cols: Vec<String> = targets.iter().map(|t| ctx.esc(t)).collect();
248 ctx.sql.push_str(&cols.join(", "));
249 ctx.sql.push(')');
250 }
251 if do_update {
252 ctx.sql.push_str(" DO UPDATE SET ");
253 let sets: Vec<String> = set_cols
254 .iter()
255 .map(|c| {
256 let e = ctx.esc(c);
257 format!("{e} = EXCLUDED.{e}")
260 })
261 .collect();
262 ctx.sql.push_str(&sets.join(", "));
263 } else {
264 ctx.sql.push_str(" DO NOTHING");
265 }
266 }
267 }
268}
269
270fn write_returning<D: Dialect>(ctx: &mut Ctx, cols: &[String]) {
273 if !D::supports_returning() || cols.is_empty() {
274 return;
275 }
276 ctx.sql.push_str(" RETURNING ");
277 let parts: Vec<String> = cols
278 .iter()
279 .map(|c| if c == "*" { "*".to_owned() } else { ctx.esc(c) })
280 .collect();
281 ctx.sql.push_str(&parts.join(", "));
282}
283
284fn write_group_by(ctx: &mut Ctx, groups: &[String], raw: Option<&(String, Vec<Value>)>) {
288 if groups.is_empty() && raw.is_none() {
289 return;
290 }
291 ctx.sql.push_str(" GROUP BY ");
292 let cols: Vec<String> = groups.iter().map(|c| ctx.esc(c)).collect();
293 ctx.sql.push_str(&cols.join(", "));
294 if let Some((sql, binds)) = raw {
295 if !groups.is_empty() {
296 ctx.sql.push_str(", ");
297 }
298 ctx.sql.push_str(sql);
300 ctx.binds.extend(binds.iter().cloned());
301 }
302}
303
304fn write_select_list<D: Dialect>(ctx: &mut Ctx, qb: &QueryBuilder<D>) {
311 if qb.select_cols.is_empty()
312 && qb.select_exprs.is_empty()
313 && qb.select_raw.is_empty()
314 && qb.select_subqueries.is_empty()
315 {
316 ctx.sql.push('*');
317 return;
318 }
319 let mut wrote_any = false;
320 for c in &qb.select_cols {
321 if wrote_any {
322 ctx.sql.push_str(", ");
323 }
324 let e = ctx.esc(c);
325 ctx.sql.push_str(&e);
326 wrote_any = true;
327 }
328 for expr in &qb.select_exprs {
329 if wrote_any {
330 ctx.sql.push_str(", ");
331 }
332 match expr {
333 SelectExpr::Agg { func, col, alias } => {
334 ctx.sql.push_str(func.as_str());
335 ctx.sql.push('(');
336 if col == "*" {
338 ctx.sql.push('*');
339 } else {
340 let c = ctx.esc(col);
341 ctx.sql.push_str(&c);
342 }
343 ctx.sql.push(')');
344 if let Some(a) = alias {
345 let a = ctx.esc(a);
346 ctx.sql.push_str(" AS ");
347 ctx.sql.push_str(&a);
348 }
349 }
350 SelectExpr::ColAs { col, alias } => {
351 let c = ctx.esc(col);
352 let a = ctx.esc(alias);
353 ctx.sql.push_str(&c);
354 ctx.sql.push_str(" AS ");
355 ctx.sql.push_str(&a);
356 }
357 }
358 wrote_any = true;
359 }
360 for (sql, binds) in &qb.select_raw {
361 if wrote_any {
362 ctx.sql.push_str(", ");
363 }
364 ctx.sql.push_str(sql);
366 ctx.binds.extend(binds.iter().cloned());
367 wrote_any = true;
368 }
369 for (alias, sub) in &qb.select_subqueries {
370 if wrote_any {
371 ctx.sql.push_str(", ");
372 }
373 ctx.sql.push('(');
374 compile_into::<D>(ctx, sub);
375 ctx.sql.push_str(") AS ");
376 let a = ctx.esc(alias);
377 ctx.sql.push_str(&a);
378 wrote_any = true;
379 }
380}
381
382fn write_joins<D: Dialect>(ctx: &mut Ctx, joins: &[Join], db: Option<&str>) {
386 for j in joins {
387 let kw = match j.kind {
388 JoinKind::Inner => " INNER JOIN ",
389 JoinKind::Left => " LEFT JOIN ",
390 JoinKind::Right => " RIGHT JOIN ",
391 JoinKind::FullOuter => " FULL OUTER JOIN ",
392 JoinKind::Cross => " CROSS JOIN ",
393 };
394 ctx.sql.push_str(kw);
395 let table = ctx.qualify(db, &j.table);
396 ctx.sql.push_str(&table);
397 if j.on.is_empty() {
398 continue;
399 }
400 ctx.sql.push_str(" ON ");
401 for (i, cond) in j.on.iter().enumerate() {
402 if i > 0 {
403 ctx.sql.push_str(" AND ");
404 }
405 match cond {
406 JoinCond::On(c, op, c2) => {
407 let l = ctx.esc(c);
408 let r = ctx.esc(c2);
409 ctx.sql.push_str(&l);
410 ctx.sql.push(' ');
411 ctx.sql.push_str(op);
412 ctx.sql.push(' ');
413 ctx.sql.push_str(&r);
414 }
415 JoinCond::OnVal(c, op, v) => {
416 let l = ctx.esc(c);
417 ctx.sql.push_str(&l);
418 ctx.sql.push(' ');
419 ctx.sql.push_str(op);
420 ctx.sql.push(' ');
421 ctx.placeholder::<D>(v.clone());
422 }
423 JoinCond::OnRaw(sql, binds) => {
424 ctx.sql.push_str(sql);
426 ctx.binds.extend(binds.iter().cloned());
427 }
428 }
429 }
430 }
431}
432
433fn write_having<D: Dialect>(ctx: &mut Ctx, havings: &[Having]) {
435 if havings.is_empty() {
436 return;
437 }
438 ctx.sql.push_str(" HAVING ");
439 for (i, h) in havings.iter().enumerate() {
440 if i > 0 {
441 ctx.sql.push_str(" AND ");
442 }
443 match h {
444 Having::Col { col, op, val } => {
445 let c = ctx.esc(col);
446 ctx.sql.push_str(&c);
447 ctx.sql.push(' ');
448 ctx.sql.push_str(op);
449 ctx.sql.push(' ');
450 ctx.placeholder::<D>(val.clone());
451 }
452 Having::Raw { sql, binds } => {
453 ctx.sql.push_str(sql);
455 ctx.binds.extend(binds.iter().cloned());
456 }
457 }
458 }
459}
460
461fn write_ctes<D: Dialect>(ctx: &mut Ctx, ctes: &[Cte<D>]) {
466 if ctes.is_empty() {
467 return;
468 }
469 ctx.sql.push_str("WITH ");
470 if ctes.iter().any(|c| c.recursive) {
471 ctx.sql.push_str("RECURSIVE ");
472 }
473 for (i, cte) in ctes.iter().enumerate() {
474 if i > 0 {
475 ctx.sql.push_str(", ");
476 }
477 let name = ctx.esc(&cte.name);
478 ctx.sql.push_str(&name);
479 ctx.sql.push_str(" AS (");
480 compile_into::<D>(ctx, &cte.query);
481 ctx.sql.push(')');
482 }
483 ctx.sql.push(' ');
484}
485
486fn write_unions<D: Dialect>(ctx: &mut Ctx, unions: &[(bool, QueryBuilder<D>)]) {
488 for (all, arm) in unions {
489 ctx.sql
490 .push_str(if *all { " UNION ALL " } else { " UNION " });
491 compile_into::<D>(ctx, arm);
492 }
493}
494
495fn write_order_by(ctx: &mut Ctx, orders: &[(String, Order)], raw: Option<&(String, Vec<Value>)>) {
499 if orders.is_empty() && raw.is_none() {
500 return;
501 }
502 ctx.sql.push_str(" ORDER BY ");
503 let cols: Vec<String> = orders
504 .iter()
505 .map(|(c, o)| {
506 let dir = match o {
507 Order::Asc => "ASC",
508 Order::Desc => "DESC",
509 };
510 format!("{} {}", ctx.esc(c), dir)
511 })
512 .collect();
513 ctx.sql.push_str(&cols.join(", "));
514 if let Some((sql, binds)) = raw {
515 if !orders.is_empty() {
516 ctx.sql.push_str(", ");
517 }
518 ctx.sql.push_str(sql);
520 ctx.binds.extend(binds.iter().cloned());
521 }
522}
523
524fn write_limit_offset<D: Dialect>(ctx: &mut Ctx, limit: Option<i64>, offset: Option<i64>) {
529 if offset.is_some() && limit.is_none() {
530 panic!("offset(...) requires limit(...)");
531 }
532 if let Some(n) = limit {
533 ctx.sql.push_str(" LIMIT ");
534 ctx.placeholder::<D>(Value::I64(n));
535 }
536 if let Some(n) = offset {
537 ctx.sql.push_str(" OFFSET ");
538 ctx.placeholder::<D>(Value::I64(n));
539 }
540}
541
542fn write_lock<D: Dialect>(ctx: &mut Ctx, lock: Option<&Lock>) {
547 let Some(lock) = lock else {
548 return;
549 };
550 if !D::supports_row_locking() {
551 return;
552 }
553 ctx.sql.push_str(match lock.strength {
554 LockStrength::Update => " FOR UPDATE",
555 LockStrength::Share => " FOR SHARE",
556 });
557 if let Some(wait) = lock.wait {
558 ctx.sql.push_str(match wait {
559 LockWait::SkipLocked => " SKIP LOCKED",
560 LockWait::NoWait => " NOWAIT",
561 });
562 }
563}
564
565fn is_omitted<D: Dialect>(p: &Predicate<D>) -> bool {
569 matches!(p, Predicate::Group { preds, .. } if preds.is_empty())
570}
571
572fn write_wheres<D: Dialect>(ctx: &mut Ctx, wheres: &[Predicate<D>]) {
573 if wheres.iter().all(is_omitted) {
575 return;
576 }
577 ctx.sql.push_str(" WHERE ");
578 write_clause_list::<D>(ctx, wheres);
579}
580
581fn write_clause_list<D: Dialect>(ctx: &mut Ctx, preds: &[Predicate<D>]) {
586 let mut wrote_any = false;
587 for p in preds.iter() {
588 if is_omitted(p) {
589 continue;
590 }
591 if wrote_any {
592 let sep = match p {
593 Predicate::Group {
594 outer_conj: Conj::Or,
595 ..
596 } => " OR ",
597 _ => " AND ",
598 };
599 ctx.sql.push_str(sep);
600 }
601 write_pred::<D>(ctx, p);
602 wrote_any = true;
603 }
604}
605
606fn write_pred<D: Dialect>(ctx: &mut Ctx, pred: &Predicate<D>) {
607 match pred {
608 Predicate::Binary { col, op, val } => {
609 let col = ctx.esc(col);
610 ctx.sql.push_str(&col);
611 ctx.sql.push(' ');
612 ctx.sql.push_str(op);
613 ctx.sql.push(' ');
614 ctx.placeholder::<D>(val.clone());
615 }
616 Predicate::In { col, neg, vals } => {
617 if vals.is_empty() {
618 ctx.sql.push_str(if *neg { "1 = 1" } else { "1 = 0" });
620 return;
621 }
622 let col = ctx.esc(col);
623 ctx.sql.push_str(&col);
624 ctx.sql.push_str(if *neg { " NOT IN (" } else { " IN (" });
625 for (i, v) in vals.iter().enumerate() {
626 if i > 0 {
627 ctx.sql.push_str(", ");
628 }
629 ctx.placeholder::<D>(v.clone());
630 }
631 ctx.sql.push(')');
632 }
633 Predicate::Null { col, neg } => {
634 let col = ctx.esc(col);
635 ctx.sql.push_str(&col);
636 ctx.sql
637 .push_str(if *neg { " IS NOT NULL" } else { " IS NULL" });
638 }
639 Predicate::Between { col, lo, hi } => {
640 let col = ctx.esc(col);
641 ctx.sql.push_str(&col);
642 ctx.sql.push_str(" BETWEEN ");
643 ctx.placeholder::<D>(lo.clone());
644 ctx.sql.push_str(" AND ");
645 ctx.placeholder::<D>(hi.clone());
646 }
647 Predicate::ILike { col, val } => {
648 let col = ctx.esc(col);
649 if D::ilike_is_native() {
650 ctx.sql.push_str(&col);
652 ctx.sql.push_str(" ILIKE ");
653 ctx.placeholder::<D>(val.clone());
654 } else {
655 ctx.sql.push_str("LOWER(");
657 ctx.sql.push_str(&col);
658 ctx.sql.push_str(") LIKE LOWER(");
659 ctx.placeholder::<D>(val.clone());
660 ctx.sql.push(')');
661 }
662 }
663 Predicate::JsonContains { col, val } => {
664 let col = ctx.esc(col);
666 ctx.sql.push_str(&col);
667 ctx.sql.push_str(" @> ");
668 ctx.placeholder::<D>(val.clone());
669 }
670 Predicate::Raw { sql, binds } => {
671 ctx.sql.push_str(sql);
673 ctx.binds.extend(binds.iter().cloned());
674 }
675 Predicate::Group {
676 outer_conj: _,
677 preds,
678 } => {
679 ctx.sql.push('(');
693 write_clause_list::<D>(ctx, preds);
694 ctx.sql.push(')');
695 }
696 Predicate::Column { lhs, op, rhs } => {
697 let l = ctx.esc(lhs);
698 let r = ctx.esc(rhs);
699 ctx.sql.push_str(&l);
700 ctx.sql.push(' ');
701 ctx.sql.push_str(op);
702 ctx.sql.push(' ');
703 ctx.sql.push_str(&r);
704 }
705 Predicate::Exists { neg, sub } => {
706 ctx.sql
707 .push_str(if *neg { "NOT EXISTS (" } else { "EXISTS (" });
708 compile_into::<D>(ctx, sub);
709 ctx.sql.push(')');
710 }
711 Predicate::InSubquery { col, neg, sub } => {
712 let col = ctx.esc(col);
713 ctx.sql.push_str(&col);
714 ctx.sql.push_str(if *neg { " NOT IN (" } else { " IN (" });
715 compile_into::<D>(ctx, sub);
716 ctx.sql.push(')');
717 }
718 }
719}