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 if qb.lock.is_some() && qb.method != Method::Select {
79 panic!("for_update()/for_share() is only valid on SELECT");
80 }
81
82 match qb.method {
83 Method::Select => {
84 write_ctes::<D>(ctx, &qb.ctes);
86 if !qb.distinct_on.is_empty() {
87 if !D::supports_distinct_on() {
88 panic!("DISTINCT ON requires PostgreSQL");
89 }
90 ctx.sql.push_str("SELECT DISTINCT ON (");
91 let cols: Vec<String> = qb.distinct_on.iter().map(|c| ctx.esc(c)).collect();
92 ctx.sql.push_str(&cols.join(", "));
93 ctx.sql.push_str(") ");
94 } else if qb.distinct {
95 ctx.sql.push_str("SELECT DISTINCT ");
96 } else {
97 ctx.sql.push_str("SELECT ");
98 }
99 write_select_list::<D>(ctx, qb);
100 ctx.sql.push_str(" FROM ");
101 ctx.sql.push_str(&table);
102 write_joins::<D>(ctx, &qb.joins, qb.db.as_deref());
103 write_wheres::<D>(ctx, &qb.wheres);
104 write_group_by(ctx, &qb.groups, qb.group_by_raw.as_ref());
105 write_having::<D>(ctx, &qb.havings);
106 write_order_by(ctx, &qb.orders, qb.order_by_raw.as_ref());
107 write_limit_offset::<D>(ctx, qb.limit, qb.offset);
108 write_unions::<D>(ctx, &qb.unions);
109 write_lock::<D>(ctx, qb.lock.as_ref(), !qb.unions.is_empty());
110 }
111 Method::Insert => {
112 if qb.set.is_empty() && qb.insert_rows.is_empty() {
113 panic!("insert() requires at least one column");
114 }
115
116 let mut single_rows: Vec<&(String, Value)> = qb.set.iter().collect();
120 single_rows.sort_by(|a, b| a.0.cmp(&b.0));
121 let sorted_cols: Vec<&str> = if !qb.insert_rows.is_empty() {
122 let mut cols: Vec<&str> =
123 qb.insert_rows[0].iter().map(|(k, _)| k.as_str()).collect();
124 cols.sort_unstable();
125 cols
126 } else {
127 single_rows.iter().map(|(k, _)| k.as_str()).collect()
128 };
129
130 let mysql_ignore = D::upsert_style() == UpsertStyle::OnDuplicateKey
133 && matches!(
134 qb.on_conflict.as_ref().map(|c| c.action),
135 Some(ConflictAction::DoNothing)
136 );
137 if mysql_ignore {
138 ctx.sql.push_str("INSERT IGNORE INTO ");
139 } else {
140 ctx.sql.push_str("INSERT INTO ");
141 }
142 ctx.sql.push_str(&table);
143 ctx.sql.push_str(" (");
144 let cols: Vec<String> = sorted_cols.iter().map(|k| ctx.esc(k)).collect();
145 ctx.sql.push_str(&cols.join(", "));
146 ctx.sql.push_str(") VALUES ");
147
148 if !qb.insert_rows.is_empty() {
149 for (ri, row) in qb.insert_rows.iter().enumerate() {
152 if ri > 0 {
153 ctx.sql.push_str(", ");
154 }
155 ctx.sql.push('(');
156 for (ci, col) in sorted_cols.iter().enumerate() {
157 if ci > 0 {
158 ctx.sql.push_str(", ");
159 }
160 let v = row
161 .iter()
162 .find(|(k, _)| k == col)
163 .map(|(_, v)| v.clone())
164 .unwrap_or(Value::Null);
165 ctx.placeholder::<D>(v);
166 }
167 ctx.sql.push(')');
168 }
169 } else {
170 ctx.sql.push('(');
173 for (i, (_, v)) in single_rows.iter().enumerate() {
174 if i > 0 {
175 ctx.sql.push_str(", ");
176 }
177 ctx.placeholder::<D>(v.clone());
178 }
179 ctx.sql.push(')');
180 }
181
182 if !mysql_ignore {
183 if let Some(oc) = &qb.on_conflict {
184 write_on_conflict::<D>(ctx, oc, &sorted_cols);
185 }
186 }
187 write_returning::<D>(ctx, &qb.returning);
188 }
189 Method::Update => {
190 if qb.set.is_empty() {
191 panic!("update() requires at least one column");
192 }
193 let mut rows: Vec<&(String, Value)> = qb.set.iter().collect();
194 rows.sort_by(|a, b| a.0.cmp(&b.0));
195 ctx.sql.push_str("UPDATE ");
196 ctx.sql.push_str(&table);
197 ctx.sql.push_str(" SET ");
198 for (i, (k, v)) in rows.iter().enumerate() {
199 if i > 0 {
200 ctx.sql.push_str(", ");
201 }
202 let col = ctx.esc(k);
203 ctx.sql.push_str(&col);
204 ctx.sql.push_str(" = ");
205 ctx.placeholder::<D>(v.clone());
206 }
207 write_wheres::<D>(ctx, &qb.wheres);
208 write_returning::<D>(ctx, &qb.returning);
209 }
210 Method::Delete => {
211 ctx.sql.push_str("DELETE FROM ");
212 ctx.sql.push_str(&table);
213 write_wheres::<D>(ctx, &qb.wheres);
214 write_returning::<D>(ctx, &qb.returning);
215 }
216 }
217}
218
219fn write_on_conflict<D: Dialect>(
223 ctx: &mut Ctx,
224 oc: &crate::builder::OnConflict,
225 inserted: &[&str],
226) {
227 match D::upsert_style() {
228 UpsertStyle::OnDuplicateKey => {
229 ctx.sql.push_str(" ON DUPLICATE KEY UPDATE ");
232 let sets: Vec<String> = inserted
233 .iter()
234 .map(|c| {
235 let e = ctx.esc(c);
236 format!("{e} = VALUES({e})")
237 })
238 .collect();
239 ctx.sql.push_str(&sets.join(", "));
240 }
241 UpsertStyle::OnConflict => {
242 let targets = &oc.targets;
243 let set_cols: Vec<&&str> = inserted
245 .iter()
246 .filter(|c| !targets.iter().any(|t| t == **c))
247 .collect();
248 let do_update = matches!(oc.action, ConflictAction::Merge)
249 && !targets.is_empty()
250 && !set_cols.is_empty();
251
252 ctx.sql.push_str(" ON CONFLICT");
253 if !targets.is_empty() {
254 ctx.sql.push_str(" (");
255 let cols: Vec<String> = targets.iter().map(|t| ctx.esc(t)).collect();
256 ctx.sql.push_str(&cols.join(", "));
257 ctx.sql.push(')');
258 }
259 if do_update {
260 ctx.sql.push_str(" DO UPDATE SET ");
261 let sets: Vec<String> = set_cols
262 .iter()
263 .map(|c| {
264 let e = ctx.esc(c);
265 format!("{e} = EXCLUDED.{e}")
268 })
269 .collect();
270 ctx.sql.push_str(&sets.join(", "));
271 } else {
272 ctx.sql.push_str(" DO NOTHING");
273 }
274 }
275 }
276}
277
278fn write_returning<D: Dialect>(ctx: &mut Ctx, cols: &[String]) {
281 if !D::supports_returning() || cols.is_empty() {
282 return;
283 }
284 ctx.sql.push_str(" RETURNING ");
285 let parts: Vec<String> = cols
286 .iter()
287 .map(|c| if c == "*" { "*".to_owned() } else { ctx.esc(c) })
288 .collect();
289 ctx.sql.push_str(&parts.join(", "));
290}
291
292fn write_group_by(ctx: &mut Ctx, groups: &[String], raw: Option<&(String, Vec<Value>)>) {
296 if groups.is_empty() && raw.is_none() {
297 return;
298 }
299 ctx.sql.push_str(" GROUP BY ");
300 let cols: Vec<String> = groups.iter().map(|c| ctx.esc(c)).collect();
301 ctx.sql.push_str(&cols.join(", "));
302 if let Some((sql, binds)) = raw {
303 if !groups.is_empty() {
304 ctx.sql.push_str(", ");
305 }
306 ctx.sql.push_str(sql);
308 ctx.binds.extend(binds.iter().cloned());
309 }
310}
311
312fn write_select_list<D: Dialect>(ctx: &mut Ctx, qb: &QueryBuilder<D>) {
319 if qb.select_cols.is_empty()
320 && qb.select_exprs.is_empty()
321 && qb.select_raw.is_empty()
322 && qb.select_subqueries.is_empty()
323 {
324 ctx.sql.push('*');
325 return;
326 }
327 let mut wrote_any = false;
328 for c in &qb.select_cols {
329 if wrote_any {
330 ctx.sql.push_str(", ");
331 }
332 let e = ctx.esc(c);
333 ctx.sql.push_str(&e);
334 wrote_any = true;
335 }
336 for expr in &qb.select_exprs {
337 if wrote_any {
338 ctx.sql.push_str(", ");
339 }
340 match expr {
341 SelectExpr::Agg { func, col, alias } => {
342 ctx.sql.push_str(func.as_str());
343 ctx.sql.push('(');
344 if col == "*" {
346 ctx.sql.push('*');
347 } else {
348 let c = ctx.esc(col);
349 ctx.sql.push_str(&c);
350 }
351 ctx.sql.push(')');
352 if let Some(a) = alias {
353 let a = ctx.esc(a);
354 ctx.sql.push_str(" AS ");
355 ctx.sql.push_str(&a);
356 }
357 }
358 SelectExpr::ColAs { col, alias } => {
359 let c = ctx.esc(col);
360 let a = ctx.esc(alias);
361 ctx.sql.push_str(&c);
362 ctx.sql.push_str(" AS ");
363 ctx.sql.push_str(&a);
364 }
365 }
366 wrote_any = true;
367 }
368 for (sql, binds) in &qb.select_raw {
369 if wrote_any {
370 ctx.sql.push_str(", ");
371 }
372 ctx.sql.push_str(sql);
374 ctx.binds.extend(binds.iter().cloned());
375 wrote_any = true;
376 }
377 for (alias, sub) in &qb.select_subqueries {
378 if wrote_any {
379 ctx.sql.push_str(", ");
380 }
381 ctx.sql.push('(');
382 compile_into::<D>(ctx, sub);
383 ctx.sql.push_str(") AS ");
384 let a = ctx.esc(alias);
385 ctx.sql.push_str(&a);
386 wrote_any = true;
387 }
388}
389
390fn write_joins<D: Dialect>(ctx: &mut Ctx, joins: &[Join], db: Option<&str>) {
394 for j in joins {
395 let kw = match j.kind {
396 JoinKind::Inner => " INNER JOIN ",
397 JoinKind::Left => " LEFT JOIN ",
398 JoinKind::Right => " RIGHT JOIN ",
399 JoinKind::FullOuter => " FULL OUTER JOIN ",
400 JoinKind::Cross => " CROSS JOIN ",
401 };
402 ctx.sql.push_str(kw);
403 let table = ctx.qualify(db, &j.table);
404 ctx.sql.push_str(&table);
405 if j.on.is_empty() {
406 continue;
407 }
408 ctx.sql.push_str(" ON ");
409 for (i, cond) in j.on.iter().enumerate() {
410 if i > 0 {
411 ctx.sql.push_str(" AND ");
412 }
413 match cond {
414 JoinCond::On(c, op, c2) => {
415 let l = ctx.esc(c);
416 let r = ctx.esc(c2);
417 ctx.sql.push_str(&l);
418 ctx.sql.push(' ');
419 ctx.sql.push_str(op);
420 ctx.sql.push(' ');
421 ctx.sql.push_str(&r);
422 }
423 JoinCond::OnVal(c, op, v) => {
424 let l = ctx.esc(c);
425 ctx.sql.push_str(&l);
426 ctx.sql.push(' ');
427 ctx.sql.push_str(op);
428 ctx.sql.push(' ');
429 ctx.placeholder::<D>(v.clone());
430 }
431 JoinCond::OnRaw(sql, binds) => {
432 ctx.sql.push_str(sql);
434 ctx.binds.extend(binds.iter().cloned());
435 }
436 }
437 }
438 }
439}
440
441fn write_having<D: Dialect>(ctx: &mut Ctx, havings: &[Having]) {
443 if havings.is_empty() {
444 return;
445 }
446 ctx.sql.push_str(" HAVING ");
447 for (i, h) in havings.iter().enumerate() {
448 if i > 0 {
449 ctx.sql.push_str(" AND ");
450 }
451 match h {
452 Having::Col { col, op, val } => {
453 let c = ctx.esc(col);
454 ctx.sql.push_str(&c);
455 ctx.sql.push(' ');
456 ctx.sql.push_str(op);
457 ctx.sql.push(' ');
458 ctx.placeholder::<D>(val.clone());
459 }
460 Having::Raw { sql, binds } => {
461 ctx.sql.push_str(sql);
463 ctx.binds.extend(binds.iter().cloned());
464 }
465 }
466 }
467}
468
469fn write_ctes<D: Dialect>(ctx: &mut Ctx, ctes: &[Cte<D>]) {
474 if ctes.is_empty() {
475 return;
476 }
477 ctx.sql.push_str("WITH ");
478 if ctes.iter().any(|c| c.recursive) {
479 ctx.sql.push_str("RECURSIVE ");
480 }
481 for (i, cte) in ctes.iter().enumerate() {
482 if i > 0 {
483 ctx.sql.push_str(", ");
484 }
485 let name = ctx.esc(&cte.name);
486 ctx.sql.push_str(&name);
487 ctx.sql.push_str(" AS (");
488 compile_into::<D>(ctx, &cte.query);
489 ctx.sql.push(')');
490 }
491 ctx.sql.push(' ');
492}
493
494fn write_unions<D: Dialect>(ctx: &mut Ctx, unions: &[(bool, QueryBuilder<D>)]) {
496 for (all, arm) in unions {
497 ctx.sql
498 .push_str(if *all { " UNION ALL " } else { " UNION " });
499 compile_into::<D>(ctx, arm);
500 }
501}
502
503fn write_order_by(ctx: &mut Ctx, orders: &[(String, Order)], raw: Option<&(String, Vec<Value>)>) {
507 if orders.is_empty() && raw.is_none() {
508 return;
509 }
510 ctx.sql.push_str(" ORDER BY ");
511 let cols: Vec<String> = orders
512 .iter()
513 .map(|(c, o)| {
514 let dir = match o {
515 Order::Asc => "ASC",
516 Order::Desc => "DESC",
517 };
518 format!("{} {}", ctx.esc(c), dir)
519 })
520 .collect();
521 ctx.sql.push_str(&cols.join(", "));
522 if let Some((sql, binds)) = raw {
523 if !orders.is_empty() {
524 ctx.sql.push_str(", ");
525 }
526 ctx.sql.push_str(sql);
528 ctx.binds.extend(binds.iter().cloned());
529 }
530}
531
532fn write_limit_offset<D: Dialect>(ctx: &mut Ctx, limit: Option<i64>, offset: Option<i64>) {
537 if offset.is_some() && limit.is_none() {
538 panic!("offset(...) requires limit(...)");
539 }
540 if let Some(n) = limit {
541 ctx.sql.push_str(" LIMIT ");
542 ctx.placeholder::<D>(Value::I64(n));
543 }
544 if let Some(n) = offset {
545 ctx.sql.push_str(" OFFSET ");
546 ctx.placeholder::<D>(Value::I64(n));
547 }
548}
549
550fn write_lock<D: Dialect>(ctx: &mut Ctx, lock: Option<&Lock>, has_unions: bool) {
556 let Some(lock) = lock else {
557 return;
558 };
559 if !D::supports_row_locking() {
560 return;
561 }
562 if has_unions {
566 panic!("for_update()/for_share() cannot be combined with UNION");
567 }
568 ctx.sql.push_str(match lock.strength {
569 LockStrength::Update => " FOR UPDATE",
570 LockStrength::Share => " FOR SHARE",
571 });
572 if let Some(wait) = lock.wait {
573 ctx.sql.push_str(match wait {
574 LockWait::SkipLocked => " SKIP LOCKED",
575 LockWait::NoWait => " NOWAIT",
576 });
577 }
578}
579
580fn is_omitted<D: Dialect>(p: &Predicate<D>) -> bool {
584 matches!(p, Predicate::Group { preds, .. } if preds.is_empty())
585}
586
587fn write_wheres<D: Dialect>(ctx: &mut Ctx, wheres: &[Predicate<D>]) {
588 if wheres.iter().all(is_omitted) {
590 return;
591 }
592 ctx.sql.push_str(" WHERE ");
593 write_clause_list::<D>(ctx, wheres);
594}
595
596fn write_clause_list<D: Dialect>(ctx: &mut Ctx, preds: &[Predicate<D>]) {
601 let mut wrote_any = false;
602 for p in preds.iter() {
603 if is_omitted(p) {
604 continue;
605 }
606 if wrote_any {
607 let sep = match p {
608 Predicate::Group {
609 outer_conj: Conj::Or,
610 ..
611 } => " OR ",
612 _ => " AND ",
613 };
614 ctx.sql.push_str(sep);
615 }
616 write_pred::<D>(ctx, p);
617 wrote_any = true;
618 }
619}
620
621fn write_pred<D: Dialect>(ctx: &mut Ctx, pred: &Predicate<D>) {
622 match pred {
623 Predicate::Binary { col, op, val } => {
624 let col = ctx.esc(col);
625 ctx.sql.push_str(&col);
626 ctx.sql.push(' ');
627 ctx.sql.push_str(op);
628 ctx.sql.push(' ');
629 ctx.placeholder::<D>(val.clone());
630 }
631 Predicate::In { col, neg, vals } => {
632 if vals.is_empty() {
633 ctx.sql.push_str(if *neg { "1 = 1" } else { "1 = 0" });
635 return;
636 }
637 let col = ctx.esc(col);
638 ctx.sql.push_str(&col);
639 ctx.sql.push_str(if *neg { " NOT IN (" } else { " IN (" });
640 for (i, v) in vals.iter().enumerate() {
641 if i > 0 {
642 ctx.sql.push_str(", ");
643 }
644 ctx.placeholder::<D>(v.clone());
645 }
646 ctx.sql.push(')');
647 }
648 Predicate::Null { col, neg } => {
649 let col = ctx.esc(col);
650 ctx.sql.push_str(&col);
651 ctx.sql
652 .push_str(if *neg { " IS NOT NULL" } else { " IS NULL" });
653 }
654 Predicate::Between { col, lo, hi } => {
655 let col = ctx.esc(col);
656 ctx.sql.push_str(&col);
657 ctx.sql.push_str(" BETWEEN ");
658 ctx.placeholder::<D>(lo.clone());
659 ctx.sql.push_str(" AND ");
660 ctx.placeholder::<D>(hi.clone());
661 }
662 Predicate::ILike { col, val } => {
663 let col = ctx.esc(col);
664 if D::ilike_is_native() {
665 ctx.sql.push_str(&col);
667 ctx.sql.push_str(" ILIKE ");
668 ctx.placeholder::<D>(val.clone());
669 } else {
670 ctx.sql.push_str("LOWER(");
672 ctx.sql.push_str(&col);
673 ctx.sql.push_str(") LIKE LOWER(");
674 ctx.placeholder::<D>(val.clone());
675 ctx.sql.push(')');
676 }
677 }
678 Predicate::JsonContains { col, val } => {
679 let col = ctx.esc(col);
681 ctx.sql.push_str(&col);
682 ctx.sql.push_str(" @> ");
683 ctx.placeholder::<D>(val.clone());
684 }
685 Predicate::Raw { sql, binds } => {
686 ctx.sql.push_str(sql);
688 ctx.binds.extend(binds.iter().cloned());
689 }
690 Predicate::Group {
691 outer_conj: _,
692 preds,
693 } => {
694 ctx.sql.push('(');
708 write_clause_list::<D>(ctx, preds);
709 ctx.sql.push(')');
710 }
711 Predicate::Column { lhs, op, rhs } => {
712 let l = ctx.esc(lhs);
713 let r = ctx.esc(rhs);
714 ctx.sql.push_str(&l);
715 ctx.sql.push(' ');
716 ctx.sql.push_str(op);
717 ctx.sql.push(' ');
718 ctx.sql.push_str(&r);
719 }
720 Predicate::Exists { neg, sub } => {
721 ctx.sql
722 .push_str(if *neg { "NOT EXISTS (" } else { "EXISTS (" });
723 compile_into::<D>(ctx, sub);
724 ctx.sql.push(')');
725 }
726 Predicate::InSubquery { col, neg, sub } => {
727 let col = ctx.esc(col);
728 ctx.sql.push_str(&col);
729 ctx.sql.push_str(if *neg { " NOT IN (" } else { " IN (" });
730 compile_into::<D>(ctx, sub);
731 ctx.sql.push(')');
732 }
733 }
734}