1use crate::ast::{
72 Ast, BinaryOp, CaseArm, CreateViewRef, Distinct, Expr, ExprRef, JoinKind, LiteralKind, Nulls,
73 Order, OrderItem, Quantifier, QueryBody, QueryRef, SelectRef, SetOp, Slice, Source, SourceRef,
74 StrRef, Target, UnaryOp,
75};
76use crate::matcher::NONE;
77use crate::tokenize::quoted;
78
79#[must_use]
85pub fn create_view(ast: &Ast, index: CreateViewRef) -> String {
86 let written = ast.create_view(index);
87 let name = ast.name(written.name).last().unwrap_or_default();
88 let temporary = if written.temporary { "TEMP " } else { "" };
89 let mut out = format!("CREATE {temporary}VIEW {}", quoted(name));
90 if !written.columns.is_empty() {
91 out += &format!(" ({})", names(ast, written.columns));
94 }
95 out + &format!(" AS {};", query(ast, written.query))
96}
97
98#[must_use]
100pub fn query(ast: &Ast, index: QueryRef) -> String {
101 let held = ast.query(index);
102 let mut out = match held.body {
103 QueryBody::Select(select) => selection(ast, select),
104 QueryBody::SetOp { op, quantifier, by_name, left, right } => {
105 setop(ast, op, quantifier, by_name, left, right)
106 }
107 QueryBody::Values(rows) => format!("SELECT * FROM ({}) AS valueslist", values(ast, rows)),
110 QueryBody::Describe(inner) => format!("DESCRIBE ({})", query(ast, inner)),
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
304fn expr(ast: &Ast, index: ExprRef) -> String {
306 match ast.expr(index) {
307 Expr::Star { qualifier, replacements } => star(ast, qualifier, replacements),
308 Expr::Column { name } => parts(ast, name),
309 Expr::Literal { kind, text } => literal(ast, kind, text),
310 Expr::Unary { op, operand } => unary(ast, op, operand),
311 Expr::Binary { op, left, right } => binary(ast, op, left, right),
312 Expr::Function { name, args, distinct } => call(ast, name, args, distinct),
313 Expr::Cast { operand, ty, try_cast } => {
314 let word = if try_cast { "TRY_CAST" } else { "CAST" };
315 format!("{word}({} AS {})", expr(ast, operand), typename(ast.string(ty)))
316 }
317 Expr::Case { operand, arms, otherwise } => case(ast, operand, arms, otherwise),
318 Expr::Between { operand, low, high, negated } => {
319 let written = format!(
320 "({} BETWEEN {} AND {})",
321 expr(ast, operand),
322 expr(ast, low),
323 expr(ast, high)
324 );
325 if negated { format!("(NOT {written})") } else { written }
326 }
327 Expr::In { operand, list, negated } => {
328 let written = format!("({} IN ({}))", expr(ast, operand), exprs(ast, list));
329 if negated { format!("(NOT {written})") } else { written }
330 }
331 Expr::Parameter { name } => format!("${}", ast.string(name)),
332 Expr::List { items } => format!("list_value({})", exprs(ast, items)),
334 Expr::Row { items } => format!("\"row\"({})", exprs(ast, items)),
337 Expr::Subquery { query: inner } => format!("({})", query(ast, inner)),
338 }
339}
340
341fn star(ast: &Ast, qualifier: Slice, replacements: Slice) -> String {
343 let mut out =
344 if qualifier.is_empty() { "*".to_string() } else { format!("{}.*", parts(ast, qualifier)) };
345 if !replacements.is_empty() {
346 let written: Vec<String> =
347 ast.target_list(replacements).iter().map(|target| aliased(ast, target)).collect();
348 out += &format!(" REPLACE ({})", written.join(", "));
349 }
350 out
351}
352
353fn literal(ast: &Ast, kind: LiteralKind, text: StrRef) -> String {
355 match kind {
356 LiteralKind::Null => "NULL".to_string(),
359 LiteralKind::True => "true".to_string(),
360 LiteralKind::False => "false".to_string(),
361 LiteralKind::Number => number(ast.string(text)),
362 LiteralKind::String => string(ast.string(text)),
363 LiteralKind::Blob => format!("{}::BLOB", string(ast.string(text))),
366 }
367}
368
369fn number(written: &str) -> String {
378 let text = written.replace('_', "");
379 if text.contains(['e', 'E']) {
380 return double(&text);
381 }
382 let Some((whole, fraction)) = text.split_once('.') else {
383 return leading(&text).to_string();
384 };
385 if fraction.is_empty() {
388 return leading(whole).to_string();
389 }
390 format!("{}.{fraction}", if whole.is_empty() { "" } else { leading(whole) })
391}
392
393fn leading(digits: &str) -> &str {
395 let trimmed = digits.trim_start_matches('0');
396 if trimmed.is_empty() { &digits[digits.len().saturating_sub(1)..] } else { trimmed }
397}
398
399fn double(text: &str) -> String {
407 let Ok(value) = text.parse::<f64>() else {
408 return text.to_string();
409 };
410 let shortest = format!("{value:e}");
413 let (mantissa, exponent) = shortest.split_once('e').unwrap_or((shortest.as_str(), "0"));
414 let exponent: i32 = exponent.parse().unwrap_or(0);
415 if (-4..=15).contains(&exponent) {
416 let plain = format!("{value}");
417 return if plain.contains('.') { plain } else { plain + ".0" };
418 }
419 let sign = if exponent < 0 { '-' } else { '+' };
420 format!("{mantissa}e{sign}{:02}", exponent.abs())
421}
422
423fn string(text: &str) -> String {
428 format!("'{}'", text.replace('\'', "''"))
429}
430
431fn unary(ast: &Ast, op: UnaryOp, operand: ExprRef) -> String {
433 if matches!(op, UnaryOp::Negate) {
437 if let Some(number) = negated(ast, operand) {
438 return number;
439 }
440 }
441 let written = expr(ast, operand);
442 match op {
443 UnaryOp::Not => format!("(NOT {written})"),
444 UnaryOp::Negate => format!("-({written})"),
445 UnaryOp::Plus => format!("+({written})"),
446 UnaryOp::BitNot => format!("~({written})"),
447 UnaryOp::Factorial => format!("factorial({written})"),
449 UnaryOp::IsNull => format!("({written} IS NULL)"),
450 UnaryOp::IsNotNull => format!("({written} IS NOT NULL)"),
451 UnaryOp::IsUnknown => format!("({written} IS NULL)"),
453 UnaryOp::IsNotUnknown => format!("({written} IS NOT NULL)"),
454 UnaryOp::IsTrue => distinct(&written, "true", true),
458 UnaryOp::IsNotTrue => distinct(&written, "true", false),
459 UnaryOp::IsFalse => distinct(&written, "false", true),
460 UnaryOp::IsNotFalse => distinct(&written, "false", false),
461 }
462}
463
464fn distinct(operand: &str, against: &str, same: bool) -> String {
466 let word = if same { "IS NOT DISTINCT FROM" } else { "IS DISTINCT FROM" };
467 format!("(CAST({operand} AS BOOLEAN) {word} {against})")
468}
469
470fn negated(ast: &Ast, index: ExprRef) -> Option<String> {
475 match ast.expr(index) {
476 Expr::Literal { kind: LiteralKind::Number, text } => {
477 Some(format!("-{}", number(ast.string(text))))
478 }
479 Expr::Unary { op: UnaryOp::Negate, operand } => {
480 let inner = negated(ast, operand)?;
481 Some(inner.strip_prefix('-').unwrap_or(&inner).to_string())
482 }
483 _ => None,
484 }
485}
486
487fn binary(ast: &Ast, op: BinaryOp, left: ExprRef, right: ExprRef) -> String {
489 let (left, right) = (expr(ast, left), expr(ast, right));
490 match op {
492 BinaryOp::SimilarTo => return format!("regexp_full_match({left}, {right})"),
493 BinaryOp::NotSimilarTo => return format!("(NOT regexp_full_match({left}, {right}))"),
494 BinaryOp::AtTimeZone => return format!("timezone({right}, {left})"),
496 BinaryOp::Collate => return format!("{left} COLLATE {right}"),
498 _ => {}
499 }
500 let word = match op {
501 BinaryOp::Or => "OR",
502 BinaryOp::And => "AND",
503 BinaryOp::Eq => "=",
504 BinaryOp::NotEq => "!=",
505 BinaryOp::Lt => "<",
506 BinaryOp::Gt => ">",
507 BinaryOp::LtEq => "<=",
508 BinaryOp::GtEq => ">=",
509 BinaryOp::IsDistinctFrom => "IS DISTINCT FROM",
510 BinaryOp::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
511 BinaryOp::Add => "+",
512 BinaryOp::Subtract => "-",
513 BinaryOp::Multiply => "*",
514 BinaryOp::Divide => "/",
515 BinaryOp::IntegerDivide => "//",
516 BinaryOp::Modulo => "%",
517 BinaryOp::Power => "**",
520 BinaryOp::BitAnd => "&",
521 BinaryOp::BitOr => "|",
522 BinaryOp::ShiftLeft => "<<",
523 BinaryOp::ShiftRight => ">>",
524 BinaryOp::Concat => "||",
525 BinaryOp::Like => "~~",
528 BinaryOp::NotLike => "!~~",
529 BinaryOp::ILike => "~~*",
530 BinaryOp::NotILike => "!~~*",
531 BinaryOp::Glob => "~~~",
532 BinaryOp::Regex => "~",
533 BinaryOp::RegexInsensitive => "~*",
534 BinaryOp::NotRegexInsensitive => "!~*",
535 BinaryOp::Arrow => "->",
536 BinaryOp::LongArrow => "->>",
537 BinaryOp::Contains => "@>",
538 BinaryOp::ContainedBy => "<@",
539 BinaryOp::Overlaps => "&&",
540 BinaryOp::StartsWith => "^@",
541 BinaryOp::InetContainedByOrEq => "<<=",
542 BinaryOp::InetContainsOrEq => ">>=",
543 BinaryOp::Named(name) => ast.string(name),
544 BinaryOp::SimilarTo | BinaryOp::NotSimilarTo | BinaryOp::AtTimeZone | BinaryOp::Collate => {
545 unreachable!("the four that return above")
546 }
547 };
548 format!("({left} {word} {right})")
549}
550
551fn call(ast: &Ast, name: Slice, args: Slice, distinct: bool) -> String {
553 let written = parts(ast, name);
554 let list = ast.expr_list(args);
555 if list.len() == 1
558 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
559 if qualifier.is_empty() && replacements.is_empty())
560 && written.eq_ignore_ascii_case("count")
561 {
562 return "count_star()".to_string();
563 }
564 let word = if distinct { "DISTINCT " } else { "" };
565 format!("{}({word}{})", operator(ast, name, &written), exprs(ast, args))
566}
567
568fn operator(ast: &Ast, name: Slice, written: &str) -> String {
575 let one = ast.name(name).next().unwrap_or_default();
576 let alone = ast.name(name).count() == 1;
577 if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
578 return "COALESCE".to_string();
579 }
580 written.to_string()
581}
582
583fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
589 let mut out = "CASE ".to_string();
590 for arm in ast.arm_list(arms) {
591 let when = when(ast, operand, arm);
592 out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
593 }
594 let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
595 out + &format!(" ELSE {last} END")
596}
597
598fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
601 if operand == NONE {
602 return expr(ast, arm.when);
603 }
604 format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
605}
606
607fn typename(text: &str) -> String {
621 let text = text.trim();
622 if let Some(open) = suffix(text) {
625 return typename(&text[..open]) + &text[open..];
626 }
627 let (base, arguments) = arguments(text);
628 let Some(name) = standard(base) else {
629 let base = unquote(base);
630 return match arguments {
631 Some(arguments) => format!("{}({arguments})", catalogued(&base)),
632 None => catalogued(&base),
633 };
634 };
635 match (name, arguments) {
636 ("STRUCT" | "UNION", Some(inside)) => {
639 let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
640 format!("{name}({})", written.join(", "))
641 }
642 ("MAP", Some(inside)) => {
643 let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
644 format!("{name}({})", written.join(", "))
645 }
646 ("DECIMAL" | "VARCHAR", Some(inside)) => {
649 format!("{name}({})", pieces(inside).join(", "))
650 }
651 _ => name.to_string(),
654 }
655}
656
657fn catalogued(base: &str) -> String {
670 if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
671}
672
673fn unquote(base: &str) -> String {
675 match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
676 Some(inside) => inside.replace("\"\"", "\""),
677 None => base.to_string(),
678 }
679}
680
681fn suffix(text: &str) -> Option<usize> {
683 let rest = text.strip_suffix(']')?;
684 let open = rest.rfind('[')?;
685 rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
686}
687
688fn arguments(text: &str) -> (&str, Option<&str>) {
690 let Some(rest) = text.strip_suffix(')') else {
691 return (text, None);
692 };
693 let mut depth = 0usize;
694 for (at, byte) in rest.bytes().enumerate() {
695 match byte {
696 b'(' if depth == 0 => depth = 1,
697 b'(' => depth += 1,
698 b')' => depth -= 1,
699 _ => continue,
700 }
701 if depth == 1 && byte == b'(' {
702 return (rest[..at].trim(), Some(rest[at + 1..].trim()));
703 }
704 }
705 (text, None)
706}
707
708fn pieces(inside: &str) -> Vec<&str> {
710 let mut found = Vec::new();
711 let (mut depth, mut quoted, mut start) = (0usize, false, 0usize);
712 for (at, byte) in inside.bytes().enumerate() {
713 match byte {
714 b'"' => quoted = !quoted,
715 b'(' | b'[' if !quoted => depth += 1,
716 b')' | b']' if !quoted => depth = depth.saturating_sub(1),
717 b',' if !quoted && depth == 0 => {
718 found.push(inside[start..at].trim());
719 start = at + 1;
720 }
721 _ => {}
722 }
723 }
724 found.push(inside[start..].trim());
725 found
726}
727
728fn field(piece: &str) -> String {
730 let mut quoting = false;
731 for (at, byte) in piece.bytes().enumerate() {
732 match byte {
733 b'"' => quoting = !quoting,
734 byte if byte.is_ascii_whitespace() && !quoting => {
735 let name = piece[..at].trim();
736 let name =
737 if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
738 return format!("{name} {}", typename(&piece[at + 1..]));
739 }
740 _ => {}
741 }
742 }
743 piece.to_string()
744}
745
746fn standard(base: &str) -> Option<&'static str> {
752 const NAMES: &[(&str, &str)] = &[
753 ("BOOLEAN", "BOOLEAN"),
754 ("INT", "INTEGER"),
755 ("INTEGER", "INTEGER"),
756 ("SMALLINT", "SMALLINT"),
757 ("BIGINT", "BIGINT"),
758 ("DEC", "DECIMAL"),
759 ("DECIMAL", "DECIMAL"),
760 ("NUMERIC", "DECIMAL"),
761 ("REAL", "FLOAT"),
762 ("FLOAT", "FLOAT"),
763 ("DOUBLE PRECISION", "DOUBLE"),
764 ("CHAR", "VARCHAR"),
765 ("CHARACTER", "VARCHAR"),
766 ("CHARACTER VARYING", "VARCHAR"),
767 ("NATIONAL CHARACTER", "VARCHAR"),
768 ("NATIONAL CHARACTER VARYING", "VARCHAR"),
769 ("VARCHAR", "VARCHAR"),
770 ("BIT", "BIT"),
771 ("DATE", "DATE"),
772 ("TIME", "TIME"),
773 ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
774 ("TIME WITHOUT TIME ZONE", "TIME"),
775 ("TIMESTAMP", "TIMESTAMP"),
776 ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
777 ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
778 ("INTERVAL", "INTERVAL"),
779 ("STRUCT", "STRUCT"),
780 ("UNION", "UNION"),
781 ("MAP", "MAP"),
782 ];
783 let written: Vec<&str> = base.split_whitespace().collect();
784 let written = written.join(" ");
785 NAMES
786 .iter()
787 .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
788 .map(|(_, name)| *name)
789}
790
791fn exprs(ast: &Ast, list: Slice) -> String {
793 let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
794 written.join(", ")
795}
796
797fn names(ast: &Ast, list: Slice) -> String {
799 ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
800}
801
802fn parts(ast: &Ast, list: Slice) -> String {
804 ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
805}
806
807#[cfg(test)]
808mod tests {
809 use super::create_view;
810 use crate::ast::Statement;
811 use crate::transform::parse_ast;
812
813 fn whole(sql: &str) -> String {
815 let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
816 let Statement::CreateView(index) = ast.statements[0] else {
817 panic!("that was not a create view");
818 };
819 create_view(&ast, index)
820 }
821
822 fn body(query: &str) -> String {
824 let written = whole(&format!("CREATE VIEW v AS {query}"));
825 written
826 .strip_prefix("CREATE VIEW v AS ")
827 .and_then(|rest| rest.strip_suffix(';'))
828 .expect("the statement wrapper is there")
829 .to_string()
830 }
831
832 #[test]
833 fn a_statement_loses_its_qualification_and_its_or_replace() {
834 assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
835 assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
836 assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
837 assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
838 }
839
840 #[test]
842 fn an_alias_list_is_written_with_a_space_in_front_of_it() {
843 assert_eq!(
844 whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
845 r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
846 );
847 }
848
849 #[test]
850 fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
851 assert_eq!(
852 whole("CREATE VIEW v AS SELECT X /* a note */ FROM T"),
853 "CREATE VIEW v AS SELECT X FROM T;"
854 );
855 }
856
857 #[test]
858 fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
859 assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
860 assert_eq!(
861 body("SELECT x > 1 AND y < 2 OR b FROM t"),
862 "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
863 );
864 assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
865 assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
866 assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
867 assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
868 }
869
870 #[test]
873 fn a_minus_in_front_of_a_constant_folds_into_it() {
874 assert_eq!(body("SELECT -1"), "SELECT -1");
875 assert_eq!(body("SELECT - -3"), "SELECT 3");
876 assert_eq!(body("SELECT +3"), "SELECT +(3)");
877 }
878
879 #[test]
880 fn the_null_tests_and_the_boolean_tests() {
881 assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
882 assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
883 assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
884 assert_eq!(
885 body("SELECT b IS TRUE FROM t"),
886 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
887 );
888 assert_eq!(
889 body("SELECT b IS NOT TRUE FROM t"),
890 "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
891 );
892 assert_eq!(
893 body("SELECT b IS FALSE FROM t"),
894 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
895 );
896 assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
897 assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
898 assert_eq!(
899 body("SELECT x IS DISTINCT FROM y FROM t"),
900 "SELECT (x IS DISTINCT FROM y) FROM t"
901 );
902 }
903
904 #[test]
905 fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
906 assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
907 assert_eq!(
908 body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
909 "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
910 );
911 assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
912 assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
913 }
914
915 #[test]
918 fn the_pattern_operators_come_back_as_symbols() {
919 assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
920 assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
921 assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
922 assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
923 assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
924 }
925
926 #[test]
927 fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
928 assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
929 assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
930 assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
931 assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
932 assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
933 }
934
935 #[test]
937 fn a_case_is_written_the_long_way_round() {
938 assert_eq!(
939 body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
940 "SELECT CASE WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
941 );
942 assert_eq!(
943 body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
944 "SELECT CASE WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
945 );
946 }
947
948 #[test]
949 fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
950 assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
951 assert_eq!(
952 body("SELECT cast(x as decimal(4,1)) FROM t"),
953 "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
954 );
955 assert_eq!(
956 body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
957 "SELECT TRY_CAST(s AS INTEGER) FROM t"
958 );
959 }
960
961 #[test]
963 fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
964 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
965 assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
966 assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
967 assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
968 assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
969 assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
970 assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
973 assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
974 assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
975 assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
976 assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
977 assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
978 assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
979 assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
980 }
981
982 #[test]
984 fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
985 assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
986 assert_eq!(
987 body("SELECT CAST(x AS struct(\"A b\" int))"),
988 "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
989 );
990 }
991
992 #[test]
994 fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
995 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
996 assert_eq!(cast("text"), "SELECT CAST(x AS text)");
997 assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
998 assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
999 assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1000 assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1001 assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1002 assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1003 assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1005 assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1006 assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1007 assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1008 }
1009
1010 #[test]
1011 fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1012 assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1013 assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1014 assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1015 assert_eq!(body("SELECT []"), "SELECT list_value()");
1016 }
1017
1018 #[test]
1021 fn a_function_name_is_quoted_when_it_is_a_keyword() {
1022 assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1023 assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1024 }
1025
1026 #[test]
1027 fn the_literals() {
1028 assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1029 assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1030 assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1031 }
1032
1033 #[test]
1035 fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1036 assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1037 assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1038 assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1039 assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1040 assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1041 }
1042
1043 #[test]
1045 fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1046 assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1047 assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1048 assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1049 assert_eq!(
1051 body("SELECT extract(millisecond FROM d)"),
1052 "SELECT date_part('MILLISECONDS', d)"
1053 );
1054 assert_eq!(
1055 body("SELECT extract(microseconds FROM d)"),
1056 "SELECT date_part('MICROSECONDS', d)"
1057 );
1058 assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1059 assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1061 assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1062 }
1063
1064 #[test]
1066 fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1067 assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1068 assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1069 assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1071 assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1073 assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1074 }
1075
1076 #[test]
1077 fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1078 assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1079 assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1080 assert_eq!(
1081 body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1082 "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1083 );
1084 assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1085 assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1086 assert_eq!(
1087 body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1088 "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1089 );
1090 assert_eq!(
1091 body("SELECT DISTINCT ON (x) x, y FROM t"),
1092 "SELECT DISTINCT ON (x) x, y FROM t"
1093 );
1094 }
1095
1096 #[test]
1099 fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1100 assert_eq!(
1101 body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1102 "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1103 );
1104 assert_eq!(
1105 body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1106 "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1107 );
1108 assert_eq!(
1109 body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1110 "(SELECT x FROM t) UNION (SELECT y FROM t)"
1111 );
1112 }
1113
1114 #[test]
1115 fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1116 assert_eq!(
1117 body("VALUES (1, 'a'), (2, 'b')"),
1118 "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1119 );
1120 }
1121
1122 #[test]
1124 fn a_from_list_has_a_space_before_the_comma() {
1125 assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1126 }
1127
1128 #[test]
1129 fn a_from_item_and_its_aliases() {
1130 assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1131 assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1132 assert_eq!(
1133 body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1134 "SELECT 1 FROM (SELECT x FROM t) AS sub"
1135 );
1136 assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1137 }
1138
1139 #[test]
1142 fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1143 assert_eq!(
1144 body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1145 "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1146 );
1147 assert_eq!(
1148 body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1149 "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1150 );
1151 assert_eq!(
1152 body("SELECT 1 FROM t CROSS JOIN t AS u"),
1153 "SELECT 1 FROM (t CROSS JOIN t AS u)"
1154 );
1155 assert_eq!(
1156 body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1157 "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1158 );
1159 assert_eq!(
1160 body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1161 "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1162 );
1163 assert_eq!(
1164 body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1165 "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1166 );
1167 }
1168
1169 #[test]
1170 fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1171 assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1172 assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1173 assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1174 assert_eq!(
1175 body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1176 "SELECT * REPLACE ((x + 1) AS x) FROM t"
1177 );
1178 }
1179
1180 #[test]
1181 fn a_describe_gets_parentheses_round_what_it_describes() {
1182 assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1183 }
1184}