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