1use crate::ast::{
71 Ast, BinaryOp, CaseArm, CreateViewRef, Distinct, Expr, ExprRef, JoinKind, LiteralKind, Nulls,
72 Order, OrderItem, Quantifier, QueryBody, QueryRef, SelectRef, SetOp, Slice, Source, SourceRef,
73 StrRef, Target, UnaryOp, WindowBound, WindowExclude, WindowRef, WindowUnit,
74};
75use crate::matcher::NONE;
76use crate::tokenize::quoted;
77
78#[must_use]
84pub fn create_view(ast: &Ast, index: CreateViewRef) -> String {
85 let written = ast.create_view(index);
86 let name = ast.name(written.name).last().unwrap_or_default();
87 let temporary = if written.temporary { "TEMP " } else { "" };
88 let mut out = format!("CREATE {temporary}VIEW {}", quoted(name));
89 if !written.columns.is_empty() {
90 out += &format!(" ({})", names(ast, written.columns));
93 }
94 out + &format!(" AS {};", query(ast, written.query))
95}
96
97#[must_use]
99pub fn query(ast: &Ast, index: QueryRef) -> String {
100 let held = ast.query(index);
101 let mut out = match held.body {
102 QueryBody::Select(select) => selection(ast, select),
103 QueryBody::SetOp { op, quantifier, by_name, left, right } => {
104 setop(ast, op, quantifier, by_name, left, right)
105 }
106 QueryBody::Values(rows) => format!("SELECT * FROM ({}) AS valueslist", values(ast, rows)),
109 QueryBody::Describe(inner) => format!("DESCRIBE ({})", query(ast, inner)),
110 QueryBody::Show { name, .. } => format!("SHOW {}", ast.name_text(name)),
111 };
112 if held.order_by_all {
113 out += " ORDER BY COLUMNS(*)";
115 } else if !held.order_by.is_empty() {
116 let items: Vec<String> =
117 ast.order_list(held.order_by).iter().map(|item| order(ast, item)).collect();
118 out += &format!(" ORDER BY {}", items.join(", "));
119 }
120 if held.limit != NONE {
121 if held.limit_percent {
124 out += &format!(" LIMIT ({}) %", expr(ast, held.limit));
125 } else {
126 out += &format!(" LIMIT {}", expr(ast, held.limit));
127 }
128 }
129 if held.offset != NONE {
130 out += &format!(" OFFSET {}", expr(ast, held.offset));
131 }
132 out
133}
134
135fn setop(
142 ast: &Ast,
143 op: SetOp,
144 quantifier: Quantifier,
145 by_name: bool,
146 left: QueryRef,
147 right: QueryRef,
148) -> String {
149 let word = match op {
150 SetOp::Union => "UNION",
151 SetOp::Except => "EXCEPT",
152 SetOp::Intersect => "INTERSECT",
153 };
154 let all = if matches!(quantifier, Quantifier::All) { " ALL" } else { "" };
156 let named = if by_name { " BY NAME" } else { "" };
157 format!("{}{word}{all}{named} {}", branch(ast, left, true), branch(ast, right, false))
158}
159
160fn branch(ast: &Ast, index: QueryRef, left: bool) -> String {
162 let text = query(ast, index);
163 if matches!(ast.query(index).body, QueryBody::SetOp { .. }) {
164 return text;
165 }
166 if left { format!("({text}) ") } else { format!("({text})") }
167}
168
169fn selection(ast: &Ast, index: SelectRef) -> String {
171 let held = ast.select(index);
172 let mut out = "SELECT".to_string();
173 match held.distinct {
174 Distinct::No => {}
175 Distinct::Yes => out += " DISTINCT",
176 Distinct::On(list) => out += &format!(" DISTINCT ON ({})", exprs(ast, list)),
177 }
178 let targets: Vec<String> =
179 ast.target_list(held.targets).iter().map(|target| aliased(ast, target)).collect();
180 out += &format!(" {}", targets.join(", "));
181 if !held.from.is_empty() {
182 let sources: Vec<String> =
184 ast.source_list(held.from).iter().map(|&index| source(ast, index)).collect();
185 out += &format!(" FROM {}", sources.join(" , "));
186 }
187 if held.filter != NONE {
188 out += &format!(" WHERE {}", expr(ast, held.filter));
189 }
190 if held.group_by_all {
191 out += " GROUP BY ALL";
192 } else if !held.group_by.is_empty() {
193 out += &format!(" GROUP BY {}", exprs(ast, held.group_by));
194 }
195 if held.having != NONE {
196 out += &format!(" HAVING {}", expr(ast, held.having));
197 }
198 out
199}
200
201fn aliased(ast: &Ast, target: &Target) -> String {
203 let written = expr(ast, target.expr);
204 if target.alias == NONE {
205 return written;
206 }
207 format!("{written} AS {}", quoted(ast.string(target.alias)))
208}
209
210fn order(ast: &Ast, item: &OrderItem) -> String {
212 let mut out = expr(ast, item.expr);
213 match item.order {
214 Order::Unstated => {}
215 Order::Ascending => out += " ASC",
216 Order::Descending => out += " DESC",
217 }
218 match item.nulls {
219 Nulls::Unstated => {}
220 Nulls::First => out += " NULLS FIRST",
221 Nulls::Last => out += " NULLS LAST",
222 }
223 out
224}
225
226fn source(ast: &Ast, index: SourceRef) -> String {
228 match ast.source(index) {
229 Source::Table { name, alias, columns } => label(ast, parts(ast, name), alias, columns),
230 Source::Subquery { query: inner, alias, columns } => {
231 label(ast, format!("({})", query(ast, inner)), alias, columns)
232 }
233 Source::Function { name, args, alias, columns, .. } => {
234 let written: Vec<String> =
235 ast.target_list(args).iter().map(|arg| argument(ast, arg)).collect();
236 let call = format!("{}({})", parts(ast, name), written.join(", "));
237 label(ast, call, alias, columns)
238 }
239 Source::Values { rows, alias, columns } => {
242 let inner = format!("(SELECT * FROM ({}) AS valueslist)", values(ast, rows));
243 label(ast, inner, alias, columns)
244 }
245 Source::Join { left, right, kind, natural, on, using } => {
246 let word = match kind {
247 JoinKind::Inner => "INNER",
248 JoinKind::Left => "LEFT",
249 JoinKind::Right => "RIGHT",
250 JoinKind::Full => "FULL",
252 JoinKind::Semi => "SEMI",
253 JoinKind::Anti => "ANTI",
254 JoinKind::Cross => "CROSS",
255 JoinKind::Positional => "POSITIONAL",
256 };
257 let natural = if natural { "NATURAL " } else { "" };
258 let mut out =
259 format!("({} {natural}{word} JOIN {}", source(ast, left), source(ast, right));
260 if on != NONE {
261 out += &format!(" ON ({})", expr(ast, on));
264 }
265 if !using.is_empty() {
266 out += &format!(" USING ({})", names(ast, using));
267 }
268 out + ")"
269 }
270 }
271}
272
273fn argument(ast: &Ast, arg: &Target) -> String {
279 if arg.alias == NONE {
280 return expr(ast, arg.expr);
281 }
282 format!("({} = {})", quoted(ast.string(arg.alias)), expr(ast, arg.expr))
283}
284
285fn label(ast: &Ast, written: String, alias: StrRef, columns: Slice) -> String {
287 let mut out = written;
288 if alias != NONE {
289 out += &format!(" AS {}", quoted(ast.string(alias)));
290 }
291 if !columns.is_empty() {
292 out += &format!("({})", names(ast, columns));
293 }
294 out
295}
296
297fn values(ast: &Ast, rows: Slice) -> String {
299 let written: Vec<String> =
300 ast.rows(rows).iter().map(|&row| format!("({})", exprs(ast, row))).collect();
301 format!("VALUES {}", written.join(", "))
302}
303
304pub fn expression(ast: &Ast, index: ExprRef) -> String {
310 expr(ast, index)
311}
312
313fn expr(ast: &Ast, index: ExprRef) -> String {
315 match ast.expr(index) {
316 Expr::Star { qualifier, replacements } => star(ast, qualifier, replacements),
317 Expr::Column { name } => parts(ast, name),
318 Expr::Literal { kind, text } => literal(ast, kind, text),
319 Expr::Unary { op, operand } => unary(ast, op, operand),
320 Expr::Binary { op, left, right } => binary(ast, op, left, right),
321 Expr::Function { name, args, distinct } => call(ast, name, args, distinct),
322 Expr::Window { name, args, distinct, ignore_nulls, spec } => {
323 window(ast, name, args, distinct, ignore_nulls, spec)
324 }
325 Expr::Cast { operand, ty, try_cast } => {
326 let word = if try_cast { "TRY_CAST" } else { "CAST" };
327 format!("{word}({} AS {})", expr(ast, operand), typename(ast.string(ty)))
328 }
329 Expr::Case { operand, arms, otherwise } => case(ast, operand, arms, otherwise),
330 Expr::Between { operand, low, high, negated } => {
331 let written = format!(
332 "({} BETWEEN {} AND {})",
333 expr(ast, operand),
334 expr(ast, low),
335 expr(ast, high)
336 );
337 if negated { format!("(NOT {written})") } else { written }
338 }
339 Expr::In { operand, list, negated } => {
340 let written = format!("({} IN ({}))", expr(ast, operand), exprs(ast, list));
341 if negated { format!("(NOT {written})") } else { written }
342 }
343 Expr::InSubquery { operand, query: inner, negated } => {
344 let any = format!("({} = ANY({}))", expr(ast, operand), query(ast, inner));
345 if negated { format!("(NOT {any})") } else { any }
346 }
347 Expr::QuantifiedSubquery { operand, op, query: inner, all } => {
348 let (op, negate) = if all { (negated_comparison(op), true) } else { (op, false) };
349 let word = comparison_word(op);
350 let any = format!("({} {word} ANY({}))", expr(ast, operand), query(ast, inner));
351 if negate { format!("(NOT {any})") } else { any }
352 }
353 Expr::Parameter { name } => format!("${}", ast.string(name)),
354 Expr::List { items } => format!("list_value({})", exprs(ast, items)),
356 Expr::Row { items } => format!("\"row\"({})", exprs(ast, items)),
359 Expr::Subquery { query: inner } => format!("({})", query(ast, inner)),
360 Expr::Exists { query: inner, negated } => {
361 let exists = format!("EXISTS({})", query(ast, inner));
362 if negated { format!("(NOT {exists})") } else { exists }
363 }
364 }
365}
366
367fn comparison_word(op: BinaryOp) -> &'static str {
368 match op {
369 BinaryOp::Eq => "=",
370 BinaryOp::NotEq => "!=",
371 BinaryOp::Lt => "<",
372 BinaryOp::Gt => ">",
373 BinaryOp::LtEq => "<=",
374 BinaryOp::GtEq => ">=",
375 _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
376 }
377}
378
379fn negated_comparison(op: BinaryOp) -> BinaryOp {
380 match op {
381 BinaryOp::Eq => BinaryOp::NotEq,
382 BinaryOp::NotEq => BinaryOp::Eq,
383 BinaryOp::Lt => BinaryOp::GtEq,
384 BinaryOp::Gt => BinaryOp::LtEq,
385 BinaryOp::LtEq => BinaryOp::Gt,
386 BinaryOp::GtEq => BinaryOp::Lt,
387 _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
388 }
389}
390
391fn star(ast: &Ast, qualifier: Slice, replacements: Slice) -> String {
393 let mut out =
394 if qualifier.is_empty() { "*".to_string() } else { format!("{}.*", parts(ast, qualifier)) };
395 if !replacements.is_empty() {
396 let written: Vec<String> =
397 ast.target_list(replacements).iter().map(|target| aliased(ast, target)).collect();
398 out += &format!(" REPLACE ({})", written.join(", "));
399 }
400 out
401}
402
403fn literal(ast: &Ast, kind: LiteralKind, text: StrRef) -> String {
405 match kind {
406 LiteralKind::Null => "NULL".to_string(),
409 LiteralKind::True => "true".to_string(),
410 LiteralKind::False => "false".to_string(),
411 LiteralKind::Number => number(ast.string(text)),
412 LiteralKind::String => string(ast.string(text)),
413 LiteralKind::Blob => format!("{}::BLOB", string(ast.string(text))),
416 }
417}
418
419fn number(written: &str) -> String {
428 let text = written.replace('_', "");
429 if text.contains(['e', 'E']) {
430 return double(&text);
431 }
432 let Some((whole, fraction)) = text.split_once('.') else {
433 return leading(&text).to_string();
434 };
435 if fraction.is_empty() {
438 return leading(whole).to_string();
439 }
440 format!("{}.{fraction}", if whole.is_empty() { "" } else { leading(whole) })
441}
442
443fn leading(digits: &str) -> &str {
445 let trimmed = digits.trim_start_matches('0');
446 if trimmed.is_empty() { &digits[digits.len().saturating_sub(1)..] } else { trimmed }
447}
448
449fn double(text: &str) -> String {
457 let Ok(value) = text.parse::<f64>() else {
458 return text.to_string();
459 };
460 let shortest = format!("{value:e}");
463 let (mantissa, exponent) = shortest.split_once('e').unwrap_or((shortest.as_str(), "0"));
464 let exponent: i32 = exponent.parse().unwrap_or(0);
465 if (-4..=15).contains(&exponent) {
466 let plain = format!("{value}");
467 return if plain.contains('.') { plain } else { plain + ".0" };
468 }
469 let sign = if exponent < 0 { '-' } else { '+' };
470 format!("{mantissa}e{sign}{:02}", exponent.abs())
471}
472
473fn string(text: &str) -> String {
478 format!("'{}'", text.replace('\'', "''"))
479}
480
481fn unary(ast: &Ast, op: UnaryOp, operand: ExprRef) -> String {
483 if matches!(op, UnaryOp::Negate) {
487 if let Some(number) = negated(ast, operand) {
488 return number;
489 }
490 }
491 let written = expr(ast, operand);
492 match op {
493 UnaryOp::Not => format!("(NOT {written})"),
494 UnaryOp::Negate => format!("-({written})"),
495 UnaryOp::Plus => format!("+({written})"),
496 UnaryOp::BitNot => format!("~({written})"),
497 UnaryOp::Factorial => format!("factorial({written})"),
499 UnaryOp::IsNull => format!("({written} IS NULL)"),
500 UnaryOp::IsNotNull => format!("({written} IS NOT NULL)"),
501 UnaryOp::IsUnknown => format!("({written} IS NULL)"),
503 UnaryOp::IsNotUnknown => format!("({written} IS NOT NULL)"),
504 UnaryOp::IsTrue => distinct(&written, "true", true),
508 UnaryOp::IsNotTrue => distinct(&written, "true", false),
509 UnaryOp::IsFalse => distinct(&written, "false", true),
510 UnaryOp::IsNotFalse => distinct(&written, "false", false),
511 }
512}
513
514fn distinct(operand: &str, against: &str, same: bool) -> String {
516 let word = if same { "IS NOT DISTINCT FROM" } else { "IS DISTINCT FROM" };
517 format!("(CAST({operand} AS BOOLEAN) {word} {against})")
518}
519
520fn negated(ast: &Ast, index: ExprRef) -> Option<String> {
525 match ast.expr(index) {
526 Expr::Literal { kind: LiteralKind::Number, text } => {
527 Some(format!("-{}", number(ast.string(text))))
528 }
529 Expr::Unary { op: UnaryOp::Negate, operand } => {
530 let inner = negated(ast, operand)?;
531 Some(inner.strip_prefix('-').unwrap_or(&inner).to_string())
532 }
533 _ => None,
534 }
535}
536
537fn binary(ast: &Ast, op: BinaryOp, left: ExprRef, right: ExprRef) -> String {
539 let (left, right) = (expr(ast, left), expr(ast, right));
540 match op {
542 BinaryOp::SimilarTo => return format!("regexp_full_match({left}, {right})"),
543 BinaryOp::NotSimilarTo => return format!("(NOT regexp_full_match({left}, {right}))"),
544 BinaryOp::AtTimeZone => return format!("timezone({right}, {left})"),
546 BinaryOp::Collate => return format!("{left} COLLATE {right}"),
548 _ => {}
549 }
550 let word = match op {
551 BinaryOp::Or => "OR",
552 BinaryOp::And => "AND",
553 BinaryOp::Eq => "=",
554 BinaryOp::NotEq => "!=",
555 BinaryOp::Lt => "<",
556 BinaryOp::Gt => ">",
557 BinaryOp::LtEq => "<=",
558 BinaryOp::GtEq => ">=",
559 BinaryOp::IsDistinctFrom => "IS DISTINCT FROM",
560 BinaryOp::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
561 BinaryOp::Add => "+",
562 BinaryOp::Subtract => "-",
563 BinaryOp::Multiply => "*",
564 BinaryOp::Divide => "/",
565 BinaryOp::IntegerDivide => "//",
566 BinaryOp::Modulo => "%",
567 BinaryOp::Power => "**",
570 BinaryOp::BitAnd => "&",
571 BinaryOp::BitOr => "|",
572 BinaryOp::ShiftLeft => "<<",
573 BinaryOp::ShiftRight => ">>",
574 BinaryOp::Concat => "||",
575 BinaryOp::Like => "~~",
578 BinaryOp::NotLike => "!~~",
579 BinaryOp::ILike => "~~*",
580 BinaryOp::NotILike => "!~~*",
581 BinaryOp::Glob => "~~~",
582 BinaryOp::Regex => "~",
583 BinaryOp::NotRegex => "!~",
584 BinaryOp::RegexInsensitive => "~*",
585 BinaryOp::NotRegexInsensitive => "!~*",
586 BinaryOp::Arrow => "->",
587 BinaryOp::LongArrow => "->>",
588 BinaryOp::Contains => "@>",
589 BinaryOp::ContainedBy => "<@",
590 BinaryOp::Overlaps => "&&",
591 BinaryOp::StartsWith => "^@",
592 BinaryOp::InetContainedByOrEq => "<<=",
593 BinaryOp::InetContainsOrEq => ">>=",
594 BinaryOp::Named(name) => ast.string(name),
595 BinaryOp::SimilarTo | BinaryOp::NotSimilarTo | BinaryOp::AtTimeZone | BinaryOp::Collate => {
596 unreachable!("the four that return above")
597 }
598 };
599 format!("({left} {word} {right})")
600}
601
602fn call(ast: &Ast, name: Slice, args: Slice, distinct: bool) -> String {
604 let written = parts(ast, name);
605 let list = ast.expr_list(args);
606 if written.eq_ignore_ascii_case("count") {
610 let starred = list.len() == 1
611 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
612 if qualifier.is_empty() && replacements.is_empty());
613 if starred || list.is_empty() {
614 return "count_star()".to_string();
615 }
616 }
617 let word = if distinct { "DISTINCT " } else { "" };
618 format!("{}({word}{})", operator(ast, name, &written), exprs(ast, args))
619}
620
621fn window(
628 ast: &Ast,
629 name: Slice,
630 args: Slice,
631 distinct: bool,
632 ignore_nulls: bool,
633 spec: WindowRef,
634) -> String {
635 let word = if distinct { "DISTINCT " } else { "" };
636 let nulls = if ignore_nulls { " IGNORE NULLS" } else { "" };
638 let written = parts(ast, name);
639 let list = ast.expr_list(args);
643 let bare = list.len() == 1
644 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
645 if qualifier.is_empty() && replacements.is_empty());
646 let inner = if bare { String::new() } else { exprs(ast, args) };
647 let call = format!("{}({word}{inner}{nulls})", operator(ast, name, &written));
648 let held = ast.window(spec);
649 let mut inside: Vec<String> = Vec::new();
650 if !held.partition.is_empty() {
651 inside.push(format!("PARTITION BY {}", exprs(ast, held.partition)));
652 }
653 if !held.order.is_empty() {
654 let items: Vec<String> =
655 ast.order_list(held.order).iter().map(|item| order(ast, item)).collect();
656 inside.push(format!("ORDER BY {}", items.join(", ")));
657 }
658 if !held.frame_is_default() {
659 let unit = match held.unit {
660 WindowUnit::Rows => "ROWS",
661 WindowUnit::Range => "RANGE",
662 WindowUnit::Groups => "GROUPS",
663 };
664 let mut frame =
665 format!("{unit} BETWEEN {} AND {}", bound(ast, held.start), bound(ast, held.end));
666 frame += match held.exclude {
667 WindowExclude::NoOthers => "",
668 WindowExclude::CurrentRow => " EXCLUDE CURRENT ROW",
669 WindowExclude::Group => " EXCLUDE GROUP",
670 WindowExclude::Ties => " EXCLUDE TIES",
671 };
672 inside.push(frame);
673 }
674 format!("{call} OVER ({})", inside.join(" "))
675}
676
677fn bound(ast: &Ast, end: WindowBound) -> String {
679 match end {
680 WindowBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
681 WindowBound::Preceding(offset) => format!("{} PRECEDING", expr(ast, offset)),
682 WindowBound::CurrentRow => "CURRENT ROW".to_string(),
683 WindowBound::Following(offset) => format!("{} FOLLOWING", expr(ast, offset)),
684 WindowBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
685 }
686}
687
688fn operator(ast: &Ast, name: Slice, written: &str) -> String {
695 let one = ast.name(name).next().unwrap_or_default();
696 let alone = ast.name(name).count() == 1;
697 if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
698 return "COALESCE".to_string();
699 }
700 written.to_string()
701}
702
703fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
709 let mut out = "CASE ".to_string();
710 for arm in ast.arm_list(arms) {
711 let when = when(ast, operand, arm);
712 out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
713 }
714 let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
715 out + &format!(" ELSE {last} END")
716}
717
718fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
721 if operand == NONE {
722 return expr(ast, arm.when);
723 }
724 format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
725}
726
727fn typename(text: &str) -> String {
741 let text = text.trim();
742 if let Some(open) = suffix(text) {
745 return typename(&text[..open]) + &text[open..];
746 }
747 let (base, arguments) = arguments(text);
748 let Some(name) = standard(base) else {
749 let base = unquote(base);
750 return match arguments {
751 Some(arguments) => format!("{}({arguments})", catalogued(&base)),
752 None => catalogued(&base),
753 };
754 };
755 match (name, arguments) {
756 ("STRUCT" | "UNION", Some(inside)) => {
759 let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
760 format!("{name}({})", written.join(", "))
761 }
762 ("MAP", Some(inside)) => {
763 let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
764 format!("{name}({})", written.join(", "))
765 }
766 ("DECIMAL" | "VARCHAR", Some(inside)) => {
769 format!("{name}({})", pieces(inside).join(", "))
770 }
771 _ => name.to_string(),
774 }
775}
776
777fn catalogued(base: &str) -> String {
790 if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
791}
792
793fn unquote(base: &str) -> String {
795 match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
796 Some(inside) => inside.replace("\"\"", "\""),
797 None => base.to_string(),
798 }
799}
800
801fn suffix(text: &str) -> Option<usize> {
803 let rest = text.strip_suffix(']')?;
804 let open = rest.rfind('[')?;
805 rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
806}
807
808fn arguments(text: &str) -> (&str, Option<&str>) {
810 let Some(rest) = text.strip_suffix(')') else {
811 return (text, None);
812 };
813 let mut depth = 0usize;
814 for (at, byte) in rest.bytes().enumerate() {
815 match byte {
816 b'(' if depth == 0 => depth = 1,
817 b'(' => depth += 1,
818 b')' => depth -= 1,
819 _ => continue,
820 }
821 if depth == 1 && byte == b'(' {
822 return (rest[..at].trim(), Some(rest[at + 1..].trim()));
823 }
824 }
825 (text, None)
826}
827
828fn pieces(inside: &str) -> Vec<&str> {
830 let mut found = Vec::new();
831 let (mut depth, mut quoted, mut start) = (0usize, false, 0usize);
832 for (at, byte) in inside.bytes().enumerate() {
833 match byte {
834 b'"' => quoted = !quoted,
835 b'(' | b'[' if !quoted => depth += 1,
836 b')' | b']' if !quoted => depth = depth.saturating_sub(1),
837 b',' if !quoted && depth == 0 => {
838 found.push(inside[start..at].trim());
839 start = at + 1;
840 }
841 _ => {}
842 }
843 }
844 found.push(inside[start..].trim());
845 found
846}
847
848fn field(piece: &str) -> String {
850 let mut quoting = false;
851 for (at, byte) in piece.bytes().enumerate() {
852 match byte {
853 b'"' => quoting = !quoting,
854 byte if byte.is_ascii_whitespace() && !quoting => {
855 let name = piece[..at].trim();
856 let name =
857 if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
858 return format!("{name} {}", typename(&piece[at + 1..]));
859 }
860 _ => {}
861 }
862 }
863 piece.to_string()
864}
865
866fn standard(base: &str) -> Option<&'static str> {
872 const NAMES: &[(&str, &str)] = &[
873 ("BOOLEAN", "BOOLEAN"),
874 ("INT", "INTEGER"),
875 ("INTEGER", "INTEGER"),
876 ("SMALLINT", "SMALLINT"),
877 ("BIGINT", "BIGINT"),
878 ("DEC", "DECIMAL"),
879 ("DECIMAL", "DECIMAL"),
880 ("NUMERIC", "DECIMAL"),
881 ("REAL", "FLOAT"),
882 ("FLOAT", "FLOAT"),
883 ("DOUBLE PRECISION", "DOUBLE"),
884 ("CHAR", "VARCHAR"),
885 ("CHARACTER", "VARCHAR"),
886 ("CHARACTER VARYING", "VARCHAR"),
887 ("NATIONAL CHARACTER", "VARCHAR"),
888 ("NATIONAL CHARACTER VARYING", "VARCHAR"),
889 ("VARCHAR", "VARCHAR"),
890 ("BIT", "BIT"),
891 ("DATE", "DATE"),
892 ("TIME", "TIME"),
893 ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
894 ("TIME WITHOUT TIME ZONE", "TIME"),
895 ("TIMESTAMP", "TIMESTAMP"),
896 ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
897 ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
898 ("INTERVAL", "INTERVAL"),
899 ("STRUCT", "STRUCT"),
900 ("UNION", "UNION"),
901 ("MAP", "MAP"),
902 ];
903 let written: Vec<&str> = base.split_whitespace().collect();
904 let written = written.join(" ");
905 NAMES
906 .iter()
907 .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
908 .map(|(_, name)| *name)
909}
910
911fn exprs(ast: &Ast, list: Slice) -> String {
913 let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
914 written.join(", ")
915}
916
917fn names(ast: &Ast, list: Slice) -> String {
919 ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
920}
921
922fn parts(ast: &Ast, list: Slice) -> String {
924 ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
925}
926
927#[cfg(test)]
928mod tests {
929 use super::create_view;
930 use crate::ast::Statement;
931 use crate::transform::parse_ast;
932
933 fn whole(sql: &str) -> String {
935 let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
936 let Statement::CreateView(index) = ast.statements[0] else {
937 panic!("that was not a create view");
938 };
939 create_view(&ast, index)
940 }
941
942 fn body(query: &str) -> String {
944 let written = whole(&format!("CREATE VIEW v AS {query}"));
945 written
946 .strip_prefix("CREATE VIEW v AS ")
947 .and_then(|rest| rest.strip_suffix(';'))
948 .expect("the statement wrapper is there")
949 .to_string()
950 }
951
952 #[test]
953 fn a_statement_loses_its_qualification_and_its_or_replace() {
954 assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
955 assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
956 assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
957 assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
958 }
959
960 #[test]
962 fn an_alias_list_is_written_with_a_space_in_front_of_it() {
963 assert_eq!(
964 whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
965 r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
966 );
967 }
968
969 #[test]
970 fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
971 assert_eq!(
972 whole("CREATE VIEW v AS SELECT X /* a note */ FROM T"),
973 "CREATE VIEW v AS SELECT X FROM T;"
974 );
975 }
976
977 #[test]
978 fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
979 assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
980 assert_eq!(
981 body("SELECT x > 1 AND y < 2 OR b FROM t"),
982 "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
983 );
984 assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
985 assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
986 assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
987 assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
988 }
989
990 #[test]
993 fn a_minus_in_front_of_a_constant_folds_into_it() {
994 assert_eq!(body("SELECT -1"), "SELECT -1");
995 assert_eq!(body("SELECT - -3"), "SELECT 3");
996 assert_eq!(body("SELECT +3"), "SELECT +(3)");
997 }
998
999 #[test]
1000 fn the_null_tests_and_the_boolean_tests() {
1001 assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
1002 assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
1003 assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
1004 assert_eq!(
1005 body("SELECT b IS TRUE FROM t"),
1006 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
1007 );
1008 assert_eq!(
1009 body("SELECT b IS NOT TRUE FROM t"),
1010 "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
1011 );
1012 assert_eq!(
1013 body("SELECT b IS FALSE FROM t"),
1014 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
1015 );
1016 assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
1017 assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
1018 assert_eq!(
1019 body("SELECT x IS DISTINCT FROM y FROM t"),
1020 "SELECT (x IS DISTINCT FROM y) FROM t"
1021 );
1022 }
1023
1024 #[test]
1025 fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
1026 assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
1027 assert_eq!(
1028 body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
1029 "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
1030 );
1031 assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
1032 assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
1033 assert_eq!(body("SELECT x IN (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1034 assert_eq!(
1035 body("SELECT x NOT IN (SELECT y FROM t)"),
1036 "SELECT (NOT (x = ANY(SELECT y FROM t)))"
1037 );
1038 assert_eq!(body("SELECT x = ANY (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1039 assert_eq!(
1040 body("SELECT x > ALL (SELECT y FROM t)"),
1041 "SELECT (NOT (x <= ANY(SELECT y FROM t)))"
1042 );
1043 }
1044
1045 #[test]
1048 fn the_pattern_operators_come_back_as_symbols() {
1049 assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
1050 assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
1051 assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
1052 assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
1053 assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
1054 assert_eq!(body("SELECT s !~ 'a' FROM t"), "SELECT (s !~ 'a') FROM t");
1055 assert_eq!(
1056 body("SELECT s NOT SIMILAR TO 'a' FROM t"),
1057 "SELECT (NOT regexp_full_match(s, 'a')) FROM t"
1058 );
1059 }
1060
1061 #[test]
1062 fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
1063 assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
1064 assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
1065 assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
1066 assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
1067 assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
1068 }
1069
1070 #[test]
1072 fn a_case_is_written_the_long_way_round() {
1073 assert_eq!(
1074 body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
1075 "SELECT CASE WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
1076 );
1077 assert_eq!(
1078 body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
1079 "SELECT CASE WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
1080 );
1081 }
1082
1083 #[test]
1084 fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
1085 assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
1086 assert_eq!(
1087 body("SELECT cast(x as decimal(4,1)) FROM t"),
1088 "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
1089 );
1090 assert_eq!(
1091 body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
1092 "SELECT TRY_CAST(s AS INTEGER) FROM t"
1093 );
1094 }
1095
1096 #[test]
1098 fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
1099 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1100 assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
1101 assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
1102 assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
1103 assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
1104 assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
1105 assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
1108 assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
1109 assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
1110 assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
1111 assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
1112 assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
1113 assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
1114 assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
1115 }
1116
1117 #[test]
1119 fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
1120 assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
1121 assert_eq!(
1122 body("SELECT CAST(x AS struct(\"A b\" int))"),
1123 "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
1124 );
1125 }
1126
1127 #[test]
1129 fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
1130 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1131 assert_eq!(cast("text"), "SELECT CAST(x AS text)");
1132 assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
1133 assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
1134 assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1135 assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1136 assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1137 assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1138 assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1140 assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1141 assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1142 assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1143 }
1144
1145 #[test]
1146 fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1147 assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1148 assert_eq!(body("SELECT count() FROM t"), "SELECT count_star() FROM t");
1149 assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1150 assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1151 assert_eq!(body("SELECT []"), "SELECT list_value()");
1152 }
1153
1154 #[test]
1157 fn a_function_name_is_quoted_when_it_is_a_keyword() {
1158 assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1159 assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1160 }
1161
1162 #[test]
1163 fn the_literals() {
1164 assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1165 assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1166 assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1167 }
1168
1169 #[test]
1171 fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1172 assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1173 assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1174 assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1175 assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1176 assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1177 }
1178
1179 #[test]
1181 fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1182 assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1183 assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1184 assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1185 assert_eq!(
1187 body("SELECT extract(millisecond FROM d)"),
1188 "SELECT date_part('MILLISECONDS', d)"
1189 );
1190 assert_eq!(
1191 body("SELECT extract(microseconds FROM d)"),
1192 "SELECT date_part('MICROSECONDS', d)"
1193 );
1194 assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1195 assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1197 assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1198 }
1199
1200 #[test]
1202 fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1203 assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1204 assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1205 assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1207 assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1209 assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1210 }
1211
1212 #[test]
1213 fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1214 assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1215 assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1216 assert_eq!(
1217 body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1218 "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1219 );
1220 assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1221 assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1222 assert_eq!(
1223 body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1224 "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1225 );
1226 assert_eq!(
1227 body("SELECT DISTINCT ON (x) x, y FROM t"),
1228 "SELECT DISTINCT ON (x) x, y FROM t"
1229 );
1230 }
1231
1232 #[test]
1235 fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1236 assert_eq!(
1237 body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1238 "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1239 );
1240 assert_eq!(
1241 body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1242 "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1243 );
1244 assert_eq!(
1245 body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1246 "(SELECT x FROM t) UNION (SELECT y FROM t)"
1247 );
1248 }
1249
1250 #[test]
1251 fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1252 assert_eq!(
1253 body("VALUES (1, 'a'), (2, 'b')"),
1254 "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1255 );
1256 }
1257
1258 #[test]
1260 fn a_from_list_has_a_space_before_the_comma() {
1261 assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1262 }
1263
1264 #[test]
1265 fn a_from_item_and_its_aliases() {
1266 assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1267 assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1268 assert_eq!(
1269 body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1270 "SELECT 1 FROM (SELECT x FROM t) AS sub"
1271 );
1272 assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1273 }
1274
1275 #[test]
1278 fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1279 assert_eq!(
1280 body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1281 "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1282 );
1283 assert_eq!(
1284 body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1285 "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1286 );
1287 assert_eq!(
1288 body("SELECT 1 FROM t CROSS JOIN t AS u"),
1289 "SELECT 1 FROM (t CROSS JOIN t AS u)"
1290 );
1291 assert_eq!(
1292 body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1293 "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1294 );
1295 assert_eq!(
1296 body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1297 "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1298 );
1299 assert_eq!(
1300 body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1301 "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1302 );
1303 }
1304
1305 #[test]
1306 fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1307 assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1308 assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1309 assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1310 assert_eq!(
1311 body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1312 "SELECT * REPLACE ((x + 1) AS x) FROM t"
1313 );
1314 }
1315
1316 #[test]
1317 fn a_describe_gets_parentheses_round_what_it_describes() {
1318 assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1319 }
1320
1321 #[test]
1324 fn a_window_is_written_with_the_parts_that_were_written_in_it() {
1325 assert_eq!(
1326 body("SELECT row_number() OVER () AS n FROM t"),
1327 "SELECT row_number() OVER () AS n FROM t"
1328 );
1329 assert_eq!(
1330 body(
1331 "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1332 ),
1333 "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1334 );
1335 assert_eq!(
1336 body(
1337 "SELECT sum(i) OVER (PARTITION BY i, i+1 ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1338 ),
1339 "SELECT sum(i) OVER (PARTITION BY i, (i + 1) ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1340 );
1341 assert_eq!(
1342 body("SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"),
1343 "SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"
1344 );
1345 assert_eq!(
1346 body("SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"),
1347 "SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"
1348 );
1349 assert_eq!(
1350 body("SELECT first_value(i RESPECT NULLS) OVER (ORDER BY i) AS n FROM t"),
1351 "SELECT first_value(i) OVER (ORDER BY i) AS n FROM t"
1352 );
1353 assert_eq!(
1354 body("SELECT count(*) OVER () AS n FROM t"),
1355 "SELECT count() OVER () AS n FROM t"
1356 );
1357 assert_eq!(
1358 body("SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"),
1359 "SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"
1360 );
1361 }
1362
1363 #[test]
1365 fn a_frame_is_written_only_when_it_is_not_the_one_that_was_assumed() {
1366 assert_eq!(
1367 body(
1368 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1369 ),
1370 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1371 );
1372 assert_eq!(
1373 body("SELECT sum(i) OVER (ORDER BY i RANGE UNBOUNDED PRECEDING) AS n FROM t"),
1374 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1375 );
1376 assert_eq!(
1377 body(
1378 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS n FROM t"
1379 ),
1380 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1381 );
1382 assert_eq!(
1383 body("SELECT sum(i) OVER (ORDER BY i ROWS CURRENT ROW) AS n FROM t"),
1384 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) AS n FROM t"
1385 );
1386 assert_eq!(
1387 body(
1388 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1+1) PRECEDING AND CURRENT ROW) AS n FROM t"
1389 ),
1390 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1 + 1) PRECEDING AND CURRENT ROW) AS n FROM t"
1391 );
1392 assert_eq!(
1393 body(
1394 "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1395 ),
1396 "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1397 );
1398 assert_eq!(
1399 body(
1400 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1401 ),
1402 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1403 );
1404 assert_eq!(
1405 body(
1406 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1407 ),
1408 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1409 );
1410 }
1411
1412 #[test]
1415 fn a_frame_that_covers_the_partition_is_written_as_a_row_count() {
1416 for unit in ["ROWS", "RANGE", "GROUPS"] {
1417 assert_eq!(
1418 body(&format!(
1419 "SELECT sum(i) OVER (ORDER BY i {unit} BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1420 )),
1421 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1422 );
1423 }
1424 assert_eq!(
1425 body(
1426 "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1427 ),
1428 "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1429 );
1430 }
1431
1432 #[test]
1435 fn a_named_window_is_written_out_where_it_was_used() {
1436 assert_eq!(
1437 body("SELECT sum(i) OVER w AS n FROM t WINDOW w AS (PARTITION BY i ORDER BY i)"),
1438 "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1439 );
1440 assert_eq!(
1441 body("SELECT sum(i) OVER (w) AS n FROM t WINDOW w AS (ORDER BY i)"),
1442 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1443 );
1444 assert_eq!(
1445 body(
1446 "SELECT sum(i) OVER (w ROWS UNBOUNDED PRECEDING) AS n FROM t WINDOW w AS (PARTITION BY i)"
1447 ),
1448 "SELECT sum(i) OVER (PARTITION BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1449 );
1450 assert_eq!(
1451 body("SELECT sum(i) OVER (w PARTITION BY i) AS n FROM t WINDOW w AS (ORDER BY i)"),
1452 "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1453 );
1454 assert_eq!(
1455 body("SELECT sum(i) OVER v AS n FROM t WINDOW w AS (ORDER BY i), v AS (w)"),
1456 "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1457 );
1458 }
1459}