1use crate::builder::{
9 ConflictAction, Cte, Having, Join, JoinCond, JoinKind, Lock, LockStrength, LockWait, Method,
10 Order, QueryBuilder, SelectExpr, SetExpr,
11};
12use crate::dialect::{Dialect, UpsertStyle};
13use crate::error::BuildError;
14use crate::ident::escape_identifier;
15use crate::value::Value;
16use crate::where_::{Conj, Predicate};
17
18struct Ctx {
25 sql: String,
26 binds: Vec<Value>,
27 quote: char,
28}
29
30impl Ctx {
31 fn placeholder<D: Dialect>(&mut self, val: Value) {
33 self.binds.push(val);
34 D::write_placeholder(&mut self.sql, self.binds.len());
35 }
36
37 fn esc(&self, ident: &str) -> String {
45 escape_identifier(ident, self.quote)
46 }
47
48 fn qualify(&self, db: Option<&str>, table: &str) -> String {
51 match db {
52 Some(d) => format!("{}.{}", self.esc(d), self.esc(table)),
53 None => self.esc(table),
54 }
55 }
56}
57
58pub fn compile<D: Dialect>(qb: &QueryBuilder<D>) -> (String, Vec<Value>) {
66 try_compile(qb).unwrap_or_else(|e| panic!("{e}"))
67}
68
69pub fn try_compile<D: Dialect>(qb: &QueryBuilder<D>) -> Result<(String, Vec<Value>), BuildError> {
72 let mut ctx = Ctx {
73 sql: String::new(),
74 binds: Vec::new(),
75 quote: D::quote_char(),
76 };
77 compile_into::<D>(&mut ctx, qb)?;
78 Ok((ctx.sql, ctx.binds))
79}
80
81fn compile_into<D: Dialect>(ctx: &mut Ctx, qb: &QueryBuilder<D>) -> Result<(), BuildError> {
85 if let Some(e) = &qb.error {
89 return Err(e.clone());
90 }
91
92 let table = ctx.qualify(qb.db.as_deref(), &qb.table);
93
94 if qb.lock.is_some() && qb.method != Method::Select {
99 return Err(BuildError::LockRequiresSelect);
100 }
101
102 match qb.method {
103 Method::Select => {
104 write_ctes::<D>(ctx, &qb.ctes)?;
106 if !qb.distinct_on.is_empty() {
107 if !D::supports_distinct_on() {
108 return Err(BuildError::DistinctOnRequiresPostgres);
109 }
110 ctx.sql.push_str("SELECT DISTINCT ON (");
111 let cols: Vec<String> = qb.distinct_on.iter().map(|c| ctx.esc(c)).collect();
112 ctx.sql.push_str(&cols.join(", "));
113 ctx.sql.push_str(") ");
114 } else if qb.distinct {
115 ctx.sql.push_str("SELECT DISTINCT ");
116 } else {
117 ctx.sql.push_str("SELECT ");
118 }
119 write_select_list::<D>(ctx, qb)?;
120 ctx.sql.push_str(" FROM ");
121 ctx.sql.push_str(&table);
122 write_joins::<D>(ctx, &qb.joins, qb.db.as_deref());
123 write_wheres::<D>(ctx, &qb.wheres)?;
124 write_group_by(ctx, &qb.groups, qb.group_by_raw.as_ref());
125 write_having::<D>(ctx, &qb.havings);
126 write_order_by(ctx, &qb.orders, qb.order_by_raw.as_ref());
127 write_limit_offset::<D>(ctx, qb.limit, qb.offset)?;
128 write_unions::<D>(ctx, &qb.unions)?;
129 write_lock::<D>(ctx, qb.lock.as_ref(), !qb.unions.is_empty())?;
130 }
131 Method::Insert => {
132 if qb.set.is_empty() && qb.insert_rows.is_empty() {
133 return Err(BuildError::EmptyInsert);
134 }
135
136 let mut single_rows: Vec<&(String, Value)> = qb.set.iter().collect();
140 single_rows.sort_by(|a, b| a.0.cmp(&b.0));
141 let sorted_cols: Vec<&str> = if !qb.insert_rows.is_empty() {
142 let mut cols: Vec<&str> =
143 qb.insert_rows[0].iter().map(|(k, _)| k.as_str()).collect();
144 cols.sort_unstable();
145 cols
146 } else {
147 single_rows.iter().map(|(k, _)| k.as_str()).collect()
148 };
149
150 let mysql_ignore = D::upsert_style() == UpsertStyle::OnDuplicateKey
153 && matches!(
154 qb.on_conflict.as_ref().map(|c| c.action),
155 Some(ConflictAction::DoNothing)
156 );
157 if mysql_ignore {
158 ctx.sql.push_str("INSERT IGNORE INTO ");
159 } else {
160 ctx.sql.push_str("INSERT INTO ");
161 }
162 ctx.sql.push_str(&table);
163 ctx.sql.push_str(" (");
164 let cols: Vec<String> = sorted_cols.iter().map(|k| ctx.esc(k)).collect();
165 ctx.sql.push_str(&cols.join(", "));
166 ctx.sql.push_str(") VALUES ");
167
168 if !qb.insert_rows.is_empty() {
169 for (ri, row) in qb.insert_rows.iter().enumerate() {
172 if ri > 0 {
173 ctx.sql.push_str(", ");
174 }
175 ctx.sql.push('(');
176 for (ci, col) in sorted_cols.iter().enumerate() {
177 if ci > 0 {
178 ctx.sql.push_str(", ");
179 }
180 let v = row
181 .iter()
182 .find(|(k, _)| k == col)
183 .map(|(_, v)| v.clone())
184 .unwrap_or(Value::Null);
185 ctx.placeholder::<D>(v);
186 }
187 ctx.sql.push(')');
188 }
189 } else {
190 ctx.sql.push('(');
193 for (i, (_, v)) in single_rows.iter().enumerate() {
194 if i > 0 {
195 ctx.sql.push_str(", ");
196 }
197 ctx.placeholder::<D>(v.clone());
198 }
199 ctx.sql.push(')');
200 }
201
202 if !mysql_ignore {
203 if let Some(oc) = &qb.on_conflict {
204 write_on_conflict::<D>(ctx, oc, &sorted_cols);
205 }
206 }
207 write_returning::<D>(ctx, &qb.returning);
208 }
209 Method::Update => {
210 if qb.set.is_empty() && qb.set_exprs.is_empty() {
211 return Err(BuildError::EmptyUpdate);
212 }
213 let mut rows: Vec<&(String, Value)> = qb.set.iter().collect();
214 rows.sort_by(|a, b| a.0.cmp(&b.0));
215 ctx.sql.push_str("UPDATE ");
216 ctx.sql.push_str(&table);
217 ctx.sql.push_str(" SET ");
218 for (i, (k, v)) in rows.iter().enumerate() {
219 if i > 0 {
220 ctx.sql.push_str(", ");
221 }
222 let col = ctx.esc(k);
223 ctx.sql.push_str(&col);
224 ctx.sql.push_str(" = ");
225 ctx.placeholder::<D>(v.clone());
226 }
227 for (i, ex) in qb.set_exprs.iter().enumerate() {
228 if i > 0 || !rows.is_empty() {
229 ctx.sql.push_str(", ");
230 }
231 match ex {
232 SetExpr::Raw { col, expr, binds } => {
233 let col = ctx.esc(col);
234 ctx.sql.push_str(&col);
235 ctx.sql.push_str(" = ");
236 ctx.sql.push_str(expr);
238 ctx.binds.extend(binds.iter().cloned());
239 }
240 SetExpr::Step { col, by, neg } => {
241 let col = ctx.esc(col);
242 ctx.sql.push_str(&col);
243 ctx.sql.push_str(" = ");
244 ctx.sql.push_str(&col);
245 ctx.sql.push_str(if *neg { " - " } else { " + " });
246 ctx.placeholder::<D>(by.clone());
247 }
248 }
249 }
250 write_wheres::<D>(ctx, &qb.wheres)?;
251 write_returning::<D>(ctx, &qb.returning);
252 }
253 Method::Delete => {
254 ctx.sql.push_str("DELETE FROM ");
255 ctx.sql.push_str(&table);
256 write_wheres::<D>(ctx, &qb.wheres)?;
257 write_returning::<D>(ctx, &qb.returning);
258 }
259 }
260 Ok(())
261}
262
263fn write_on_conflict<D: Dialect>(
267 ctx: &mut Ctx,
268 oc: &crate::builder::OnConflict,
269 inserted: &[&str],
270) {
271 match D::upsert_style() {
272 UpsertStyle::OnDuplicateKey => {
273 ctx.sql.push_str(" ON DUPLICATE KEY UPDATE ");
276 let sets: Vec<String> = inserted
277 .iter()
278 .map(|c| {
279 let e = ctx.esc(c);
280 format!("{e} = VALUES({e})")
281 })
282 .collect();
283 ctx.sql.push_str(&sets.join(", "));
284 }
285 UpsertStyle::OnConflict => {
286 let targets = &oc.targets;
287 let set_cols: Vec<&&str> = inserted
289 .iter()
290 .filter(|c| !targets.iter().any(|t| t == **c))
291 .collect();
292 let do_update = matches!(oc.action, ConflictAction::Merge)
293 && !targets.is_empty()
294 && !set_cols.is_empty();
295
296 ctx.sql.push_str(" ON CONFLICT");
297 if !targets.is_empty() {
298 ctx.sql.push_str(" (");
299 let cols: Vec<String> = targets.iter().map(|t| ctx.esc(t)).collect();
300 ctx.sql.push_str(&cols.join(", "));
301 ctx.sql.push(')');
302 }
303 if do_update {
304 ctx.sql.push_str(" DO UPDATE SET ");
305 let sets: Vec<String> = set_cols
306 .iter()
307 .map(|c| {
308 let e = ctx.esc(c);
309 format!("{e} = EXCLUDED.{e}")
312 })
313 .collect();
314 ctx.sql.push_str(&sets.join(", "));
315 } else {
316 ctx.sql.push_str(" DO NOTHING");
317 }
318 }
319 }
320}
321
322fn write_returning<D: Dialect>(ctx: &mut Ctx, cols: &[String]) {
325 if !D::supports_returning() || cols.is_empty() {
326 return;
327 }
328 ctx.sql.push_str(" RETURNING ");
329 let parts: Vec<String> = cols
330 .iter()
331 .map(|c| if c == "*" { "*".to_owned() } else { ctx.esc(c) })
332 .collect();
333 ctx.sql.push_str(&parts.join(", "));
334}
335
336fn write_group_by(ctx: &mut Ctx, groups: &[String], raw: Option<&(String, Vec<Value>)>) {
340 if groups.is_empty() && raw.is_none() {
341 return;
342 }
343 ctx.sql.push_str(" GROUP BY ");
344 let cols: Vec<String> = groups.iter().map(|c| ctx.esc(c)).collect();
345 ctx.sql.push_str(&cols.join(", "));
346 if let Some((sql, binds)) = raw {
347 if !groups.is_empty() {
348 ctx.sql.push_str(", ");
349 }
350 ctx.sql.push_str(sql);
352 ctx.binds.extend(binds.iter().cloned());
353 }
354}
355
356fn write_select_list<D: Dialect>(ctx: &mut Ctx, qb: &QueryBuilder<D>) -> Result<(), BuildError> {
363 if qb.select_cols.is_empty()
364 && qb.select_exprs.is_empty()
365 && qb.select_raw.is_empty()
366 && qb.select_subqueries.is_empty()
367 {
368 ctx.sql.push('*');
369 return Ok(());
370 }
371 let mut wrote_any = false;
372 for c in &qb.select_cols {
373 if wrote_any {
374 ctx.sql.push_str(", ");
375 }
376 let e = ctx.esc(c);
377 ctx.sql.push_str(&e);
378 wrote_any = true;
379 }
380 for expr in &qb.select_exprs {
381 if wrote_any {
382 ctx.sql.push_str(", ");
383 }
384 match expr {
385 SelectExpr::Agg { func, col, alias } => {
386 ctx.sql.push_str(func.as_str());
387 ctx.sql.push('(');
388 if col == "*" {
390 ctx.sql.push('*');
391 } else {
392 let c = ctx.esc(col);
393 ctx.sql.push_str(&c);
394 }
395 ctx.sql.push(')');
396 if let Some(a) = alias {
397 let a = ctx.esc(a);
398 ctx.sql.push_str(" AS ");
399 ctx.sql.push_str(&a);
400 }
401 }
402 SelectExpr::ColAs { col, alias } => {
403 let c = ctx.esc(col);
404 let a = ctx.esc(alias);
405 ctx.sql.push_str(&c);
406 ctx.sql.push_str(" AS ");
407 ctx.sql.push_str(&a);
408 }
409 }
410 wrote_any = true;
411 }
412 for (sql, binds) in &qb.select_raw {
413 if wrote_any {
414 ctx.sql.push_str(", ");
415 }
416 ctx.sql.push_str(sql);
418 ctx.binds.extend(binds.iter().cloned());
419 wrote_any = true;
420 }
421 for (alias, sub) in &qb.select_subqueries {
422 if wrote_any {
423 ctx.sql.push_str(", ");
424 }
425 ctx.sql.push('(');
426 compile_into::<D>(ctx, sub)?;
427 ctx.sql.push_str(") AS ");
428 let a = ctx.esc(alias);
429 ctx.sql.push_str(&a);
430 wrote_any = true;
431 }
432 Ok(())
433}
434
435fn write_joins<D: Dialect>(ctx: &mut Ctx, joins: &[Join], db: Option<&str>) {
439 for j in joins {
440 let kw = match j.kind {
441 JoinKind::Inner => " INNER JOIN ",
442 JoinKind::Left => " LEFT JOIN ",
443 JoinKind::Right => " RIGHT JOIN ",
444 JoinKind::FullOuter => " FULL OUTER JOIN ",
445 JoinKind::Cross => " CROSS JOIN ",
446 };
447 ctx.sql.push_str(kw);
448 let table = ctx.qualify(db, &j.table);
449 ctx.sql.push_str(&table);
450 if j.on.is_empty() {
451 continue;
452 }
453 ctx.sql.push_str(" ON ");
454 for (i, cond) in j.on.iter().enumerate() {
455 if i > 0 {
456 ctx.sql.push_str(" AND ");
457 }
458 match cond {
459 JoinCond::On(c, op, c2) => {
460 let l = ctx.esc(c);
461 let r = ctx.esc(c2);
462 ctx.sql.push_str(&l);
463 ctx.sql.push(' ');
464 ctx.sql.push_str(op);
465 ctx.sql.push(' ');
466 ctx.sql.push_str(&r);
467 }
468 JoinCond::OnVal(c, op, v) => {
469 let l = ctx.esc(c);
470 ctx.sql.push_str(&l);
471 ctx.sql.push(' ');
472 ctx.sql.push_str(op);
473 ctx.sql.push(' ');
474 ctx.placeholder::<D>(v.clone());
475 }
476 JoinCond::OnRaw(sql, binds) => {
477 ctx.sql.push_str(sql);
479 ctx.binds.extend(binds.iter().cloned());
480 }
481 }
482 }
483 }
484}
485
486fn write_having<D: Dialect>(ctx: &mut Ctx, havings: &[Having]) {
488 if havings.is_empty() {
489 return;
490 }
491 ctx.sql.push_str(" HAVING ");
492 for (i, h) in havings.iter().enumerate() {
493 if i > 0 {
494 ctx.sql.push_str(" AND ");
495 }
496 match h {
497 Having::Col { col, op, val } => {
498 let c = ctx.esc(col);
499 ctx.sql.push_str(&c);
500 ctx.sql.push(' ');
501 ctx.sql.push_str(op);
502 ctx.sql.push(' ');
503 ctx.placeholder::<D>(val.clone());
504 }
505 Having::Raw { sql, binds } => {
506 ctx.sql.push_str(sql);
508 ctx.binds.extend(binds.iter().cloned());
509 }
510 }
511 }
512}
513
514fn write_ctes<D: Dialect>(ctx: &mut Ctx, ctes: &[Cte<D>]) -> Result<(), BuildError> {
519 if ctes.is_empty() {
520 return Ok(());
521 }
522 ctx.sql.push_str("WITH ");
523 if ctes.iter().any(|c| c.recursive) {
524 ctx.sql.push_str("RECURSIVE ");
525 }
526 for (i, cte) in ctes.iter().enumerate() {
527 if i > 0 {
528 ctx.sql.push_str(", ");
529 }
530 let name = ctx.esc(&cte.name);
531 ctx.sql.push_str(&name);
532 ctx.sql.push_str(" AS (");
533 compile_into::<D>(ctx, &cte.query)?;
534 ctx.sql.push(')');
535 }
536 ctx.sql.push(' ');
537 Ok(())
538}
539
540fn write_unions<D: Dialect>(
542 ctx: &mut Ctx,
543 unions: &[(bool, QueryBuilder<D>)],
544) -> Result<(), BuildError> {
545 for (all, arm) in unions {
546 ctx.sql
547 .push_str(if *all { " UNION ALL " } else { " UNION " });
548 compile_into::<D>(ctx, arm)?;
549 }
550 Ok(())
551}
552
553fn write_order_by(ctx: &mut Ctx, orders: &[(String, Order)], raw: Option<&(String, Vec<Value>)>) {
557 if orders.is_empty() && raw.is_none() {
558 return;
559 }
560 ctx.sql.push_str(" ORDER BY ");
561 let cols: Vec<String> = orders
562 .iter()
563 .map(|(c, o)| {
564 let dir = match o {
565 Order::Asc => "ASC",
566 Order::Desc => "DESC",
567 };
568 format!("{} {}", ctx.esc(c), dir)
569 })
570 .collect();
571 ctx.sql.push_str(&cols.join(", "));
572 if let Some((sql, binds)) = raw {
573 if !orders.is_empty() {
574 ctx.sql.push_str(", ");
575 }
576 ctx.sql.push_str(sql);
578 ctx.binds.extend(binds.iter().cloned());
579 }
580}
581
582fn write_limit_offset<D: Dialect>(
587 ctx: &mut Ctx,
588 limit: Option<i64>,
589 offset: Option<i64>,
590) -> Result<(), BuildError> {
591 if offset.is_some() && limit.is_none() {
592 return Err(BuildError::OffsetWithoutLimit);
593 }
594 if let Some(n) = limit {
595 ctx.sql.push_str(" LIMIT ");
596 ctx.placeholder::<D>(Value::I64(n));
597 }
598 if let Some(n) = offset {
599 ctx.sql.push_str(" OFFSET ");
600 ctx.placeholder::<D>(Value::I64(n));
601 }
602 Ok(())
603}
604
605fn write_lock<D: Dialect>(
611 ctx: &mut Ctx,
612 lock: Option<&Lock>,
613 has_unions: bool,
614) -> Result<(), BuildError> {
615 let Some(lock) = lock else {
616 return Ok(());
617 };
618 if !D::supports_row_locking() {
619 return Ok(());
620 }
621 if has_unions {
625 return Err(BuildError::LockWithUnion);
626 }
627 ctx.sql.push_str(match lock.strength {
628 LockStrength::Update => " FOR UPDATE",
629 LockStrength::Share => " FOR SHARE",
630 });
631 if let Some(wait) = lock.wait {
632 ctx.sql.push_str(match wait {
633 LockWait::SkipLocked => " SKIP LOCKED",
634 LockWait::NoWait => " NOWAIT",
635 });
636 }
637 Ok(())
638}
639
640fn is_omitted<D: Dialect>(p: &Predicate<D>) -> bool {
644 matches!(p, Predicate::Group { preds, .. } if preds.is_empty())
645}
646
647fn write_wheres<D: Dialect>(ctx: &mut Ctx, wheres: &[Predicate<D>]) -> Result<(), BuildError> {
648 if wheres.iter().all(is_omitted) {
650 return Ok(());
651 }
652 ctx.sql.push_str(" WHERE ");
653 write_clause_list::<D>(ctx, wheres)
654}
655
656fn write_clause_list<D: Dialect>(ctx: &mut Ctx, preds: &[Predicate<D>]) -> Result<(), BuildError> {
661 let mut wrote_any = false;
662 for p in preds.iter() {
663 if is_omitted(p) {
664 continue;
665 }
666 if wrote_any {
667 let sep = match p {
668 Predicate::Group {
669 outer_conj: Conj::Or,
670 ..
671 } => " OR ",
672 _ => " AND ",
673 };
674 ctx.sql.push_str(sep);
675 }
676 write_pred::<D>(ctx, p)?;
677 wrote_any = true;
678 }
679 Ok(())
680}
681
682fn write_pred<D: Dialect>(ctx: &mut Ctx, pred: &Predicate<D>) -> Result<(), BuildError> {
683 match pred {
684 Predicate::Binary { col, op, val } => {
685 let col = ctx.esc(col);
686 ctx.sql.push_str(&col);
687 ctx.sql.push(' ');
688 ctx.sql.push_str(op);
689 ctx.sql.push(' ');
690 ctx.placeholder::<D>(val.clone());
691 }
692 Predicate::In { col, neg, vals } => {
693 if vals.is_empty() {
694 ctx.sql.push_str(if *neg { "1 = 1" } else { "1 = 0" });
696 return Ok(());
697 }
698 let col = ctx.esc(col);
699 ctx.sql.push_str(&col);
700 ctx.sql.push_str(if *neg { " NOT IN (" } else { " IN (" });
701 for (i, v) in vals.iter().enumerate() {
702 if i > 0 {
703 ctx.sql.push_str(", ");
704 }
705 ctx.placeholder::<D>(v.clone());
706 }
707 ctx.sql.push(')');
708 }
709 Predicate::Null { col, neg } => {
710 let col = ctx.esc(col);
711 ctx.sql.push_str(&col);
712 ctx.sql
713 .push_str(if *neg { " IS NOT NULL" } else { " IS NULL" });
714 }
715 Predicate::Between { col, lo, hi } => {
716 let col = ctx.esc(col);
717 ctx.sql.push_str(&col);
718 ctx.sql.push_str(" BETWEEN ");
719 ctx.placeholder::<D>(lo.clone());
720 ctx.sql.push_str(" AND ");
721 ctx.placeholder::<D>(hi.clone());
722 }
723 Predicate::ILike { col, val } => {
724 let col = ctx.esc(col);
725 if D::ilike_is_native() {
726 ctx.sql.push_str(&col);
728 ctx.sql.push_str(" ILIKE ");
729 ctx.placeholder::<D>(val.clone());
730 } else {
731 ctx.sql.push_str("LOWER(");
733 ctx.sql.push_str(&col);
734 ctx.sql.push_str(") LIKE LOWER(");
735 ctx.placeholder::<D>(val.clone());
736 ctx.sql.push(')');
737 }
738 }
739 Predicate::JsonContains { col, val } => {
740 let col = ctx.esc(col);
742 ctx.sql.push_str(&col);
743 ctx.sql.push_str(" @> ");
744 ctx.placeholder::<D>(val.clone());
745 }
746 Predicate::Raw { sql, binds } => {
747 ctx.sql.push_str(sql);
749 ctx.binds.extend(binds.iter().cloned());
750 }
751 Predicate::Group {
752 outer_conj: _,
753 preds,
754 } => {
755 ctx.sql.push('(');
769 write_clause_list::<D>(ctx, preds)?;
770 ctx.sql.push(')');
771 }
772 Predicate::Column { lhs, op, rhs } => {
773 let l = ctx.esc(lhs);
774 let r = ctx.esc(rhs);
775 ctx.sql.push_str(&l);
776 ctx.sql.push(' ');
777 ctx.sql.push_str(op);
778 ctx.sql.push(' ');
779 ctx.sql.push_str(&r);
780 }
781 Predicate::Exists { neg, sub } => {
782 ctx.sql
783 .push_str(if *neg { "NOT EXISTS (" } else { "EXISTS (" });
784 compile_into::<D>(ctx, sub)?;
785 ctx.sql.push(')');
786 }
787 Predicate::InSubquery { col, neg, sub } => {
788 let col = ctx.esc(col);
789 ctx.sql.push_str(&col);
790 ctx.sql.push_str(if *neg { " NOT IN (" } else { " IN (" });
791 compile_into::<D>(ctx, sub)?;
792 ctx.sql.push(')');
793 }
794 }
795 Ok(())
796}