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::InSubquery { operand, query: inner, negated } => {
333 let any = format!("({} = ANY({}))", expr(ast, operand), query(ast, inner));
334 if negated { format!("(NOT {any})") } else { any }
335 }
336 Expr::QuantifiedSubquery { operand, op, query: inner, all } => {
337 let (op, negate) = if all { (negated_comparison(op), true) } else { (op, false) };
338 let word = comparison_word(op);
339 let any = format!("({} {word} ANY({}))", expr(ast, operand), query(ast, inner));
340 if negate { format!("(NOT {any})") } else { any }
341 }
342 Expr::Parameter { name } => format!("${}", ast.string(name)),
343 Expr::List { items } => format!("list_value({})", exprs(ast, items)),
345 Expr::Row { items } => format!("\"row\"({})", exprs(ast, items)),
348 Expr::Subquery { query: inner } => format!("({})", query(ast, inner)),
349 Expr::Exists { query: inner, negated } => {
350 let exists = format!("EXISTS({})", query(ast, inner));
351 if negated { format!("(NOT {exists})") } else { exists }
352 }
353 }
354}
355
356fn comparison_word(op: BinaryOp) -> &'static str {
357 match op {
358 BinaryOp::Eq => "=",
359 BinaryOp::NotEq => "!=",
360 BinaryOp::Lt => "<",
361 BinaryOp::Gt => ">",
362 BinaryOp::LtEq => "<=",
363 BinaryOp::GtEq => ">=",
364 _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
365 }
366}
367
368fn negated_comparison(op: BinaryOp) -> BinaryOp {
369 match op {
370 BinaryOp::Eq => BinaryOp::NotEq,
371 BinaryOp::NotEq => BinaryOp::Eq,
372 BinaryOp::Lt => BinaryOp::GtEq,
373 BinaryOp::Gt => BinaryOp::LtEq,
374 BinaryOp::LtEq => BinaryOp::Gt,
375 BinaryOp::GtEq => BinaryOp::Lt,
376 _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
377 }
378}
379
380fn star(ast: &Ast, qualifier: Slice, replacements: Slice) -> String {
382 let mut out =
383 if qualifier.is_empty() { "*".to_string() } else { format!("{}.*", parts(ast, qualifier)) };
384 if !replacements.is_empty() {
385 let written: Vec<String> =
386 ast.target_list(replacements).iter().map(|target| aliased(ast, target)).collect();
387 out += &format!(" REPLACE ({})", written.join(", "));
388 }
389 out
390}
391
392fn literal(ast: &Ast, kind: LiteralKind, text: StrRef) -> String {
394 match kind {
395 LiteralKind::Null => "NULL".to_string(),
398 LiteralKind::True => "true".to_string(),
399 LiteralKind::False => "false".to_string(),
400 LiteralKind::Number => number(ast.string(text)),
401 LiteralKind::String => string(ast.string(text)),
402 LiteralKind::Blob => format!("{}::BLOB", string(ast.string(text))),
405 }
406}
407
408fn number(written: &str) -> String {
417 let text = written.replace('_', "");
418 if text.contains(['e', 'E']) {
419 return double(&text);
420 }
421 let Some((whole, fraction)) = text.split_once('.') else {
422 return leading(&text).to_string();
423 };
424 if fraction.is_empty() {
427 return leading(whole).to_string();
428 }
429 format!("{}.{fraction}", if whole.is_empty() { "" } else { leading(whole) })
430}
431
432fn leading(digits: &str) -> &str {
434 let trimmed = digits.trim_start_matches('0');
435 if trimmed.is_empty() { &digits[digits.len().saturating_sub(1)..] } else { trimmed }
436}
437
438fn double(text: &str) -> String {
446 let Ok(value) = text.parse::<f64>() else {
447 return text.to_string();
448 };
449 let shortest = format!("{value:e}");
452 let (mantissa, exponent) = shortest.split_once('e').unwrap_or((shortest.as_str(), "0"));
453 let exponent: i32 = exponent.parse().unwrap_or(0);
454 if (-4..=15).contains(&exponent) {
455 let plain = format!("{value}");
456 return if plain.contains('.') { plain } else { plain + ".0" };
457 }
458 let sign = if exponent < 0 { '-' } else { '+' };
459 format!("{mantissa}e{sign}{:02}", exponent.abs())
460}
461
462fn string(text: &str) -> String {
467 format!("'{}'", text.replace('\'', "''"))
468}
469
470fn unary(ast: &Ast, op: UnaryOp, operand: ExprRef) -> String {
472 if matches!(op, UnaryOp::Negate) {
476 if let Some(number) = negated(ast, operand) {
477 return number;
478 }
479 }
480 let written = expr(ast, operand);
481 match op {
482 UnaryOp::Not => format!("(NOT {written})"),
483 UnaryOp::Negate => format!("-({written})"),
484 UnaryOp::Plus => format!("+({written})"),
485 UnaryOp::BitNot => format!("~({written})"),
486 UnaryOp::Factorial => format!("factorial({written})"),
488 UnaryOp::IsNull => format!("({written} IS NULL)"),
489 UnaryOp::IsNotNull => format!("({written} IS NOT NULL)"),
490 UnaryOp::IsUnknown => format!("({written} IS NULL)"),
492 UnaryOp::IsNotUnknown => format!("({written} IS NOT NULL)"),
493 UnaryOp::IsTrue => distinct(&written, "true", true),
497 UnaryOp::IsNotTrue => distinct(&written, "true", false),
498 UnaryOp::IsFalse => distinct(&written, "false", true),
499 UnaryOp::IsNotFalse => distinct(&written, "false", false),
500 }
501}
502
503fn distinct(operand: &str, against: &str, same: bool) -> String {
505 let word = if same { "IS NOT DISTINCT FROM" } else { "IS DISTINCT FROM" };
506 format!("(CAST({operand} AS BOOLEAN) {word} {against})")
507}
508
509fn negated(ast: &Ast, index: ExprRef) -> Option<String> {
514 match ast.expr(index) {
515 Expr::Literal { kind: LiteralKind::Number, text } => {
516 Some(format!("-{}", number(ast.string(text))))
517 }
518 Expr::Unary { op: UnaryOp::Negate, operand } => {
519 let inner = negated(ast, operand)?;
520 Some(inner.strip_prefix('-').unwrap_or(&inner).to_string())
521 }
522 _ => None,
523 }
524}
525
526fn binary(ast: &Ast, op: BinaryOp, left: ExprRef, right: ExprRef) -> String {
528 let (left, right) = (expr(ast, left), expr(ast, right));
529 match op {
531 BinaryOp::SimilarTo => return format!("regexp_full_match({left}, {right})"),
532 BinaryOp::NotSimilarTo => return format!("(NOT regexp_full_match({left}, {right}))"),
533 BinaryOp::AtTimeZone => return format!("timezone({right}, {left})"),
535 BinaryOp::Collate => return format!("{left} COLLATE {right}"),
537 _ => {}
538 }
539 let word = match op {
540 BinaryOp::Or => "OR",
541 BinaryOp::And => "AND",
542 BinaryOp::Eq => "=",
543 BinaryOp::NotEq => "!=",
544 BinaryOp::Lt => "<",
545 BinaryOp::Gt => ">",
546 BinaryOp::LtEq => "<=",
547 BinaryOp::GtEq => ">=",
548 BinaryOp::IsDistinctFrom => "IS DISTINCT FROM",
549 BinaryOp::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
550 BinaryOp::Add => "+",
551 BinaryOp::Subtract => "-",
552 BinaryOp::Multiply => "*",
553 BinaryOp::Divide => "/",
554 BinaryOp::IntegerDivide => "//",
555 BinaryOp::Modulo => "%",
556 BinaryOp::Power => "**",
559 BinaryOp::BitAnd => "&",
560 BinaryOp::BitOr => "|",
561 BinaryOp::ShiftLeft => "<<",
562 BinaryOp::ShiftRight => ">>",
563 BinaryOp::Concat => "||",
564 BinaryOp::Like => "~~",
567 BinaryOp::NotLike => "!~~",
568 BinaryOp::ILike => "~~*",
569 BinaryOp::NotILike => "!~~*",
570 BinaryOp::Glob => "~~~",
571 BinaryOp::Regex => "~",
572 BinaryOp::NotRegex => "!~",
573 BinaryOp::RegexInsensitive => "~*",
574 BinaryOp::NotRegexInsensitive => "!~*",
575 BinaryOp::Arrow => "->",
576 BinaryOp::LongArrow => "->>",
577 BinaryOp::Contains => "@>",
578 BinaryOp::ContainedBy => "<@",
579 BinaryOp::Overlaps => "&&",
580 BinaryOp::StartsWith => "^@",
581 BinaryOp::InetContainedByOrEq => "<<=",
582 BinaryOp::InetContainsOrEq => ">>=",
583 BinaryOp::Named(name) => ast.string(name),
584 BinaryOp::SimilarTo | BinaryOp::NotSimilarTo | BinaryOp::AtTimeZone | BinaryOp::Collate => {
585 unreachable!("the four that return above")
586 }
587 };
588 format!("({left} {word} {right})")
589}
590
591fn call(ast: &Ast, name: Slice, args: Slice, distinct: bool) -> String {
593 let written = parts(ast, name);
594 let list = ast.expr_list(args);
595 if list.len() == 1
598 && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
599 if qualifier.is_empty() && replacements.is_empty())
600 && written.eq_ignore_ascii_case("count")
601 {
602 return "count_star()".to_string();
603 }
604 let word = if distinct { "DISTINCT " } else { "" };
605 format!("{}({word}{})", operator(ast, name, &written), exprs(ast, args))
606}
607
608fn operator(ast: &Ast, name: Slice, written: &str) -> String {
615 let one = ast.name(name).next().unwrap_or_default();
616 let alone = ast.name(name).count() == 1;
617 if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
618 return "COALESCE".to_string();
619 }
620 written.to_string()
621}
622
623fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
629 let mut out = "CASE ".to_string();
630 for arm in ast.arm_list(arms) {
631 let when = when(ast, operand, arm);
632 out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
633 }
634 let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
635 out + &format!(" ELSE {last} END")
636}
637
638fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
641 if operand == NONE {
642 return expr(ast, arm.when);
643 }
644 format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
645}
646
647fn typename(text: &str) -> String {
661 let text = text.trim();
662 if let Some(open) = suffix(text) {
665 return typename(&text[..open]) + &text[open..];
666 }
667 let (base, arguments) = arguments(text);
668 let Some(name) = standard(base) else {
669 let base = unquote(base);
670 return match arguments {
671 Some(arguments) => format!("{}({arguments})", catalogued(&base)),
672 None => catalogued(&base),
673 };
674 };
675 match (name, arguments) {
676 ("STRUCT" | "UNION", Some(inside)) => {
679 let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
680 format!("{name}({})", written.join(", "))
681 }
682 ("MAP", Some(inside)) => {
683 let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
684 format!("{name}({})", written.join(", "))
685 }
686 ("DECIMAL" | "VARCHAR", Some(inside)) => {
689 format!("{name}({})", pieces(inside).join(", "))
690 }
691 _ => name.to_string(),
694 }
695}
696
697fn catalogued(base: &str) -> String {
710 if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
711}
712
713fn unquote(base: &str) -> String {
715 match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
716 Some(inside) => inside.replace("\"\"", "\""),
717 None => base.to_string(),
718 }
719}
720
721fn suffix(text: &str) -> Option<usize> {
723 let rest = text.strip_suffix(']')?;
724 let open = rest.rfind('[')?;
725 rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
726}
727
728fn arguments(text: &str) -> (&str, Option<&str>) {
730 let Some(rest) = text.strip_suffix(')') else {
731 return (text, None);
732 };
733 let mut depth = 0usize;
734 for (at, byte) in rest.bytes().enumerate() {
735 match byte {
736 b'(' if depth == 0 => depth = 1,
737 b'(' => depth += 1,
738 b')' => depth -= 1,
739 _ => continue,
740 }
741 if depth == 1 && byte == b'(' {
742 return (rest[..at].trim(), Some(rest[at + 1..].trim()));
743 }
744 }
745 (text, None)
746}
747
748fn pieces(inside: &str) -> Vec<&str> {
750 let mut found = Vec::new();
751 let (mut depth, mut quoted, mut start) = (0usize, false, 0usize);
752 for (at, byte) in inside.bytes().enumerate() {
753 match byte {
754 b'"' => quoted = !quoted,
755 b'(' | b'[' if !quoted => depth += 1,
756 b')' | b']' if !quoted => depth = depth.saturating_sub(1),
757 b',' if !quoted && depth == 0 => {
758 found.push(inside[start..at].trim());
759 start = at + 1;
760 }
761 _ => {}
762 }
763 }
764 found.push(inside[start..].trim());
765 found
766}
767
768fn field(piece: &str) -> String {
770 let mut quoting = false;
771 for (at, byte) in piece.bytes().enumerate() {
772 match byte {
773 b'"' => quoting = !quoting,
774 byte if byte.is_ascii_whitespace() && !quoting => {
775 let name = piece[..at].trim();
776 let name =
777 if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
778 return format!("{name} {}", typename(&piece[at + 1..]));
779 }
780 _ => {}
781 }
782 }
783 piece.to_string()
784}
785
786fn standard(base: &str) -> Option<&'static str> {
792 const NAMES: &[(&str, &str)] = &[
793 ("BOOLEAN", "BOOLEAN"),
794 ("INT", "INTEGER"),
795 ("INTEGER", "INTEGER"),
796 ("SMALLINT", "SMALLINT"),
797 ("BIGINT", "BIGINT"),
798 ("DEC", "DECIMAL"),
799 ("DECIMAL", "DECIMAL"),
800 ("NUMERIC", "DECIMAL"),
801 ("REAL", "FLOAT"),
802 ("FLOAT", "FLOAT"),
803 ("DOUBLE PRECISION", "DOUBLE"),
804 ("CHAR", "VARCHAR"),
805 ("CHARACTER", "VARCHAR"),
806 ("CHARACTER VARYING", "VARCHAR"),
807 ("NATIONAL CHARACTER", "VARCHAR"),
808 ("NATIONAL CHARACTER VARYING", "VARCHAR"),
809 ("VARCHAR", "VARCHAR"),
810 ("BIT", "BIT"),
811 ("DATE", "DATE"),
812 ("TIME", "TIME"),
813 ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
814 ("TIME WITHOUT TIME ZONE", "TIME"),
815 ("TIMESTAMP", "TIMESTAMP"),
816 ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
817 ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
818 ("INTERVAL", "INTERVAL"),
819 ("STRUCT", "STRUCT"),
820 ("UNION", "UNION"),
821 ("MAP", "MAP"),
822 ];
823 let written: Vec<&str> = base.split_whitespace().collect();
824 let written = written.join(" ");
825 NAMES
826 .iter()
827 .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
828 .map(|(_, name)| *name)
829}
830
831fn exprs(ast: &Ast, list: Slice) -> String {
833 let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
834 written.join(", ")
835}
836
837fn names(ast: &Ast, list: Slice) -> String {
839 ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
840}
841
842fn parts(ast: &Ast, list: Slice) -> String {
844 ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
845}
846
847#[cfg(test)]
848mod tests {
849 use super::create_view;
850 use crate::ast::Statement;
851 use crate::transform::parse_ast;
852
853 fn whole(sql: &str) -> String {
855 let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
856 let Statement::CreateView(index) = ast.statements[0] else {
857 panic!("that was not a create view");
858 };
859 create_view(&ast, index)
860 }
861
862 fn body(query: &str) -> String {
864 let written = whole(&format!("CREATE VIEW v AS {query}"));
865 written
866 .strip_prefix("CREATE VIEW v AS ")
867 .and_then(|rest| rest.strip_suffix(';'))
868 .expect("the statement wrapper is there")
869 .to_string()
870 }
871
872 #[test]
873 fn a_statement_loses_its_qualification_and_its_or_replace() {
874 assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
875 assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
876 assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
877 assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
878 }
879
880 #[test]
882 fn an_alias_list_is_written_with_a_space_in_front_of_it() {
883 assert_eq!(
884 whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
885 r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
886 );
887 }
888
889 #[test]
890 fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
891 assert_eq!(
892 whole("CREATE VIEW v AS SELECT X /* a note */ FROM T"),
893 "CREATE VIEW v AS SELECT X FROM T;"
894 );
895 }
896
897 #[test]
898 fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
899 assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
900 assert_eq!(
901 body("SELECT x > 1 AND y < 2 OR b FROM t"),
902 "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
903 );
904 assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
905 assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
906 assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
907 assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
908 }
909
910 #[test]
913 fn a_minus_in_front_of_a_constant_folds_into_it() {
914 assert_eq!(body("SELECT -1"), "SELECT -1");
915 assert_eq!(body("SELECT - -3"), "SELECT 3");
916 assert_eq!(body("SELECT +3"), "SELECT +(3)");
917 }
918
919 #[test]
920 fn the_null_tests_and_the_boolean_tests() {
921 assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
922 assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
923 assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
924 assert_eq!(
925 body("SELECT b IS TRUE FROM t"),
926 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
927 );
928 assert_eq!(
929 body("SELECT b IS NOT TRUE FROM t"),
930 "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
931 );
932 assert_eq!(
933 body("SELECT b IS FALSE FROM t"),
934 "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
935 );
936 assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
937 assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
938 assert_eq!(
939 body("SELECT x IS DISTINCT FROM y FROM t"),
940 "SELECT (x IS DISTINCT FROM y) FROM t"
941 );
942 }
943
944 #[test]
945 fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
946 assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
947 assert_eq!(
948 body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
949 "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
950 );
951 assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
952 assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
953 assert_eq!(body("SELECT x IN (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
954 assert_eq!(
955 body("SELECT x NOT IN (SELECT y FROM t)"),
956 "SELECT (NOT (x = ANY(SELECT y FROM t)))"
957 );
958 assert_eq!(body("SELECT x = ANY (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
959 assert_eq!(
960 body("SELECT x > ALL (SELECT y FROM t)"),
961 "SELECT (NOT (x <= ANY(SELECT y FROM t)))"
962 );
963 }
964
965 #[test]
968 fn the_pattern_operators_come_back_as_symbols() {
969 assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
970 assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
971 assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
972 assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
973 assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
974 assert_eq!(body("SELECT s !~ 'a' FROM t"), "SELECT (s !~ 'a') FROM t");
975 assert_eq!(
976 body("SELECT s NOT SIMILAR TO 'a' FROM t"),
977 "SELECT (NOT regexp_full_match(s, 'a')) FROM t"
978 );
979 }
980
981 #[test]
982 fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
983 assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
984 assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
985 assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
986 assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
987 assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
988 }
989
990 #[test]
992 fn a_case_is_written_the_long_way_round() {
993 assert_eq!(
994 body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
995 "SELECT CASE WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
996 );
997 assert_eq!(
998 body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
999 "SELECT CASE WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
1000 );
1001 }
1002
1003 #[test]
1004 fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
1005 assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
1006 assert_eq!(
1007 body("SELECT cast(x as decimal(4,1)) FROM t"),
1008 "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
1009 );
1010 assert_eq!(
1011 body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
1012 "SELECT TRY_CAST(s AS INTEGER) FROM t"
1013 );
1014 }
1015
1016 #[test]
1018 fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
1019 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1020 assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
1021 assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
1022 assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
1023 assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
1024 assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
1025 assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
1028 assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
1029 assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
1030 assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
1031 assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
1032 assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
1033 assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
1034 assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
1035 }
1036
1037 #[test]
1039 fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
1040 assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
1041 assert_eq!(
1042 body("SELECT CAST(x AS struct(\"A b\" int))"),
1043 "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
1044 );
1045 }
1046
1047 #[test]
1049 fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
1050 let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1051 assert_eq!(cast("text"), "SELECT CAST(x AS text)");
1052 assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
1053 assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
1054 assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1055 assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1056 assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1057 assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1058 assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1060 assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1061 assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1062 assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1063 }
1064
1065 #[test]
1066 fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1067 assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1068 assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1069 assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1070 assert_eq!(body("SELECT []"), "SELECT list_value()");
1071 }
1072
1073 #[test]
1076 fn a_function_name_is_quoted_when_it_is_a_keyword() {
1077 assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1078 assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1079 }
1080
1081 #[test]
1082 fn the_literals() {
1083 assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1084 assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1085 assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1086 }
1087
1088 #[test]
1090 fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1091 assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1092 assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1093 assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1094 assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1095 assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1096 }
1097
1098 #[test]
1100 fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1101 assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1102 assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1103 assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1104 assert_eq!(
1106 body("SELECT extract(millisecond FROM d)"),
1107 "SELECT date_part('MILLISECONDS', d)"
1108 );
1109 assert_eq!(
1110 body("SELECT extract(microseconds FROM d)"),
1111 "SELECT date_part('MICROSECONDS', d)"
1112 );
1113 assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1114 assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1116 assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1117 }
1118
1119 #[test]
1121 fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1122 assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1123 assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1124 assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1126 assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1128 assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1129 }
1130
1131 #[test]
1132 fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1133 assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1134 assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1135 assert_eq!(
1136 body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1137 "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1138 );
1139 assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1140 assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1141 assert_eq!(
1142 body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1143 "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1144 );
1145 assert_eq!(
1146 body("SELECT DISTINCT ON (x) x, y FROM t"),
1147 "SELECT DISTINCT ON (x) x, y FROM t"
1148 );
1149 }
1150
1151 #[test]
1154 fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1155 assert_eq!(
1156 body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1157 "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1158 );
1159 assert_eq!(
1160 body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1161 "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1162 );
1163 assert_eq!(
1164 body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1165 "(SELECT x FROM t) UNION (SELECT y FROM t)"
1166 );
1167 }
1168
1169 #[test]
1170 fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1171 assert_eq!(
1172 body("VALUES (1, 'a'), (2, 'b')"),
1173 "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1174 );
1175 }
1176
1177 #[test]
1179 fn a_from_list_has_a_space_before_the_comma() {
1180 assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1181 }
1182
1183 #[test]
1184 fn a_from_item_and_its_aliases() {
1185 assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1186 assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1187 assert_eq!(
1188 body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1189 "SELECT 1 FROM (SELECT x FROM t) AS sub"
1190 );
1191 assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1192 }
1193
1194 #[test]
1197 fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1198 assert_eq!(
1199 body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1200 "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1201 );
1202 assert_eq!(
1203 body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1204 "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1205 );
1206 assert_eq!(
1207 body("SELECT 1 FROM t CROSS JOIN t AS u"),
1208 "SELECT 1 FROM (t CROSS JOIN t AS u)"
1209 );
1210 assert_eq!(
1211 body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1212 "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1213 );
1214 assert_eq!(
1215 body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1216 "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1217 );
1218 assert_eq!(
1219 body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1220 "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1221 );
1222 }
1223
1224 #[test]
1225 fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1226 assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1227 assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1228 assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1229 assert_eq!(
1230 body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1231 "SELECT * REPLACE ((x + 1) AS x) FROM t"
1232 );
1233 }
1234
1235 #[test]
1236 fn a_describe_gets_parentheses_round_what_it_describes() {
1237 assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1238 }
1239}