Skip to main content

rudb_parse/
deparse.rs

1//! An [`Ast`] written back out as SQL, the way DuckDB writes one.
2//!
3//! `duckdb_views().sql` is a deparse of the body rather than the text somebody typed, which was
4//! measured: a view created with odd spacing, lower case type names and a comment in the middle
5//! comes back normalised and without the comment. So the column needs a writer, and the writer has
6//! to agree with the pin character for character or the column is a divergence on every view a
7//! harness looks at.
8//!
9//! # This is not a pretty printer and it is not the printer in `transform`'s tests
10//!
11//! Two things are going on in the pin's output and only one of them is printing. `count(*)` comes
12//! back as `count_star()`, `x IS TRUE` as `(CAST(x AS BOOLEAN) IS NOT DISTINCT FROM true)`,
13//! `s LIKE 'a'` as `(s ~~ 'a')`, `[1, 2]` as `list_value(1, 2)`, `x IN (SELECT ...)` as
14//! `(x = ANY(SELECT ...))` and a simple `CASE x WHEN 1` as a searched `CASE` with an `ELSE NULL`
15//! nobody wrote. Those are rewrites DuckDB's transformer does on the way in, and what gets printed
16//! is the rewritten tree. rudb's AST keeps the written form, deliberately, because an error message
17//! should say what was written. So the rewrites happen here, at the point of printing, and every one
18//! of them is a line in this file with the measurement it came from next to it.
19//!
20//! `transform`'s tests have a printer of their own and it stays. It answers a different question:
21//! what shape did the transform produce. Printing `IS TRUE` as a cast and a distinct test would hide
22//! exactly the bug those tests are there to catch.
23//!
24//! # The parentheses
25//!
26//! Every binary operation is parenthesised, whatever the precedence, so `x + y * 2 - 1` is
27//! `((x + (y * 2)) - 1)`. Every unary one parenthesises its operand instead, so `-x` is `-(x)`. That
28//! is upstream's rule and it is also the only rule that is safe without a precedence table, since a
29//! printer that leaves parentheses out has to be right about precedence in both directions.
30//!
31//! A few of the quirks that follow from printing this way are upstream's rather than anybody's
32//! design, and they are reproduced because the column is a comparison. `CASE` is followed by two
33//! spaces, because the slot for the operand of a simple `CASE` is filled in unconditionally and a
34//! searched one leaves it empty. A `FROM` list has a space before each comma. A chain of three set
35//! operations loses the space before the second operator.
36//!
37//! # What does not agree yet
38//!
39//! Three things, and none of them is a printing question. Each one is a place where rudb's transform
40//! threw away something the pin kept, so the answer is in `transform` and not here, and each has an
41//! issue of its own. Two hundred and sixty two view bodies were measured against the pin and these
42//! four lines are what is left over.
43//!
44//! `^` and `**` are one [`BinaryOp::Power`] here and two operators there, and the pin keeps whichever
45//! was written all the way down to the function it resolves: `[1] ^ [2]` and `[1] ** [2]` fail with
46//! different names in the message. A view written with one comes back with the other.
47//!
48//! `LIMIT ALL` is dropped by the transform, since it means no limit, and the pin writes it back as
49//! `LIMIT NULL`.
50//!
51//! A subscript is rewritten by the transform into the call it stands for, so `[1, 2][1]` is
52//! `array_extract(list_value(1, 2), 1)` here and `list_value(1, 2)[1]` there. The pin does the same
53//! rewrite at bind time and prints the subscript, so this one is a matter of doing it later.
54//!
55//! # What the parser cannot reach yet
56//!
57//! A body the parser refuses never gets here, so none of the following is a divergence today. They
58//! were measured anyway, at the same time as the rest, because the measurement is the expensive part
59//! and whoever adds the syntax will need the answer. A
60//! `FILTER` keeps its own parentheses and parenthesises the condition inside them. `EXISTS` is
61//! written without a space before the parenthesis. `x IN (SELECT ...)` is `(x = ANY(SELECT ...))` and
62//! `x > ALL (SELECT ...)` is `(NOT (x <= ANY(SELECT ...)))`. A `WITH` loses the space after the last
63//! bracket, so it reads `WITH a AS (SELECT 1 AS n)SELECT n FROM a`, and a recursive one writes the
64//! column list as ` (n)` with a space. `LATERAL` goes. `TABLESAMPLE 10 PERCENT` is `TABLESAMPLE
65//! System(10.0 PERCENT)`. `CUBE (x, y)` and `ROLLUP (x, y)` are both written out as the
66//! `GROUPING SETS` they stand for. `{'a': 1}` is `struct_pack(a := 1)` and `MAP {'a': 1}` is
67//! `"map"(list_value('a'), list_value(1))`. A list comprehension is expanded into the three nested
68//! lambdas it is made of.
69
70use crate::ast::{
71    Ast, BinaryOp, CaseArm, CreateViewRef, Distinct, Expr, ExprRef, JoinKind, LiteralKind, Nulls,
72    Order, OrderItem, Quantifier, QueryBody, QueryRef, SelectRef, SetOp, Slice, Source, SourceRef,
73    StrRef, Target, UnaryOp, WindowBound, WindowExclude, WindowRef, WindowUnit,
74};
75use crate::matcher::NONE;
76use crate::tokenize::quoted;
77
78/// A `CREATE VIEW` written back out, which is what `duckdb_views()` reports as `sql`.
79///
80/// The name loses its qualification, which was measured: `CREATE VIEW main.v AS ...` comes back as
81/// `CREATE VIEW v AS ...`. So does `OR REPLACE` and so does `IF NOT EXISTS`, since what the column
82/// answers is what this view is and not what the statement that made it asked for.
83#[must_use]
84pub fn create_view(ast: &Ast, index: CreateViewRef) -> String {
85    let written = ast.create_view(index);
86    let name = ast.name(written.name).last().unwrap_or_default();
87    let temporary = if written.temporary { "TEMP " } else { "" };
88    let mut out = format!("CREATE {temporary}VIEW {}", quoted(name));
89    if !written.columns.is_empty() {
90        // A space before the parenthesis, where `CREATE TABLE t(x INTEGER)` has none. Both were
91        // measured and they really do differ.
92        out += &format!(" ({})", names(ast, written.columns));
93    }
94    out + &format!(" AS {};", query(ast, written.query))
95}
96
97/// One query written back out.
98#[must_use]
99pub fn query(ast: &Ast, index: QueryRef) -> String {
100    let held = ast.query(index);
101    let mut out = match held.body {
102        QueryBody::Select(select) => selection(ast, select),
103        QueryBody::SetOp { op, quantifier, by_name, left, right } => {
104            setop(ast, op, quantifier, by_name, left, right)
105        }
106        // A `VALUES` on its own becomes a select over it, named the way upstream names it. The name
107        // is not a choice here: `CREATE VIEW v AS VALUES (1)` comes back with `AS valueslist` on it.
108        QueryBody::Values(rows) => format!("SELECT * FROM ({}) AS valueslist", values(ast, rows)),
109        QueryBody::Describe(inner) => format!("DESCRIBE ({})", query(ast, inner)),
110        QueryBody::Show { name, .. } => format!("SHOW {}", ast.name_text(name)),
111    };
112    if held.order_by_all {
113        // `ORDER BY ALL` is a star over the columns by the time it is printed.
114        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        // `LIMIT 10 PERCENT` comes back as `LIMIT (10) %`, which is upstream writing the percent
122        // sign into the slot an operator goes in and getting the spacing wrong. Reproduced.
123        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
135/// A set operation, with the spacing bug upstream has in it.
136///
137/// Each side is wrapped in parentheses unless it is itself a set operation, in which case it is
138/// written bare. The bare case also loses the space that would follow it, which is why
139/// `a UNION b UNION c` comes back as `(a) UNION (b)UNION (c)` and not with a space there. That is
140/// the pin's output and it is a comparison, so it is what this writes.
141fn 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    // `UNION DISTINCT` comes back as `UNION`, since distinct is what the operator does anyway.
155    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
160/// One side of a set operation, parenthesised unless it is a set operation itself.
161fn 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
169/// One select block, without the modifiers that hang off the query around it.
170fn 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        // A space before the comma, which is upstream's and was measured on `FROM t t1, t t2`.
183        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
201/// One entry of a target list, with its alias if it was given one.
202fn 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
210/// One entry of an order by list.
211fn 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
226/// One entry of a `FROM` clause.
227fn 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        // A `VALUES` in a `FROM` clause is wrapped in a select of its own, named `valueslist`, and
240        // then given whatever alias was written. Measured, including the name.
241        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                // `FULL OUTER JOIN` loses the `OUTER`, and a `NATURAL JOIN` gains an `INNER`.
251                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                // A second pair of parentheses around a condition that has its own, so an equality
262                // comes out as `ON ((a.x = b.y))`.
263                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
273/// One argument of a table function, which is an expression or a name and an expression.
274///
275/// A named one is parenthesised and written with `=`, so `read_csv(f, header = true)` comes back as
276/// `read_csv(f, ("header" = true))`. The name goes through the quoting rule like any other
277/// identifier, which is why `header` gains quotes there.
278fn 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
285/// A from item with its alias and column list, if it was given either.
286fn 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
297/// The rows of a `VALUES`, with the keyword in front of them.
298fn 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
304/// One expression, written back out.
305///
306/// Public because the binder names an unaliased target after the text that produced it, and for a
307/// window call that text is this one: `SELECT sum(x) OVER (ORDER BY x)` has a column called
308/// `sum(x) OVER (ORDER BY x)` and getting there any other way would be a second deparser.
309pub fn expression(ast: &Ast, index: ExprRef) -> String {
310    expr(ast, index)
311}
312
313/// One expression.
314fn expr(ast: &Ast, index: ExprRef) -> String {
315    match ast.expr(index) {
316        Expr::Star { qualifier, replacements } => star(ast, qualifier, replacements),
317        Expr::Column { name } => parts(ast, name),
318        Expr::Literal { kind, text } => literal(ast, kind, text),
319        Expr::Unary { op, operand } => unary(ast, op, operand),
320        Expr::Binary { op, left, right } => binary(ast, op, left, right),
321        Expr::Function { name, args, distinct, filter } => call(ast, name, args, distinct, filter),
322        Expr::Window { name, args, distinct, filter, ignore_nulls, spec } => {
323            window(ast, name, args, distinct, filter, ignore_nulls, spec)
324        }
325        Expr::Cast { operand, ty, try_cast } => {
326            let word = if try_cast { "TRY_CAST" } else { "CAST" };
327            format!("{word}({} AS {})", expr(ast, operand), typename(ast.string(ty)))
328        }
329        Expr::Case { operand, arms, otherwise } => case(ast, operand, arms, otherwise),
330        Expr::Between { operand, low, high, negated } => {
331            let written = format!(
332                "({} BETWEEN {} AND {})",
333                expr(ast, operand),
334                expr(ast, low),
335                expr(ast, high)
336            );
337            if negated { format!("(NOT {written})") } else { written }
338        }
339        Expr::In { operand, list, negated } => {
340            let written = format!("({} IN ({}))", expr(ast, operand), exprs(ast, list));
341            if negated { format!("(NOT {written})") } else { written }
342        }
343        Expr::InSubquery { operand, query: inner, negated } => {
344            let any = format!("({} = ANY({}))", expr(ast, operand), query(ast, inner));
345            if negated { format!("(NOT {any})") } else { any }
346        }
347        Expr::QuantifiedSubquery { operand, op, query: inner, all } => {
348            let (op, negate) = if all { (negated_comparison(op), true) } else { (op, false) };
349            let word = comparison_word(op);
350            let any = format!("({} {word} ANY({}))", expr(ast, operand), query(ast, inner));
351            if negate { format!("(NOT {any})") } else { any }
352        }
353        Expr::Parameter { name } => format!("${}", ast.string(name)),
354        // A bracketed list is a call to `list_value`, including when it is empty.
355        Expr::List { items } => format!("list_value({})", exprs(ast, items)),
356        // And a parenthesised list is a call to `row`, which needs its quotes because it is a
357        // keyword.
358        Expr::Row { items } => format!("\"row\"({})", exprs(ast, items)),
359        Expr::Subquery { query: inner } => format!("({})", query(ast, inner)),
360        Expr::Exists { query: inner, negated } => {
361            let exists = format!("EXISTS({})", query(ast, inner));
362            if negated { format!("(NOT {exists})") } else { exists }
363        }
364    }
365}
366
367fn comparison_word(op: BinaryOp) -> &'static str {
368    match op {
369        BinaryOp::Eq => "=",
370        BinaryOp::NotEq => "!=",
371        BinaryOp::Lt => "<",
372        BinaryOp::Gt => ">",
373        BinaryOp::LtEq => "<=",
374        BinaryOp::GtEq => ">=",
375        _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
376    }
377}
378
379fn negated_comparison(op: BinaryOp) -> BinaryOp {
380    match op {
381        BinaryOp::Eq => BinaryOp::NotEq,
382        BinaryOp::NotEq => BinaryOp::Eq,
383        BinaryOp::Lt => BinaryOp::GtEq,
384        BinaryOp::Gt => BinaryOp::LtEq,
385        BinaryOp::LtEq => BinaryOp::Gt,
386        BinaryOp::GtEq => BinaryOp::Lt,
387        _ => unreachable!("the grammar permits only a comparison before ANY or ALL"),
388    }
389}
390
391/// A star, with the qualifier and the replace list it may have been written with.
392fn star(ast: &Ast, qualifier: Slice, replacements: Slice) -> String {
393    let mut out =
394        if qualifier.is_empty() { "*".to_string() } else { format!("{}.*", parts(ast, qualifier)) };
395    if !replacements.is_empty() {
396        let written: Vec<String> =
397            ast.target_list(replacements).iter().map(|target| aliased(ast, target)).collect();
398        out += &format!(" REPLACE ({})", written.join(", "));
399    }
400    out
401}
402
403/// One literal.
404fn literal(ast: &Ast, kind: LiteralKind, text: StrRef) -> String {
405    match kind {
406        // `true` and `false` in lower case, which is the pin's spelling whichever way they were
407        // written.
408        LiteralKind::Null => "NULL".to_string(),
409        LiteralKind::True => "true".to_string(),
410        LiteralKind::False => "false".to_string(),
411        LiteralKind::Number => number(ast.string(text)),
412        LiteralKind::String => string(ast.string(text)),
413        // A blob prints as a string of its escaped form cast to `BLOB`, so `X'ab'` comes back as
414        // `'\xAB'::BLOB`.
415        LiteralKind::Blob => format!("{}::BLOB", string(ast.string(text))),
416    }
417}
418
419/// A numeric literal, written back as the value it was read as rather than as the text.
420///
421/// The value is what upstream prints, so the shape of the literal decides the shape of the answer.
422/// A literal with an exponent in it is a DOUBLE and comes back in whatever form a double prints in.
423/// One with a point in it is a DECIMAL of the width and scale that were written, so the digits after
424/// the point survive exactly, trailing zeros and all, and only the digits in front of it are tidied.
425/// One with neither is an integer. The underscores a long number can be written with are a way of
426/// writing it and not part of it, so `1_000` is `1000` in all three.
427fn number(written: &str) -> String {
428    let text = written.replace('_', "");
429    if text.contains(['e', 'E']) {
430        return double(&text);
431    }
432    let Some((whole, fraction)) = text.split_once('.') else {
433        return leading(&text).to_string();
434    };
435    // `1.` is a decimal of scale zero, which prints without the point, and `.5` keeps the empty
436    // side it was written with rather than growing a zero. Both were measured.
437    if fraction.is_empty() {
438        return leading(whole).to_string();
439    }
440    format!("{}.{fraction}", if whole.is_empty() { "" } else { leading(whole) })
441}
442
443/// A run of digits with the zeros in front of it dropped, down to one digit.
444fn leading(digits: &str) -> &str {
445    let trimmed = digits.trim_start_matches('0');
446    if trimmed.is_empty() { &digits[digits.len().saturating_sub(1)..] } else { trimmed }
447}
448
449/// A double, in the form the formatting library upstream uses prints one in.
450///
451/// Plain digits while the decimal exponent is between minus four and fifteen, and the exponent form
452/// outside that, with at least two digits of exponent and a sign that is written even when it is a
453/// plus. So `1e3` is `1000.0`, `1e15` is `1000000000000000.0`, `1e16` is `1e+16`, `5e-4` is `0.0005`
454/// and `5e-5` is `5e-05`. A plain one always has a point in it, which is what tells a double from an
455/// integer when it is read back.
456fn double(text: &str) -> String {
457    let Ok(value) = text.parse::<f64>() else {
458        return text.to_string();
459    };
460    // The shortest digits that read back as this value, which is what `{:e}` is, and the exponent
461    // that goes with them. Rust writes that form as `2.5e-5`, so the exponent is the tail.
462    let shortest = format!("{value:e}");
463    let (mantissa, exponent) = shortest.split_once('e').unwrap_or((shortest.as_str(), "0"));
464    let exponent: i32 = exponent.parse().unwrap_or(0);
465    if (-4..=15).contains(&exponent) {
466        let plain = format!("{value}");
467        return if plain.contains('.') { plain } else { plain + ".0" };
468    }
469    let sign = if exponent < 0 { '-' } else { '+' };
470    format!("{mantissa}e{sign}{:02}", exponent.abs())
471}
472
473/// A string literal, with the one character that has to be escaped escaped.
474///
475/// Only the quote. A newline written as `e'\n'` comes back as a real newline inside the quotes,
476/// which was measured, so everything else goes out as the byte it is.
477fn string(text: &str) -> String {
478    format!("'{}'", text.replace('\'', "''"))
479}
480
481/// A prefix or postfix operator.
482fn unary(ast: &Ast, op: UnaryOp, operand: ExprRef) -> String {
483    // `-1` is a number and not a negation of one, so a minus in front of a numeric constant folds
484    // into it and `- -3` folds twice and comes back as `3`. A plus does not fold, which is why
485    // `+3` comes back as `+(3)`.
486    if matches!(op, UnaryOp::Negate) {
487        if let Some(number) = negated(ast, operand) {
488            return number;
489        }
490    }
491    let written = expr(ast, operand);
492    match op {
493        UnaryOp::Not => format!("(NOT {written})"),
494        UnaryOp::Negate => format!("-({written})"),
495        UnaryOp::Plus => format!("+({written})"),
496        UnaryOp::BitNot => format!("~({written})"),
497        // `x!` is a call to `factorial` by the time it is printed.
498        UnaryOp::Factorial => format!("factorial({written})"),
499        UnaryOp::IsNull => format!("({written} IS NULL)"),
500        UnaryOp::IsNotNull => format!("({written} IS NOT NULL)"),
501        // `IS UNKNOWN` is `IS NULL` and nothing else, so it prints as the thing it means.
502        UnaryOp::IsUnknown => format!("({written} IS NULL)"),
503        UnaryOp::IsNotUnknown => format!("({written} IS NOT NULL)"),
504        // And the four tests against a boolean are a cast and a distinct test, which is what they
505        // are defined to be: `x IS TRUE` is false rather than null for a null `x`, and a plain
506        // `x = true` would not be.
507        UnaryOp::IsTrue => distinct(&written, "true", true),
508        UnaryOp::IsNotTrue => distinct(&written, "true", false),
509        UnaryOp::IsFalse => distinct(&written, "false", true),
510        UnaryOp::IsNotFalse => distinct(&written, "false", false),
511    }
512}
513
514/// What `IS TRUE` and its three relatives are written as.
515fn distinct(operand: &str, against: &str, same: bool) -> String {
516    let word = if same { "IS NOT DISTINCT FROM" } else { "IS DISTINCT FROM" };
517    format!("(CAST({operand} AS BOOLEAN) {word} {against})")
518}
519
520/// The text of a numeric constant with a minus applied to it, and `None` for anything else.
521///
522/// Recursive, because the fold happens on the way in and applies again to what it produced. A minus
523/// in front of a minus in front of `3` is the constant `3`.
524fn negated(ast: &Ast, index: ExprRef) -> Option<String> {
525    match ast.expr(index) {
526        Expr::Literal { kind: LiteralKind::Number, text } => {
527            Some(format!("-{}", number(ast.string(text))))
528        }
529        Expr::Unary { op: UnaryOp::Negate, operand } => {
530            let inner = negated(ast, operand)?;
531            Some(inner.strip_prefix('-').unwrap_or(&inner).to_string())
532        }
533        _ => None,
534    }
535}
536
537/// An infix operator, parenthesised.
538fn binary(ast: &Ast, op: BinaryOp, left: ExprRef, right: ExprRef) -> String {
539    let (left, right) = (expr(ast, left), expr(ast, right));
540    // The three that are not written as an operator at all.
541    match op {
542        BinaryOp::SimilarTo => return format!("regexp_full_match({left}, {right})"),
543        BinaryOp::NotSimilarTo => return format!("(NOT regexp_full_match({left}, {right}))"),
544        // The arguments swap, so `ts AT TIME ZONE 'UTC'` is `timezone('UTC', ts)`.
545        BinaryOp::AtTimeZone => return format!("timezone({right}, {left})"),
546        // And the one that is written as an operator and is not parenthesised.
547        BinaryOp::Collate => return format!("{left} COLLATE {right}"),
548        _ => {}
549    }
550    let word = match op {
551        BinaryOp::Or => "OR",
552        BinaryOp::And => "AND",
553        BinaryOp::Eq => "=",
554        BinaryOp::NotEq => "!=",
555        BinaryOp::Lt => "<",
556        BinaryOp::Gt => ">",
557        BinaryOp::LtEq => "<=",
558        BinaryOp::GtEq => ">=",
559        BinaryOp::IsDistinctFrom => "IS DISTINCT FROM",
560        BinaryOp::IsNotDistinctFrom => "IS NOT DISTINCT FROM",
561        BinaryOp::Add => "+",
562        BinaryOp::Subtract => "-",
563        BinaryOp::Multiply => "*",
564        BinaryOp::Divide => "/",
565        BinaryOp::IntegerDivide => "//",
566        BinaryOp::Modulo => "%",
567        // Whichever of `^` and `**` was written is what the pin prints, and both arrive here as one
568        // operator, so one spelling has to stand for both. See the module doc.
569        BinaryOp::Power => "**",
570        BinaryOp::BitAnd => "&",
571        BinaryOp::BitOr => "|",
572        BinaryOp::ShiftLeft => "<<",
573        BinaryOp::ShiftRight => ">>",
574        BinaryOp::Concat => "||",
575        // The four pattern operators have a word spelling and a symbol spelling, and the symbol is
576        // what comes back whichever was written.
577        BinaryOp::Like => "~~",
578        BinaryOp::NotLike => "!~~",
579        BinaryOp::ILike => "~~*",
580        BinaryOp::NotILike => "!~~*",
581        BinaryOp::Glob => "~~~",
582        BinaryOp::Regex => "~",
583        BinaryOp::NotRegex => "!~",
584        BinaryOp::RegexInsensitive => "~*",
585        BinaryOp::NotRegexInsensitive => "!~*",
586        BinaryOp::Arrow => "->",
587        BinaryOp::LongArrow => "->>",
588        BinaryOp::Contains => "@>",
589        BinaryOp::ContainedBy => "<@",
590        BinaryOp::Overlaps => "&&",
591        BinaryOp::StartsWith => "^@",
592        BinaryOp::InetContainedByOrEq => "<<=",
593        BinaryOp::InetContainsOrEq => ">>=",
594        BinaryOp::Named(name) => ast.string(name),
595        BinaryOp::SimilarTo | BinaryOp::NotSimilarTo | BinaryOp::AtTimeZone | BinaryOp::Collate => {
596            unreachable!("the four that return above")
597        }
598    };
599    format!("({left} {word} {right})")
600}
601
602/// A function call.
603fn call(ast: &Ast, name: Slice, args: Slice, distinct: bool, filter: ExprRef) -> String {
604    let written = parts(ast, name);
605    let list = ast.expr_list(args);
606    // `count(*)` is a different function from `count`, and the star is how it is spelled rather than
607    // an argument it takes, so it prints under the name it really has. `count()` with nothing in it
608    // is the third spelling of the same function and prints under that name too.
609    if written.eq_ignore_ascii_case("count") {
610        let starred = list.len() == 1
611            && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
612                if qualifier.is_empty() && replacements.is_empty());
613        if starred || list.is_empty() {
614            return format!("count_star(){}", filtered(ast, filter));
615        }
616    }
617    let word = if distinct { "DISTINCT " } else { "" };
618    format!(
619        "{}({word}{}){}",
620        operator(ast, name, &written),
621        exprs(ast, args),
622        filtered(ast, filter)
623    )
624}
625
626/// The `FILTER` a call was written with, or nothing at all when it was written without one.
627///
628/// The word `WHERE` is always printed even when it was not written, because upstream prints it: a
629/// view defined with `FILTER (x > 1)` comes back with `FILTER (WHERE (x > 1))`.
630fn filtered(ast: &Ast, filter: ExprRef) -> String {
631    if filter == NONE { String::new() } else { format!(" FILTER (WHERE {})", expr(ast, filter)) }
632}
633
634/// A call with its window, which is the form a window target with no alias is named after.
635///
636/// The frame is printed only when it is not the default one, which is what upstream does and which
637/// is why `sum(x) OVER (ORDER BY x RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)` comes back
638/// as `sum(x) OVER (ORDER BY x)`. A single bound is printed as the pair it stands for, so
639/// `ROWS UNBOUNDED PRECEDING` comes back as `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`.
640fn window(
641    ast: &Ast,
642    name: Slice,
643    args: Slice,
644    distinct: bool,
645    filter: ExprRef,
646    ignore_nulls: bool,
647    spec: WindowRef,
648) -> String {
649    let word = if distinct { "DISTINCT " } else { "" };
650    // `RESPECT NULLS` is the default and upstream drops it, so only the other one is written.
651    let nulls = if ignore_nulls { " IGNORE NULLS" } else { "" };
652    let written = parts(ast, name);
653    // `count(*) OVER ()` comes back as `count() OVER ()`, where the same call without a window
654    // comes back as `count_star()`. The star goes and the name stays, which is upstream's answer
655    // and not the one the ordinary call path gives.
656    let list = ast.expr_list(args);
657    let bare = list.len() == 1
658        && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
659            if qualifier.is_empty() && replacements.is_empty());
660    let inner = if bare { String::new() } else { exprs(ast, args) };
661    let call =
662        format!("{}({word}{inner}{nulls}){}", operator(ast, name, &written), filtered(ast, filter));
663    let held = ast.window(spec);
664    let mut inside: Vec<String> = Vec::new();
665    if !held.partition.is_empty() {
666        inside.push(format!("PARTITION BY {}", exprs(ast, held.partition)));
667    }
668    if !held.order.is_empty() {
669        let items: Vec<String> =
670            ast.order_list(held.order).iter().map(|item| order(ast, item)).collect();
671        inside.push(format!("ORDER BY {}", items.join(", ")));
672    }
673    if !held.frame_is_default() {
674        let unit = match held.unit {
675            WindowUnit::Rows => "ROWS",
676            WindowUnit::Range => "RANGE",
677            WindowUnit::Groups => "GROUPS",
678        };
679        let mut frame =
680            format!("{unit} BETWEEN {} AND {}", bound(ast, held.start), bound(ast, held.end));
681        frame += match held.exclude {
682            WindowExclude::NoOthers => "",
683            WindowExclude::CurrentRow => " EXCLUDE CURRENT ROW",
684            WindowExclude::Group => " EXCLUDE GROUP",
685            WindowExclude::Ties => " EXCLUDE TIES",
686        };
687        inside.push(frame);
688    }
689    format!("{call} OVER ({})", inside.join(" "))
690}
691
692/// One end of a window frame.
693fn bound(ast: &Ast, end: WindowBound) -> String {
694    match end {
695        WindowBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
696        WindowBound::Preceding(offset) => format!("{} PRECEDING", expr(ast, offset)),
697        WindowBound::CurrentRow => "CURRENT ROW".to_string(),
698        WindowBound::Following(offset) => format!("{} FOLLOWING", expr(ast, offset)),
699        WindowBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
700    }
701}
702
703/// The name a call is written back under, which is the name it was written with for all but two.
704///
705/// `coalesce` and `ifnull` are grammar rules rather than function names, so they come back as the
706/// one thing the rule stands for, upper case and unquoted. That holds for the one argument form as
707/// well: `coalesce(x)` is `COALESCE(x)` and not `x`. No other name does this, which was measured,
708/// and `nullif` is the one to check against because it looks like it should and does not.
709fn operator(ast: &Ast, name: Slice, written: &str) -> String {
710    let one = ast.name(name).next().unwrap_or_default();
711    let alone = ast.name(name).count() == 1;
712    if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
713        return "COALESCE".to_string();
714    }
715    written.to_string()
716}
717
718/// A `CASE`, always searched and always with an `ELSE`.
719///
720/// A simple `CASE x WHEN 1 THEN 'a'` is rewritten into `CASE WHEN x = 1 THEN 'a' ELSE NULL END` on
721/// the way in, so both forms print the same way. The two spaces after `CASE` are upstream leaving
722/// the operand slot empty and writing the space around it anyway.
723fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
724    let mut out = "CASE ".to_string();
725    for arm in ast.arm_list(arms) {
726        let when = when(ast, operand, arm);
727        out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
728    }
729    let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
730    out + &format!(" ELSE {last} END")
731}
732
733/// The condition of one arm, which is the arm's own for a searched `CASE` and an equality for a
734/// simple one.
735fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
736    if operand == NONE {
737        return expr(ast, arm.when);
738    }
739    format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
740}
741
742/// A type as upstream writes one, which is two rules and not one.
743///
744/// A name the SQL standard spells is resolved and written back under the one name its type has, so
745/// `int` is `INTEGER`, `numeric(5)` is `DECIMAL(5)`, `character varying` is `VARCHAR` and `real` is
746/// `FLOAT`. Every other name is written back exactly as somebody typed it, case and all and without
747/// quotes, so `text` stays `text`, `TEXT` stays `TEXT` and `int4` stays `int4`. All of that was
748/// measured a name at a time, and the split is not arbitrary: the standard names are the ones the
749/// grammar has rules for, and everything else is a name the parser hands to the catalog to look up
750/// later, so the text is all it has.
751///
752/// This walks the text rather than going through the type system, because the type system throws
753/// away what has to survive here. `DECIMAL(5)` and `DECIMAL` both become a width and a scale, and
754/// `VARCHAR(10)` becomes `VARCHAR`, but upstream prints back the length that was written.
755fn typename(text: &str) -> String {
756    let text = text.trim();
757    // A trailing `[]` or `[3]` is a list or an array of whatever is in front of it, and the element
758    // is resolved the same way: `int[]` is `INTEGER[]` while `int4[]` stays `int4[]`.
759    if let Some(open) = suffix(text) {
760        return typename(&text[..open]) + &text[open..];
761    }
762    let (base, arguments) = arguments(text);
763    let Some(name) = standard(base) else {
764        let base = unquote(base);
765        return match arguments {
766            Some(arguments) => format!("{}({arguments})", catalogued(&base)),
767            None => catalogued(&base),
768        };
769    };
770    match (name, arguments) {
771        // `STRUCT(a bool)` and `UNION(a int)` are a name and a type each, and the name keeps the
772        // case it was written in while the type goes round again.
773        ("STRUCT" | "UNION", Some(inside)) => {
774            let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
775            format!("{name}({})", written.join(", "))
776        }
777        ("MAP", Some(inside)) => {
778            let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
779            format!("{name}({})", written.join(", "))
780        }
781        // The width and the scale of a decimal and the length of a string survive, because upstream
782        // prints the modifiers it was given rather than the ones the type ended up with.
783        ("DECIMAL" | "VARCHAR", Some(inside)) => {
784            format!("{name}({})", pieces(inside).join(", "))
785        }
786        // And everything else drops them, because they chose the type rather than sitting on it.
787        // `float(10)` is a `FLOAT` and there is nothing left of the ten.
788        _ => name.to_string(),
789    }
790}
791
792/// A type name the grammar has no rule for, which is a name for the catalog to look up later.
793///
794/// Written back as it stands, with the case it was written in and with no quotes, because all the
795/// parser has is the text. `bool`, `TEXT`, `int4`, `timestamptz` and `Mixed` all come back exactly
796/// as they went in, which was measured a name at a time.
797///
798/// `json` is the one exception in the whole list and it comes back quoted. That is not a rule about
799/// json, it is what happens to a name the parser resolves on its own rather than leaving for the
800/// catalog: the type it lands on carries the written name as its label, and a label is written back
801/// through the identifier rule, which quotes a keyword. `json` is the only name that is both a
802/// keyword and one of those, so it is the only one where the difference shows. The case that was
803/// written survives it, so `JSON` is `"JSON"` and `json` is `"json"`.
804fn catalogued(base: &str) -> String {
805    if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
806}
807
808/// A name with its quotes taken off, if it had any.
809fn unquote(base: &str) -> String {
810    match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
811        Some(inside) => inside.replace("\"\"", "\""),
812        None => base.to_string(),
813    }
814}
815
816/// Where the trailing `[]` or `[3]` of a list or an array type starts, if there is one.
817fn suffix(text: &str) -> Option<usize> {
818    let rest = text.strip_suffix(']')?;
819    let open = rest.rfind('[')?;
820    rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
821}
822
823/// A type split into the name and whatever was in the parentheses after it.
824fn arguments(text: &str) -> (&str, Option<&str>) {
825    let Some(rest) = text.strip_suffix(')') else {
826        return (text, None);
827    };
828    let mut depth = 0usize;
829    for (at, byte) in rest.bytes().enumerate() {
830        match byte {
831            b'(' if depth == 0 => depth = 1,
832            b'(' => depth += 1,
833            b')' => depth -= 1,
834            _ => continue,
835        }
836        if depth == 1 && byte == b'(' {
837            return (rest[..at].trim(), Some(rest[at + 1..].trim()));
838        }
839    }
840    (text, None)
841}
842
843/// The entries of an argument list, split on the commas that are not inside anything.
844fn pieces(inside: &str) -> Vec<&str> {
845    let mut found = Vec::new();
846    let (mut depth, mut quoted, mut start) = (0usize, false, 0usize);
847    for (at, byte) in inside.bytes().enumerate() {
848        match byte {
849            b'"' => quoted = !quoted,
850            b'(' | b'[' if !quoted => depth += 1,
851            b')' | b']' if !quoted => depth = depth.saturating_sub(1),
852            b',' if !quoted && depth == 0 => {
853                found.push(inside[start..at].trim());
854                start = at + 1;
855            }
856            _ => {}
857        }
858    }
859    found.push(inside[start..].trim());
860    found
861}
862
863/// One field of a `STRUCT` or a `UNION`, which is a name and then a type.
864fn field(piece: &str) -> String {
865    let mut quoting = false;
866    for (at, byte) in piece.bytes().enumerate() {
867        match byte {
868            b'"' => quoting = !quoting,
869            byte if byte.is_ascii_whitespace() && !quoting => {
870                let name = piece[..at].trim();
871                let name =
872                    if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
873                return format!("{name} {}", typename(&piece[at + 1..]));
874            }
875            _ => {}
876        }
877    }
878    piece.to_string()
879}
880
881/// The one name a type the SQL standard spells is written back under, and nothing for any other.
882///
883/// Several words for the ones the standard writes with several. The list is short because it is the
884/// standard's list and not DuckDB's: `HUGEINT`, `TEXT`, `BLOB` and the rest of the names DuckDB adds
885/// are not in here, and they are the ones that come back exactly as they were written.
886fn standard(base: &str) -> Option<&'static str> {
887    const NAMES: &[(&str, &str)] = &[
888        ("BOOLEAN", "BOOLEAN"),
889        ("INT", "INTEGER"),
890        ("INTEGER", "INTEGER"),
891        ("SMALLINT", "SMALLINT"),
892        ("BIGINT", "BIGINT"),
893        ("DEC", "DECIMAL"),
894        ("DECIMAL", "DECIMAL"),
895        ("NUMERIC", "DECIMAL"),
896        ("REAL", "FLOAT"),
897        ("FLOAT", "FLOAT"),
898        ("DOUBLE PRECISION", "DOUBLE"),
899        ("CHAR", "VARCHAR"),
900        ("CHARACTER", "VARCHAR"),
901        ("CHARACTER VARYING", "VARCHAR"),
902        ("NATIONAL CHARACTER", "VARCHAR"),
903        ("NATIONAL CHARACTER VARYING", "VARCHAR"),
904        ("VARCHAR", "VARCHAR"),
905        ("BIT", "BIT"),
906        ("DATE", "DATE"),
907        ("TIME", "TIME"),
908        ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
909        ("TIME WITHOUT TIME ZONE", "TIME"),
910        ("TIMESTAMP", "TIMESTAMP"),
911        ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
912        ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
913        ("INTERVAL", "INTERVAL"),
914        ("STRUCT", "STRUCT"),
915        ("UNION", "UNION"),
916        ("MAP", "MAP"),
917    ];
918    let written: Vec<&str> = base.split_whitespace().collect();
919    let written = written.join(" ");
920    NAMES
921        .iter()
922        .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
923        .map(|(_, name)| *name)
924}
925
926/// A run of expressions, comma separated.
927fn exprs(ast: &Ast, list: Slice) -> String {
928    let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
929    written.join(", ")
930}
931
932/// A run of identifiers, comma separated, each quoted if it has to be.
933fn names(ast: &Ast, list: Slice) -> String {
934    ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
935}
936
937/// A dotted name, each part quoted if it has to be.
938fn parts(ast: &Ast, list: Slice) -> String {
939    ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
940}
941
942#[cfg(test)]
943mod tests {
944    use super::create_view;
945    use crate::ast::Statement;
946    use crate::transform::parse_ast;
947
948    /// The whole statement, deparsed.
949    fn whole(sql: &str) -> String {
950        let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
951        let Statement::CreateView(index) = ast.statements[0] else {
952            panic!("that was not a create view");
953        };
954        create_view(&ast, index)
955    }
956
957    /// Just the body, which is what most of these are about.
958    fn body(query: &str) -> String {
959        let written = whole(&format!("CREATE VIEW v AS {query}"));
960        written
961            .strip_prefix("CREATE VIEW v AS ")
962            .and_then(|rest| rest.strip_suffix(';'))
963            .expect("the statement wrapper is there")
964            .to_string()
965    }
966
967    #[test]
968    fn a_statement_loses_its_qualification_and_its_or_replace() {
969        assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
970        assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
971        assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
972        assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
973    }
974
975    /// A space before the parenthesis here, and none in a `CREATE TABLE`. Both measured.
976    #[test]
977    fn an_alias_list_is_written_with_a_space_in_front_of_it() {
978        assert_eq!(
979            whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
980            r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
981        );
982    }
983
984    #[test]
985    fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
986        assert_eq!(
987            whole("CREATE VIEW v AS SELECT  X /* a note */ FROM   T"),
988            "CREATE VIEW v AS SELECT X FROM T;"
989        );
990    }
991
992    #[test]
993    fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
994        assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
995        assert_eq!(
996            body("SELECT x > 1 AND y < 2 OR b FROM t"),
997            "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
998        );
999        assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
1000        assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
1001        assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
1002        assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
1003    }
1004
1005    /// A minus in front of a number is part of the number, and it folds as many times as it is
1006    /// written. A plus is not part of one and does not fold.
1007    #[test]
1008    fn a_minus_in_front_of_a_constant_folds_into_it() {
1009        assert_eq!(body("SELECT -1"), "SELECT -1");
1010        assert_eq!(body("SELECT - -3"), "SELECT 3");
1011        assert_eq!(body("SELECT +3"), "SELECT +(3)");
1012    }
1013
1014    #[test]
1015    fn the_null_tests_and_the_boolean_tests() {
1016        assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
1017        assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
1018        assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
1019        assert_eq!(
1020            body("SELECT b IS TRUE FROM t"),
1021            "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
1022        );
1023        assert_eq!(
1024            body("SELECT b IS NOT TRUE FROM t"),
1025            "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
1026        );
1027        assert_eq!(
1028            body("SELECT b IS FALSE FROM t"),
1029            "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
1030        );
1031        assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
1032        assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
1033        assert_eq!(
1034            body("SELECT x IS DISTINCT FROM y FROM t"),
1035            "SELECT (x IS DISTINCT FROM y) FROM t"
1036        );
1037    }
1038
1039    #[test]
1040    fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
1041        assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
1042        assert_eq!(
1043            body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
1044            "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
1045        );
1046        assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
1047        assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
1048        assert_eq!(body("SELECT x IN (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1049        assert_eq!(
1050            body("SELECT x NOT IN (SELECT y FROM t)"),
1051            "SELECT (NOT (x = ANY(SELECT y FROM t)))"
1052        );
1053        assert_eq!(body("SELECT x = ANY (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1054        assert_eq!(
1055            body("SELECT x > ALL (SELECT y FROM t)"),
1056            "SELECT (NOT (x <= ANY(SELECT y FROM t)))"
1057        );
1058    }
1059
1060    /// The four pattern operators have a word spelling and a symbol spelling, and the symbol is
1061    /// what comes back either way.
1062    #[test]
1063    fn the_pattern_operators_come_back_as_symbols() {
1064        assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
1065        assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
1066        assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
1067        assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
1068        assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
1069        assert_eq!(body("SELECT s !~ 'a' FROM t"), "SELECT (s !~ 'a') FROM t");
1070        assert_eq!(
1071            body("SELECT s NOT SIMILAR TO 'a' FROM t"),
1072            "SELECT (NOT regexp_full_match(s, 'a')) FROM t"
1073        );
1074    }
1075
1076    #[test]
1077    fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
1078        assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
1079        assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
1080        assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
1081        assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
1082        assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
1083    }
1084
1085    /// Always searched, always with an `ELSE`, and two spaces after the keyword.
1086    #[test]
1087    fn a_case_is_written_the_long_way_round() {
1088        assert_eq!(
1089            body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
1090            "SELECT CASE  WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
1091        );
1092        assert_eq!(
1093            body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
1094            "SELECT CASE  WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
1095        );
1096    }
1097
1098    #[test]
1099    fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
1100        assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
1101        assert_eq!(
1102            body("SELECT cast(x as decimal(4,1)) FROM t"),
1103            "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
1104        );
1105        assert_eq!(
1106            body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
1107            "SELECT TRY_CAST(s AS INTEGER) FROM t"
1108        );
1109    }
1110
1111    /// The names the grammar has a rule for, which come back under the one name the type has.
1112    #[test]
1113    fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
1114        let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1115        assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
1116        assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
1117        assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
1118        assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
1119        assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
1120        // The argument of a float chose the type rather than sitting on it, so there is nothing
1121        // left of the ten by the time it is written back.
1122        assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
1123        assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
1124        assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
1125        assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
1126        assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
1127        assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
1128        assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
1129        assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
1130    }
1131
1132    /// A struct keeps the case of the field name and resolves the field type.
1133    #[test]
1134    fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
1135        assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
1136        assert_eq!(
1137            body("SELECT CAST(x AS struct(\"A b\" int))"),
1138            "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
1139        );
1140    }
1141
1142    /// And every other name is the catalog's business, so the text is written back as it stands.
1143    #[test]
1144    fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
1145        let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1146        assert_eq!(cast("text"), "SELECT CAST(x AS text)");
1147        assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
1148        assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
1149        assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1150        assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1151        assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1152        assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1153        // The one name that comes back quoted, for the reason written on `catalogued`.
1154        assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1155        assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1156        assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1157        assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1158    }
1159
1160    #[test]
1161    fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1162        assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1163        assert_eq!(body("SELECT count() FROM t"), "SELECT count_star() FROM t");
1164        assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1165        assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1166        assert_eq!(body("SELECT []"), "SELECT list_value()");
1167    }
1168
1169    /// A function name goes through the same quoting rule an identifier does, so the ones that are
1170    /// keywords in a class come back quoted.
1171    #[test]
1172    fn a_function_name_is_quoted_when_it_is_a_keyword() {
1173        assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1174        assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1175    }
1176
1177    #[test]
1178    fn the_literals() {
1179        assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1180        assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1181        assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1182    }
1183
1184    /// A number comes back as the value it was read as, which is three rules and not one.
1185    #[test]
1186    fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1187        assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1188        assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1189        assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1190        assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1191        assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1192    }
1193
1194    /// The part of an `EXTRACT` is a keyword and a keyword has one spelling.
1195    #[test]
1196    fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1197        assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1198        assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1199        assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1200        // Two of the thirteen are written back plural, which is a list and not a rule.
1201        assert_eq!(
1202            body("SELECT extract(millisecond FROM d)"),
1203            "SELECT date_part('MILLISECONDS', d)"
1204        );
1205        assert_eq!(
1206            body("SELECT extract(microseconds FROM d)"),
1207            "SELECT date_part('MICROSECONDS', d)"
1208        );
1209        assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1210        // A word the grammar does not name as a keyword is an identifier and keeps its case.
1211        assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1212        assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1213    }
1214
1215    /// The two spellings of the one operator, which is not a function name however much it looks it.
1216    #[test]
1217    fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1218        assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1219        assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1220        // Including the one argument form, which is not folded away.
1221        assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1222        // And no other name does this, which `nullif` is the one to check against.
1223        assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1224        assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1225    }
1226
1227    #[test]
1228    fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1229        assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1230        assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1231        assert_eq!(
1232            body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1233            "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1234        );
1235        assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1236        assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1237        assert_eq!(
1238            body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1239            "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1240        );
1241        assert_eq!(
1242            body("SELECT DISTINCT ON (x) x, y FROM t"),
1243            "SELECT DISTINCT ON (x) x, y FROM t"
1244        );
1245    }
1246
1247    /// A branch that is a set operation of its own is written bare, and a bare left branch loses the
1248    /// space that would follow it. Upstream's, and reproduced because the column is a comparison.
1249    #[test]
1250    fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1251        assert_eq!(
1252            body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1253            "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1254        );
1255        assert_eq!(
1256            body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1257            "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1258        );
1259        assert_eq!(
1260            body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1261            "(SELECT x FROM t) UNION (SELECT y FROM t)"
1262        );
1263    }
1264
1265    #[test]
1266    fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1267        assert_eq!(
1268            body("VALUES (1, 'a'), (2, 'b')"),
1269            "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1270        );
1271    }
1272
1273    /// A space before each comma, which is upstream's and is not a typo here.
1274    #[test]
1275    fn a_from_list_has_a_space_before_the_comma() {
1276        assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1277    }
1278
1279    #[test]
1280    fn a_from_item_and_its_aliases() {
1281        assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1282        assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1283        assert_eq!(
1284            body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1285            "SELECT 1 FROM (SELECT x FROM t) AS sub"
1286        );
1287        assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1288    }
1289
1290    /// Joins are parenthesised, `FULL OUTER` loses a word, `NATURAL` gains one, and an `ON` gets a
1291    /// second pair of parentheses on top of the ones the condition already has.
1292    #[test]
1293    fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1294        assert_eq!(
1295            body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1296            "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1297        );
1298        assert_eq!(
1299            body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1300            "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1301        );
1302        assert_eq!(
1303            body("SELECT 1 FROM t CROSS JOIN t AS u"),
1304            "SELECT 1 FROM (t CROSS JOIN t AS u)"
1305        );
1306        assert_eq!(
1307            body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1308            "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1309        );
1310        assert_eq!(
1311            body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1312            "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1313        );
1314        assert_eq!(
1315            body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1316            "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1317        );
1318    }
1319
1320    #[test]
1321    fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1322        assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1323        assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1324        assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1325        assert_eq!(
1326            body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1327            "SELECT * REPLACE ((x + 1) AS x) FROM t"
1328        );
1329    }
1330
1331    #[test]
1332    fn a_describe_gets_parentheses_round_what_it_describes() {
1333        assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1334    }
1335
1336    /// Every string on the right was read out of `duckdb_views()` on the pin for the query on the
1337    /// left, which is also the column name the same window gets when the target has no alias.
1338    #[test]
1339    fn a_window_is_written_with_the_parts_that_were_written_in_it() {
1340        assert_eq!(
1341            body("SELECT row_number() OVER () AS n FROM t"),
1342            "SELECT row_number() OVER () AS n FROM t"
1343        );
1344        assert_eq!(
1345            body(
1346                "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1347            ),
1348            "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1349        );
1350        assert_eq!(
1351            body(
1352                "SELECT sum(i) OVER (PARTITION BY i, i+1 ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1353            ),
1354            "SELECT sum(i) OVER (PARTITION BY i, (i + 1) ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1355        );
1356        assert_eq!(
1357            body("SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"),
1358            "SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"
1359        );
1360        assert_eq!(
1361            body("SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"),
1362            "SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"
1363        );
1364        assert_eq!(
1365            body("SELECT first_value(i RESPECT NULLS) OVER (ORDER BY i) AS n FROM t"),
1366            "SELECT first_value(i) OVER (ORDER BY i) AS n FROM t"
1367        );
1368        assert_eq!(
1369            body("SELECT count(*) OVER () AS n FROM t"),
1370            "SELECT count() OVER () AS n FROM t"
1371        );
1372        assert_eq!(
1373            body("SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"),
1374            "SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"
1375        );
1376    }
1377
1378    /// Every string on the right was read out of `duckdb_views()` on the pin. The word `WHERE` is
1379    /// written back whether or not it was written, because the pin writes it back either way.
1380    #[test]
1381    fn a_filter_is_written_after_the_call_and_before_the_over() {
1382        assert_eq!(
1383            body("SELECT sum(x) FILTER (WHERE y > 1) FROM t"),
1384            "SELECT sum(x) FILTER (WHERE (y > 1)) FROM t"
1385        );
1386        assert_eq!(
1387            body("SELECT sum(x) FILTER (y > 1) FROM t"),
1388            "SELECT sum(x) FILTER (WHERE (y > 1)) FROM t"
1389        );
1390        assert_eq!(
1391            body("SELECT count(*) FILTER (WHERE b) FROM t"),
1392            "SELECT count_star() FILTER (WHERE b) FROM t"
1393        );
1394        assert_eq!(
1395            body("SELECT count() FILTER (WHERE b) FROM t"),
1396            "SELECT count_star() FILTER (WHERE b) FROM t"
1397        );
1398        assert_eq!(
1399            body("SELECT sum(x) FILTER (WHERE y > 1) OVER (ORDER BY x) FROM t"),
1400            "SELECT sum(x) FILTER (WHERE (y > 1)) OVER (ORDER BY x) FROM t"
1401        );
1402        assert_eq!(
1403            body("SELECT sum(DISTINCT x) FILTER (WHERE b) OVER () FROM t"),
1404            "SELECT sum(DISTINCT x) FILTER (WHERE b) OVER () FROM t"
1405        );
1406        assert_eq!(
1407            body("SELECT count(*) FILTER (WHERE b) OVER () FROM t"),
1408            "SELECT count() FILTER (WHERE b) OVER () FROM t"
1409        );
1410    }
1411
1412    /// The default frame is not written, and neither is the `RANGE` that was written in its place.
1413    #[test]
1414    fn a_frame_is_written_only_when_it_is_not_the_one_that_was_assumed() {
1415        assert_eq!(
1416            body(
1417                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1418            ),
1419            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1420        );
1421        assert_eq!(
1422            body("SELECT sum(i) OVER (ORDER BY i RANGE UNBOUNDED PRECEDING) AS n FROM t"),
1423            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1424        );
1425        assert_eq!(
1426            body(
1427                "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS n FROM t"
1428            ),
1429            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1430        );
1431        assert_eq!(
1432            body("SELECT sum(i) OVER (ORDER BY i ROWS CURRENT ROW) AS n FROM t"),
1433            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) AS n FROM t"
1434        );
1435        assert_eq!(
1436            body(
1437                "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1+1) PRECEDING AND CURRENT ROW) AS n FROM t"
1438            ),
1439            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1 + 1) PRECEDING AND CURRENT ROW) AS n FROM t"
1440        );
1441        assert_eq!(
1442            body(
1443                "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1444            ),
1445            "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1446        );
1447        assert_eq!(
1448            body(
1449                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1450            ),
1451            "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1452        );
1453        assert_eq!(
1454            body(
1455                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1456            ),
1457            "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1458        );
1459    }
1460
1461    /// A frame over the whole partition measures the same however it says it does, and the three
1462    /// spellings come back as the one upstream picks.
1463    #[test]
1464    fn a_frame_that_covers_the_partition_is_written_as_a_row_count() {
1465        for unit in ["ROWS", "RANGE", "GROUPS"] {
1466            assert_eq!(
1467                body(&format!(
1468                    "SELECT sum(i) OVER (ORDER BY i {unit} BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1469                )),
1470                "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1471            );
1472        }
1473        assert_eq!(
1474            body(
1475                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1476            ),
1477            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1478        );
1479    }
1480
1481    /// A named window is gone by the time anything is written back out, which is upstream's answer
1482    /// as well: the `WINDOW` clause does not survive a round trip through the catalog there.
1483    #[test]
1484    fn a_named_window_is_written_out_where_it_was_used() {
1485        assert_eq!(
1486            body("SELECT sum(i) OVER w AS n FROM t WINDOW w AS (PARTITION BY i ORDER BY i)"),
1487            "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1488        );
1489        assert_eq!(
1490            body("SELECT sum(i) OVER (w) AS n FROM t WINDOW w AS (ORDER BY i)"),
1491            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1492        );
1493        assert_eq!(
1494            body(
1495                "SELECT sum(i) OVER (w ROWS UNBOUNDED PRECEDING) AS n FROM t WINDOW w AS (PARTITION BY i)"
1496            ),
1497            "SELECT sum(i) OVER (PARTITION BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1498        );
1499        assert_eq!(
1500            body("SELECT sum(i) OVER (w PARTITION BY i) AS n FROM t WINDOW w AS (ORDER BY i)"),
1501            "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1502        );
1503        assert_eq!(
1504            body("SELECT sum(i) OVER v AS n FROM t WINDOW w AS (ORDER BY i), v AS (w)"),
1505            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1506        );
1507    }
1508}