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 QueryBody::Show { name, .. } => format!("SHOW {}", ast.name_text(name)),
112 };
113 if held.order_by_all {
114 out += " ORDER BY COLUMNS(*)";
116 } else if !held.order_by.is_empty() {
117 let items: Vec<String> =
118 ast.order_list(held.order_by).iter().map(|item| order(ast, item)).collect();
119 out += &format!(" ORDER BY {}", items.join(", "));
120 }
121 if held.limit != NONE {
122 if held.limit_percent {
125 out += &format!(" LIMIT ({}) %", expr(ast, held.limit));
126 } else {
127 out += &format!(" LIMIT {}", expr(ast, held.limit));
128 }
129 }
130 if held.offset != NONE {
131 out += &format!(" OFFSET {}", expr(ast, held.offset));
132 }
133 out
134}
135
136fn setop(
143 ast: &Ast,
144 op: SetOp,
145 quantifier: Quantifier,
146 by_name: bool,
147 left: QueryRef,
148 right: QueryRef,
149) -> String {
150 let word = match op {
151 SetOp::Union => "UNION",
152 SetOp::Except => "EXCEPT",
153 SetOp::Intersect => "INTERSECT",
154 };
155 let all = if matches!(quantifier, Quantifier::All) { " ALL" } else { "" };
157 let named = if by_name { " BY NAME" } else { "" };
158 format!("{}{word}{all}{named} {}", branch(ast, left, true), branch(ast, right, false))
159}
160
161fn branch(ast: &Ast, index: QueryRef, left: bool) -> String {
163 let text = query(ast, index);
164 if matches!(ast.query(index).body, QueryBody::SetOp { .. }) {
165 return text;
166 }
167 if left { format!("({text}) ") } else { format!("({text})") }
168}
169
170fn selection(ast: &Ast, index: SelectRef) -> String {
172 let held = ast.select(index);
173 let mut out = "SELECT".to_string();
174 match held.distinct {
175 Distinct::No => {}
176 Distinct::Yes => out += " DISTINCT",
177 Distinct::On(list) => out += &format!(" DISTINCT ON ({})", exprs(ast, list)),
178 }
179 let targets: Vec<String> =
180 ast.target_list(held.targets).iter().map(|target| aliased(ast, target)).collect();
181 out += &format!(" {}", targets.join(", "));
182 if !held.from.is_empty() {
183 let sources: Vec<String> =
185 ast.source_list(held.from).iter().map(|&index| source(ast, index)).collect();
186 out += &format!(" FROM {}", sources.join(" , "));
187 }
188 if held.filter != NONE {
189 out += &format!(" WHERE {}", expr(ast, held.filter));
190 }
191 if held.group_by_all {
192 out += " GROUP BY ALL";
193 } else if !held.group_by.is_empty() {
194 out += &format!(" GROUP BY {}", exprs(ast, held.group_by));
195 }
196 if held.having != NONE {
197 out += &format!(" HAVING {}", expr(ast, held.having));
198 }
199 out
200}
201
202fn aliased(ast: &Ast, target: &Target) -> String {
204 let written = expr(ast, target.expr);
205 if target.alias == NONE {
206 return written;
207 }
208 format!("{written} AS {}", quoted(ast.string(target.alias)))
209}
210
211fn order(ast: &Ast, item: &OrderItem) -> String {
213 let mut out = expr(ast, item.expr);
214 match item.order {
215 Order::Unstated => {}
216 Order::Ascending => out += " ASC",
217 Order::Descending => out += " DESC",
218 }
219 match item.nulls {
220 Nulls::Unstated => {}
221 Nulls::First => out += " NULLS FIRST",
222 Nulls::Last => out += " NULLS LAST",
223 }
224 out
225}
226
227fn source(ast: &Ast, index: SourceRef) -> String {
229 match ast.source(index) {
230 Source::Table { name, alias, columns } => label(ast, parts(ast, name), alias, columns),
231 Source::Subquery { query: inner, alias, columns } => {
232 label(ast, format!("({})", query(ast, inner)), alias, columns)
233 }
234 Source::Function { name, args, alias, columns, .. } => {
235 let written: Vec<String> =
236 ast.target_list(args).iter().map(|arg| argument(ast, arg)).collect();
237 let call = format!("{}({})", parts(ast, name), written.join(", "));
238 label(ast, call, alias, columns)
239 }
240 Source::Values { rows, alias, columns } => {
243 let inner = format!("(SELECT * FROM ({}) AS valueslist)", values(ast, rows));
244 label(ast, inner, alias, columns)
245 }
246 Source::Join { left, right, kind, natural, on, using } => {
247 let word = match kind {
248 JoinKind::Inner => "INNER",
249 JoinKind::Left => "LEFT",
250 JoinKind::Right => "RIGHT",
251 JoinKind::Full => "FULL",
253 JoinKind::Semi => "SEMI",
254 JoinKind::Anti => "ANTI",
255 JoinKind::Cross => "CROSS",
256 JoinKind::Positional => "POSITIONAL",
257 };
258 let natural = if natural { "NATURAL " } else { "" };
259 let mut out =
260 format!("({} {natural}{word} JOIN {}", source(ast, left), source(ast, right));
261 if on != NONE {
262 out += &format!(" ON ({})", expr(ast, on));
265 }
266 if !using.is_empty() {
267 out += &format!(" USING ({})", names(ast, using));
268 }
269 out + ")"
270 }
271 }
272}
273
274fn argument(ast: &Ast, arg: &Target) -> String {
280 if arg.alias == NONE {
281 return expr(ast, arg.expr);
282 }
283 format!("({} = {})", quoted(ast.string(arg.alias)), expr(ast, arg.expr))
284}
285
286fn label(ast: &Ast, written: String, alias: StrRef, columns: Slice) -> String {
288 let mut out = written;
289 if alias != NONE {
290 out += &format!(" AS {}", quoted(ast.string(alias)));
291 }
292 if !columns.is_empty() {
293 out += &format!("({})", names(ast, columns));
294 }
295 out
296}
297
298fn values(ast: &Ast, rows: Slice) -> String {
300 let written: Vec<String> =
301 ast.rows(rows).iter().map(|&row| format!("({})", exprs(ast, row))).collect();
302 format!("VALUES {}", written.join(", "))
303}
304
305fn expr(ast: &Ast, index: ExprRef) -> String {
307 match ast.expr(index) {
308 Expr::Star { qualifier, replacements } => star(ast, qualifier, replacements),
309 Expr::Column { name } => parts(ast, name),
310 Expr::Literal { kind, text } => literal(ast, kind, text),
311 Expr::Unary { op, operand } => unary(ast, op, operand),
312 Expr::Binary { op, left, right } => binary(ast, op, left, right),
313 Expr::Function { name, args, distinct } => call(ast, name, args, distinct),
314 Expr::Cast { operand, ty, try_cast } => {
315 let word = if try_cast { "TRY_CAST" } else { "CAST" };
316 format!("{word}({} AS {})", expr(ast, operand), typename(ast.string(ty)))
317 }
318 Expr::Case { operand, arms, otherwise } => case(ast, operand, arms, otherwise),
319 Expr::Between { operand, low, high, negated } => {
320 let written = format!(
321 "({} BETWEEN {} AND {})",
322 expr(ast, operand),
323 expr(ast, low),
324 expr(ast, high)
325 );
326 if negated { format!("(NOT {written})") } else { written }
327 }
328 Expr::In { operand, list, negated } => {
329 let written = format!("({} IN ({}))", expr(ast, operand), exprs(ast, list));
330 if negated { format!("(NOT {written})") } else { written }
331 }
332 Expr::Parameter { name } => format!("${}", ast.string(name)),
333 Expr::List { items } => format!("list_value({})", exprs(ast, items)),
335 Expr::Row { items } => format!("\"row\"({})", exprs(ast, items)),
338 Expr::Subquery { query: inner } => format!("({})", query(ast, inner)),
339 }
340}
341
342fn star(ast: &Ast, qualifier: Slice, replacements: Slice) -> String {
344 let mut out =
345 if qualifier.is_empty() { "*".to_string() } else { format!("{}.*", parts(ast, qualifier)) };
346 if !replacements.is_empty() {
347 let written: Vec<String> =
348 ast.target_list(replacements).iter().map(|target| aliased(ast, target)).collect();
349 out += &format!(" REPLACE ({})", written.join(", "));
350 }
351 out
352}
353
354fn literal(ast: &Ast, kind: LiteralKind, text: StrRef) -> String {
356 match kind {
357 LiteralKind::Null => "NULL".to_string(),
360 LiteralKind::True => "true".to_string(),
361 LiteralKind::False => "false".to_string(),
362 LiteralKind::Number => number(ast.string(text)),
363 LiteralKind::String => string(ast.string(text)),
364 LiteralKind::Blob => format!("{}::BLOB", string(ast.string(text))),
367 }
368}
369
370fn number(written: &str) -> String {
379 let text = written.replace('_', "");
380 if text.contains(['e', 'E']) {
381 return double(&text);
382 }
383 let Some((whole, fraction)) = text.split_once('.') else {
384 return leading(&text).to_string();
385 };
386 if fraction.is_empty() {
389 return leading(whole).to_string();
390 }
391 format!("{}.{fraction}", if whole.is_empty() { "" } else { leading(whole) })
392}
393
394fn leading(digits: &str) -> &str {
396 let trimmed = digits.trim_start_matches('0');
397 if trimmed.is_empty() { &digits[digits.len().saturating_sub(1)..] } else { trimmed }
398}
399
400fn double(text: &str) -> String {
408 let Ok(value) = text.parse::<f64>() else {
409 return text.to_string();
410 };
411 let shortest = format!("{value:e}");
414 let (mantissa, exponent) = shortest.split_once('e').unwrap_or((shortest.as_str(), "0"));
415 let exponent: i32 = exponent.parse().unwrap_or(0);
416 if (-4..=15).contains(&exponent) {
417 let plain = format!("{value}");
418 return if plain.contains('.') { plain } else { plain + ".0" };
419 }
420 let sign = if exponent < 0 { '-' } else { '+' };
421 format!("{mantissa}e{sign}{:02}", exponent.abs())
422}
423
424fn string(text: &str) -> String {
429 format!("'{}'", text.replace('\'', "''"))
430}
431
432fn unary(ast: &Ast, op: UnaryOp, operand: ExprRef) -> String {
434 if matches!(op, UnaryOp::Negate) {
438 if let Some(number) = negated(ast, operand) {
439 return number;
440 }
441 }
442 let written = expr(ast, operand);
443 match op {
444 UnaryOp::Not => format!("(NOT {written})"),
445 UnaryOp::Negate => format!("-({written})"),
446 UnaryOp::Plus => format!("+({written})"),
447 UnaryOp::BitNot => format!("~({written})"),
448 UnaryOp::Factorial => format!("factorial({written})"),
450 UnaryOp::IsNull => format!("({written} IS NULL)"),
451 UnaryOp::IsNotNull => format!("({written} IS NOT NULL)"),
452 UnaryOp::IsUnknown => format!("({written} IS NULL)"),
454 UnaryOp::IsNotUnknown => format!("({written} IS NOT NULL)"),
455 UnaryOp::IsTrue => distinct(&written, "true", true),
459 UnaryOp::IsNotTrue => distinct(&written, "true", false),
460 UnaryOp::IsFalse => distinct(&written, "false", true),
461 UnaryOp::IsNotFalse => distinct(&written, "false", false),
462 }
463}
464
465fn distinct(operand: &str, against: &str, same: bool) -> String {
467 let word = if same { "IS NOT DISTINCT FROM" } else { "IS DISTINCT FROM" };
468 format!("(CAST({operand} AS BOOLEAN) {word} {against})")
469}
470
471fn negated(ast: &Ast, index: ExprRef) -> Option<String> {
476 match ast.expr(index) {
477 Expr::Literal { kind: LiteralKind::Number, text } => {
478 Some(format!("-{}", number(ast.string(text))))
479 }
480 Expr::Unary { op: UnaryOp::Negate, operand } => {
481 let inner = negated(ast, operand)?;
482 Some(inner.strip_prefix('-').unwrap_or(&inner).to_string())
483 }
484 _ => None,
485 }
486}
487
488fn binary(ast: &Ast, op: BinaryOp, left: ExprRef, right: ExprRef) -> String {
490 let (left, right) = (expr(ast, left), expr(ast, right));
491 match op {
493 BinaryOp::SimilarTo => return format!("regexp_full_match({left}, {right})"),
494 BinaryOp::NotSimilarTo => return format!("(NOT regexp_full_match({left}, {right}))"),
495 BinaryOp::AtTimeZone => return format!("timezone({right}, {left})"),
497 BinaryOp::Collate => return format!("{left} COLLATE {right}"),
499 _ => {}
500 }
501 let word = match op {
502 BinaryOp::Or => "OR",
503 BinaryOp::And => "AND",
504 BinaryOp::Eq => "=",
505 BinaryOp::NotEq => "!=",
506 BinaryOp::Lt => "<",
507 BinaryOp::Gt => ">",
508 BinaryOp::LtEq => "<=",
509 BinaryOp::GtEq => ">=",
510 BinaryOp::IsDistinctFrom => "IS DISTINCT FROM",
511 BinaryOp::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
512 BinaryOp::Add => "+",
513 BinaryOp::Subtract => "-",
514 BinaryOp::Multiply => "*",
515 BinaryOp::Divide => "/",
516 BinaryOp::IntegerDivide => "//",
517 BinaryOp::Modulo => "%",
518 BinaryOp::Power => "**",
521 BinaryOp::BitAnd => "&",
522 BinaryOp::BitOr => "|",
523 BinaryOp::ShiftLeft => "<<",
524 BinaryOp::ShiftRight => ">>",
525 BinaryOp::Concat => "||",
526 BinaryOp::Like => "~~",
529 BinaryOp::NotLike => "!~~",
530 BinaryOp::ILike => "~~*",
531 BinaryOp::NotILike => "!~~*",
532 BinaryOp::Glob => "~~~",
533 BinaryOp::Regex => "~",
534 BinaryOp::NotRegex => "!~",
535 BinaryOp::RegexInsensitive => "~*",
536 BinaryOp::NotRegexInsensitive => "!~*",
537 BinaryOp::Arrow => "->",
538 BinaryOp::LongArrow => "->>",
539 BinaryOp::Contains => "@>",
540 BinaryOp::ContainedBy => "<@",
541 BinaryOp::Overlaps => "&&",
542 BinaryOp::StartsWith => "^@",
543 BinaryOp::InetContainedByOrEq => "<<=",
544 BinaryOp::InetContainsOrEq => ">>=",
545 BinaryOp::Named(name) => ast.string(name),
546 BinaryOp::SimilarTo | BinaryOp::NotSimilarTo | BinaryOp::AtTimeZone | BinaryOp::Collate => {
547 unreachable!("the four that return above")
548 }
549 };
550 format!("({left} {word} {right})")
551}
552
553fn call(ast: &Ast, name: Slice, args: Slice, distinct: bool) -> String {
555 let written = parts(ast, name);
556 let list = ast.expr_list(args);
557 if list.len() == 1
560 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
561 if qualifier.is_empty() && replacements.is_empty())
562 && written.eq_ignore_ascii_case("count")
563 {
564 return "count_star()".to_string();
565 }
566 let word = if distinct { "DISTINCT " } else { "" };
567 format!("{}({word}{})", operator(ast, name, &written), exprs(ast, args))
568}
569
570fn operator(ast: &Ast, name: Slice, written: &str) -> String {
577 let one = ast.name(name).next().unwrap_or_default();
578 let alone = ast.name(name).count() == 1;
579 if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
580 return "COALESCE".to_string();
581 }
582 written.to_string()
583}
584
585fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
591 let mut out = "CASE ".to_string();
592 for arm in ast.arm_list(arms) {
593 let when = when(ast, operand, arm);
594 out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
595 }
596 let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
597 out + &format!(" ELSE {last} END")
598}
599
600fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
603 if operand == NONE {
604 return expr(ast, arm.when);
605 }
606 format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
607}
608
609fn typename(text: &str) -> String {
623 let text = text.trim();
624 if let Some(open) = suffix(text) {
627 return typename(&text[..open]) + &text[open..];
628 }
629 let (base, arguments) = arguments(text);
630 let Some(name) = standard(base) else {
631 let base = unquote(base);
632 return match arguments {
633 Some(arguments) => format!("{}({arguments})", catalogued(&base)),
634 None => catalogued(&base),
635 };
636 };
637 match (name, arguments) {
638 ("STRUCT" | "UNION", Some(inside)) => {
641 let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
642 format!("{name}({})", written.join(", "))
643 }
644 ("MAP", Some(inside)) => {
645 let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
646 format!("{name}({})", written.join(", "))
647 }
648 ("DECIMAL" | "VARCHAR", Some(inside)) => {
651 format!("{name}({})", pieces(inside).join(", "))
652 }
653 _ => name.to_string(),
656 }
657}
658
659fn catalogued(base: &str) -> String {
672 if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
673}
674
675fn unquote(base: &str) -> String {
677 match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
678 Some(inside) => inside.replace("\"\"", "\""),
679 None => base.to_string(),
680 }
681}
682
683fn suffix(text: &str) -> Option<usize> {
685 let rest = text.strip_suffix(']')?;
686 let open = rest.rfind('[')?;
687 rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
688}
689
690fn arguments(text: &str) -> (&str, Option<&str>) {
692 let Some(rest) = text.strip_suffix(')') else {
693 return (text, None);
694 };
695 let mut depth = 0usize;
696 for (at, byte) in rest.bytes().enumerate() {
697 match byte {
698 b'(' if depth == 0 => depth = 1,
699 b'(' => depth += 1,
700 b')' => depth -= 1,
701 _ => continue,
702 }
703 if depth == 1 && byte == b'(' {
704 return (rest[..at].trim(), Some(rest[at + 1..].trim()));
705 }
706 }
707 (text, None)
708}
709
710fn pieces(inside: &str) -> Vec<&str> {
712 let mut found = Vec::new();
713 let (mut depth, mut quoted, mut start) = (0usize, false, 0usize);
714 for (at, byte) in inside.bytes().enumerate() {
715 match byte {
716 b'"' => quoted = !quoted,
717 b'(' | b'[' if !quoted => depth += 1,
718 b')' | b']' if !quoted => depth = depth.saturating_sub(1),
719 b',' if !quoted && depth == 0 => {
720 found.push(inside[start..at].trim());
721 start = at + 1;
722 }
723 _ => {}
724 }
725 }
726 found.push(inside[start..].trim());
727 found
728}
729
730fn field(piece: &str) -> String {
732 let mut quoting = false;
733 for (at, byte) in piece.bytes().enumerate() {
734 match byte {
735 b'"' => quoting = !quoting,
736 byte if byte.is_ascii_whitespace() && !quoting => {
737 let name = piece[..at].trim();
738 let name =
739 if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
740 return format!("{name} {}", typename(&piece[at + 1..]));
741 }
742 _ => {}
743 }
744 }
745 piece.to_string()
746}
747
748fn standard(base: &str) -> Option<&'static str> {
754 const NAMES: &[(&str, &str)] = &[
755 ("BOOLEAN", "BOOLEAN"),
756 ("INT", "INTEGER"),
757 ("INTEGER", "INTEGER"),
758 ("SMALLINT", "SMALLINT"),
759 ("BIGINT", "BIGINT"),
760 ("DEC", "DECIMAL"),
761 ("DECIMAL", "DECIMAL"),
762 ("NUMERIC", "DECIMAL"),
763 ("REAL", "FLOAT"),
764 ("FLOAT", "FLOAT"),
765 ("DOUBLE PRECISION", "DOUBLE"),
766 ("CHAR", "VARCHAR"),
767 ("CHARACTER", "VARCHAR"),
768 ("CHARACTER VARYING", "VARCHAR"),
769 ("NATIONAL CHARACTER", "VARCHAR"),
770 ("NATIONAL CHARACTER VARYING", "VARCHAR"),
771 ("VARCHAR", "VARCHAR"),
772 ("BIT", "BIT"),
773 ("DATE", "DATE"),
774 ("TIME", "TIME"),
775 ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
776 ("TIME WITHOUT TIME ZONE", "TIME"),
777 ("TIMESTAMP", "TIMESTAMP"),
778 ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
779 ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
780 ("INTERVAL", "INTERVAL"),
781 ("STRUCT", "STRUCT"),
782 ("UNION", "UNION"),
783 ("MAP", "MAP"),
784 ];
785 let written: Vec<&str> = base.split_whitespace().collect();
786 let written = written.join(" ");
787 NAMES
788 .iter()
789 .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
790 .map(|(_, name)| *name)
791}
792
793fn exprs(ast: &Ast, list: Slice) -> String {
795 let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
796 written.join(", ")
797}
798
799fn names(ast: &Ast, list: Slice) -> String {
801 ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
802}
803
804fn parts(ast: &Ast, list: Slice) -> String {
806 ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
807}
808
809#[cfg(test)]
810mod tests {
811 use super::create_view;
812 use crate::ast::Statement;
813 use crate::transform::parse_ast;
814
815 fn whole(sql: &str) -> String {
817 let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
818 let Statement::CreateView(index) = ast.statements[0] else {
819 panic!("that was not a create view");
820 };
821 create_view(&ast, index)
822 }
823
824 fn body(query: &str) -> String {
826 let written = whole(&format!("CREATE VIEW v AS {query}"));
827 written
828 .strip_prefix("CREATE VIEW v AS ")
829 .and_then(|rest| rest.strip_suffix(';'))
830 .expect("the statement wrapper is there")
831 .to_string()
832 }
833
834 #[test]
835 fn a_statement_loses_its_qualification_and_its_or_replace() {
836 assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
837 assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
838 assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
839 assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
840 }
841
842 #[test]
844 fn an_alias_list_is_written_with_a_space_in_front_of_it() {
845 assert_eq!(
846 whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
847 r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
848 );
849 }
850
851 #[test]
852 fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
853 assert_eq!(
854 whole("CREATE VIEW v AS SELECT X /* a note */ FROM T"),
855 "CREATE VIEW v AS SELECT X FROM T;"
856 );
857 }
858
859 #[test]
860 fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
861 assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
862 assert_eq!(
863 body("SELECT x > 1 AND y < 2 OR b FROM t"),
864 "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
865 );
866 assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
867 assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
868 assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
869 assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
870 }
871
872 #[test]
875 fn a_minus_in_front_of_a_constant_folds_into_it() {
876 assert_eq!(body("SELECT -1"), "SELECT -1");
877 assert_eq!(body("SELECT - -3"), "SELECT 3");
878 assert_eq!(body("SELECT +3"), "SELECT +(3)");
879 }
880
881 #[test]
882 fn the_null_tests_and_the_boolean_tests() {
883 assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
884 assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
885 assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
886 assert_eq!(
887 body("SELECT b IS TRUE FROM t"),
888 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
889 );
890 assert_eq!(
891 body("SELECT b IS NOT TRUE FROM t"),
892 "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
893 );
894 assert_eq!(
895 body("SELECT b IS FALSE FROM t"),
896 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
897 );
898 assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
899 assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
900 assert_eq!(
901 body("SELECT x IS DISTINCT FROM y FROM t"),
902 "SELECT (x IS DISTINCT FROM y) FROM t"
903 );
904 }
905
906 #[test]
907 fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
908 assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
909 assert_eq!(
910 body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
911 "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
912 );
913 assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
914 assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
915 }
916
917 #[test]
920 fn the_pattern_operators_come_back_as_symbols() {
921 assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
922 assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
923 assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
924 assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
925 assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
926 assert_eq!(body("SELECT s !~ 'a' FROM t"), "SELECT (s !~ 'a') FROM t");
927 assert_eq!(
928 body("SELECT s NOT SIMILAR TO 'a' FROM t"),
929 "SELECT (NOT regexp_full_match(s, 'a')) FROM t"
930 );
931 }
932
933 #[test]
934 fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
935 assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
936 assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
937 assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
938 assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
939 assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
940 }
941
942 #[test]
944 fn a_case_is_written_the_long_way_round() {
945 assert_eq!(
946 body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
947 "SELECT CASE WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
948 );
949 assert_eq!(
950 body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
951 "SELECT CASE WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
952 );
953 }
954
955 #[test]
956 fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
957 assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
958 assert_eq!(
959 body("SELECT cast(x as decimal(4,1)) FROM t"),
960 "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
961 );
962 assert_eq!(
963 body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
964 "SELECT TRY_CAST(s AS INTEGER) FROM t"
965 );
966 }
967
968 #[test]
970 fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
971 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
972 assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
973 assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
974 assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
975 assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
976 assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
977 assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
980 assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
981 assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
982 assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
983 assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
984 assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
985 assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
986 assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
987 }
988
989 #[test]
991 fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
992 assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
993 assert_eq!(
994 body("SELECT CAST(x AS struct(\"A b\" int))"),
995 "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
996 );
997 }
998
999 #[test]
1001 fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
1002 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1003 assert_eq!(cast("text"), "SELECT CAST(x AS text)");
1004 assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
1005 assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
1006 assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1007 assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1008 assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1009 assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1010 assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1012 assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1013 assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1014 assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1015 }
1016
1017 #[test]
1018 fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1019 assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1020 assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1021 assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1022 assert_eq!(body("SELECT []"), "SELECT list_value()");
1023 }
1024
1025 #[test]
1028 fn a_function_name_is_quoted_when_it_is_a_keyword() {
1029 assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1030 assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1031 }
1032
1033 #[test]
1034 fn the_literals() {
1035 assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1036 assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1037 assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1038 }
1039
1040 #[test]
1042 fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1043 assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1044 assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1045 assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1046 assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1047 assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1048 }
1049
1050 #[test]
1052 fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1053 assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1054 assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1055 assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1056 assert_eq!(
1058 body("SELECT extract(millisecond FROM d)"),
1059 "SELECT date_part('MILLISECONDS', d)"
1060 );
1061 assert_eq!(
1062 body("SELECT extract(microseconds FROM d)"),
1063 "SELECT date_part('MICROSECONDS', d)"
1064 );
1065 assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1066 assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1068 assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1069 }
1070
1071 #[test]
1073 fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1074 assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1075 assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1076 assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1078 assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1080 assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1081 }
1082
1083 #[test]
1084 fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1085 assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1086 assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1087 assert_eq!(
1088 body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1089 "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1090 );
1091 assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1092 assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1093 assert_eq!(
1094 body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1095 "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1096 );
1097 assert_eq!(
1098 body("SELECT DISTINCT ON (x) x, y FROM t"),
1099 "SELECT DISTINCT ON (x) x, y FROM t"
1100 );
1101 }
1102
1103 #[test]
1106 fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1107 assert_eq!(
1108 body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1109 "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1110 );
1111 assert_eq!(
1112 body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1113 "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1114 );
1115 assert_eq!(
1116 body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1117 "(SELECT x FROM t) UNION (SELECT y FROM t)"
1118 );
1119 }
1120
1121 #[test]
1122 fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1123 assert_eq!(
1124 body("VALUES (1, 'a'), (2, 'b')"),
1125 "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1126 );
1127 }
1128
1129 #[test]
1131 fn a_from_list_has_a_space_before_the_comma() {
1132 assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1133 }
1134
1135 #[test]
1136 fn a_from_item_and_its_aliases() {
1137 assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1138 assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1139 assert_eq!(
1140 body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1141 "SELECT 1 FROM (SELECT x FROM t) AS sub"
1142 );
1143 assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1144 }
1145
1146 #[test]
1149 fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1150 assert_eq!(
1151 body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1152 "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1153 );
1154 assert_eq!(
1155 body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1156 "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1157 );
1158 assert_eq!(
1159 body("SELECT 1 FROM t CROSS JOIN t AS u"),
1160 "SELECT 1 FROM (t CROSS JOIN t AS u)"
1161 );
1162 assert_eq!(
1163 body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1164 "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1165 );
1166 assert_eq!(
1167 body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1168 "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1169 );
1170 assert_eq!(
1171 body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1172 "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1173 );
1174 }
1175
1176 #[test]
1177 fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1178 assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1179 assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1180 assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1181 assert_eq!(
1182 body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1183 "SELECT * REPLACE ((x + 1) AS x) FROM t"
1184 );
1185 }
1186
1187 #[test]
1188 fn a_describe_gets_parentheses_round_what_it_describes() {
1189 assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1190 }
1191}