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