1use crate::ast::{
67 Ast, BinaryOp, CaseArm, CreateViewRef, Distinct, Expr, ExprRef, JoinKind, LiteralKind, Nulls,
68 Order, OrderItem, Quantifier, QueryBody, QueryRef, SelectRef, SetOp, Slice, Source, SourceRef,
69 StrRef, Target, UnaryOp, WindowBound, WindowExclude, WindowUnit,
70};
71use crate::matcher::NONE;
72use crate::tokenize::quoted;
73
74#[must_use]
80pub fn create_view(ast: &Ast, index: CreateViewRef) -> String {
81 let written = ast.create_view(index);
82 let name = ast.name(written.name).last().unwrap_or_default();
83 let temporary = if written.temporary { "TEMP " } else { "" };
84 let mut out = format!("CREATE {temporary}VIEW {}", quoted(name));
85 if !written.columns.is_empty() {
86 out += &format!(" ({})", names(ast, written.columns));
89 }
90 out + &format!(" AS {};", query(ast, written.query))
91}
92
93#[must_use]
95pub fn query(ast: &Ast, index: QueryRef) -> String {
96 let held = ast.query(index);
97 let mut out = with(ast, held.ctes);
98 out += &match held.body {
99 QueryBody::Select(select) => selection(ast, select),
100 QueryBody::SetOp { op, quantifier, by_name, left, right } => {
101 setop(ast, op, quantifier, by_name, left, right)
102 }
103 QueryBody::Values(rows) => format!("SELECT * FROM ({}) AS valueslist", values(ast, rows)),
106 QueryBody::Describe(inner) => format!("DESCRIBE ({})", query(ast, inner)),
107 QueryBody::Show { name, .. } => format!("SHOW {}", ast.name_text(name)),
108 };
109 if held.order_by_all {
110 out += " ORDER BY COLUMNS(*)";
112 } else if !held.order_by.is_empty() {
113 let items: Vec<String> =
114 ast.order_list(held.order_by).iter().map(|item| order(ast, item)).collect();
115 out += &format!(" ORDER BY {}", items.join(", "));
116 }
117 if held.limit != NONE {
118 if held.limit_percent {
121 out += &format!(" LIMIT ({}) %", expr(ast, held.limit));
122 } else {
123 out += &format!(" LIMIT {}", expr(ast, held.limit));
124 }
125 }
126 if held.offset != NONE {
127 out += &format!(" OFFSET {}", expr(ast, held.offset));
128 }
129 out
130}
131
132fn with(ast: &Ast, ctes: Slice) -> String {
138 if ctes.is_empty() {
139 return String::new();
140 }
141 let written: Vec<String> = ast
142 .cte_list(ctes)
143 .iter()
144 .map(|&index| {
145 let held = ast.cte(index);
146 let columns = if held.columns.is_empty() {
147 String::new()
148 } else {
149 format!(" ({})", names(ast, held.columns))
150 };
151 format!(
152 "{}{columns} AS MATERIALIZED ({})",
153 quoted(ast.string(held.name)),
154 query(ast, held.query)
155 )
156 })
157 .collect();
158 format!("WITH {}", written.join(", "))
159}
160
161fn setop(
168 ast: &Ast,
169 op: SetOp,
170 quantifier: Quantifier,
171 by_name: bool,
172 left: QueryRef,
173 right: QueryRef,
174) -> String {
175 let word = match op {
176 SetOp::Union => "UNION",
177 SetOp::Except => "EXCEPT",
178 SetOp::Intersect => "INTERSECT",
179 };
180 let all = if matches!(quantifier, Quantifier::All) { " ALL" } else { "" };
182 let named = if by_name { " BY NAME" } else { "" };
183 format!("{}{word}{all}{named} {}", branch(ast, left, true), branch(ast, right, false))
184}
185
186fn branch(ast: &Ast, index: QueryRef, left: bool) -> String {
188 let text = query(ast, index);
189 if matches!(ast.query(index).body, QueryBody::SetOp { .. }) {
190 return text;
191 }
192 if left { format!("({text}) ") } else { format!("({text})") }
193}
194
195fn selection(ast: &Ast, index: SelectRef) -> String {
197 let held = ast.select(index);
198 let mut out = "SELECT".to_string();
199 match held.distinct {
200 Distinct::No => {}
201 Distinct::Yes => out += " DISTINCT",
202 Distinct::On(list) => out += &format!(" DISTINCT ON ({})", exprs(ast, list)),
203 }
204 let targets: Vec<String> =
205 ast.target_list(held.targets).iter().map(|target| aliased(ast, target)).collect();
206 out += &format!(" {}", targets.join(", "));
207 if !held.from.is_empty() {
208 let sources: Vec<String> =
210 ast.source_list(held.from).iter().map(|&index| source(ast, index)).collect();
211 out += &format!(" FROM {}", sources.join(" , "));
212 }
213 if held.filter != NONE {
214 out += &format!(" WHERE {}", expr(ast, held.filter));
215 }
216 if held.group_by_all {
217 out += " GROUP BY ALL";
218 } else if !held.group_by.is_empty() {
219 out += &format!(" GROUP BY {}", exprs(ast, held.group_by));
220 }
221 if held.having != NONE {
222 out += &format!(" HAVING {}", expr(ast, held.having));
223 }
224 out
225}
226
227fn aliased(ast: &Ast, target: &Target) -> String {
229 let written = expr(ast, target.expr);
230 if target.alias == NONE {
231 return written;
232 }
233 format!("{written} AS {}", quoted(ast.string(target.alias)))
234}
235
236fn order(ast: &Ast, item: &OrderItem) -> String {
238 let mut out = expr(ast, item.expr);
239 match item.order {
240 Order::Unstated => {}
241 Order::Ascending => out += " ASC",
242 Order::Descending => out += " DESC",
243 }
244 match item.nulls {
245 Nulls::Unstated => {}
246 Nulls::First => out += " NULLS FIRST",
247 Nulls::Last => out += " NULLS LAST",
248 }
249 out
250}
251
252fn source(ast: &Ast, index: SourceRef) -> String {
254 match ast.source(index) {
255 Source::Table { name, alias, columns } => label(ast, parts(ast, name), alias, columns),
256 Source::Cte { cte, alias, columns } => {
259 label(ast, quoted(ast.string(ast.cte(cte).name)), alias, columns)
260 }
261 Source::Subquery { query: inner, alias, columns } => {
262 label(ast, format!("({})", query(ast, inner)), alias, columns)
263 }
264 Source::Function { name, args, alias, columns, .. } => {
265 let written: Vec<String> =
266 ast.target_list(args).iter().map(|arg| argument(ast, arg)).collect();
267 let call = format!("{}({})", parts(ast, name), written.join(", "));
268 label(ast, call, alias, columns)
269 }
270 Source::Values { rows, alias, columns } => {
273 let inner = format!("(SELECT * FROM ({}) AS valueslist)", values(ast, rows));
274 label(ast, inner, alias, columns)
275 }
276 Source::Join { left, right, kind, natural, on, using } => {
277 let word = match kind {
278 JoinKind::Inner => "INNER",
279 JoinKind::Left => "LEFT",
280 JoinKind::Right => "RIGHT",
281 JoinKind::Full => "FULL",
283 JoinKind::Semi => "SEMI",
284 JoinKind::Anti => "ANTI",
285 JoinKind::Cross => "CROSS",
286 JoinKind::Positional => "POSITIONAL",
287 };
288 let natural = if natural { "NATURAL " } else { "" };
289 let mut out =
290 format!("({} {natural}{word} JOIN {}", source(ast, left), source(ast, right));
291 if on != NONE {
292 out += &format!(" ON ({})", expr(ast, on));
295 }
296 if !using.is_empty() {
297 out += &format!(" USING ({})", names(ast, using));
298 }
299 out + ")"
300 }
301 }
302}
303
304fn argument(ast: &Ast, arg: &Target) -> String {
310 if arg.alias == NONE {
311 return expr(ast, arg.expr);
312 }
313 format!("({} = {})", quoted(ast.string(arg.alias)), expr(ast, arg.expr))
314}
315
316fn label(ast: &Ast, written: String, alias: StrRef, columns: Slice) -> String {
318 let mut out = written;
319 if alias != NONE {
320 out += &format!(" AS {}", quoted(ast.string(alias)));
321 }
322 if !columns.is_empty() {
323 out += &format!("({})", names(ast, columns));
324 }
325 out
326}
327
328fn values(ast: &Ast, rows: Slice) -> String {
330 let written: Vec<String> =
331 ast.rows(rows).iter().map(|&row| format!("({})", exprs(ast, row))).collect();
332 format!("VALUES {}", written.join(", "))
333}
334
335pub fn expression(ast: &Ast, index: ExprRef) -> String {
341 expr(ast, index)
342}
343
344fn expr(ast: &Ast, index: ExprRef) -> String {
346 match ast.expr(index) {
347 Expr::Star { qualifier, replacements } => star(ast, qualifier, replacements),
348 Expr::Column { name } => parts(ast, name),
349 Expr::Literal { kind, text } => literal(ast, kind, text),
350 Expr::Unary { op, operand } => unary(ast, op, operand),
351 Expr::Binary { op, left, right } => binary(ast, op, left, right),
352 Expr::Function { name, args, distinct, filter } => {
353 let sorted = ast.aggregate_order(index);
354 call(ast, name, args, distinct, filter, ast.named_args(index), sorted)
355 }
356 held @ Expr::Window { .. } => window(ast, held),
357 Expr::Cast { operand, ty, try_cast } => {
358 let word = if try_cast { "TRY_CAST" } else { "CAST" };
359 format!("{word}({} AS {})", expr(ast, operand), typename(ast.string(ty)))
360 }
361 Expr::Case { operand, arms, otherwise } => case(ast, operand, arms, otherwise),
362 Expr::Between { operand, low, high, negated } => {
363 let written = format!(
364 "({} BETWEEN {} AND {})",
365 expr(ast, operand),
366 expr(ast, low),
367 expr(ast, high)
368 );
369 if negated { format!("(NOT {written})") } else { written }
370 }
371 Expr::In { operand, list, negated } => {
372 let written = format!("({} IN ({}))", expr(ast, operand), exprs(ast, list));
373 if negated { format!("(NOT {written})") } else { written }
374 }
375 Expr::InSubquery { operand, query: inner, negated } => {
376 let any = format!("({} = ANY({}))", expr(ast, operand), query(ast, inner));
377 if negated { format!("(NOT {any})") } else { any }
378 }
379 Expr::QuantifiedSubquery { operand, op, query: inner, all } => {
380 let (op, negate) = if all { (negated_comparison(op), true) } else { (op, false) };
381 let word = comparison_word(op);
382 let any = format!("({} {word} ANY({}))", expr(ast, operand), query(ast, inner));
383 if negate { format!("(NOT {any})") } else { any }
384 }
385 Expr::Parameter { name } => format!("${}", ast.string(name)),
386 Expr::Default => "DEFAULT".to_string(),
387 Expr::List { items } => format!("list_value({})", exprs(ast, items)),
389 Expr::Row { items } => format!("\"row\"({})", exprs(ast, items)),
392 Expr::Struct { names, values } => {
394 let fields: Vec<String> = ast
395 .name(names)
396 .zip(ast.expr_list(values))
397 .map(|(name, &value)| format!("{} := {}", quoted(name), expr(ast, value)))
398 .collect();
399 format!("struct_pack({})", fields.join(", "))
400 }
401 Expr::Lambda { params, body } => {
403 let params: Vec<String> = ast.name(params).map(quoted).collect();
404 format!("(lambda {}: {})", params.join(", "), expr(ast, body))
405 }
406 Expr::Subquery { query: inner, array: false } => format!("({})", query(ast, inner)),
407 Expr::Subquery { query: inner, array: true } => format!("ARRAY({})", query(ast, inner)),
408 Expr::Exists { query: inner, negated } => {
409 let exists = format!("EXISTS({})", query(ast, inner));
410 if negated { format!("(NOT {exists})") } else { exists }
411 }
412 }
413}
414
415fn comparison_word(op: BinaryOp) -> &'static str {
416 match op {
417 BinaryOp::Eq => "=",
418 BinaryOp::NotEq => "!=",
419 BinaryOp::Lt => "<",
420 BinaryOp::Gt => ">",
421 BinaryOp::LtEq => "<=",
422 BinaryOp::GtEq => ">=",
423 _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
424 }
425}
426
427fn negated_comparison(op: BinaryOp) -> BinaryOp {
428 match op {
429 BinaryOp::Eq => BinaryOp::NotEq,
430 BinaryOp::NotEq => BinaryOp::Eq,
431 BinaryOp::Lt => BinaryOp::GtEq,
432 BinaryOp::Gt => BinaryOp::LtEq,
433 BinaryOp::LtEq => BinaryOp::Gt,
434 BinaryOp::GtEq => BinaryOp::Lt,
435 _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
436 }
437}
438
439fn star(ast: &Ast, qualifier: Slice, replacements: Slice) -> String {
441 let mut out =
442 if qualifier.is_empty() { "*".to_string() } else { format!("{}.*", parts(ast, qualifier)) };
443 if !replacements.is_empty() {
444 let written: Vec<String> =
445 ast.target_list(replacements).iter().map(|target| aliased(ast, target)).collect();
446 out += &format!(" REPLACE ({})", written.join(", "));
447 }
448 out
449}
450
451fn literal(ast: &Ast, kind: LiteralKind, text: StrRef) -> String {
453 match kind {
454 LiteralKind::Null => "NULL".to_string(),
457 LiteralKind::True => "true".to_string(),
458 LiteralKind::False => "false".to_string(),
459 LiteralKind::Number => number(ast.string(text)),
460 LiteralKind::String => string(ast.string(text)),
461 LiteralKind::Blob => format!("{}::BLOB", string(ast.string(text))),
464 }
465}
466
467fn number(written: &str) -> String {
476 let text = written.replace('_', "");
477 if text.contains(['e', 'E']) {
478 return double(&text);
479 }
480 let Some((whole, fraction)) = text.split_once('.') else {
481 return leading(&text).to_string();
482 };
483 if fraction.is_empty() {
486 return leading(whole).to_string();
487 }
488 format!("{}.{fraction}", if whole.is_empty() { "" } else { leading(whole) })
489}
490
491fn leading(digits: &str) -> &str {
493 let trimmed = digits.trim_start_matches('0');
494 if trimmed.is_empty() { &digits[digits.len().saturating_sub(1)..] } else { trimmed }
495}
496
497fn double(text: &str) -> String {
505 let Ok(value) = text.parse::<f64>() else {
506 return text.to_string();
507 };
508 let shortest = format!("{value:e}");
511 let (mantissa, exponent) = shortest.split_once('e').unwrap_or((shortest.as_str(), "0"));
512 let exponent: i32 = exponent.parse().unwrap_or(0);
513 if (-4..=15).contains(&exponent) {
514 let plain = format!("{value}");
515 return if plain.contains('.') { plain } else { plain + ".0" };
516 }
517 let sign = if exponent < 0 { '-' } else { '+' };
518 format!("{mantissa}e{sign}{:02}", exponent.abs())
519}
520
521fn string(text: &str) -> String {
526 format!("'{}'", text.replace('\'', "''"))
527}
528
529fn unary(ast: &Ast, op: UnaryOp, operand: ExprRef) -> String {
531 if matches!(op, UnaryOp::Negate)
535 && let Some(number) = negated(ast, operand)
536 {
537 return number;
538 }
539 let written = expr(ast, operand);
540 match op {
541 UnaryOp::Not => format!("(NOT {written})"),
542 UnaryOp::Negate => format!("-({written})"),
543 UnaryOp::Plus => format!("+({written})"),
544 UnaryOp::BitNot => format!("~({written})"),
545 UnaryOp::Factorial => format!("factorial({written})"),
547 UnaryOp::IsNull => format!("({written} IS NULL)"),
548 UnaryOp::IsNotNull => format!("({written} IS NOT NULL)"),
549 UnaryOp::IsUnknown => format!("({written} IS NULL)"),
551 UnaryOp::IsNotUnknown => format!("({written} IS NOT NULL)"),
552 UnaryOp::IsTrue => distinct(&written, "true", true),
556 UnaryOp::IsNotTrue => distinct(&written, "true", false),
557 UnaryOp::IsFalse => distinct(&written, "false", true),
558 UnaryOp::IsNotFalse => distinct(&written, "false", false),
559 }
560}
561
562fn distinct(operand: &str, against: &str, same: bool) -> String {
564 let word = if same { "IS NOT DISTINCT FROM" } else { "IS DISTINCT FROM" };
565 format!("(CAST({operand} AS BOOLEAN) {word} {against})")
566}
567
568fn negated(ast: &Ast, index: ExprRef) -> Option<String> {
573 match ast.expr(index) {
574 Expr::Literal { kind: LiteralKind::Number, text } => {
575 Some(format!("-{}", number(ast.string(text))))
576 }
577 Expr::Unary { op: UnaryOp::Negate, operand } => {
578 let inner = negated(ast, operand)?;
579 Some(inner.strip_prefix('-').unwrap_or(&inner).to_string())
580 }
581 _ => None,
582 }
583}
584
585fn binary(ast: &Ast, op: BinaryOp, left: ExprRef, right: ExprRef) -> String {
587 let (left, right) = (expr(ast, left), expr(ast, right));
588 match op {
590 BinaryOp::SimilarTo => return format!("regexp_full_match({left}, {right})"),
591 BinaryOp::NotSimilarTo => return format!("(NOT regexp_full_match({left}, {right}))"),
592 BinaryOp::AtTimeZone => return format!("timezone({right}, {left})"),
594 BinaryOp::Collate => return format!("{left} COLLATE {right}"),
596 _ => {}
597 }
598 let word = match op {
599 BinaryOp::Or => "OR",
600 BinaryOp::And => "AND",
601 BinaryOp::Eq => "=",
602 BinaryOp::NotEq => "!=",
603 BinaryOp::Lt => "<",
604 BinaryOp::Gt => ">",
605 BinaryOp::LtEq => "<=",
606 BinaryOp::GtEq => ">=",
607 BinaryOp::IsDistinctFrom => "IS DISTINCT FROM",
608 BinaryOp::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
609 BinaryOp::Add => "+",
610 BinaryOp::Subtract => "-",
611 BinaryOp::Multiply => "*",
612 BinaryOp::Divide => "/",
613 BinaryOp::IntegerDivide => "//",
614 BinaryOp::Modulo => "%",
615 BinaryOp::Power => "**",
618 BinaryOp::Caret => "^",
619 BinaryOp::BitAnd => "&",
620 BinaryOp::BitOr => "|",
621 BinaryOp::ShiftLeft => "<<",
622 BinaryOp::ShiftRight => ">>",
623 BinaryOp::Concat => "||",
624 BinaryOp::Like => "~~",
627 BinaryOp::NotLike => "!~~",
628 BinaryOp::ILike => "~~*",
629 BinaryOp::NotILike => "!~~*",
630 BinaryOp::Glob => "~~~",
631 BinaryOp::Regex => "~",
632 BinaryOp::NotRegex => "!~",
633 BinaryOp::RegexInsensitive => "~*",
634 BinaryOp::NotRegexInsensitive => "!~*",
635 BinaryOp::Arrow => "->",
636 BinaryOp::LongArrow => "->>",
637 BinaryOp::Contains => "@>",
638 BinaryOp::ContainedBy => "<@",
639 BinaryOp::Overlaps => "&&",
640 BinaryOp::StartsWith => "^@",
641 BinaryOp::InetContainedByOrEq => "<<=",
642 BinaryOp::InetContainsOrEq => ">>=",
643 BinaryOp::Named(name) => ast.string(name),
644 BinaryOp::SimilarTo | BinaryOp::NotSimilarTo | BinaryOp::AtTimeZone | BinaryOp::Collate => {
645 unreachable!("the four that return above")
646 }
647 };
648 format!("({left} {word} {right})")
649}
650
651fn call(
653 ast: &Ast,
654 name: Slice,
655 args: Slice,
656 distinct: bool,
657 filter: ExprRef,
658 named: &[Target],
659 sorted: &[OrderItem],
660) -> String {
661 let written = parts(ast, name);
662 let list = ast.expr_list(args);
663 if written.eq_ignore_ascii_case("count") {
667 let starred = list.len() == 1
668 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
669 if qualifier.is_empty() && replacements.is_empty());
670 if starred || list.is_empty() {
671 return format!("count_star(){}", filtered(ast, filter));
672 }
673 }
674 let word = if distinct { "DISTINCT " } else { "" };
675 let mut listed = exprs(ast, args);
677 for target in named {
678 if !listed.is_empty() {
679 listed.push_str(", ");
680 }
681 listed.push_str(&format!(
682 "{} := {}",
683 quoted(ast.string(target.alias)),
684 expr(ast, target.expr)
685 ));
686 }
687 if !sorted.is_empty() {
688 let items: Vec<String> = sorted.iter().map(|item| order(ast, item)).collect();
689 listed.push_str(&format!(" ORDER BY {}", items.join(", ")));
690 }
691 format!("{}({word}{listed}){}", operator(ast, name, &written), filtered(ast, filter))
692}
693
694fn filtered(ast: &Ast, filter: ExprRef) -> String {
699 if filter == NONE { String::new() } else { format!(" FILTER (WHERE {})", expr(ast, filter)) }
700}
701
702fn window(ast: &Ast, held: Expr) -> String {
712 let Expr::Window { name, args, distinct, filter, ignore_nulls, order: sorted, spec } = held
713 else {
714 return String::new();
715 };
716 let word = if distinct { "DISTINCT " } else { "" };
717 let sorted = if sorted.is_empty() {
720 String::new()
721 } else {
722 let items: Vec<String> =
723 ast.order_list(sorted).iter().map(|item| order(ast, item)).collect();
724 format!(" ORDER BY {}", items.join(", "))
725 };
726 let nulls = if ignore_nulls { " IGNORE NULLS" } else { "" };
728 let written = parts(ast, name);
729 let list = ast.expr_list(args);
733 let bare = list.len() == 1
734 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
735 if qualifier.is_empty() && replacements.is_empty());
736 let inner = if bare { String::new() } else { exprs(ast, args) };
737 let call = format!(
738 "{}({word}{inner}{sorted}{nulls}){}",
739 operator(ast, name, &written),
740 filtered(ast, filter)
741 );
742 let held = ast.window(spec);
743 let mut inside: Vec<String> = Vec::new();
744 if !held.partition.is_empty() {
745 inside.push(format!("PARTITION BY {}", exprs(ast, held.partition)));
746 }
747 if !held.order.is_empty() {
748 let items: Vec<String> =
749 ast.order_list(held.order).iter().map(|item| order(ast, item)).collect();
750 inside.push(format!("ORDER BY {}", items.join(", ")));
751 }
752 if !held.frame_is_default() {
753 let unit = match held.unit {
754 WindowUnit::Rows => "ROWS",
755 WindowUnit::Range => "RANGE",
756 WindowUnit::Groups => "GROUPS",
757 };
758 let mut frame =
759 format!("{unit} BETWEEN {} AND {}", bound(ast, held.start), bound(ast, held.end));
760 frame += match held.exclude {
761 WindowExclude::NoOthers => "",
762 WindowExclude::CurrentRow => " EXCLUDE CURRENT ROW",
763 WindowExclude::Group => " EXCLUDE GROUP",
764 WindowExclude::Ties => " EXCLUDE TIES",
765 };
766 inside.push(frame);
767 }
768 format!("{call} OVER ({})", inside.join(" "))
769}
770
771fn bound(ast: &Ast, end: WindowBound) -> String {
773 match end {
774 WindowBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
775 WindowBound::Preceding(offset) => format!("{} PRECEDING", expr(ast, offset)),
776 WindowBound::CurrentRow => "CURRENT ROW".to_string(),
777 WindowBound::Following(offset) => format!("{} FOLLOWING", expr(ast, offset)),
778 WindowBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
779 }
780}
781
782fn operator(ast: &Ast, name: Slice, written: &str) -> String {
789 let one = ast.name(name).next().unwrap_or_default();
790 let alone = ast.name(name).count() == 1;
791 if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
792 return "COALESCE".to_string();
793 }
794 if alone && one.eq_ignore_ascii_case("try") {
795 return "TRY".to_string();
796 }
797 written.to_string()
798}
799
800fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
806 let mut out = "CASE ".to_string();
807 for arm in ast.arm_list(arms) {
808 let when = when(ast, operand, arm);
809 out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
810 }
811 let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
812 out + &format!(" ELSE {last} END")
813}
814
815fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
818 if operand == NONE {
819 return expr(ast, arm.when);
820 }
821 format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
822}
823
824#[must_use]
838pub fn typename(text: &str) -> String {
839 let text = text.trim();
840 if let Some(open) = suffix(text) {
843 return typename(&text[..open]) + &text[open..];
844 }
845 let (base, arguments) = arguments(text);
846 if let ("ENUM", Some(inside)) = (base.to_ascii_uppercase().as_str(), arguments) {
848 return format!("ENUM({})", pieces(inside).join(", "));
849 }
850 let Some(name) = standard(base) else {
851 let base = unquote(base);
852 return match arguments {
853 Some(arguments) => format!("{}({arguments})", catalogued(&base)),
854 None => catalogued(&base),
855 };
856 };
857 match (name, arguments) {
858 ("STRUCT" | "UNION", Some(inside)) => {
861 let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
862 format!("{name}({})", written.join(", "))
863 }
864 ("MAP", Some(inside)) => {
865 let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
866 format!("{name}({})", written.join(", "))
867 }
868 ("DECIMAL" | "VARCHAR", Some(inside)) => {
871 format!("{name}({})", pieces(inside).join(", "))
872 }
873 _ => name.to_string(),
876 }
877}
878
879fn catalogued(base: &str) -> String {
892 if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
893}
894
895fn unquote(base: &str) -> String {
897 match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
898 Some(inside) => inside.replace("\"\"", "\""),
899 None => base.to_string(),
900 }
901}
902
903fn suffix(text: &str) -> Option<usize> {
905 let rest = text.strip_suffix(']')?;
906 let open = rest.rfind('[')?;
907 rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
908}
909
910fn arguments(text: &str) -> (&str, Option<&str>) {
912 let Some(rest) = text.strip_suffix(')') else {
913 return (text, None);
914 };
915 let mut depth = 0usize;
916 for (at, byte) in rest.bytes().enumerate() {
917 match byte {
918 b'(' if depth == 0 => depth = 1,
919 b'(' => depth += 1,
920 b')' => depth -= 1,
921 _ => continue,
922 }
923 if depth == 1 && byte == b'(' {
924 return (rest[..at].trim(), Some(rest[at + 1..].trim()));
925 }
926 }
927 (text, None)
928}
929
930fn pieces(inside: &str) -> Vec<&str> {
932 let mut found = Vec::new();
933 let (mut depth, mut quoted, mut text, mut start) = (0usize, false, false, 0usize);
934 for (at, byte) in inside.bytes().enumerate() {
935 match byte {
936 b'"' if !text => quoted = !quoted,
937 b'\'' if !quoted => text = !text,
939 _ if quoted || text => {}
940 b'(' | b'[' => depth += 1,
941 b')' | b']' => depth = depth.saturating_sub(1),
942 b',' if depth == 0 => {
943 found.push(inside[start..at].trim());
944 start = at + 1;
945 }
946 _ => {}
947 }
948 }
949 found.push(inside[start..].trim());
950 found
951}
952
953fn field(piece: &str) -> String {
955 let mut quoting = false;
956 for (at, byte) in piece.bytes().enumerate() {
957 match byte {
958 b'"' => quoting = !quoting,
959 byte if byte.is_ascii_whitespace() && !quoting => {
960 let name = piece[..at].trim();
961 let name =
962 if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
963 return format!("{name} {}", typename(&piece[at + 1..]));
964 }
965 _ => {}
966 }
967 }
968 piece.to_string()
969}
970
971fn standard(base: &str) -> Option<&'static str> {
977 const NAMES: &[(&str, &str)] = &[
978 ("BOOLEAN", "BOOLEAN"),
979 ("INT", "INTEGER"),
980 ("INTEGER", "INTEGER"),
981 ("SMALLINT", "SMALLINT"),
982 ("BIGINT", "BIGINT"),
983 ("DEC", "DECIMAL"),
984 ("DECIMAL", "DECIMAL"),
985 ("NUMERIC", "DECIMAL"),
986 ("REAL", "FLOAT"),
987 ("FLOAT", "FLOAT"),
988 ("DOUBLE PRECISION", "DOUBLE"),
989 ("CHAR", "VARCHAR"),
990 ("CHARACTER", "VARCHAR"),
991 ("CHARACTER VARYING", "VARCHAR"),
992 ("NATIONAL CHARACTER", "VARCHAR"),
993 ("NATIONAL CHARACTER VARYING", "VARCHAR"),
994 ("VARCHAR", "VARCHAR"),
995 ("BIT", "BIT"),
996 ("DATE", "DATE"),
997 ("TIME", "TIME"),
998 ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
999 ("TIME WITHOUT TIME ZONE", "TIME"),
1000 ("TIMESTAMP", "TIMESTAMP"),
1001 ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
1002 ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
1003 ("INTERVAL", "INTERVAL"),
1004 ("STRUCT", "STRUCT"),
1005 ("UNION", "UNION"),
1006 ("MAP", "MAP"),
1007 ];
1008 let written: Vec<&str> = base.split_whitespace().collect();
1009 let written = written.join(" ");
1010 NAMES
1011 .iter()
1012 .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
1013 .map(|(_, name)| *name)
1014}
1015
1016fn exprs(ast: &Ast, list: Slice) -> String {
1018 let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
1019 written.join(", ")
1020}
1021
1022fn names(ast: &Ast, list: Slice) -> String {
1024 ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
1025}
1026
1027fn parts(ast: &Ast, list: Slice) -> String {
1029 ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
1030}
1031
1032#[cfg(test)]
1033mod tests {
1034 use super::create_view;
1035 use crate::ast::Statement;
1036 use crate::transform::parse_ast;
1037
1038 fn whole(sql: &str) -> String {
1040 let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
1041 let Statement::CreateView(index) = ast.statements[0] else {
1042 panic!("that was not a create view");
1043 };
1044 create_view(&ast, index)
1045 }
1046
1047 fn body(query: &str) -> String {
1049 let written = whole(&format!("CREATE VIEW v AS {query}"));
1050 written
1051 .strip_prefix("CREATE VIEW v AS ")
1052 .and_then(|rest| rest.strip_suffix(';'))
1053 .expect("the statement wrapper is there")
1054 .to_string()
1055 }
1056
1057 #[test]
1058 fn a_statement_loses_its_qualification_and_its_or_replace() {
1059 assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
1060 assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
1061 assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
1062 assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
1063 }
1064
1065 #[test]
1067 fn an_alias_list_is_written_with_a_space_in_front_of_it() {
1068 assert_eq!(
1069 whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
1070 r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
1071 );
1072 }
1073
1074 #[test]
1075 fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
1076 assert_eq!(
1077 whole("CREATE VIEW v AS SELECT X /* a note */ FROM T"),
1078 "CREATE VIEW v AS SELECT X FROM T;"
1079 );
1080 }
1081
1082 #[test]
1083 fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
1084 assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
1085 assert_eq!(
1086 body("SELECT x > 1 AND y < 2 OR b FROM t"),
1087 "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
1088 );
1089 assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
1090 assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
1091 assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
1092 assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
1093 }
1094
1095 #[test]
1098 fn a_minus_in_front_of_a_constant_folds_into_it() {
1099 assert_eq!(body("SELECT -1"), "SELECT -1");
1100 assert_eq!(body("SELECT - -3"), "SELECT 3");
1101 assert_eq!(body("SELECT +3"), "SELECT +(3)");
1102 }
1103
1104 #[test]
1105 fn the_null_tests_and_the_boolean_tests() {
1106 assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
1107 assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
1108 assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
1109 assert_eq!(
1110 body("SELECT b IS TRUE FROM t"),
1111 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
1112 );
1113 assert_eq!(
1114 body("SELECT b IS NOT TRUE FROM t"),
1115 "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
1116 );
1117 assert_eq!(
1118 body("SELECT b IS FALSE FROM t"),
1119 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
1120 );
1121 assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
1122 assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
1123 assert_eq!(
1124 body("SELECT x IS DISTINCT FROM y FROM t"),
1125 "SELECT (x IS DISTINCT FROM y) FROM t"
1126 );
1127 }
1128
1129 #[test]
1130 fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
1131 assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
1132 assert_eq!(
1133 body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
1134 "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
1135 );
1136 assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
1137 assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
1138 assert_eq!(body("SELECT x IN (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1139 assert_eq!(
1140 body("SELECT x NOT IN (SELECT y FROM t)"),
1141 "SELECT (NOT (x = ANY(SELECT y FROM t)))"
1142 );
1143 assert_eq!(body("SELECT x = ANY (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1144 assert_eq!(
1145 body("SELECT x > ALL (SELECT y FROM t)"),
1146 "SELECT (NOT (x <= ANY(SELECT y FROM t)))"
1147 );
1148 }
1149
1150 #[test]
1153 fn the_pattern_operators_come_back_as_symbols() {
1154 assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
1155 assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
1156 assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
1157 assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
1158 assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
1159 assert_eq!(body("SELECT s !~ 'a' FROM t"), "SELECT (s !~ 'a') FROM t");
1160 assert_eq!(
1161 body("SELECT s NOT SIMILAR TO 'a' FROM t"),
1162 "SELECT (NOT regexp_full_match(s, 'a')) FROM t"
1163 );
1164 }
1165
1166 #[test]
1167 fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
1168 assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
1169 assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
1170 assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
1171 assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
1172 assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
1173 }
1174
1175 #[test]
1177 fn a_case_is_written_the_long_way_round() {
1178 assert_eq!(
1179 body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
1180 "SELECT CASE WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
1181 );
1182 assert_eq!(
1183 body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
1184 "SELECT CASE WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
1185 );
1186 }
1187
1188 #[test]
1189 fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
1190 assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
1191 assert_eq!(
1192 body("SELECT cast(x as decimal(4,1)) FROM t"),
1193 "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
1194 );
1195 assert_eq!(
1196 body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
1197 "SELECT TRY_CAST(s AS INTEGER) FROM t"
1198 );
1199 }
1200
1201 #[test]
1203 fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
1204 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1205 assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
1206 assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
1207 assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
1208 assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
1209 assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
1210 assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
1213 assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
1214 assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
1215 assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
1216 assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
1217 assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
1218 assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
1219 assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
1220 }
1221
1222 #[test]
1224 fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
1225 assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
1226 assert_eq!(
1227 body("SELECT CAST(x AS struct(\"A b\" int))"),
1228 "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
1229 );
1230 }
1231
1232 #[test]
1234 fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
1235 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1236 assert_eq!(cast("text"), "SELECT CAST(x AS text)");
1237 assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
1238 assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
1239 assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1240 assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1241 assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1242 assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1243 assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1245 assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1246 assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1247 assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1248 }
1249
1250 #[test]
1251 fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1252 assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1253 assert_eq!(body("SELECT count() FROM t"), "SELECT count_star() FROM t");
1254 assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1255 assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1256 assert_eq!(body("SELECT []"), "SELECT list_value()");
1257 }
1258
1259 #[test]
1262 fn a_function_name_is_quoted_when_it_is_a_keyword() {
1263 assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1264 assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1265 }
1266
1267 #[test]
1268 fn the_literals() {
1269 assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1270 assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1271 assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1272 }
1273
1274 #[test]
1276 fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1277 assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1278 assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1279 assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1280 assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1281 assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1282 }
1283
1284 #[test]
1286 fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1287 assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1288 assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1289 assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1290 assert_eq!(
1292 body("SELECT extract(millisecond FROM d)"),
1293 "SELECT date_part('MILLISECONDS', d)"
1294 );
1295 assert_eq!(
1296 body("SELECT extract(microseconds FROM d)"),
1297 "SELECT date_part('MICROSECONDS', d)"
1298 );
1299 assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1300 assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1302 assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1303 }
1304
1305 #[test]
1307 fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1308 assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1309 assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1310 assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1312 assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1314 assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1315 }
1316
1317 #[test]
1318 fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1319 assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1320 assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1321 assert_eq!(
1322 body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1323 "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1324 );
1325 assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1326 assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1327 assert_eq!(
1328 body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1329 "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1330 );
1331 assert_eq!(
1332 body("SELECT DISTINCT ON (x) x, y FROM t"),
1333 "SELECT DISTINCT ON (x) x, y FROM t"
1334 );
1335 }
1336
1337 #[test]
1340 fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1341 assert_eq!(
1342 body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1343 "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1344 );
1345 assert_eq!(
1346 body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1347 "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1348 );
1349 assert_eq!(
1350 body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1351 "(SELECT x FROM t) UNION (SELECT y FROM t)"
1352 );
1353 }
1354
1355 #[test]
1356 fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1357 assert_eq!(
1358 body("VALUES (1, 'a'), (2, 'b')"),
1359 "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1360 );
1361 }
1362
1363 #[test]
1365 fn a_from_list_has_a_space_before_the_comma() {
1366 assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1367 }
1368
1369 #[test]
1370 fn a_from_item_and_its_aliases() {
1371 assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1372 assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1373 assert_eq!(
1374 body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1375 "SELECT 1 FROM (SELECT x FROM t) AS sub"
1376 );
1377 assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1378 }
1379
1380 #[test]
1383 fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1384 assert_eq!(
1385 body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1386 "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1387 );
1388 assert_eq!(
1389 body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1390 "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1391 );
1392 assert_eq!(
1393 body("SELECT 1 FROM t CROSS JOIN t AS u"),
1394 "SELECT 1 FROM (t CROSS JOIN t AS u)"
1395 );
1396 assert_eq!(
1397 body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1398 "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1399 );
1400 assert_eq!(
1401 body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1402 "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1403 );
1404 assert_eq!(
1405 body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1406 "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1407 );
1408 }
1409
1410 #[test]
1411 fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1412 assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1413 assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1414 assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1415 assert_eq!(
1416 body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1417 "SELECT * REPLACE ((x + 1) AS x) FROM t"
1418 );
1419 }
1420
1421 #[test]
1422 fn a_describe_gets_parentheses_round_what_it_describes() {
1423 assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1424 }
1425
1426 #[test]
1429 fn a_window_is_written_with_the_parts_that_were_written_in_it() {
1430 assert_eq!(
1431 body("SELECT row_number() OVER () AS n FROM t"),
1432 "SELECT row_number() OVER () AS n FROM t"
1433 );
1434 assert_eq!(
1435 body(
1436 "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1437 ),
1438 "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1439 );
1440 assert_eq!(
1441 body(
1442 "SELECT sum(i) OVER (PARTITION BY i, i+1 ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1443 ),
1444 "SELECT sum(i) OVER (PARTITION BY i, (i + 1) ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1445 );
1446 assert_eq!(
1447 body("SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"),
1448 "SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"
1449 );
1450 assert_eq!(
1451 body("SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"),
1452 "SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"
1453 );
1454 assert_eq!(
1455 body("SELECT first_value(i RESPECT NULLS) OVER (ORDER BY i) AS n FROM t"),
1456 "SELECT first_value(i) OVER (ORDER BY i) AS n FROM t"
1457 );
1458 assert_eq!(
1459 body("SELECT count(*) OVER () AS n FROM t"),
1460 "SELECT count() OVER () AS n FROM t"
1461 );
1462 assert_eq!(
1463 body("SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"),
1464 "SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"
1465 );
1466 }
1467
1468 #[test]
1471 fn a_filter_is_written_after_the_call_and_before_the_over() {
1472 assert_eq!(
1473 body("SELECT sum(x) FILTER (WHERE y > 1) FROM t"),
1474 "SELECT sum(x) FILTER (WHERE (y > 1)) FROM t"
1475 );
1476 assert_eq!(
1477 body("SELECT sum(x) FILTER (y > 1) FROM t"),
1478 "SELECT sum(x) FILTER (WHERE (y > 1)) FROM t"
1479 );
1480 assert_eq!(
1481 body("SELECT count(*) FILTER (WHERE b) FROM t"),
1482 "SELECT count_star() FILTER (WHERE b) FROM t"
1483 );
1484 assert_eq!(
1485 body("SELECT count() FILTER (WHERE b) FROM t"),
1486 "SELECT count_star() FILTER (WHERE b) FROM t"
1487 );
1488 assert_eq!(
1489 body("SELECT sum(x) FILTER (WHERE y > 1) OVER (ORDER BY x) FROM t"),
1490 "SELECT sum(x) FILTER (WHERE (y > 1)) OVER (ORDER BY x) FROM t"
1491 );
1492 assert_eq!(
1493 body("SELECT sum(DISTINCT x) FILTER (WHERE b) OVER () FROM t"),
1494 "SELECT sum(DISTINCT x) FILTER (WHERE b) OVER () FROM t"
1495 );
1496 assert_eq!(
1497 body("SELECT count(*) FILTER (WHERE b) OVER () FROM t"),
1498 "SELECT count() FILTER (WHERE b) OVER () FROM t"
1499 );
1500 }
1501
1502 #[test]
1504 fn a_frame_is_written_only_when_it_is_not_the_one_that_was_assumed() {
1505 assert_eq!(
1506 body(
1507 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1508 ),
1509 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1510 );
1511 assert_eq!(
1512 body("SELECT sum(i) OVER (ORDER BY i RANGE UNBOUNDED PRECEDING) AS n FROM t"),
1513 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1514 );
1515 assert_eq!(
1516 body(
1517 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS n FROM t"
1518 ),
1519 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1520 );
1521 assert_eq!(
1522 body("SELECT sum(i) OVER (ORDER BY i ROWS CURRENT ROW) AS n FROM t"),
1523 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) AS n FROM t"
1524 );
1525 assert_eq!(
1526 body(
1527 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1+1) PRECEDING AND CURRENT ROW) AS n FROM t"
1528 ),
1529 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1 + 1) PRECEDING AND CURRENT ROW) AS n FROM t"
1530 );
1531 assert_eq!(
1532 body(
1533 "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1534 ),
1535 "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1536 );
1537 assert_eq!(
1538 body(
1539 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1540 ),
1541 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1542 );
1543 assert_eq!(
1544 body(
1545 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1546 ),
1547 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1548 );
1549 }
1550
1551 #[test]
1554 fn a_frame_that_covers_the_partition_is_written_as_a_row_count() {
1555 for unit in ["ROWS", "RANGE", "GROUPS"] {
1556 assert_eq!(
1557 body(&format!(
1558 "SELECT sum(i) OVER (ORDER BY i {unit} BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1559 )),
1560 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1561 );
1562 }
1563 assert_eq!(
1564 body(
1565 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1566 ),
1567 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1568 );
1569 }
1570
1571 #[test]
1574 fn a_named_window_is_written_out_where_it_was_used() {
1575 assert_eq!(
1576 body("SELECT sum(i) OVER w AS n FROM t WINDOW w AS (PARTITION BY i ORDER BY i)"),
1577 "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1578 );
1579 assert_eq!(
1580 body("SELECT sum(i) OVER (w) AS n FROM t WINDOW w AS (ORDER BY i)"),
1581 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1582 );
1583 assert_eq!(
1584 body(
1585 "SELECT sum(i) OVER (w ROWS UNBOUNDED PRECEDING) AS n FROM t WINDOW w AS (PARTITION BY i)"
1586 ),
1587 "SELECT sum(i) OVER (PARTITION BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1588 );
1589 assert_eq!(
1590 body("SELECT sum(i) OVER (w PARTITION BY i) AS n FROM t WINDOW w AS (ORDER BY i)"),
1591 "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1592 );
1593 assert_eq!(
1594 body("SELECT sum(i) OVER v AS n FROM t WINDOW w AS (ORDER BY i), v AS (w)"),
1595 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1596 );
1597 }
1598}