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 } => call(ast, name, args, distinct),
322        Expr::Window { name, args, distinct, ignore_nulls, spec } => {
323            window(ast, name, args, distinct, 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) -> 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 "count_star()".to_string();
615        }
616    }
617    let word = if distinct { "DISTINCT " } else { "" };
618    format!("{}({word}{})", operator(ast, name, &written), exprs(ast, args))
619}
620
621/// A call with its window, which is the form a window target with no alias is named after.
622///
623/// The frame is printed only when it is not the default one, which is what upstream does and which
624/// is why `sum(x) OVER (ORDER BY x RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)` comes back
625/// as `sum(x) OVER (ORDER BY x)`. A single bound is printed as the pair it stands for, so
626/// `ROWS UNBOUNDED PRECEDING` comes back as `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`.
627fn window(
628    ast: &Ast,
629    name: Slice,
630    args: Slice,
631    distinct: bool,
632    ignore_nulls: bool,
633    spec: WindowRef,
634) -> String {
635    let word = if distinct { "DISTINCT " } else { "" };
636    // `RESPECT NULLS` is the default and upstream drops it, so only the other one is written.
637    let nulls = if ignore_nulls { " IGNORE NULLS" } else { "" };
638    let written = parts(ast, name);
639    // `count(*) OVER ()` comes back as `count() OVER ()`, where the same call without a window
640    // comes back as `count_star()`. The star goes and the name stays, which is upstream's answer
641    // and not the one the ordinary call path gives.
642    let list = ast.expr_list(args);
643    let bare = list.len() == 1
644        && matches!(ast.expr(list[0]), Expr::Star { qualifier, replacements }
645            if qualifier.is_empty() && replacements.is_empty());
646    let inner = if bare { String::new() } else { exprs(ast, args) };
647    let call = format!("{}({word}{inner}{nulls})", operator(ast, name, &written));
648    let held = ast.window(spec);
649    let mut inside: Vec<String> = Vec::new();
650    if !held.partition.is_empty() {
651        inside.push(format!("PARTITION BY {}", exprs(ast, held.partition)));
652    }
653    if !held.order.is_empty() {
654        let items: Vec<String> =
655            ast.order_list(held.order).iter().map(|item| order(ast, item)).collect();
656        inside.push(format!("ORDER BY {}", items.join(", ")));
657    }
658    if !held.frame_is_default() {
659        let unit = match held.unit {
660            WindowUnit::Rows => "ROWS",
661            WindowUnit::Range => "RANGE",
662            WindowUnit::Groups => "GROUPS",
663        };
664        let mut frame =
665            format!("{unit} BETWEEN {} AND {}", bound(ast, held.start), bound(ast, held.end));
666        frame += match held.exclude {
667            WindowExclude::NoOthers => "",
668            WindowExclude::CurrentRow => " EXCLUDE CURRENT ROW",
669            WindowExclude::Group => " EXCLUDE GROUP",
670            WindowExclude::Ties => " EXCLUDE TIES",
671        };
672        inside.push(frame);
673    }
674    format!("{call} OVER ({})", inside.join(" "))
675}
676
677/// One end of a window frame.
678fn bound(ast: &Ast, end: WindowBound) -> String {
679    match end {
680        WindowBound::UnboundedPreceding => "UNBOUNDED PRECEDING".to_string(),
681        WindowBound::Preceding(offset) => format!("{} PRECEDING", expr(ast, offset)),
682        WindowBound::CurrentRow => "CURRENT ROW".to_string(),
683        WindowBound::Following(offset) => format!("{} FOLLOWING", expr(ast, offset)),
684        WindowBound::UnboundedFollowing => "UNBOUNDED FOLLOWING".to_string(),
685    }
686}
687
688/// The name a call is written back under, which is the name it was written with for all but two.
689///
690/// `coalesce` and `ifnull` are grammar rules rather than function names, so they come back as the
691/// one thing the rule stands for, upper case and unquoted. That holds for the one argument form as
692/// well: `coalesce(x)` is `COALESCE(x)` and not `x`. No other name does this, which was measured,
693/// and `nullif` is the one to check against because it looks like it should and does not.
694fn operator(ast: &Ast, name: Slice, written: &str) -> String {
695    let one = ast.name(name).next().unwrap_or_default();
696    let alone = ast.name(name).count() == 1;
697    if alone && (one.eq_ignore_ascii_case("coalesce") || one.eq_ignore_ascii_case("ifnull")) {
698        return "COALESCE".to_string();
699    }
700    written.to_string()
701}
702
703/// A `CASE`, always searched and always with an `ELSE`.
704///
705/// A simple `CASE x WHEN 1 THEN 'a'` is rewritten into `CASE WHEN x = 1 THEN 'a' ELSE NULL END` on
706/// the way in, so both forms print the same way. The two spaces after `CASE` are upstream leaving
707/// the operand slot empty and writing the space around it anyway.
708fn case(ast: &Ast, operand: ExprRef, arms: Slice, otherwise: ExprRef) -> String {
709    let mut out = "CASE ".to_string();
710    for arm in ast.arm_list(arms) {
711        let when = when(ast, operand, arm);
712        out += &format!(" WHEN ({when}) THEN ({})", expr(ast, arm.then));
713    }
714    let last = if otherwise == NONE { "NULL".to_string() } else { expr(ast, otherwise) };
715    out + &format!(" ELSE {last} END")
716}
717
718/// The condition of one arm, which is the arm's own for a searched `CASE` and an equality for a
719/// simple one.
720fn when(ast: &Ast, operand: ExprRef, arm: &CaseArm) -> String {
721    if operand == NONE {
722        return expr(ast, arm.when);
723    }
724    format!("({} = {})", expr(ast, operand), expr(ast, arm.when))
725}
726
727/// A type as upstream writes one, which is two rules and not one.
728///
729/// A name the SQL standard spells is resolved and written back under the one name its type has, so
730/// `int` is `INTEGER`, `numeric(5)` is `DECIMAL(5)`, `character varying` is `VARCHAR` and `real` is
731/// `FLOAT`. Every other name is written back exactly as somebody typed it, case and all and without
732/// quotes, so `text` stays `text`, `TEXT` stays `TEXT` and `int4` stays `int4`. All of that was
733/// measured a name at a time, and the split is not arbitrary: the standard names are the ones the
734/// grammar has rules for, and everything else is a name the parser hands to the catalog to look up
735/// later, so the text is all it has.
736///
737/// This walks the text rather than going through the type system, because the type system throws
738/// away what has to survive here. `DECIMAL(5)` and `DECIMAL` both become a width and a scale, and
739/// `VARCHAR(10)` becomes `VARCHAR`, but upstream prints back the length that was written.
740fn typename(text: &str) -> String {
741    let text = text.trim();
742    // A trailing `[]` or `[3]` is a list or an array of whatever is in front of it, and the element
743    // is resolved the same way: `int[]` is `INTEGER[]` while `int4[]` stays `int4[]`.
744    if let Some(open) = suffix(text) {
745        return typename(&text[..open]) + &text[open..];
746    }
747    let (base, arguments) = arguments(text);
748    let Some(name) = standard(base) else {
749        let base = unquote(base);
750        return match arguments {
751            Some(arguments) => format!("{}({arguments})", catalogued(&base)),
752            None => catalogued(&base),
753        };
754    };
755    match (name, arguments) {
756        // `STRUCT(a bool)` and `UNION(a int)` are a name and a type each, and the name keeps the
757        // case it was written in while the type goes round again.
758        ("STRUCT" | "UNION", Some(inside)) => {
759            let written: Vec<String> = pieces(inside).iter().map(|piece| field(piece)).collect();
760            format!("{name}({})", written.join(", "))
761        }
762        ("MAP", Some(inside)) => {
763            let written: Vec<String> = pieces(inside).iter().map(|piece| typename(piece)).collect();
764            format!("{name}({})", written.join(", "))
765        }
766        // The width and the scale of a decimal and the length of a string survive, because upstream
767        // prints the modifiers it was given rather than the ones the type ended up with.
768        ("DECIMAL" | "VARCHAR", Some(inside)) => {
769            format!("{name}({})", pieces(inside).join(", "))
770        }
771        // And everything else drops them, because they chose the type rather than sitting on it.
772        // `float(10)` is a `FLOAT` and there is nothing left of the ten.
773        _ => name.to_string(),
774    }
775}
776
777/// A type name the grammar has no rule for, which is a name for the catalog to look up later.
778///
779/// Written back as it stands, with the case it was written in and with no quotes, because all the
780/// parser has is the text. `bool`, `TEXT`, `int4`, `timestamptz` and `Mixed` all come back exactly
781/// as they went in, which was measured a name at a time.
782///
783/// `json` is the one exception in the whole list and it comes back quoted. That is not a rule about
784/// json, it is what happens to a name the parser resolves on its own rather than leaving for the
785/// catalog: the type it lands on carries the written name as its label, and a label is written back
786/// through the identifier rule, which quotes a keyword. `json` is the only name that is both a
787/// keyword and one of those, so it is the only one where the difference shows. The case that was
788/// written survives it, so `JSON` is `"JSON"` and `json` is `"json"`.
789fn catalogued(base: &str) -> String {
790    if base.eq_ignore_ascii_case("json") { quoted(base) } else { base.to_string() }
791}
792
793/// A name with its quotes taken off, if it had any.
794fn unquote(base: &str) -> String {
795    match base.strip_prefix('"').and_then(|rest| rest.strip_suffix('"')) {
796        Some(inside) => inside.replace("\"\"", "\""),
797        None => base.to_string(),
798    }
799}
800
801/// Where the trailing `[]` or `[3]` of a list or an array type starts, if there is one.
802fn suffix(text: &str) -> Option<usize> {
803    let rest = text.strip_suffix(']')?;
804    let open = rest.rfind('[')?;
805    rest[open + 1..].bytes().all(|byte| byte.is_ascii_digit()).then_some(open)
806}
807
808/// A type split into the name and whatever was in the parentheses after it.
809fn arguments(text: &str) -> (&str, Option<&str>) {
810    let Some(rest) = text.strip_suffix(')') else {
811        return (text, None);
812    };
813    let mut depth = 0usize;
814    for (at, byte) in rest.bytes().enumerate() {
815        match byte {
816            b'(' if depth == 0 => depth = 1,
817            b'(' => depth += 1,
818            b')' => depth -= 1,
819            _ => continue,
820        }
821        if depth == 1 && byte == b'(' {
822            return (rest[..at].trim(), Some(rest[at + 1..].trim()));
823        }
824    }
825    (text, None)
826}
827
828/// The entries of an argument list, split on the commas that are not inside anything.
829fn pieces(inside: &str) -> Vec<&str> {
830    let mut found = Vec::new();
831    let (mut depth, mut quoted, mut start) = (0usize, false, 0usize);
832    for (at, byte) in inside.bytes().enumerate() {
833        match byte {
834            b'"' => quoted = !quoted,
835            b'(' | b'[' if !quoted => depth += 1,
836            b')' | b']' if !quoted => depth = depth.saturating_sub(1),
837            b',' if !quoted && depth == 0 => {
838                found.push(inside[start..at].trim());
839                start = at + 1;
840            }
841            _ => {}
842        }
843    }
844    found.push(inside[start..].trim());
845    found
846}
847
848/// One field of a `STRUCT` or a `UNION`, which is a name and then a type.
849fn field(piece: &str) -> String {
850    let mut quoting = false;
851    for (at, byte) in piece.bytes().enumerate() {
852        match byte {
853            b'"' => quoting = !quoting,
854            byte if byte.is_ascii_whitespace() && !quoting => {
855                let name = piece[..at].trim();
856                let name =
857                    if name.starts_with('"') { quoted(&unquote(name)) } else { name.to_string() };
858                return format!("{name} {}", typename(&piece[at + 1..]));
859            }
860            _ => {}
861        }
862    }
863    piece.to_string()
864}
865
866/// The one name a type the SQL standard spells is written back under, and nothing for any other.
867///
868/// Several words for the ones the standard writes with several. The list is short because it is the
869/// standard's list and not DuckDB's: `HUGEINT`, `TEXT`, `BLOB` and the rest of the names DuckDB adds
870/// are not in here, and they are the ones that come back exactly as they were written.
871fn standard(base: &str) -> Option<&'static str> {
872    const NAMES: &[(&str, &str)] = &[
873        ("BOOLEAN", "BOOLEAN"),
874        ("INT", "INTEGER"),
875        ("INTEGER", "INTEGER"),
876        ("SMALLINT", "SMALLINT"),
877        ("BIGINT", "BIGINT"),
878        ("DEC", "DECIMAL"),
879        ("DECIMAL", "DECIMAL"),
880        ("NUMERIC", "DECIMAL"),
881        ("REAL", "FLOAT"),
882        ("FLOAT", "FLOAT"),
883        ("DOUBLE PRECISION", "DOUBLE"),
884        ("CHAR", "VARCHAR"),
885        ("CHARACTER", "VARCHAR"),
886        ("CHARACTER VARYING", "VARCHAR"),
887        ("NATIONAL CHARACTER", "VARCHAR"),
888        ("NATIONAL CHARACTER VARYING", "VARCHAR"),
889        ("VARCHAR", "VARCHAR"),
890        ("BIT", "BIT"),
891        ("DATE", "DATE"),
892        ("TIME", "TIME"),
893        ("TIME WITH TIME ZONE", "TIME WITH TIME ZONE"),
894        ("TIME WITHOUT TIME ZONE", "TIME"),
895        ("TIMESTAMP", "TIMESTAMP"),
896        ("TIMESTAMP WITH TIME ZONE", "TIMESTAMP WITH TIME ZONE"),
897        ("TIMESTAMP WITHOUT TIME ZONE", "TIMESTAMP"),
898        ("INTERVAL", "INTERVAL"),
899        ("STRUCT", "STRUCT"),
900        ("UNION", "UNION"),
901        ("MAP", "MAP"),
902    ];
903    let written: Vec<&str> = base.split_whitespace().collect();
904    let written = written.join(" ");
905    NAMES
906        .iter()
907        .find(|(spelling, _)| spelling.eq_ignore_ascii_case(&written))
908        .map(|(_, name)| *name)
909}
910
911/// A run of expressions, comma separated.
912fn exprs(ast: &Ast, list: Slice) -> String {
913    let written: Vec<String> = ast.expr_list(list).iter().map(|&item| expr(ast, item)).collect();
914    written.join(", ")
915}
916
917/// A run of identifiers, comma separated, each quoted if it has to be.
918fn names(ast: &Ast, list: Slice) -> String {
919    ast.name(list).map(quoted).collect::<Vec<_>>().join(", ")
920}
921
922/// A dotted name, each part quoted if it has to be.
923fn parts(ast: &Ast, list: Slice) -> String {
924    ast.name(list).map(quoted).collect::<Vec<_>>().join(".")
925}
926
927#[cfg(test)]
928mod tests {
929    use super::create_view;
930    use crate::ast::Statement;
931    use crate::transform::parse_ast;
932
933    /// The whole statement, deparsed.
934    fn whole(sql: &str) -> String {
935        let ast = parse_ast(sql).unwrap_or_else(|error| panic!("{sql} should parse: {error}"));
936        let Statement::CreateView(index) = ast.statements[0] else {
937            panic!("that was not a create view");
938        };
939        create_view(&ast, index)
940    }
941
942    /// Just the body, which is what most of these are about.
943    fn body(query: &str) -> String {
944        let written = whole(&format!("CREATE VIEW v AS {query}"));
945        written
946            .strip_prefix("CREATE VIEW v AS ")
947            .and_then(|rest| rest.strip_suffix(';'))
948            .expect("the statement wrapper is there")
949            .to_string()
950    }
951
952    #[test]
953    fn a_statement_loses_its_qualification_and_its_or_replace() {
954        assert_eq!(whole("CREATE VIEW main.v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
955        assert_eq!(whole("CREATE OR REPLACE VIEW v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
956        assert_eq!(whole("CREATE VIEW IF NOT EXISTS v AS SELECT 1"), "CREATE VIEW v AS SELECT 1;");
957        assert_eq!(whole("CREATE TEMP VIEW v AS SELECT 1"), "CREATE TEMP VIEW v AS SELECT 1;");
958    }
959
960    /// A space before the parenthesis here, and none in a `CREATE TABLE`. Both measured.
961    #[test]
962    fn an_alias_list_is_written_with_a_space_in_front_of_it() {
963        assert_eq!(
964            whole(r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2"#),
965            r#"CREATE VIEW v ("Weird Name", "x y") AS SELECT 1, 2;"#
966        );
967    }
968
969    #[test]
970    fn comments_and_spacing_go_and_the_case_of_a_name_stays() {
971        assert_eq!(
972            whole("CREATE VIEW v AS SELECT  X /* a note */ FROM   T"),
973            "CREATE VIEW v AS SELECT X FROM T;"
974        );
975    }
976
977    #[test]
978    fn every_binary_operation_is_parenthesised_and_every_unary_one_parenthesises_its_operand() {
979        assert_eq!(body("SELECT x + y * 2 - 1 FROM t"), "SELECT ((x + (y * 2)) - 1) FROM t");
980        assert_eq!(
981            body("SELECT x > 1 AND y < 2 OR b FROM t"),
982            "SELECT (((x > 1) AND (y < 2)) OR b) FROM t"
983        );
984        assert_eq!(body("SELECT NOT b FROM t"), "SELECT (NOT b) FROM t");
985        assert_eq!(body("SELECT ~x FROM t"), "SELECT ~(x) FROM t");
986        assert_eq!(body("SELECT +x FROM t"), "SELECT +(x) FROM t");
987        assert_eq!(body("SELECT -x FROM t"), "SELECT -(x) FROM t");
988    }
989
990    /// A minus in front of a number is part of the number, and it folds as many times as it is
991    /// written. A plus is not part of one and does not fold.
992    #[test]
993    fn a_minus_in_front_of_a_constant_folds_into_it() {
994        assert_eq!(body("SELECT -1"), "SELECT -1");
995        assert_eq!(body("SELECT - -3"), "SELECT 3");
996        assert_eq!(body("SELECT +3"), "SELECT +(3)");
997    }
998
999    #[test]
1000    fn the_null_tests_and_the_boolean_tests() {
1001        assert_eq!(body("SELECT x IS NULL FROM t"), "SELECT (x IS NULL) FROM t");
1002        assert_eq!(body("SELECT x ISNULL FROM t"), "SELECT (x IS NULL) FROM t");
1003        assert_eq!(body("SELECT x NOTNULL FROM t"), "SELECT (x IS NOT NULL) FROM t");
1004        assert_eq!(
1005            body("SELECT b IS TRUE FROM t"),
1006            "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM true) FROM t"
1007        );
1008        assert_eq!(
1009            body("SELECT b IS NOT TRUE FROM t"),
1010            "SELECT (CAST(b AS BOOLEAN) IS DISTINCT FROM true) FROM t"
1011        );
1012        assert_eq!(
1013            body("SELECT b IS FALSE FROM t"),
1014            "SELECT (CAST(b AS BOOLEAN) IS NOT DISTINCT FROM false) FROM t"
1015        );
1016        assert_eq!(body("SELECT b IS UNKNOWN FROM t"), "SELECT (b IS NULL) FROM t");
1017        assert_eq!(body("SELECT b IS NOT UNKNOWN FROM t"), "SELECT (b IS NOT NULL) FROM t");
1018        assert_eq!(
1019            body("SELECT x IS DISTINCT FROM y FROM t"),
1020            "SELECT (x IS DISTINCT FROM y) FROM t"
1021        );
1022    }
1023
1024    #[test]
1025    fn a_negated_between_or_in_is_a_not_around_the_plain_one() {
1026        assert_eq!(body("SELECT x BETWEEN 1 AND 10 FROM t"), "SELECT (x BETWEEN 1 AND 10) FROM t");
1027        assert_eq!(
1028            body("SELECT x NOT BETWEEN 1 AND 2 FROM t"),
1029            "SELECT (NOT (x BETWEEN 1 AND 2)) FROM t"
1030        );
1031        assert_eq!(body("SELECT x IN (1, 2, 3) FROM t"), "SELECT (x IN (1, 2, 3)) FROM t");
1032        assert_eq!(body("SELECT x NOT IN (1, 2) FROM t"), "SELECT (NOT (x IN (1, 2))) FROM t");
1033        assert_eq!(body("SELECT x IN (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1034        assert_eq!(
1035            body("SELECT x NOT IN (SELECT y FROM t)"),
1036            "SELECT (NOT (x = ANY(SELECT y FROM t)))"
1037        );
1038        assert_eq!(body("SELECT x = ANY (SELECT y FROM t)"), "SELECT (x = ANY(SELECT y FROM t))");
1039        assert_eq!(
1040            body("SELECT x > ALL (SELECT y FROM t)"),
1041            "SELECT (NOT (x <= ANY(SELECT y FROM t)))"
1042        );
1043    }
1044
1045    /// The four pattern operators have a word spelling and a symbol spelling, and the symbol is
1046    /// what comes back either way.
1047    #[test]
1048    fn the_pattern_operators_come_back_as_symbols() {
1049        assert_eq!(body("SELECT s LIKE 'a' FROM t"), "SELECT (s ~~ 'a') FROM t");
1050        assert_eq!(body("SELECT s NOT LIKE 'a' FROM t"), "SELECT (s !~~ 'a') FROM t");
1051        assert_eq!(body("SELECT s ILIKE 'a' FROM t"), "SELECT (s ~~* 'a') FROM t");
1052        assert_eq!(body("SELECT s NOT ILIKE 'a' FROM t"), "SELECT (s !~~* 'a') FROM t");
1053        assert_eq!(body("SELECT s GLOB 'a' FROM t"), "SELECT (s ~~~ 'a') FROM t");
1054        assert_eq!(body("SELECT s !~ 'a' FROM t"), "SELECT (s !~ 'a') FROM t");
1055        assert_eq!(
1056            body("SELECT s NOT SIMILAR TO 'a' FROM t"),
1057            "SELECT (NOT regexp_full_match(s, 'a')) FROM t"
1058        );
1059    }
1060
1061    #[test]
1062    fn collate_has_no_parentheses_and_the_rest_of_the_operators_keep_their_spelling() {
1063        assert_eq!(body("SELECT s COLLATE NOCASE FROM t"), "SELECT s COLLATE NOCASE FROM t");
1064        assert_eq!(body("SELECT x // y FROM t"), "SELECT (x // y) FROM t");
1065        assert_eq!(body("SELECT x || y FROM t"), "SELECT (x || y) FROM t");
1066        assert_eq!(body("SELECT x @> y FROM t"), "SELECT (x @> y) FROM t");
1067        assert_eq!(body("SELECT x <=> y FROM t"), "SELECT (x <=> y) FROM t");
1068    }
1069
1070    /// Always searched, always with an `ELSE`, and two spaces after the keyword.
1071    #[test]
1072    fn a_case_is_written_the_long_way_round() {
1073        assert_eq!(
1074            body("SELECT CASE WHEN x > 0 THEN 'a' WHEN x < 0 THEN 'b' ELSE 'c' END FROM t"),
1075            "SELECT CASE  WHEN ((x > 0)) THEN ('a') WHEN ((x < 0)) THEN ('b') ELSE 'c' END FROM t"
1076        );
1077        assert_eq!(
1078            body("SELECT CASE x WHEN 1 THEN 'a' END FROM t"),
1079            "SELECT CASE  WHEN ((x = 1)) THEN ('a') ELSE NULL END FROM t"
1080        );
1081    }
1082
1083    #[test]
1084    fn a_cast_writes_its_type_in_upper_case_with_a_space_after_the_comma() {
1085        assert_eq!(body("SELECT x::varchar FROM t"), "SELECT CAST(x AS VARCHAR) FROM t");
1086        assert_eq!(
1087            body("SELECT cast(x as decimal(4,1)) FROM t"),
1088            "SELECT CAST(x AS DECIMAL(4, 1)) FROM t"
1089        );
1090        assert_eq!(
1091            body("SELECT TRY_CAST(s AS INTEGER) FROM t"),
1092            "SELECT TRY_CAST(s AS INTEGER) FROM t"
1093        );
1094    }
1095
1096    /// The names the grammar has a rule for, which come back under the one name the type has.
1097    #[test]
1098    fn a_standard_type_name_is_resolved_and_the_modifiers_it_was_written_with_survive() {
1099        let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1100        assert_eq!(cast("int"), "SELECT CAST(x AS INTEGER)");
1101        assert_eq!(cast("numeric(5)"), "SELECT CAST(x AS DECIMAL(5))");
1102        assert_eq!(cast("decimal"), "SELECT CAST(x AS DECIMAL)");
1103        assert_eq!(cast("varchar(10)"), "SELECT CAST(x AS VARCHAR(10))");
1104        assert_eq!(cast("national character(2)"), "SELECT CAST(x AS VARCHAR(2))");
1105        // The argument of a float chose the type rather than sitting on it, so there is nothing
1106        // left of the ten by the time it is written back.
1107        assert_eq!(cast("float(10)"), "SELECT CAST(x AS FLOAT)");
1108        assert_eq!(cast("real"), "SELECT CAST(x AS FLOAT)");
1109        assert_eq!(cast("double precision"), "SELECT CAST(x AS DOUBLE)");
1110        assert_eq!(cast("time with time zone"), "SELECT CAST(x AS TIME WITH TIME ZONE)");
1111        assert_eq!(cast("int[]"), "SELECT CAST(x AS INTEGER[])");
1112        assert_eq!(cast("int[2][3]"), "SELECT CAST(x AS INTEGER[2][3])");
1113        assert_eq!(cast("map(int, varchar)"), "SELECT CAST(x AS MAP(INTEGER, VARCHAR))");
1114        assert_eq!(cast("union(a int)"), "SELECT CAST(x AS UNION(a INTEGER))");
1115    }
1116
1117    /// A struct keeps the case of the field name and resolves the field type.
1118    #[test]
1119    fn a_struct_field_keeps_its_name_and_its_type_goes_round_again() {
1120        assert_eq!(body("SELECT CAST(x AS struct(a bool))"), "SELECT CAST(x AS STRUCT(a bool))");
1121        assert_eq!(
1122            body("SELECT CAST(x AS struct(\"A b\" int))"),
1123            "SELECT CAST(x AS STRUCT(\"A b\" INTEGER))"
1124        );
1125    }
1126
1127    /// And every other name is the catalog's business, so the text is written back as it stands.
1128    #[test]
1129    fn a_type_name_the_grammar_has_no_rule_for_keeps_the_case_it_was_written_in() {
1130        let cast = |written: &str| body(&format!("SELECT CAST(x AS {written})"));
1131        assert_eq!(cast("text"), "SELECT CAST(x AS text)");
1132        assert_eq!(cast("TEXT"), "SELECT CAST(x AS TEXT)");
1133        assert_eq!(cast("DOUBLE"), "SELECT CAST(x AS DOUBLE)");
1134        assert_eq!(cast("bool"), "SELECT CAST(x AS bool)");
1135        assert_eq!(cast("\"bool\""), "SELECT CAST(x AS bool)");
1136        assert_eq!(cast("int4[]"), "SELECT CAST(x AS int4[])");
1137        assert_eq!(cast("TIMESTAMPTZ"), "SELECT CAST(x AS TIMESTAMPTZ)");
1138        // The one name that comes back quoted, for the reason written on `catalogued`.
1139        assert_eq!(cast("JSON"), "SELECT CAST(x AS \"JSON\")");
1140        assert_eq!(cast("json"), "SELECT CAST(x AS \"json\")");
1141        assert_eq!(cast("json[]"), "SELECT CAST(x AS \"json\"[])");
1142        assert_eq!(cast("struct(a json)"), "SELECT CAST(x AS STRUCT(a \"json\"))");
1143    }
1144
1145    #[test]
1146    fn a_star_count_is_a_function_of_its_own_and_a_list_is_a_call() {
1147        assert_eq!(body("SELECT count(*) FROM t"), "SELECT count_star() FROM t");
1148        assert_eq!(body("SELECT count() FROM t"), "SELECT count_star() FROM t");
1149        assert_eq!(body("SELECT count(DISTINCT x) FROM t"), "SELECT count(DISTINCT x) FROM t");
1150        assert_eq!(body("SELECT [1, 2, 3]"), "SELECT list_value(1, 2, 3)");
1151        assert_eq!(body("SELECT []"), "SELECT list_value()");
1152    }
1153
1154    /// A function name goes through the same quoting rule an identifier does, so the ones that are
1155    /// keywords in a class come back quoted.
1156    #[test]
1157    fn a_function_name_is_quoted_when_it_is_a_keyword() {
1158        assert_eq!(body("SELECT nullif(x, 1) FROM t"), "SELECT \"nullif\"(x, 1) FROM t");
1159        assert_eq!(body("SELECT length(s) FROM t"), "SELECT length(s) FROM t");
1160    }
1161
1162    #[test]
1163    fn the_literals() {
1164        assert_eq!(body("SELECT NULL, TRUE, FALSE"), "SELECT NULL, true, false");
1165        assert_eq!(body("SELECT 1.50, .5, 1_000"), "SELECT 1.50, .5, 1000");
1166        assert_eq!(body("SELECT 'it''s'"), "SELECT 'it''s'");
1167    }
1168
1169    /// A number comes back as the value it was read as, which is three rules and not one.
1170    #[test]
1171    fn a_number_is_written_back_as_the_value_the_shape_of_it_made() {
1172        assert_eq!(body("SELECT 007, 1_000"), "SELECT 7, 1000");
1173        assert_eq!(body("SELECT 1.50, 00.5, 1., 0.0"), "SELECT 1.50, 0.5, 1, 0.0");
1174        assert_eq!(body("SELECT 1e3, 1.5e2, 1e-3, 5e-4"), "SELECT 1000.0, 150.0, 0.001, 0.0005");
1175        assert_eq!(body("SELECT 5e-5, 2.5e-5, 1e-10"), "SELECT 5e-05, 2.5e-05, 1e-10");
1176        assert_eq!(body("SELECT 1e15, 1e16, 1e100"), "SELECT 1000000000000000.0, 1e+16, 1e+100");
1177    }
1178
1179    /// The part of an `EXTRACT` is a keyword and a keyword has one spelling.
1180    #[test]
1181    fn an_extract_is_a_date_part_call_and_the_keyword_it_named_has_one_spelling() {
1182        assert_eq!(body("SELECT extract(year FROM d)"), "SELECT date_part('YEAR', d)");
1183        assert_eq!(body("SELECT extract(years FROM d)"), "SELECT date_part('YEAR', d)");
1184        assert_eq!(body("SELECT extract(seconds FROM d)"), "SELECT date_part('SECOND', d)");
1185        // Two of the thirteen are written back plural, which is a list and not a rule.
1186        assert_eq!(
1187            body("SELECT extract(millisecond FROM d)"),
1188            "SELECT date_part('MILLISECONDS', d)"
1189        );
1190        assert_eq!(
1191            body("SELECT extract(microseconds FROM d)"),
1192            "SELECT date_part('MICROSECONDS', d)"
1193        );
1194        assert_eq!(body("SELECT extract(millennia FROM d)"), "SELECT date_part('MILLENNIUM', d)");
1195        // A word the grammar does not name as a keyword is an identifier and keeps its case.
1196        assert_eq!(body("SELECT extract(epoch FROM d)"), "SELECT date_part('epoch', d)");
1197        assert_eq!(body("SELECT extract(dow FROM d)"), "SELECT date_part('dow', d)");
1198    }
1199
1200    /// The two spellings of the one operator, which is not a function name however much it looks it.
1201    #[test]
1202    fn coalesce_and_ifnull_are_one_operator_and_it_is_written_in_upper_case() {
1203        assert_eq!(body("SELECT coalesce(x, y)"), "SELECT COALESCE(x, y)");
1204        assert_eq!(body("SELECT IfNull(x, y)"), "SELECT COALESCE(x, y)");
1205        // Including the one argument form, which is not folded away.
1206        assert_eq!(body("SELECT coalesce(x)"), "SELECT COALESCE(x)");
1207        // And no other name does this, which `nullif` is the one to check against.
1208        assert_eq!(body("SELECT nullif(x, y)"), "SELECT \"nullif\"(x, y)");
1209        assert_eq!(body("SELECT greatest(x, y)"), "SELECT greatest(x, y)");
1210    }
1211
1212    #[test]
1213    fn the_modifiers_hang_off_the_query_and_not_off_the_select() {
1214        assert_eq!(body("SELECT x FROM t LIMIT 5 OFFSET 2"), "SELECT x FROM t LIMIT 5 OFFSET 2");
1215        assert_eq!(body("SELECT x FROM t LIMIT 10 PERCENT"), "SELECT x FROM t LIMIT (10) %");
1216        assert_eq!(
1217            body("SELECT x FROM t ORDER BY x ASC, y NULLS LAST"),
1218            "SELECT x FROM t ORDER BY x ASC, y NULLS LAST"
1219        );
1220        assert_eq!(body("SELECT x FROM t ORDER BY ALL"), "SELECT x FROM t ORDER BY COLUMNS(*)");
1221        assert_eq!(body("SELECT x FROM t GROUP BY ALL"), "SELECT x FROM t GROUP BY ALL");
1222        assert_eq!(
1223            body("SELECT x FROM t GROUP BY x HAVING x > 0"),
1224            "SELECT x FROM t GROUP BY x HAVING (x > 0)"
1225        );
1226        assert_eq!(
1227            body("SELECT DISTINCT ON (x) x, y FROM t"),
1228            "SELECT DISTINCT ON (x) x, y FROM t"
1229        );
1230    }
1231
1232    /// A branch that is a set operation of its own is written bare, and a bare left branch loses the
1233    /// space that would follow it. Upstream's, and reproduced because the column is a comparison.
1234    #[test]
1235    fn a_chain_of_set_operations_loses_a_space_in_the_middle() {
1236        assert_eq!(
1237            body("SELECT x FROM t UNION ALL SELECT y FROM t"),
1238            "(SELECT x FROM t) UNION ALL (SELECT y FROM t)"
1239        );
1240        assert_eq!(
1241            body("SELECT x FROM t UNION SELECT y FROM t UNION SELECT 1"),
1242            "(SELECT x FROM t) UNION (SELECT y FROM t)UNION (SELECT 1)"
1243        );
1244        assert_eq!(
1245            body("SELECT x FROM t UNION DISTINCT SELECT y FROM t"),
1246            "(SELECT x FROM t) UNION (SELECT y FROM t)"
1247        );
1248    }
1249
1250    #[test]
1251    fn a_values_body_is_wrapped_in_a_select_that_names_it() {
1252        assert_eq!(
1253            body("VALUES (1, 'a'), (2, 'b')"),
1254            "SELECT * FROM (VALUES (1, 'a'), (2, 'b')) AS valueslist"
1255        );
1256    }
1257
1258    /// A space before each comma, which is upstream's and is not a typo here.
1259    #[test]
1260    fn a_from_list_has_a_space_before_the_comma() {
1261        assert_eq!(body("SELECT 1 FROM t AS t1, t AS t2"), "SELECT 1 FROM t AS t1 , t AS t2");
1262    }
1263
1264    #[test]
1265    fn a_from_item_and_its_aliases() {
1266        assert_eq!(body("SELECT 1 FROM t AS r(n)"), "SELECT 1 FROM t AS r(n)");
1267        assert_eq!(body("SELECT 1 FROM main.t"), "SELECT 1 FROM main.t");
1268        assert_eq!(
1269            body("SELECT 1 FROM (SELECT x FROM t) AS sub"),
1270            "SELECT 1 FROM (SELECT x FROM t) AS sub"
1271        );
1272        assert_eq!(body("SELECT 1 FROM range(10)"), "SELECT 1 FROM \"range\"(10)");
1273    }
1274
1275    /// Joins are parenthesised, `FULL OUTER` loses a word, `NATURAL` gains one, and an `ON` gets a
1276    /// second pair of parentheses on top of the ones the condition already has.
1277    #[test]
1278    fn a_join_is_parenthesised_and_so_is_its_condition_twice() {
1279        assert_eq!(
1280            body("SELECT 1 FROM t AS a JOIN t AS b ON a.x = b.y"),
1281            "SELECT 1 FROM (t AS a INNER JOIN t AS b ON ((a.x = b.y)))"
1282        );
1283        assert_eq!(
1284            body("SELECT 1 FROM t LEFT JOIN t AS u USING (x)"),
1285            "SELECT 1 FROM (t LEFT JOIN t AS u USING (x))"
1286        );
1287        assert_eq!(
1288            body("SELECT 1 FROM t CROSS JOIN t AS u"),
1289            "SELECT 1 FROM (t CROSS JOIN t AS u)"
1290        );
1291        assert_eq!(
1292            body("SELECT 1 FROM t FULL OUTER JOIN t AS u ON t.x = u.x"),
1293            "SELECT 1 FROM (t FULL JOIN t AS u ON ((t.x = u.x)))"
1294        );
1295        assert_eq!(
1296            body("SELECT 1 FROM t NATURAL JOIN t AS u"),
1297            "SELECT 1 FROM (t NATURAL INNER JOIN t AS u)"
1298        );
1299        assert_eq!(
1300            body("SELECT 1 FROM t POSITIONAL JOIN t AS u"),
1301            "SELECT 1 FROM (t POSITIONAL JOIN t AS u)"
1302        );
1303    }
1304
1305    #[test]
1306    fn a_target_keeps_its_alias_and_a_star_keeps_its_replace_list() {
1307        assert_eq!(body("SELECT 1 + 2 AS \"quoted alias\""), "SELECT (1 + 2) AS \"quoted alias\"");
1308        assert_eq!(body("SELECT x AS \"select\" FROM t"), "SELECT x AS \"select\" FROM t");
1309        assert_eq!(body("SELECT t.* FROM t"), "SELECT t.* FROM t");
1310        assert_eq!(
1311            body("SELECT * REPLACE (x + 1 AS x) FROM t"),
1312            "SELECT * REPLACE ((x + 1) AS x) FROM t"
1313        );
1314    }
1315
1316    #[test]
1317    fn a_describe_gets_parentheses_round_what_it_describes() {
1318        assert_eq!(body("DESCRIBE SELECT 1"), "DESCRIBE (SELECT 1)");
1319    }
1320
1321    /// Every string on the right was read out of `duckdb_views()` on the pin for the query on the
1322    /// left, which is also the column name the same window gets when the target has no alias.
1323    #[test]
1324    fn a_window_is_written_with_the_parts_that_were_written_in_it() {
1325        assert_eq!(
1326            body("SELECT row_number() OVER () AS n FROM t"),
1327            "SELECT row_number() OVER () AS n FROM t"
1328        );
1329        assert_eq!(
1330            body(
1331                "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1332            ),
1333            "SELECT row_number() OVER (PARTITION BY a ORDER BY b DESC NULLS FIRST) AS n FROM t"
1334        );
1335        assert_eq!(
1336            body(
1337                "SELECT sum(i) OVER (PARTITION BY i, i+1 ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1338            ),
1339            "SELECT sum(i) OVER (PARTITION BY i, (i + 1) ORDER BY i ASC NULLS LAST, i DESC) AS n FROM t"
1340        );
1341        assert_eq!(
1342            body("SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"),
1343            "SELECT sum(DISTINCT i) OVER (ORDER BY i) AS n FROM t"
1344        );
1345        assert_eq!(
1346            body("SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"),
1347            "SELECT first_value(i IGNORE NULLS) OVER (ORDER BY i) AS n FROM t"
1348        );
1349        assert_eq!(
1350            body("SELECT first_value(i RESPECT NULLS) OVER (ORDER BY i) AS n FROM t"),
1351            "SELECT first_value(i) OVER (ORDER BY i) AS n FROM t"
1352        );
1353        assert_eq!(
1354            body("SELECT count(*) OVER () AS n FROM t"),
1355            "SELECT count() OVER () AS n FROM t"
1356        );
1357        assert_eq!(
1358            body("SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"),
1359            "SELECT main.sum(i) OVER (ORDER BY i) AS n FROM t"
1360        );
1361    }
1362
1363    /// The default frame is not written, and neither is the `RANGE` that was written in its place.
1364    #[test]
1365    fn a_frame_is_written_only_when_it_is_not_the_one_that_was_assumed() {
1366        assert_eq!(
1367            body(
1368                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1369            ),
1370            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1371        );
1372        assert_eq!(
1373            body("SELECT sum(i) OVER (ORDER BY i RANGE UNBOUNDED PRECEDING) AS n FROM t"),
1374            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1375        );
1376        assert_eq!(
1377            body(
1378                "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE NO OTHERS) AS n FROM t"
1379            ),
1380            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1381        );
1382        assert_eq!(
1383            body("SELECT sum(i) OVER (ORDER BY i ROWS CURRENT ROW) AS n FROM t"),
1384            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND CURRENT ROW) AS n FROM t"
1385        );
1386        assert_eq!(
1387            body(
1388                "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1+1) PRECEDING AND CURRENT ROW) AS n FROM t"
1389            ),
1390            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN (1 + 1) PRECEDING AND CURRENT ROW) AS n FROM t"
1391        );
1392        assert_eq!(
1393            body(
1394                "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1395            ),
1396            "SELECT sum(i) OVER (ORDER BY i GROUPS BETWEEN CURRENT ROW AND 2 FOLLOWING EXCLUDE CURRENT ROW) AS n FROM t"
1397        );
1398        assert_eq!(
1399            body(
1400                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1401            ),
1402            "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW EXCLUDE GROUP) AS n FROM t"
1403        );
1404        assert_eq!(
1405            body(
1406                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1407            ),
1408            "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) AS n FROM t"
1409        );
1410    }
1411
1412    /// A frame over the whole partition measures the same however it says it does, and the three
1413    /// spellings come back as the one upstream picks.
1414    #[test]
1415    fn a_frame_that_covers_the_partition_is_written_as_a_row_count() {
1416        for unit in ["ROWS", "RANGE", "GROUPS"] {
1417            assert_eq!(
1418                body(&format!(
1419                    "SELECT sum(i) OVER (ORDER BY i {unit} BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1420                )),
1421                "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS n FROM t"
1422            );
1423        }
1424        assert_eq!(
1425            body(
1426                "SELECT sum(i) OVER (ORDER BY i RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1427            ),
1428            "SELECT sum(i) OVER (ORDER BY i ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING EXCLUDE TIES) AS n FROM t"
1429        );
1430    }
1431
1432    /// A named window is gone by the time anything is written back out, which is upstream's answer
1433    /// as well: the `WINDOW` clause does not survive a round trip through the catalog there.
1434    #[test]
1435    fn a_named_window_is_written_out_where_it_was_used() {
1436        assert_eq!(
1437            body("SELECT sum(i) OVER w AS n FROM t WINDOW w AS (PARTITION BY i ORDER BY i)"),
1438            "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1439        );
1440        assert_eq!(
1441            body("SELECT sum(i) OVER (w) AS n FROM t WINDOW w AS (ORDER BY i)"),
1442            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1443        );
1444        assert_eq!(
1445            body(
1446                "SELECT sum(i) OVER (w ROWS UNBOUNDED PRECEDING) AS n FROM t WINDOW w AS (PARTITION BY i)"
1447            ),
1448            "SELECT sum(i) OVER (PARTITION BY i ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS n FROM t"
1449        );
1450        assert_eq!(
1451            body("SELECT sum(i) OVER (w PARTITION BY i) AS n FROM t WINDOW w AS (ORDER BY i)"),
1452            "SELECT sum(i) OVER (PARTITION BY i ORDER BY i) AS n FROM t"
1453        );
1454        assert_eq!(
1455            body("SELECT sum(i) OVER v AS n FROM t WINDOW w AS (ORDER BY i), v AS (w)"),
1456            "SELECT sum(i) OVER (ORDER BY i) AS n FROM t"
1457        );
1458    }
1459}