1use std::fmt::Write as _;
10
11use uqa_core::{TemporalValue, Value};
12
13use crate::ast::{
14 CteMaterialization, DeleteStmt, Expr, FrameBound, FrameMode, FromClause, InsertStmt, JoinKind,
15 LockWait, NullsOrder, OnConflictAction, OrderBy, Projection, ReturningAliases, SelectStmt,
16 SetOpKind, Statement, TableFunction, UpdateStmt, WindowReferenceKind, WindowSpec, CTE,
17};
18use crate::SQLError;
19
20pub fn statement_sql(statement: &Statement) -> Result<String, SQLError> {
22 match statement {
23 Statement::Select(select) => Ok(select_sql(select)),
24 Statement::Insert(insert) => Ok(insert_sql(insert)),
25 Statement::Update(update) => Ok(update_sql(update)),
26 Statement::Delete(delete) => Ok(delete_sql(delete)),
27 Statement::Notify { channel, payload } => {
28 let payload = if payload.is_empty() {
29 String::new()
30 } else {
31 format!(", {}", string_literal(payload))
32 };
33 Ok(format!("NOTIFY {}{payload}", ident(channel)))
34 }
35 _ => Err(SQLError::Internal(
36 "durable rewrite-rule action has an unsupported statement kind".into(),
37 )),
38 }
39}
40
41pub fn expression_sql(expression: &Expr) -> Result<String, SQLError> {
43 render_expr(expression)
44}
45
46fn insert_sql(statement: &InsertStmt) -> String {
47 let mut rendered = with_sql(&statement.with);
48 rendered.push_str("INSERT INTO ");
49 rendered.push_str(&only_relation(
50 &statement.table,
51 statement.include_descendants,
52 ));
53 render_target_alias(&mut rendered, &statement.table, &statement.target_qualifier);
54 if !statement.columns.is_empty() {
55 rendered.push_str(" (");
56 rendered.push_str(&ident_list(&statement.columns));
57 rendered.push(')');
58 }
59 if statement.rows.as_slice() == [Vec::new()] {
60 rendered.push_str(" DEFAULT VALUES");
61 } else if !statement.rows.is_empty() {
62 rendered.push_str(" VALUES ");
63 rendered.push_str(&rows_sql(&statement.rows));
64 } else if let Some(select) = statement.select_source.as_deref() {
65 rendered.push(' ');
66 rendered.push_str(&select_sql(select));
67 }
68 if let Some(conflict) = &statement.on_conflict {
69 rendered.push_str(" ON CONFLICT");
70 if let Some(constraint) = &conflict.constraint {
71 rendered.push_str(" ON CONSTRAINT ");
72 rendered.push_str(&ident(constraint));
73 }
74 if !conflict.conflict_columns.is_empty() || !conflict.expressions.is_empty() {
75 rendered.push_str(" (");
76 let keys = conflict
77 .conflict_columns
78 .iter()
79 .map(|name| ident(name))
80 .chain(
81 conflict
82 .expressions
83 .iter()
84 .map(|expr| format!("({})", expr_sql(expr))),
85 )
86 .collect::<Vec<_>>();
87 rendered.push_str(&keys.join(", "));
88 rendered.push(')');
89 }
90 if let Some(predicate) = &conflict.predicate {
91 rendered.push_str(" WHERE ");
92 rendered.push_str(&expr_sql(predicate));
93 }
94 match &conflict.action {
95 OnConflictAction::Nothing => rendered.push_str(" DO NOTHING"),
96 OnConflictAction::Update {
97 assignments,
98 r#where,
99 } => {
100 rendered.push_str(" DO UPDATE SET ");
101 rendered.push_str(&assignments_sql(assignments));
102 if let Some(predicate) = r#where {
103 rendered.push_str(" WHERE ");
104 rendered.push_str(&expr_sql(predicate));
105 }
106 }
107 }
108 }
109 render_returning(
110 &mut rendered,
111 &statement.returning_aliases,
112 &statement.returning,
113 );
114 rendered
115}
116
117fn update_sql(statement: &UpdateStmt) -> String {
118 let mut rendered = with_sql(&statement.with);
119 rendered.push_str("UPDATE ");
120 rendered.push_str(&only_relation(
121 &statement.table,
122 statement.include_descendants,
123 ));
124 render_target_alias(&mut rendered, &statement.table, &statement.target_qualifier);
125 rendered.push_str(" SET ");
126 rendered.push_str(&assignments_sql(&statement.assignments));
127 if let Some(source) = &statement.from {
128 rendered.push_str(" FROM ");
129 rendered.push_str(&from_sql(source));
130 }
131 if let Some(predicate) = &statement.r#where {
132 rendered.push_str(" WHERE ");
133 rendered.push_str(&expr_sql(predicate));
134 }
135 render_returning(
136 &mut rendered,
137 &statement.returning_aliases,
138 &statement.returning,
139 );
140 rendered
141}
142
143fn delete_sql(statement: &DeleteStmt) -> String {
144 let mut rendered = with_sql(&statement.with);
145 rendered.push_str("DELETE FROM ");
146 rendered.push_str(&only_relation(
147 &statement.table,
148 statement.include_descendants,
149 ));
150 render_target_alias(&mut rendered, &statement.table, &statement.target_qualifier);
151 if let Some(source) = &statement.using {
152 rendered.push_str(" USING ");
153 rendered.push_str(&from_sql(source));
154 }
155 if let Some(predicate) = &statement.r#where {
156 rendered.push_str(" WHERE ");
157 rendered.push_str(&expr_sql(predicate));
158 }
159 render_returning(
160 &mut rendered,
161 &statement.returning_aliases,
162 &statement.returning,
163 );
164 rendered
165}
166
167fn select_sql(statement: &SelectStmt) -> String {
168 let mut rendered = with_sql(&statement.with);
169 if let Some(set) = statement.set_op.as_deref() {
170 let left = set
171 .left
172 .as_deref()
173 .map_or_else(|| select_body_sql(statement), select_sql);
174 rendered.push('(');
175 rendered.push_str(&left);
176 rendered.push_str(") ");
177 rendered.push_str(match set.kind {
178 SetOpKind::Union => "UNION",
179 SetOpKind::Intersect => "INTERSECT",
180 SetOpKind::Except => "EXCEPT",
181 });
182 if set.all {
183 rendered.push_str(" ALL");
184 }
185 rendered.push_str(" (");
186 rendered.push_str(&select_sql(&set.right));
187 rendered.push(')');
188 render_order_limit_offset(
189 &mut rendered,
190 &set.combined_order_by,
191 set.combined_limit.as_ref(),
192 set.combined_with_ties,
193 set.combined_offset.as_ref(),
194 );
195 return rendered;
196 }
197 rendered.push_str(&select_body_sql(statement));
198 rendered
199}
200
201fn select_body_sql(statement: &SelectStmt) -> String {
202 let mut rendered = String::new();
203 if statement.values.is_empty() {
204 rendered.push_str("SELECT");
205 if !statement.distinct_on.is_empty() {
206 rendered.push_str(" DISTINCT ON (");
207 rendered.push_str(&expr_list(&statement.distinct_on));
208 rendered.push(')');
209 } else if statement.distinct {
210 rendered.push_str(" DISTINCT");
211 }
212 rendered.push(' ');
213 rendered.push_str(&projections_sql(&statement.projections));
214 if let Some(source) = &statement.from {
215 rendered.push_str(" FROM ");
216 rendered.push_str(&from_sql(source));
217 }
218 if let Some(predicate) = &statement.r#where {
219 rendered.push_str(" WHERE ");
220 rendered.push_str(&expr_sql(predicate));
221 }
222 if !statement.grouping_sets.is_empty() {
223 rendered.push_str(" GROUP BY ");
224 if statement.group_distinct {
225 rendered.push_str("DISTINCT ");
226 }
227 rendered.push_str("GROUPING SETS (");
228 rendered.push_str(
229 &statement
230 .grouping_sets
231 .iter()
232 .map(|set| format!("({})", expr_list(set)))
233 .collect::<Vec<_>>()
234 .join(", "),
235 );
236 rendered.push(')');
237 } else if !statement.group_by.is_empty() {
238 rendered.push_str(" GROUP BY ");
239 if statement.group_distinct {
240 rendered.push_str("DISTINCT ");
241 }
242 rendered.push_str(&expr_list(&statement.group_by));
243 }
244 if let Some(predicate) = &statement.having {
245 rendered.push_str(" HAVING ");
246 rendered.push_str(&expr_sql(predicate));
247 }
248 } else {
249 rendered.push_str("VALUES ");
250 rendered.push_str(&rows_sql(&statement.values));
251 }
252 render_order_limit_offset(
253 &mut rendered,
254 &statement.order_by,
255 statement.limit.as_ref(),
256 statement.with_ties,
257 statement.offset.as_ref(),
258 );
259 for locking in &statement.locking {
260 rendered.push(' ');
261 rendered.push_str(locking.strength.sql_name());
262 if !locking.relations.is_empty() {
263 rendered.push_str(" OF ");
264 rendered.push_str(&ident_list(&locking.relations));
265 }
266 rendered.push_str(match locking.wait {
267 LockWait::Block => "",
268 LockWait::SkipLocked => " SKIP LOCKED",
269 LockWait::NoWait => " NOWAIT",
270 });
271 }
272 rendered
273}
274
275#[expect(
276 clippy::too_many_lines,
277 reason = "exhaustive FROM rendering keeps each AST variant visibly complete"
278)]
279fn from_sql(source: &FromClause) -> String {
280 match source {
281 FromClause::Table {
282 name,
283 alias,
284 column_aliases,
285 include_descendants,
286 ..
287 } => {
288 let mut rendered = only_relation(name, *include_descendants);
289 render_relation_alias(&mut rendered, alias.as_deref(), column_aliases);
290 rendered
291 }
292 FromClause::Join {
293 left,
294 right,
295 kind,
296 on,
297 using,
298 natural,
299 alias,
300 column_aliases,
301 lateral,
302 } => {
303 let mut rendered = String::from("(");
304 rendered.push_str(&from_sql(left));
305 rendered.push(' ');
306 if *natural {
307 rendered.push_str("NATURAL ");
308 }
309 rendered.push_str(match kind {
310 JoinKind::Inner => "JOIN",
311 JoinKind::Left => "LEFT JOIN",
312 JoinKind::Right => "RIGHT JOIN",
313 JoinKind::Full => "FULL JOIN",
314 JoinKind::Cross => "CROSS JOIN",
315 });
316 rendered.push(' ');
317 if *lateral {
318 rendered.push_str("LATERAL ");
319 }
320 rendered.push_str(&from_sql(right));
321 if let Some(predicate) = on {
322 rendered.push_str(" ON ");
323 rendered.push_str(&expr_sql(predicate));
324 } else if let Some(using) = using {
325 rendered.push_str(" USING (");
326 rendered.push_str(&ident_list(&using.columns));
327 rendered.push(')');
328 if let Some(alias) = &using.alias {
329 rendered.push_str(" AS ");
330 rendered.push_str(&ident(alias));
331 }
332 }
333 rendered.push(')');
334 render_relation_alias(&mut rendered, alias.as_deref(), column_aliases);
335 rendered
336 }
337 FromClause::Values {
338 rows,
339 alias,
340 column_aliases,
341 ..
342 } => {
343 let mut rendered = format!("(VALUES {})", rows_sql(rows));
344 render_relation_alias(&mut rendered, alias.as_deref(), column_aliases);
345 rendered
346 }
347 FromClause::Function {
348 name,
349 output_name: _,
350 relations,
351 args,
352 alias,
353 column_aliases,
354 ordinality,
355 column_types,
356 ..
357 } => {
358 let mut arguments = args.iter().map(expr_sql).collect::<Vec<_>>();
359 if let Some(relations) = relations {
360 arguments.insert(0, relations.left.clone());
361 arguments.insert(2, relations.right.clone());
362 }
363 let mut rendered = format!("{name}({})", arguments.join(", "));
364 if *ordinality {
365 rendered.push_str(" WITH ORDINALITY");
366 }
367 render_function_alias(
368 &mut rendered,
369 alias.as_deref(),
370 column_aliases,
371 column_types,
372 );
373 rendered
374 }
375 FromClause::FunctionGroup {
376 functions,
377 alias,
378 column_aliases,
379 ordinality,
380 } => {
381 let mut rendered = format!(
382 "ROWS FROM ({})",
383 functions
384 .iter()
385 .map(table_function_sql)
386 .collect::<Vec<_>>()
387 .join(", ")
388 );
389 if *ordinality {
390 rendered.push_str(" WITH ORDINALITY");
391 }
392 render_relation_alias(&mut rendered, alias.as_deref(), column_aliases);
393 rendered
394 }
395 FromClause::Subquery {
396 body,
397 alias,
398 column_aliases,
399 } => {
400 let mut rendered = format!("({})", select_sql(body));
401 render_relation_alias(&mut rendered, alias.as_deref(), column_aliases);
402 rendered
403 }
404 }
405}
406
407fn table_function_sql(function: &TableFunction) -> String {
408 let mut arguments = function.args.iter().map(expr_sql).collect::<Vec<_>>();
409 if let Some(relations) = &function.relations {
410 arguments.insert(0, relations.left.clone());
411 arguments.insert(2, relations.right.clone());
412 }
413 let mut rendered = format!("{}({})", function.name, arguments.join(", "));
414 if !function.column_types.is_empty() {
415 rendered.push_str(" AS (");
416 rendered.push_str(
417 &function
418 .column_aliases
419 .iter()
420 .zip(&function.column_types)
421 .map(|(name, ty)| format!("{} {ty}", ident(name)))
422 .collect::<Vec<_>>()
423 .join(", "),
424 );
425 rendered.push(')');
426 }
427 rendered
428}
429
430#[expect(
431 clippy::too_many_lines,
432 reason = "exhaustive scalar rendering keeps every durable AST variant explicit"
433)]
434fn render_expr(expression: &Expr) -> Result<String, SQLError> {
435 Ok(match expression {
436 Expr::Star => "*".into(),
437 Expr::QualifiedStar(qualifier) => format!("{}.*", ident(qualifier)),
438 Expr::Default => "DEFAULT".into(),
439 Expr::Column(name) => ident(name),
440 Expr::QualifiedColumn { qualifier, column } => {
441 format!("{}.{}", ident(qualifier), ident(column))
442 }
443 Expr::InternalColumn(column) => {
444 return Err(SQLError::Internal(format!(
445 "executor-only column {column:?} reached durable SQL rendering"
446 )))
447 }
448 Expr::Literal(value) => value_sql(value),
449 Expr::Param(index) => format!("${index}"),
450 Expr::Func {
451 name,
452 args,
453 distinct,
454 order_by,
455 filter,
456 ..
457 } => {
458 let mut arguments = args.iter().map(expr_sql).collect::<Vec<_>>().join(", ");
459 if *distinct {
460 arguments = format!("DISTINCT {arguments}");
461 }
462 if !order_by.is_empty() {
463 if !arguments.is_empty() {
464 arguments.push(' ');
465 }
466 arguments.push_str("ORDER BY ");
467 arguments.push_str(&order_by_sql(order_by));
468 }
469 let mut rendered = format!("{name}({arguments})");
470 if let Some(filter) = filter {
471 write!(&mut rendered, " FILTER (WHERE {})", expr_sql(filter))
472 .expect("writing to a String cannot fail");
473 }
474 rendered
475 }
476 Expr::Array(items) => format!("ARRAY[{}]", expr_list(items)),
477 Expr::Row(items) => format!("ROW({})", expr_list(items)),
478 Expr::Binary { op, lhs, rhs } => format!(
479 "({} {} {})",
480 expr_sql(lhs),
481 binary_operator_sql(*op),
482 expr_sql(rhs)
483 ),
484 Expr::UnaryMinus(inner) => format!("(-{})", expr_sql(inner)),
485 Expr::Not(inner) => format!("(NOT {})", expr_sql(inner)),
486 Expr::And(items) => format!(
487 "({})",
488 items.iter().map(expr_sql).collect::<Vec<_>>().join(" AND ")
489 ),
490 Expr::Or(items) => format!(
491 "({})",
492 items.iter().map(expr_sql).collect::<Vec<_>>().join(" OR ")
493 ),
494 Expr::IsNull { expr, negated } => format!(
495 "({} IS {}NULL)",
496 expr_sql(expr),
497 if *negated { "NOT " } else { "" }
498 ),
499 Expr::Between { expr, low, high } => format!(
500 "({} BETWEEN {} AND {})",
501 expr_sql(expr),
502 expr_sql(low),
503 expr_sql(high)
504 ),
505 Expr::InList {
506 expr,
507 list,
508 negated,
509 } => format!(
510 "({} {}IN ({}))",
511 expr_sql(expr),
512 if *negated { "NOT " } else { "" },
513 expr_list(list)
514 ),
515 Expr::WindowCall { name, args, spec } => {
516 format!("{name}({}) OVER {}", expr_list(args), window_sql(spec))
517 }
518 Expr::Case {
519 base,
520 when,
521 else_branch,
522 } => {
523 let mut rendered = String::from("CASE");
524 if let Some(base) = base {
525 rendered.push(' ');
526 rendered.push_str(&expr_sql(base));
527 }
528 for (condition, result) in when {
529 write!(
530 &mut rendered,
531 " WHEN {} THEN {}",
532 expr_sql(condition),
533 expr_sql(result)
534 )
535 .expect("writing to a String cannot fail");
536 }
537 if let Some(branch) = else_branch {
538 rendered.push_str(" ELSE ");
539 rendered.push_str(&expr_sql(branch));
540 }
541 rendered.push_str(" END");
542 rendered
543 }
544 Expr::Cast { expr, ty } => format!("CAST({} AS {ty})", expr_sql(expr)),
545 Expr::ScalarSubquery(body) => format!("({})", select_sql(body)),
546 Expr::Exists { body, negated } => format!(
547 "{}EXISTS ({})",
548 if *negated { "NOT " } else { "" },
549 select_sql(body)
550 ),
551 Expr::InSubquery {
552 expr,
553 body,
554 negated,
555 } => format!(
556 "({} {}IN ({}))",
557 expr_sql(expr),
558 if *negated { "NOT " } else { "" },
559 select_sql(body)
560 ),
561 })
562}
563
564fn window_sql(spec: &WindowSpec) -> String {
565 if let Some(reference) = &spec.reference {
566 if reference.kind == WindowReferenceKind::Direct
567 && spec.partition_by.is_empty()
568 && spec.order_by.is_empty()
569 && spec.frame.is_none()
570 {
571 return ident(&reference.name);
572 }
573 }
574 let mut parts = Vec::new();
575 if let Some(reference) = &spec.reference {
576 parts.push(ident(&reference.name));
577 }
578 if !spec.partition_by.is_empty() {
579 parts.push(format!("PARTITION BY {}", expr_list(&spec.partition_by)));
580 }
581 if !spec.order_by.is_empty() {
582 parts.push(format!("ORDER BY {}", order_by_sql(&spec.order_by)));
583 }
584 if let Some(frame) = &spec.frame {
585 parts.push(format!(
586 "{} BETWEEN {} AND {}",
587 match frame.mode {
588 FrameMode::Rows => "ROWS",
589 FrameMode::Range => "RANGE",
590 FrameMode::Groups => "GROUPS",
591 },
592 frame_bound_sql(&frame.start),
593 frame_bound_sql(&frame.end)
594 ));
595 }
596 format!("({})", parts.join(" "))
597}
598
599const fn binary_operator_sql(operator: crate::ast::BinaryOp) -> &'static str {
600 match operator {
601 crate::ast::BinaryOp::Equal => "=",
602 crate::ast::BinaryOp::NotEqual => "<>",
603 crate::ast::BinaryOp::Less => "<",
604 crate::ast::BinaryOp::LessEqual => "<=",
605 crate::ast::BinaryOp::Greater => ">",
606 crate::ast::BinaryOp::GreaterEqual => ">=",
607 crate::ast::BinaryOp::Add => "+",
608 crate::ast::BinaryOp::Subtract => "-",
609 crate::ast::BinaryOp::Multiply => "*",
610 crate::ast::BinaryOp::Divide => "/",
611 }
612}
613
614fn frame_bound_sql(bound: &FrameBound) -> String {
615 match bound {
616 FrameBound::UnboundedPreceding => "UNBOUNDED PRECEDING".into(),
617 FrameBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".into(),
618 FrameBound::CurrentRow => "CURRENT ROW".into(),
619 FrameBound::Preceding(expression) => format!("{} PRECEDING", expr_sql(expression)),
620 FrameBound::Following(expression) => format!("{} FOLLOWING", expr_sql(expression)),
621 }
622}
623
624fn with_sql(ctes: &[CTE]) -> String {
625 if ctes.is_empty() {
626 return String::new();
627 }
628 let recursive = ctes.iter().any(|cte| cte.recursive);
629 format!(
630 "WITH {}{} ",
631 if recursive { "RECURSIVE " } else { "" },
632 ctes.iter().map(cte_sql).collect::<Vec<_>>().join(", ")
633 )
634}
635
636fn cte_sql(cte: &CTE) -> String {
637 let mut rendered = ident(&cte.name);
638 if !cte.columns.is_empty() {
639 rendered.push_str(" (");
640 rendered.push_str(&ident_list(&cte.columns));
641 rendered.push(')');
642 }
643 rendered.push_str(" AS ");
644 rendered.push_str(match cte.materialization {
645 CteMaterialization::Default => "",
646 CteMaterialization::Materialized => "MATERIALIZED ",
647 CteMaterialization::NotMaterialized => "NOT MATERIALIZED ",
648 });
649 rendered.push('(');
650 rendered.push_str(&select_sql(&cte.query));
651 rendered.push(')');
652 if let Some(search) = &cte.search {
653 write!(
654 &mut rendered,
655 " SEARCH {} FIRST BY {} SET {}",
656 if search.breadth_first {
657 "BREADTH"
658 } else {
659 "DEPTH"
660 },
661 ident_list(&search.columns),
662 ident(&search.sequence_column)
663 )
664 .expect("writing to a String cannot fail");
665 }
666 if let Some(cycle) = &cte.cycle {
667 write!(
668 &mut rendered,
669 " CYCLE {} SET {} TO {} DEFAULT {} USING {}",
670 ident_list(&cycle.columns),
671 ident(&cycle.mark_column),
672 expr_sql(&cycle.mark_value),
673 expr_sql(&cycle.mark_default),
674 ident(&cycle.path_column)
675 )
676 .expect("writing to a String cannot fail");
677 }
678 rendered
679}
680
681fn render_order_limit_offset(
682 rendered: &mut String,
683 order_by: &[OrderBy],
684 limit: Option<&Expr>,
685 with_ties: bool,
686 offset: Option<&Expr>,
687) {
688 if !order_by.is_empty() {
689 rendered.push_str(" ORDER BY ");
690 rendered.push_str(&order_by_sql(order_by));
691 }
692 if with_ties {
693 if let Some(offset) = offset {
694 rendered.push_str(" OFFSET ");
695 rendered.push_str(&expr_sql(offset));
696 }
697 if let Some(limit) = limit {
698 rendered.push_str(" FETCH FIRST ");
699 rendered.push_str(&expr_sql(limit));
700 rendered.push_str(" ROWS WITH TIES");
701 }
702 } else {
703 if let Some(limit) = limit {
704 rendered.push_str(" LIMIT ");
705 rendered.push_str(&expr_sql(limit));
706 }
707 if let Some(offset) = offset {
708 rendered.push_str(" OFFSET ");
709 rendered.push_str(&expr_sql(offset));
710 }
711 }
712}
713
714fn render_returning(rendered: &mut String, aliases: &ReturningAliases, projections: &[Projection]) {
715 if projections.is_empty() {
716 return;
717 }
718 rendered.push_str(" RETURNING ");
719 if aliases.old_explicit || aliases.new_explicit {
720 rendered.push_str("WITH (");
721 let mut names = Vec::new();
722 if aliases.old_explicit {
723 names.push(format!("OLD AS {}", ident(&aliases.old)));
724 }
725 if aliases.new_explicit {
726 names.push(format!("NEW AS {}", ident(&aliases.new)));
727 }
728 rendered.push_str(&names.join(", "));
729 rendered.push_str(") ");
730 }
731 rendered.push_str(&projections_sql(projections));
732}
733
734fn render_target_alias(rendered: &mut String, relation: &str, qualifier: &str) {
735 if relation_local_name(relation) != qualifier {
736 rendered.push_str(" AS ");
737 rendered.push_str(&ident(qualifier));
738 }
739}
740
741fn render_relation_alias(rendered: &mut String, alias: Option<&str>, columns: &[String]) {
742 if let Some(alias) = alias {
743 rendered.push_str(" AS ");
744 rendered.push_str(&ident(alias));
745 if !columns.is_empty() {
746 rendered.push('(');
747 rendered.push_str(&ident_list(columns));
748 rendered.push(')');
749 }
750 }
751}
752
753fn render_function_alias(
754 rendered: &mut String,
755 alias: Option<&str>,
756 columns: &[String],
757 types: &[String],
758) {
759 if let Some(alias) = alias {
760 rendered.push_str(" AS ");
761 rendered.push_str(&ident(alias));
762 } else if !types.is_empty() {
763 rendered.push_str(" AS");
764 }
765 if !types.is_empty() {
766 rendered.push_str(" (");
767 rendered.push_str(
768 &columns
769 .iter()
770 .zip(types)
771 .map(|(name, ty)| format!("{} {ty}", ident(name)))
772 .collect::<Vec<_>>()
773 .join(", "),
774 );
775 rendered.push(')');
776 } else if !columns.is_empty() {
777 rendered.push('(');
778 rendered.push_str(&ident_list(columns));
779 rendered.push(')');
780 }
781}
782
783fn assignments_sql(assignments: &[(String, Expr)]) -> String {
784 assignments
785 .iter()
786 .map(|(column, expression)| format!("{} = {}", ident(column), expr_sql(expression)))
787 .collect::<Vec<_>>()
788 .join(", ")
789}
790
791fn projections_sql(projections: &[Projection]) -> String {
792 projections
793 .iter()
794 .map(|projection| {
795 let mut rendered = expr_sql(&projection.expr);
796 if let Some(alias) = &projection.alias {
797 rendered.push_str(" AS ");
798 rendered.push_str(&ident(alias));
799 }
800 rendered
801 })
802 .collect::<Vec<_>>()
803 .join(", ")
804}
805
806fn order_by_sql(order_by: &[OrderBy]) -> String {
807 order_by
808 .iter()
809 .map(|order| {
810 let mut rendered = expr_sql(&order.expr);
811 if order.descending {
812 rendered.push_str(" DESC");
813 }
814 match order.nulls {
815 Some(NullsOrder::First) => rendered.push_str(" NULLS FIRST"),
816 Some(NullsOrder::Last) => rendered.push_str(" NULLS LAST"),
817 None => {}
818 }
819 rendered
820 })
821 .collect::<Vec<_>>()
822 .join(", ")
823}
824
825fn rows_sql(rows: &[Vec<Expr>]) -> String {
826 rows.iter()
827 .map(|row| format!("({})", expr_list(row)))
828 .collect::<Vec<_>>()
829 .join(", ")
830}
831
832fn expr_list(expressions: &[Expr]) -> String {
833 expressions
834 .iter()
835 .map(expr_sql)
836 .collect::<Vec<_>>()
837 .join(", ")
838}
839
840fn expr_sql(expression: &Expr) -> String {
841 render_expr(expression).expect("durable SQL AST cannot contain executor-only columns")
842}
843
844fn only_relation(name: &str, include_descendants: bool) -> String {
845 if include_descendants {
846 name.to_string()
847 } else {
848 format!("ONLY {name}")
849 }
850}
851
852fn ident_list(names: &[String]) -> String {
853 names
854 .iter()
855 .map(|name| ident(name))
856 .collect::<Vec<_>>()
857 .join(", ")
858}
859
860fn ident(name: &str) -> String {
861 crate::expr::quote_ident(name)
862}
863
864fn relation_local_name(name: &str) -> &str {
865 let mut quoted = false;
866 let mut last_dot = None;
867 let bytes = name.as_bytes();
868 let mut position = 0;
869 while position < bytes.len() {
870 match bytes[position] {
871 b'"' if quoted && bytes.get(position + 1) == Some(&b'"') => position += 2,
872 b'"' => {
873 quoted = !quoted;
874 position += 1;
875 }
876 b'.' if !quoted => {
877 last_dot = Some(position);
878 position += 1;
879 }
880 _ => position += 1,
881 }
882 }
883 let component = &name[last_dot.map_or(0, |dot| dot + 1)..];
884 component
885 .strip_prefix('"')
886 .and_then(|component| component.strip_suffix('"'))
887 .unwrap_or(component)
888}
889
890fn string_literal(value: &str) -> String {
891 format!("'{}'", value.replace('\'', "''"))
892}
893
894fn value_sql(value: &Value) -> String {
895 match value {
896 Value::Null => "NULL".into(),
897 Value::Void => "''::void".into(),
898 Value::Bool(value) => if *value { "true" } else { "false" }.into(),
899 Value::Int(value) => value.to_string(),
900 Value::Float(value) if value.is_finite() => value.to_string(),
901 Value::Float(value) => format!("{}::double precision", string_literal(&value.to_string())),
902 Value::Str(value) => string_literal(value),
903 Value::FixedChar(value) => format!("{}::character", string_literal(value)),
904 Value::Bytes(value) => {
905 let mut hex = String::new();
906 for byte in value {
907 write!(&mut hex, "{byte:02x}").expect("writing to a String cannot fail");
908 }
909 format!("{}::bytea", string_literal(&format!("\\x{hex}")))
910 }
911 Value::Temporal(value) => {
912 let ty = match value {
913 TemporalValue::Date { .. } => "date",
914 TemporalValue::Time { .. } => "time",
915 TemporalValue::TimeTz { .. } => "time with time zone",
916 TemporalValue::Timestamp { .. } => "timestamp",
917 TemporalValue::TimestampTz { .. } => "timestamp with time zone",
918 TemporalValue::Interval { .. } => "interval",
919 };
920 format!("{}::{ty}", string_literal(&value.to_sql_string()))
921 }
922 Value::Decimal(value) if value.is_nan() || value.is_infinite() => {
923 format!("{}::numeric", string_literal(&value.to_sql_string()))
924 }
925 Value::Decimal(value) => format!("{}::numeric", value.to_sql_string()),
926 Value::Json(value) => format!("{}::json", string_literal(value)),
927 Value::JsonB(value) => format!("{}::jsonb", string_literal(value)),
928 Value::Array(array) => format!(
929 "ARRAY[{}]",
930 array
931 .elements()
932 .iter()
933 .map(value_sql)
934 .collect::<Vec<_>>()
935 .join(", ")
936 ),
937 Value::List(values) => format!(
938 "ARRAY[{}]",
939 values.iter().map(value_sql).collect::<Vec<_>>().join(", ")
940 ),
941 Value::Row(values) => format!(
942 "ROW({})",
943 values.iter().map(value_sql).collect::<Vec<_>>().join(", ")
944 ),
945 Value::Record(fields) => format!(
946 "ROW({})",
947 fields
948 .iter()
949 .map(|(_, value)| value_sql(value))
950 .collect::<Vec<_>>()
951 .join(", ")
952 ),
953 Value::Map(value) => format!(
954 "{}::jsonb",
955 string_literal(
956 &serde_json::to_string(value)
957 .expect("serializing an in-memory Value map cannot fail")
958 )
959 ),
960 }
961}
962
963#[cfg(test)]
964mod tests {
965 use super::statement_sql;
966
967 #[test]
968 fn rendered_rule_action_shapes_round_trip_stably() {
969 for sql in [
970 "SELECT source.key_value, row_number() OVER (ORDER BY source.key_value ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS sequence FROM left_table AS source(key_value, payload) JOIN right_table AS other USING (key_value) WHERE source.payload IS NOT NULL ORDER BY sequence LIMIT 2 OFFSET 1",
971 "WITH source(value) AS MATERIALIZED (SELECT 1) SELECT value FROM source UNION ALL SELECT 2 ORDER BY value",
972 "INSERT INTO target_table AS target(id, value) VALUES (1, 'one') ON CONFLICT (id) DO UPDATE SET value = excluded.value WHERE target.id = 1 RETURNING WITH (OLD AS before, NEW AS after) after.id",
973 "UPDATE target_table AS target SET value = source.value FROM source_table AS source(id, value) WHERE target.id = source.id RETURNING target.id",
974 "DELETE FROM target_table AS target USING source_table AS source(id) WHERE target.id = source.id RETURNING target.id",
975 "NOTIFY rule_channel, 'payload'",
976 ] {
977 let mut statements = crate::compile(sql).unwrap_or_else(|error| panic!("{sql}: {error}"));
978 let rendered = statement_sql(&statements.remove(0))
979 .unwrap_or_else(|error| panic!("render {sql}: {error}"));
980 let mut reparsed = crate::compile(&rendered)
981 .unwrap_or_else(|error| panic!("reparse `{rendered}` from `{sql}`: {error}"));
982 let rerendered = statement_sql(&reparsed.remove(0))
983 .unwrap_or_else(|error| panic!("rerender `{rendered}`: {error}"));
984 assert_eq!(rerendered, rendered, "unstable SQL rendering for `{sql}`");
985 }
986 }
987}