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