Skip to main content

fsqlite_ast/
display.rs

1//! SQL pretty-printing via `fmt::Display` for AST nodes.
2//!
3//! Every major AST type implements `Display` to reconstruct valid SQL text.
4//! This enables the round-trip property: `parse(sql).to_string()` should
5//! parse identically to the original.
6
7#[allow(clippy::wildcard_imports)]
8use crate::*;
9use fsqlite_types::SqliteValue;
10use smallvec::SmallVec;
11use std::fmt;
12
13// ---------------------------------------------------------------------------
14// Helper: write a comma-separated list
15// ---------------------------------------------------------------------------
16
17fn comma_list<T: fmt::Display>(f: &mut fmt::Formatter<'_>, items: &[T]) -> fmt::Result {
18    for (i, item) in items.iter().enumerate() {
19        if i > 0 {
20            f.write_str(", ")?;
21        }
22        write!(f, "{item}")?;
23    }
24    Ok(())
25}
26
27fn comma_list_fn<T>(
28    f: &mut fmt::Formatter<'_>,
29    items: &[T],
30    fmt_item: impl Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result,
31) -> fmt::Result {
32    for (i, item) in items.iter().enumerate() {
33        if i > 0 {
34            f.write_str(", ")?;
35        }
36        fmt_item(item, f)?;
37    }
38    Ok(())
39}
40
41// ---------------------------------------------------------------------------
42// Helper: quote an identifier if needed
43// ---------------------------------------------------------------------------
44
45// Keep this list in sync with `fsqlite-parser`'s `TokenKind::keyword_str`.
46const SQL_KEYWORDS: &[&str] = &[
47    "ABORT",
48    "ACTION",
49    "ADD",
50    "AFTER",
51    "ALL",
52    "ALTER",
53    "ALWAYS",
54    "ANALYZE",
55    "AND",
56    "AS",
57    "ASC",
58    "ATTACH",
59    "AUTOINCREMENT",
60    "BEFORE",
61    "BEGIN",
62    "BETWEEN",
63    "BY",
64    "CASCADE",
65    "CASE",
66    "CAST",
67    "CHECK",
68    "COLLATE",
69    "COLUMN",
70    "COMMIT",
71    "COMMITSEQ",
72    "CONCURRENT",
73    "CONFLICT",
74    "CONSTRAINT",
75    "CREATE",
76    "CROSS",
77    "CURRENT_DATE",
78    "CURRENT_TIME",
79    "CURRENT_TIMESTAMP",
80    "DATABASE",
81    "DEFAULT",
82    "DEFERRABLE",
83    "DEFERRED",
84    "DELETE",
85    "DESC",
86    "DETACH",
87    "DISTINCT",
88    "DO",
89    "DROP",
90    "EACH",
91    "ELSE",
92    "END",
93    "ESCAPE",
94    "EXCEPT",
95    "EXCLUDE",
96    "EXCLUSIVE",
97    "EXISTS",
98    "EXPLAIN",
99    "FAIL",
100    "FALSE",
101    "FILTER",
102    "FIRST",
103    "FOLLOWING",
104    "FOR",
105    "FOREIGN",
106    "FROM",
107    "FULL",
108    "GENERATED",
109    "GLOB",
110    "GROUP",
111    "GROUPS",
112    "HAVING",
113    "IF",
114    "IGNORE",
115    "IMMEDIATE",
116    "IN",
117    "INDEX",
118    "INDEXED",
119    "INITIALLY",
120    "INNER",
121    "INSERT",
122    "INSTEAD",
123    "INTERSECT",
124    "INTO",
125    "IS",
126    "ISNULL",
127    "JOIN",
128    "KEY",
129    "LAST",
130    "LEFT",
131    "LIKE",
132    "LIMIT",
133    "MATCH",
134    "MATERIALIZED",
135    "NATURAL",
136    "NO",
137    "NOT",
138    "NOTHING",
139    "NOTNULL",
140    "NULL",
141    "NULLS",
142    "OF",
143    "OFFSET",
144    "ON",
145    "OR",
146    "ORDER",
147    "OTHERS",
148    "OUTER",
149    "OVER",
150    "PARTITION",
151    "PLAN",
152    "PRAGMA",
153    "PRECEDING",
154    "PRIMARY",
155    "QUERY",
156    "RAISE",
157    "RANGE",
158    "RECURSIVE",
159    "REFERENCES",
160    "REGEXP",
161    "REINDEX",
162    "RELEASE",
163    "RENAME",
164    "REPLACE",
165    "RESTRICT",
166    "RETURNING",
167    "RIGHT",
168    "ROLLBACK",
169    "ROW",
170    "ROWS",
171    "SAVEPOINT",
172    "SELECT",
173    "SET",
174    "STORED",
175    "STRICT",
176    "TABLE",
177    "TEMP",
178    "TEMPORARY",
179    "THEN",
180    "TIES",
181    "TO",
182    "TRANSACTION",
183    "TRIGGER",
184    "TRUE",
185    "UNBOUNDED",
186    "UNION",
187    "UNIQUE",
188    "UPDATE",
189    "USING",
190    "VACUUM",
191    "VALUES",
192    "VIEW",
193    "VIRTUAL",
194    "WHEN",
195    "WHERE",
196    "WINDOW",
197    "WITH",
198    "WITHOUT",
199];
200
201fn is_sql_keyword(name: &str) -> bool {
202    name.is_ascii()
203        && SQL_KEYWORDS
204            .iter()
205            .any(|keyword| keyword.eq_ignore_ascii_case(name))
206}
207
208/// Returns true if the name needs quoting (contains special chars or is a keyword).
209fn needs_quoting(name: &str) -> bool {
210    if name.is_empty() {
211        return true;
212    }
213    let first = name.as_bytes()[0];
214    if !(first.is_ascii_alphabetic() || first == b'_') {
215        return true;
216    }
217    name.bytes()
218        .any(|b| !(b.is_ascii_alphanumeric() || b == b'_'))
219        || is_sql_keyword(name)
220}
221
222pub fn write_ident(f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
223    if needs_quoting(name) {
224        write!(f, "\"{}\"", name.replace('"', "\"\""))
225    } else {
226        f.write_str(name)
227    }
228}
229
230/// Conditionally-quoted identifier as a `String`.
231///
232/// Quotes only when SQLite would (special characters, a keyword, or empty),
233/// matching stock's minimal `sqlite_master` rendering. The `String` form of
234/// [`write_ident`], reusing the same `needs_quoting` predicate.
235#[must_use]
236pub fn quote_ident_if_needed(name: &str) -> String {
237    if needs_quoting(name) {
238        format!("\"{}\"", name.replace('"', "\"\""))
239    } else {
240        name.to_owned()
241    }
242}
243
244pub fn write_qualified_name(
245    f: &mut fmt::Formatter<'_>,
246    name: &crate::QualifiedName,
247) -> fmt::Result {
248    if let Some(ref schema) = name.schema {
249        write_ident(f, schema)?;
250        f.write_str(".")?;
251    }
252    write_ident(f, &name.name)
253}
254
255const PREC_OR: u8 = 1;
256const PREC_AND: u8 = 3;
257const PREC_NOT: u8 = 5;
258const PREC_EQUALITY: u8 = 7;
259const PREC_COMPARISON: u8 = 9;
260const PREC_ESCAPE: u8 = 11;
261const PREC_BITWISE: u8 = 13;
262const PREC_ADD: u8 = 15;
263const PREC_MULTIPLY: u8 = 17;
264const PREC_CONCAT: u8 = 19;
265const PREC_COLLATE: u8 = 21;
266const PREC_UNARY: u8 = 23;
267const PREC_ATOM: u8 = u8::MAX;
268
269#[derive(Clone, Copy)]
270enum ExprParent {
271    Binary(BinaryOp),
272    Unary(UnaryOp),
273    Between,
274    In,
275    Like,
276    Escape,
277    Collate,
278    IsNull,
279    Json,
280}
281
282#[derive(Clone, Copy, PartialEq, Eq)]
283enum OperandSide {
284    Left,
285    Right,
286    Prefix,
287}
288
289fn binary_precedence(op: BinaryOp) -> u8 {
290    match op {
291        BinaryOp::Or => PREC_OR,
292        BinaryOp::And => PREC_AND,
293        BinaryOp::Eq | BinaryOp::Ne | BinaryOp::Is | BinaryOp::IsNot => PREC_EQUALITY,
294        BinaryOp::Lt | BinaryOp::Le | BinaryOp::Gt | BinaryOp::Ge => PREC_COMPARISON,
295        BinaryOp::BitAnd | BinaryOp::BitOr | BinaryOp::ShiftLeft | BinaryOp::ShiftRight => {
296            PREC_BITWISE
297        }
298        BinaryOp::Add | BinaryOp::Subtract => PREC_ADD,
299        BinaryOp::Multiply | BinaryOp::Divide | BinaryOp::Modulo => PREC_MULTIPLY,
300        BinaryOp::Concat => PREC_CONCAT,
301    }
302}
303
304fn parent_precedence(parent: ExprParent) -> u8 {
305    match parent {
306        ExprParent::Binary(op) => binary_precedence(op),
307        ExprParent::Unary(UnaryOp::Not) => PREC_NOT,
308        ExprParent::Unary(_) => PREC_UNARY,
309        ExprParent::Between | ExprParent::In | ExprParent::Like | ExprParent::IsNull => {
310            PREC_EQUALITY
311        }
312        ExprParent::Escape => PREC_ESCAPE,
313        ExprParent::Collate => PREC_COLLATE,
314        ExprParent::Json => PREC_CONCAT,
315    }
316}
317
318fn expr_precedence(expr: &Expr) -> u8 {
319    match expr {
320        Expr::BinaryOp { op, .. } => binary_precedence(*op),
321        Expr::UnaryOp {
322            op: UnaryOp::Not, ..
323        }
324        | Expr::Exists { not: true, .. } => PREC_NOT,
325        Expr::UnaryOp { .. } => PREC_UNARY,
326        Expr::Literal(Literal::Integer(value), _) if *value < 0 => PREC_UNARY,
327        Expr::Literal(Literal::Float(value), _) if !value.is_nan() && value.is_sign_negative() => {
328            PREC_UNARY
329        }
330        Expr::BoundOuterValue {
331            value: SqliteValue::Integer(value),
332            ..
333        } if *value < 0 => PREC_UNARY,
334        Expr::BoundOuterValue {
335            value: SqliteValue::Float(value),
336            ..
337        } if !value.is_nan() && value.is_sign_negative() => PREC_UNARY,
338        Expr::Between { .. } | Expr::In { .. } | Expr::Like { .. } | Expr::IsNull { .. } => {
339            PREC_EQUALITY
340        }
341        Expr::JsonAccess { .. } => PREC_CONCAT,
342        Expr::Collate { .. } => PREC_COLLATE,
343        Expr::Literal(..)
344        | Expr::BoundOuterValue { .. }
345        | Expr::Column(..)
346        | Expr::Case { .. }
347        | Expr::Cast { .. }
348        | Expr::Exists { .. }
349        | Expr::Subquery(..)
350        | Expr::FunctionCall { .. }
351        | Expr::Raise { .. }
352        | Expr::RowValue(..)
353        | Expr::Placeholder(..) => PREC_ATOM,
354    }
355}
356
357/// Decide whether an operator child needs grouping in its exact parent
358/// context. SQLite's infix operators are left-associative: an equal-precedence
359/// left child is already grouped correctly, while an equal-precedence right
360/// child must normally stay parenthesized. AND and OR are the only right-side
361/// exceptions because their associative chains have a deliberately flat
362/// canonical form.
363fn operand_needs_parentheses(expr: &Expr, parent: ExprParent, side: OperandSide) -> bool {
364    let child_precedence = expr_precedence(expr);
365    let parent_precedence = parent_precedence(parent);
366    if child_precedence != parent_precedence {
367        return child_precedence < parent_precedence;
368    }
369    if side == OperandSide::Left {
370        return false;
371    }
372    if side == OperandSide::Right {
373        return !matches!(
374            (parent, expr),
375            (
376                ExprParent::Binary(BinaryOp::And),
377                Expr::BinaryOp {
378                    op: BinaryOp::And,
379                    ..
380                }
381            ) | (
382                ExprParent::Binary(BinaryOp::Or),
383                Expr::BinaryOp {
384                    op: BinaryOp::Or,
385                    ..
386                }
387            )
388        );
389    }
390    true
391}
392
393enum ExprWriteTask<'a> {
394    Expr(&'a Expr),
395    Operand {
396        expr: &'a Expr,
397        parent: ExprParent,
398        side: OperandSide,
399    },
400    Statement(&'a Statement),
401    Select(&'a SelectStatement),
402    SelectBody(&'a SelectBody),
403    With(&'a WithClause),
404    Cte(&'a Cte),
405    SelectCore(&'a SelectCore),
406    ResultColumn(&'a ResultColumn),
407    From(&'a FromClause),
408    Table(&'a TableOrSubquery),
409    Join(&'a JoinClause),
410    JoinConstraint(&'a JoinConstraint),
411    WindowDef(&'a WindowDef),
412    Limit(&'a LimitClause),
413    Update(&'a UpdateStatement),
414    CreateTrigger(&'a CreateTriggerStatement),
415    Text(&'static str),
416    Ident(&'a str),
417    Literal(&'a Literal),
418    BoundOuterValue(&'a SqliteValue),
419    Column(&'a ColumnRef),
420    BinaryOp(&'a BinaryOp),
421    CompoundOp(&'a CompoundOp),
422    JoinType(&'a JoinType),
423    UnaryOp(&'a UnaryOp),
424    LikeOp(&'a LikeOp),
425    TypeName(&'a TypeName),
426    Placeholder(&'a PlaceholderType),
427    QualifiedName(&'a QualifiedName),
428    ParenthesizedSelect(&'a SelectStatement),
429    OrderingTerm(&'a OrderingTerm),
430    Window(&'a WindowSpec),
431    Frame(&'a FrameSpec),
432    FrameBound(&'a FrameBound),
433}
434
435const INLINE_EXPR_WRITE_TASKS: usize = 32;
436
437struct ExprWriteTaskStack<'a> {
438    tasks: SmallVec<[ExprWriteTask<'a>; INLINE_EXPR_WRITE_TASKS]>,
439    #[cfg(test)]
440    peak_len: usize,
441    #[cfg(test)]
442    ever_spilled: bool,
443}
444
445impl<'a> ExprWriteTaskStack<'a> {
446    fn new(task: ExprWriteTask<'a>) -> Self {
447        let mut tasks = SmallVec::new();
448        tasks.push(task);
449        Self {
450            tasks,
451            #[cfg(test)]
452            peak_len: 1,
453            #[cfg(test)]
454            ever_spilled: false,
455        }
456    }
457
458    fn push(&mut self, task: ExprWriteTask<'a>) {
459        self.tasks.push(task);
460        #[cfg(test)]
461        {
462            self.peak_len = self.peak_len.max(self.tasks.len());
463            self.ever_spilled |= self.tasks.spilled();
464        }
465    }
466
467    fn pop(&mut self) -> Option<ExprWriteTask<'a>> {
468        self.tasks.pop()
469    }
470
471    #[cfg(test)]
472    fn stats(&self) -> ExprWriteTaskStackStats {
473        ExprWriteTaskStackStats {
474            peak_len: self.peak_len,
475            spilled: self.ever_spilled,
476        }
477    }
478}
479
480#[cfg(test)]
481#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
482struct ExprWriteTaskStackStats {
483    peak_len: usize,
484    spilled: bool,
485}
486
487#[cfg(test)]
488std::thread_local! {
489    static LAST_EXPR_WRITE_TASK_STACK_STATS: std::cell::Cell<ExprWriteTaskStackStats> =
490        const { std::cell::Cell::new(ExprWriteTaskStackStats { peak_len: 0, spilled: false }) };
491}
492
493fn push_comma_separated_exprs<'a>(tasks: &mut ExprWriteTaskStack<'a>, exprs: &'a [Expr]) {
494    for (index, expr) in exprs.iter().enumerate().rev() {
495        tasks.push(ExprWriteTask::Expr(expr));
496        if index > 0 {
497            tasks.push(ExprWriteTask::Text(", "));
498        }
499    }
500}
501
502fn push_comma_separated_ordering_terms<'a>(
503    tasks: &mut ExprWriteTaskStack<'a>,
504    terms: &'a [OrderingTerm],
505) {
506    for (index, term) in terms.iter().enumerate().rev() {
507        tasks.push(ExprWriteTask::OrderingTerm(term));
508        if index > 0 {
509            tasks.push(ExprWriteTask::Text(", "));
510        }
511    }
512}
513
514fn push_comma_separated_result_columns<'a>(
515    tasks: &mut ExprWriteTaskStack<'a>,
516    columns: &'a [ResultColumn],
517) {
518    for (index, column) in columns.iter().enumerate().rev() {
519        tasks.push(ExprWriteTask::ResultColumn(column));
520        if index > 0 {
521            tasks.push(ExprWriteTask::Text(", "));
522        }
523    }
524}
525
526fn push_comma_separated_ctes<'a>(tasks: &mut ExprWriteTaskStack<'a>, ctes: &'a [Cte]) {
527    for (index, cte) in ctes.iter().enumerate().rev() {
528        tasks.push(ExprWriteTask::Cte(cte));
529        if index > 0 {
530            tasks.push(ExprWriteTask::Text(", "));
531        }
532    }
533}
534
535fn push_comma_separated_window_defs<'a>(
536    tasks: &mut ExprWriteTaskStack<'a>,
537    windows: &'a [WindowDef],
538) {
539    for (index, window) in windows.iter().enumerate().rev() {
540        tasks.push(ExprWriteTask::WindowDef(window));
541        if index > 0 {
542            tasks.push(ExprWriteTask::Text(", "));
543        }
544    }
545}
546
547#[allow(clippy::too_many_lines)]
548fn write_expr(f: &mut fmt::Formatter<'_>, root: &Expr) -> fmt::Result {
549    write_expr_tasks(f, ExprWriteTask::Expr(root))
550}
551
552fn write_select(f: &mut fmt::Formatter<'_>, root: &SelectStatement) -> fmt::Result {
553    write_expr_tasks(f, ExprWriteTask::Select(root))
554}
555
556fn write_select_body(f: &mut fmt::Formatter<'_>, root: &SelectBody) -> fmt::Result {
557    write_expr_tasks(f, ExprWriteTask::SelectBody(root))
558}
559
560fn write_select_core(f: &mut fmt::Formatter<'_>, root: &SelectCore) -> fmt::Result {
561    write_expr_tasks(f, ExprWriteTask::SelectCore(root))
562}
563
564fn write_from(f: &mut fmt::Formatter<'_>, root: &FromClause) -> fmt::Result {
565    write_expr_tasks(f, ExprWriteTask::From(root))
566}
567
568fn write_table(f: &mut fmt::Formatter<'_>, root: &TableOrSubquery) -> fmt::Result {
569    write_expr_tasks(f, ExprWriteTask::Table(root))
570}
571
572fn write_join(f: &mut fmt::Formatter<'_>, root: &JoinClause) -> fmt::Result {
573    write_expr_tasks(f, ExprWriteTask::Join(root))
574}
575
576fn write_update(f: &mut fmt::Formatter<'_>, root: &UpdateStatement) -> fmt::Result {
577    write_expr_tasks(f, ExprWriteTask::Update(root))
578}
579
580fn write_create_trigger(f: &mut fmt::Formatter<'_>, root: &CreateTriggerStatement) -> fmt::Result {
581    write_expr_tasks(f, ExprWriteTask::CreateTrigger(root))
582}
583
584fn write_statement(f: &mut fmt::Formatter<'_>, root: &Statement) -> fmt::Result {
585    write_expr_tasks(f, ExprWriteTask::Statement(root))
586}
587
588#[allow(clippy::too_many_lines)]
589fn write_expr_tasks(f: &mut fmt::Formatter<'_>, root: ExprWriteTask<'_>) -> fmt::Result {
590    #[cfg(test)]
591    LAST_EXPR_WRITE_TASK_STACK_STATS.set(ExprWriteTaskStackStats::default());
592    let mut tasks = ExprWriteTaskStack::new(root);
593    while let Some(task) = tasks.pop() {
594        match task {
595            ExprWriteTask::Text(text) => f.write_str(text)?,
596            ExprWriteTask::Ident(name) => write_ident(f, name)?,
597            ExprWriteTask::Literal(literal) => write!(f, "{literal}")?,
598            ExprWriteTask::BoundOuterValue(value) => write_sqlite_value_literal(f, value)?,
599            ExprWriteTask::Column(column) => write!(f, "{column}")?,
600            ExprWriteTask::BinaryOp(op) => write!(f, "{op}")?,
601            ExprWriteTask::CompoundOp(op) => write!(f, "{op}")?,
602            ExprWriteTask::JoinType(join_type) => write!(f, "{join_type}")?,
603            ExprWriteTask::UnaryOp(op) => write!(f, "{op}")?,
604            ExprWriteTask::LikeOp(op) => write!(f, "{op}")?,
605            ExprWriteTask::TypeName(type_name) => write!(f, "{type_name}")?,
606            ExprWriteTask::Placeholder(placeholder) => write!(f, "{placeholder}")?,
607            ExprWriteTask::QualifiedName(name) => write!(f, "{name}")?,
608            ExprWriteTask::ParenthesizedSelect(select) => {
609                tasks.push(ExprWriteTask::Text(")"));
610                tasks.push(ExprWriteTask::Select(select));
611                tasks.push(ExprWriteTask::Text("("));
612            }
613            ExprWriteTask::Operand { expr, parent, side } => {
614                if operand_needs_parentheses(expr, parent, side) {
615                    tasks.push(ExprWriteTask::Text(")"));
616                    tasks.push(ExprWriteTask::Expr(expr));
617                    tasks.push(ExprWriteTask::Text("("));
618                } else {
619                    tasks.push(ExprWriteTask::Expr(expr));
620                }
621            }
622            ExprWriteTask::Statement(statement) => match statement {
623                Statement::Select(select) => tasks.push(ExprWriteTask::Select(select)),
624                Statement::Update(update) => tasks.push(ExprWriteTask::Update(update)),
625                Statement::CreateTrigger(trigger) => {
626                    tasks.push(ExprWriteTask::CreateTrigger(trigger));
627                }
628                Statement::Explain { query_plan, stmt } => {
629                    tasks.push(ExprWriteTask::Statement(stmt));
630                    if *query_plan {
631                        tasks.push(ExprWriteTask::Text("EXPLAIN QUERY PLAN "));
632                    } else {
633                        tasks.push(ExprWriteTask::Text("EXPLAIN "));
634                    }
635                }
636                Statement::Insert(insert) => write!(f, "{insert}")?,
637                Statement::Delete(delete) => write!(f, "{delete}")?,
638                Statement::CreateTable(create) => write!(f, "{create}")?,
639                Statement::CreateIndex(create) => write!(f, "{create}")?,
640                Statement::CreateView(create) => write!(f, "{create}")?,
641                Statement::CreateVirtualTable(create) => write!(f, "{create}")?,
642                Statement::Drop(drop) => write!(f, "{drop}")?,
643                Statement::AlterTable(alter) => write!(f, "{alter}")?,
644                Statement::Begin(begin) => write!(f, "{begin}")?,
645                Statement::Commit => f.write_str("COMMIT")?,
646                Statement::Rollback(rollback) => write!(f, "{rollback}")?,
647                Statement::Savepoint(name) => {
648                    f.write_str("SAVEPOINT ")?;
649                    write_ident(f, name)?;
650                }
651                Statement::Release(name) => {
652                    f.write_str("RELEASE ")?;
653                    write_ident(f, name)?;
654                }
655                Statement::Attach(attach) => write!(f, "{attach}")?,
656                Statement::Detach(schema) => {
657                    f.write_str("DETACH ")?;
658                    write_ident(f, schema)?;
659                }
660                Statement::Pragma(pragma) => write!(f, "{pragma}")?,
661                Statement::Vacuum(vacuum) => write!(f, "{vacuum}")?,
662                Statement::Reindex(None) => f.write_str("REINDEX")?,
663                Statement::Reindex(Some(name)) => write!(f, "REINDEX {name}")?,
664                Statement::Analyze(None) => f.write_str("ANALYZE")?,
665                Statement::Analyze(Some(name)) => write!(f, "ANALYZE {name}")?,
666            },
667            ExprWriteTask::Select(select) => {
668                if let Some(limit) = &select.limit {
669                    tasks.push(ExprWriteTask::Limit(limit));
670                    tasks.push(ExprWriteTask::Text(" "));
671                }
672                if !select.order_by.is_empty() {
673                    push_comma_separated_ordering_terms(&mut tasks, &select.order_by);
674                    tasks.push(ExprWriteTask::Text(" ORDER BY "));
675                }
676                for (op, core) in select.body.compounds.iter().rev() {
677                    tasks.push(ExprWriteTask::SelectCore(core));
678                    tasks.push(ExprWriteTask::Text(" "));
679                    tasks.push(ExprWriteTask::CompoundOp(op));
680                    tasks.push(ExprWriteTask::Text(" "));
681                }
682                tasks.push(ExprWriteTask::SelectCore(&select.body.select));
683                if let Some(with) = &select.with {
684                    tasks.push(ExprWriteTask::Text(" "));
685                    tasks.push(ExprWriteTask::With(with));
686                }
687            }
688            ExprWriteTask::SelectBody(body) => {
689                for (op, core) in body.compounds.iter().rev() {
690                    tasks.push(ExprWriteTask::SelectCore(core));
691                    tasks.push(ExprWriteTask::Text(" "));
692                    tasks.push(ExprWriteTask::CompoundOp(op));
693                    tasks.push(ExprWriteTask::Text(" "));
694                }
695                tasks.push(ExprWriteTask::SelectCore(&body.select));
696            }
697            ExprWriteTask::With(with) => {
698                push_comma_separated_ctes(&mut tasks, &with.ctes);
699                if with.recursive {
700                    tasks.push(ExprWriteTask::Text("WITH RECURSIVE "));
701                } else {
702                    tasks.push(ExprWriteTask::Text("WITH "));
703                }
704            }
705            ExprWriteTask::Cte(cte) => {
706                tasks.push(ExprWriteTask::Text(")"));
707                tasks.push(ExprWriteTask::Select(&cte.query));
708                tasks.push(ExprWriteTask::Text("("));
709                match cte.materialized {
710                    Some(CteMaterialized::Materialized) => {
711                        tasks.push(ExprWriteTask::Text("MATERIALIZED "));
712                    }
713                    Some(CteMaterialized::NotMaterialized) => {
714                        tasks.push(ExprWriteTask::Text("NOT MATERIALIZED "));
715                    }
716                    None => {}
717                }
718                tasks.push(ExprWriteTask::Text(" AS "));
719                if !cte.columns.is_empty() {
720                    tasks.push(ExprWriteTask::Text(")"));
721                    for (index, column) in cte.columns.iter().enumerate().rev() {
722                        tasks.push(ExprWriteTask::Ident(column));
723                        if index > 0 {
724                            tasks.push(ExprWriteTask::Text(", "));
725                        }
726                    }
727                    tasks.push(ExprWriteTask::Text("("));
728                }
729                tasks.push(ExprWriteTask::Ident(&cte.name));
730            }
731            ExprWriteTask::SelectCore(core) => match core {
732                SelectCore::Select {
733                    distinct,
734                    columns,
735                    from,
736                    where_clause,
737                    group_by,
738                    having,
739                    windows,
740                } => {
741                    if !windows.is_empty() {
742                        push_comma_separated_window_defs(&mut tasks, windows);
743                        tasks.push(ExprWriteTask::Text(" WINDOW "));
744                    }
745                    if let Some(having) = having {
746                        tasks.push(ExprWriteTask::Expr(having));
747                        tasks.push(ExprWriteTask::Text(" HAVING "));
748                    }
749                    if !group_by.is_empty() {
750                        push_comma_separated_exprs(&mut tasks, group_by);
751                        tasks.push(ExprWriteTask::Text(" GROUP BY "));
752                    }
753                    if let Some(where_clause) = where_clause {
754                        tasks.push(ExprWriteTask::Expr(where_clause));
755                        tasks.push(ExprWriteTask::Text(" WHERE "));
756                    }
757                    if let Some(from) = from {
758                        tasks.push(ExprWriteTask::From(from));
759                        tasks.push(ExprWriteTask::Text(" FROM "));
760                    }
761                    push_comma_separated_result_columns(&mut tasks, columns);
762                    if *distinct == Distinctness::Distinct {
763                        tasks.push(ExprWriteTask::Text("SELECT DISTINCT "));
764                    } else {
765                        tasks.push(ExprWriteTask::Text("SELECT "));
766                    }
767                }
768                SelectCore::Values(rows) => {
769                    for (row_index, row) in rows.iter().enumerate().rev() {
770                        tasks.push(ExprWriteTask::Text(")"));
771                        push_comma_separated_exprs(&mut tasks, row);
772                        tasks.push(ExprWriteTask::Text("("));
773                        if row_index > 0 {
774                            tasks.push(ExprWriteTask::Text(", "));
775                        }
776                    }
777                    tasks.push(ExprWriteTask::Text("VALUES "));
778                }
779            },
780            ExprWriteTask::ResultColumn(column) => match column {
781                ResultColumn::Star => tasks.push(ExprWriteTask::Text("*")),
782                ResultColumn::TableStar(name) => {
783                    tasks.push(ExprWriteTask::Text(".*"));
784                    tasks.push(ExprWriteTask::QualifiedName(name));
785                }
786                ResultColumn::Expr { expr, alias } => {
787                    if let Some(alias) = alias {
788                        tasks.push(ExprWriteTask::Ident(alias));
789                        tasks.push(ExprWriteTask::Text(" AS "));
790                    }
791                    tasks.push(ExprWriteTask::Expr(expr));
792                }
793            },
794            ExprWriteTask::From(from) => {
795                for join in from.joins.iter().rev() {
796                    tasks.push(ExprWriteTask::Join(join));
797                    tasks.push(ExprWriteTask::Text(" "));
798                }
799                tasks.push(ExprWriteTask::Table(&from.source));
800            }
801            ExprWriteTask::Table(table) => match table {
802                TableOrSubquery::Table {
803                    name,
804                    alias,
805                    index_hint,
806                    time_travel,
807                } => {
808                    if let Some(time_travel) = time_travel {
809                        write!(f, "{name}")?;
810                        if let Some(alias) = alias {
811                            f.write_str(" AS ")?;
812                            write_ident(f, alias)?;
813                        }
814                        if let Some(index_hint) = index_hint {
815                            write!(f, " {index_hint}")?;
816                        }
817                        write!(f, " {time_travel}")?;
818                    } else {
819                        if let Some(index_hint) = index_hint {
820                            match index_hint {
821                                IndexHint::IndexedBy(name) => {
822                                    tasks.push(ExprWriteTask::Ident(name));
823                                    tasks.push(ExprWriteTask::Text(" INDEXED BY "));
824                                }
825                                IndexHint::NotIndexed => {
826                                    tasks.push(ExprWriteTask::Text(" NOT INDEXED"));
827                                }
828                            }
829                        }
830                        if let Some(alias) = alias {
831                            tasks.push(ExprWriteTask::Ident(alias));
832                            tasks.push(ExprWriteTask::Text(" AS "));
833                        }
834                        tasks.push(ExprWriteTask::QualifiedName(name));
835                    }
836                }
837                TableOrSubquery::Subquery { query, alias } => {
838                    if let Some(alias) = alias {
839                        tasks.push(ExprWriteTask::Ident(alias));
840                        tasks.push(ExprWriteTask::Text(" AS "));
841                    }
842                    tasks.push(ExprWriteTask::Text(")"));
843                    tasks.push(ExprWriteTask::Select(query));
844                    tasks.push(ExprWriteTask::Text("("));
845                }
846                TableOrSubquery::TableFunction { name, args, alias } => {
847                    if let Some(alias) = alias {
848                        tasks.push(ExprWriteTask::Ident(alias));
849                        tasks.push(ExprWriteTask::Text(" AS "));
850                    }
851                    tasks.push(ExprWriteTask::Text(")"));
852                    push_comma_separated_exprs(&mut tasks, args);
853                    tasks.push(ExprWriteTask::Text("("));
854                    tasks.push(ExprWriteTask::Ident(name));
855                }
856                TableOrSubquery::ParenJoin(inner) => {
857                    tasks.push(ExprWriteTask::Text(")"));
858                    tasks.push(ExprWriteTask::From(inner));
859                    tasks.push(ExprWriteTask::Text("("));
860                }
861            },
862            ExprWriteTask::Join(join) => {
863                if let Some(constraint) = &join.constraint {
864                    tasks.push(ExprWriteTask::JoinConstraint(constraint));
865                    tasks.push(ExprWriteTask::Text(" "));
866                }
867                tasks.push(ExprWriteTask::Table(&join.table));
868                tasks.push(ExprWriteTask::Text(" "));
869                tasks.push(ExprWriteTask::JoinType(&join.join_type));
870            }
871            ExprWriteTask::JoinConstraint(constraint) => match constraint {
872                JoinConstraint::On(expr) => {
873                    tasks.push(ExprWriteTask::Expr(expr));
874                    tasks.push(ExprWriteTask::Text("ON "));
875                }
876                JoinConstraint::Using(columns) => {
877                    tasks.push(ExprWriteTask::Text(")"));
878                    for (index, column) in columns.iter().enumerate().rev() {
879                        tasks.push(ExprWriteTask::Ident(column));
880                        if index > 0 {
881                            tasks.push(ExprWriteTask::Text(", "));
882                        }
883                    }
884                    tasks.push(ExprWriteTask::Text("USING ("));
885                }
886            },
887            ExprWriteTask::WindowDef(window) => {
888                tasks.push(ExprWriteTask::Window(&window.spec));
889                tasks.push(ExprWriteTask::Text(" AS "));
890                tasks.push(ExprWriteTask::Ident(&window.name));
891            }
892            ExprWriteTask::Limit(limit) => {
893                if let Some(offset) = &limit.offset {
894                    tasks.push(ExprWriteTask::Expr(offset));
895                    tasks.push(ExprWriteTask::Text(" OFFSET "));
896                }
897                tasks.push(ExprWriteTask::Expr(&limit.limit));
898                tasks.push(ExprWriteTask::Text("LIMIT "));
899            }
900            ExprWriteTask::Update(update) => {
901                if let Some(limit) = &update.limit {
902                    tasks.push(ExprWriteTask::Limit(limit));
903                    tasks.push(ExprWriteTask::Text(" "));
904                }
905                if !update.order_by.is_empty() {
906                    push_comma_separated_ordering_terms(&mut tasks, &update.order_by);
907                    tasks.push(ExprWriteTask::Text(" ORDER BY "));
908                }
909                if !update.returning.is_empty() {
910                    push_comma_separated_result_columns(&mut tasks, &update.returning);
911                    tasks.push(ExprWriteTask::Text(" RETURNING "));
912                }
913                if let Some(where_clause) = &update.where_clause {
914                    tasks.push(ExprWriteTask::Expr(where_clause));
915                    tasks.push(ExprWriteTask::Text(" WHERE "));
916                }
917                if let Some(from) = &update.from {
918                    tasks.push(ExprWriteTask::From(from));
919                    tasks.push(ExprWriteTask::Text(" FROM "));
920                }
921
922                if let Some(with) = &update.with {
923                    write!(f, "{with} ")?;
924                }
925                f.write_str("UPDATE")?;
926                if let Some(action) = &update.or_conflict {
927                    write!(f, " OR {action}")?;
928                }
929                write!(f, " {} SET ", update.table)?;
930                comma_list(f, &update.assignments)?;
931            }
932            ExprWriteTask::CreateTrigger(trigger) => {
933                tasks.push(ExprWriteTask::Text("END"));
934                for statement in trigger.body.iter().rev() {
935                    tasks.push(ExprWriteTask::Text("; "));
936                    tasks.push(ExprWriteTask::Statement(statement));
937                }
938
939                f.write_str("CREATE ")?;
940                if trigger.temporary {
941                    f.write_str("TEMP ")?;
942                }
943                f.write_str("TRIGGER ")?;
944                if trigger.if_not_exists {
945                    f.write_str("IF NOT EXISTS ")?;
946                }
947                write!(
948                    f,
949                    "{} {} {} ON ",
950                    trigger.name, trigger.timing, trigger.event
951                )?;
952                write_ident(f, &trigger.table)?;
953                if trigger.for_each_row {
954                    f.write_str(" FOR EACH ROW")?;
955                }
956                if let Some(when) = &trigger.when {
957                    write!(f, " WHEN {when}")?;
958                }
959                f.write_str(" BEGIN ")?;
960            }
961            ExprWriteTask::OrderingTerm(term) => {
962                if let Some(nulls) = term.nulls {
963                    match nulls {
964                        NullsOrder::First => tasks.push(ExprWriteTask::Text(" NULLS FIRST")),
965                        NullsOrder::Last => tasks.push(ExprWriteTask::Text(" NULLS LAST")),
966                    }
967                }
968                if let Some(direction) = term.direction {
969                    match direction {
970                        SortDirection::Asc => tasks.push(ExprWriteTask::Text(" ASC")),
971                        SortDirection::Desc => tasks.push(ExprWriteTask::Text(" DESC")),
972                    }
973                }
974                tasks.push(ExprWriteTask::Expr(&term.expr));
975            }
976            ExprWriteTask::Window(window) => {
977                let has_base = window.window_ref.is_some();
978                let has_partition = !window.partition_by.is_empty();
979                let has_order = !window.order_by.is_empty();
980                tasks.push(ExprWriteTask::Text(")"));
981                if let Some(frame) = &window.frame {
982                    tasks.push(ExprWriteTask::Frame(frame));
983                    if has_base || has_partition || has_order {
984                        tasks.push(ExprWriteTask::Text(" "));
985                    }
986                }
987                if has_order {
988                    push_comma_separated_ordering_terms(&mut tasks, &window.order_by);
989                    tasks.push(ExprWriteTask::Text("ORDER BY "));
990                    if has_base || has_partition {
991                        tasks.push(ExprWriteTask::Text(" "));
992                    }
993                }
994                if has_partition {
995                    push_comma_separated_exprs(&mut tasks, &window.partition_by);
996                    tasks.push(ExprWriteTask::Text("PARTITION BY "));
997                    if has_base {
998                        tasks.push(ExprWriteTask::Text(" "));
999                    }
1000                }
1001                if let Some(window_ref) = &window.window_ref {
1002                    tasks.push(ExprWriteTask::Ident(window_ref.name()));
1003                }
1004                tasks.push(ExprWriteTask::Text("("));
1005            }
1006            ExprWriteTask::Frame(frame) => {
1007                if let Some(exclude) = frame.exclude {
1008                    match exclude {
1009                        FrameExclude::NoOthers => {
1010                            tasks.push(ExprWriteTask::Text(" EXCLUDE NO OTHERS"));
1011                        }
1012                        FrameExclude::CurrentRow => {
1013                            tasks.push(ExprWriteTask::Text(" EXCLUDE CURRENT ROW"));
1014                        }
1015                        FrameExclude::Group => {
1016                            tasks.push(ExprWriteTask::Text(" EXCLUDE GROUP"));
1017                        }
1018                        FrameExclude::Ties => {
1019                            tasks.push(ExprWriteTask::Text(" EXCLUDE TIES"));
1020                        }
1021                    }
1022                }
1023                if let Some(end) = &frame.end {
1024                    tasks.push(ExprWriteTask::FrameBound(end));
1025                    tasks.push(ExprWriteTask::Text(" AND "));
1026                    tasks.push(ExprWriteTask::FrameBound(&frame.start));
1027                    tasks.push(ExprWriteTask::Text(" BETWEEN "));
1028                } else {
1029                    tasks.push(ExprWriteTask::FrameBound(&frame.start));
1030                    tasks.push(ExprWriteTask::Text(" "));
1031                }
1032                match frame.frame_type {
1033                    FrameType::Rows => tasks.push(ExprWriteTask::Text("ROWS")),
1034                    FrameType::Range => tasks.push(ExprWriteTask::Text("RANGE")),
1035                    FrameType::Groups => tasks.push(ExprWriteTask::Text("GROUPS")),
1036                }
1037            }
1038            ExprWriteTask::FrameBound(bound) => match bound {
1039                FrameBound::UnboundedPreceding => {
1040                    tasks.push(ExprWriteTask::Text("UNBOUNDED PRECEDING"));
1041                }
1042                FrameBound::Preceding(expr) => {
1043                    tasks.push(ExprWriteTask::Text(" PRECEDING"));
1044                    tasks.push(ExprWriteTask::Expr(expr));
1045                }
1046                FrameBound::CurrentRow => tasks.push(ExprWriteTask::Text("CURRENT ROW")),
1047                FrameBound::Following(expr) => {
1048                    tasks.push(ExprWriteTask::Text(" FOLLOWING"));
1049                    tasks.push(ExprWriteTask::Expr(expr));
1050                }
1051                FrameBound::UnboundedFollowing => {
1052                    tasks.push(ExprWriteTask::Text("UNBOUNDED FOLLOWING"));
1053                }
1054            },
1055            ExprWriteTask::Expr(expr) => match expr {
1056                Expr::Literal(literal, _) => tasks.push(ExprWriteTask::Literal(literal)),
1057                Expr::BoundOuterValue { value, .. } => {
1058                    tasks.push(ExprWriteTask::BoundOuterValue(value));
1059                }
1060                Expr::Column(column, _) => tasks.push(ExprWriteTask::Column(column)),
1061                Expr::BinaryOp {
1062                    left, op, right, ..
1063                } => {
1064                    tasks.push(ExprWriteTask::Operand {
1065                        expr: right,
1066                        parent: ExprParent::Binary(*op),
1067                        side: OperandSide::Right,
1068                    });
1069                    tasks.push(ExprWriteTask::Text(" "));
1070                    tasks.push(ExprWriteTask::BinaryOp(op));
1071                    tasks.push(ExprWriteTask::Text(" "));
1072                    tasks.push(ExprWriteTask::Operand {
1073                        expr: left,
1074                        parent: ExprParent::Binary(*op),
1075                        side: OperandSide::Left,
1076                    });
1077                }
1078                Expr::UnaryOp { op, expr, .. } => {
1079                    tasks.push(ExprWriteTask::Operand {
1080                        expr,
1081                        parent: ExprParent::Unary(*op),
1082                        side: OperandSide::Prefix,
1083                    });
1084                    if matches!(op, UnaryOp::Not) {
1085                        tasks.push(ExprWriteTask::Text("NOT "));
1086                    } else {
1087                        tasks.push(ExprWriteTask::UnaryOp(op));
1088                    }
1089                }
1090                Expr::Between {
1091                    expr,
1092                    low,
1093                    high,
1094                    not,
1095                    ..
1096                } => {
1097                    tasks.push(ExprWriteTask::Operand {
1098                        expr: high,
1099                        parent: ExprParent::Between,
1100                        side: OperandSide::Right,
1101                    });
1102                    tasks.push(ExprWriteTask::Text(" AND "));
1103                    tasks.push(ExprWriteTask::Operand {
1104                        expr: low,
1105                        parent: ExprParent::Between,
1106                        side: OperandSide::Right,
1107                    });
1108                    tasks.push(ExprWriteTask::Text(" BETWEEN "));
1109                    if *not {
1110                        tasks.push(ExprWriteTask::Text(" NOT"));
1111                    }
1112                    tasks.push(ExprWriteTask::Operand {
1113                        expr,
1114                        parent: ExprParent::Between,
1115                        side: OperandSide::Left,
1116                    });
1117                }
1118                Expr::In { expr, set, not, .. } => {
1119                    match set {
1120                        InSet::List(items) => {
1121                            tasks.push(ExprWriteTask::Text(")"));
1122                            push_comma_separated_exprs(&mut tasks, items);
1123                            tasks.push(ExprWriteTask::Text("("));
1124                        }
1125                        InSet::Subquery(select) => {
1126                            tasks.push(ExprWriteTask::ParenthesizedSelect(select));
1127                        }
1128                        InSet::Table(name) => tasks.push(ExprWriteTask::QualifiedName(name)),
1129                    }
1130                    tasks.push(ExprWriteTask::Text(" IN "));
1131                    if *not {
1132                        tasks.push(ExprWriteTask::Text(" NOT"));
1133                    }
1134                    tasks.push(ExprWriteTask::Operand {
1135                        expr,
1136                        parent: ExprParent::In,
1137                        side: OperandSide::Left,
1138                    });
1139                }
1140                Expr::Like {
1141                    expr,
1142                    pattern,
1143                    escape,
1144                    op,
1145                    not,
1146                    ..
1147                } => {
1148                    if let Some(escape) = escape {
1149                        tasks.push(ExprWriteTask::Operand {
1150                            expr: escape,
1151                            parent: ExprParent::Escape,
1152                            side: OperandSide::Right,
1153                        });
1154                        tasks.push(ExprWriteTask::Text(" ESCAPE "));
1155                    }
1156                    tasks.push(ExprWriteTask::Operand {
1157                        expr: pattern,
1158                        parent: ExprParent::Like,
1159                        side: OperandSide::Right,
1160                    });
1161                    tasks.push(ExprWriteTask::Text(" "));
1162                    tasks.push(ExprWriteTask::LikeOp(op));
1163                    tasks.push(ExprWriteTask::Text(" "));
1164                    if *not {
1165                        tasks.push(ExprWriteTask::Text(" NOT"));
1166                    }
1167                    tasks.push(ExprWriteTask::Operand {
1168                        expr,
1169                        parent: ExprParent::Like,
1170                        side: OperandSide::Left,
1171                    });
1172                }
1173                Expr::Case {
1174                    operand,
1175                    whens,
1176                    else_expr,
1177                    ..
1178                } => {
1179                    tasks.push(ExprWriteTask::Text(" END"));
1180                    if let Some(else_expr) = else_expr {
1181                        tasks.push(ExprWriteTask::Expr(else_expr));
1182                        tasks.push(ExprWriteTask::Text(" ELSE "));
1183                    }
1184                    for (condition, result) in whens.iter().rev() {
1185                        tasks.push(ExprWriteTask::Expr(result));
1186                        tasks.push(ExprWriteTask::Text(" THEN "));
1187                        tasks.push(ExprWriteTask::Expr(condition));
1188                        tasks.push(ExprWriteTask::Text(" WHEN "));
1189                    }
1190                    if let Some(operand) = operand {
1191                        tasks.push(ExprWriteTask::Expr(operand));
1192                        tasks.push(ExprWriteTask::Text(" "));
1193                    }
1194                    tasks.push(ExprWriteTask::Text("CASE"));
1195                }
1196                Expr::Cast {
1197                    expr, type_name, ..
1198                } => {
1199                    tasks.push(ExprWriteTask::Text(")"));
1200                    tasks.push(ExprWriteTask::TypeName(type_name));
1201                    tasks.push(ExprWriteTask::Text(" AS "));
1202                    tasks.push(ExprWriteTask::Expr(expr));
1203                    tasks.push(ExprWriteTask::Text("CAST("));
1204                }
1205                Expr::Exists { subquery, not, .. } => {
1206                    tasks.push(ExprWriteTask::Text(")"));
1207                    tasks.push(ExprWriteTask::Select(subquery));
1208                    if *not {
1209                        tasks.push(ExprWriteTask::Text("NOT EXISTS ("));
1210                    } else {
1211                        tasks.push(ExprWriteTask::Text("EXISTS ("));
1212                    }
1213                }
1214                Expr::Subquery(select, _) => {
1215                    tasks.push(ExprWriteTask::Text(")"));
1216                    tasks.push(ExprWriteTask::Select(select));
1217                    tasks.push(ExprWriteTask::Text("("));
1218                }
1219                Expr::FunctionCall {
1220                    name,
1221                    args,
1222                    distinct,
1223                    order_by,
1224                    filter,
1225                    over,
1226                    ..
1227                } => {
1228                    if let Some(window) = over {
1229                        match &window.window_ref {
1230                            Some(WindowReference::Direct(name))
1231                                if window.partition_by.is_empty()
1232                                    && window.order_by.is_empty()
1233                                    && window.frame.is_none() =>
1234                            {
1235                                tasks.push(ExprWriteTask::Ident(name));
1236                            }
1237                            _ => tasks.push(ExprWriteTask::Window(window)),
1238                        }
1239                        tasks.push(ExprWriteTask::Text(" OVER "));
1240                    }
1241                    if let Some(filter) = filter {
1242                        tasks.push(ExprWriteTask::Text(")"));
1243                        tasks.push(ExprWriteTask::Expr(filter));
1244                        tasks.push(ExprWriteTask::Text(" FILTER (WHERE "));
1245                    }
1246                    tasks.push(ExprWriteTask::Text(")"));
1247                    if !order_by.is_empty() {
1248                        push_comma_separated_ordering_terms(&mut tasks, order_by);
1249                        tasks.push(ExprWriteTask::Text(" ORDER BY "));
1250                    }
1251                    match args {
1252                        FunctionArgs::Star => tasks.push(ExprWriteTask::Text("*")),
1253                        FunctionArgs::List(items) => {
1254                            push_comma_separated_exprs(&mut tasks, items);
1255                        }
1256                    }
1257                    if *distinct {
1258                        tasks.push(ExprWriteTask::Text("DISTINCT "));
1259                    }
1260                    tasks.push(ExprWriteTask::Text("("));
1261                    tasks.push(ExprWriteTask::Ident(name));
1262                }
1263                Expr::Collate {
1264                    expr, collation, ..
1265                } => {
1266                    tasks.push(ExprWriteTask::Ident(collation));
1267                    tasks.push(ExprWriteTask::Text(" COLLATE "));
1268                    tasks.push(ExprWriteTask::Operand {
1269                        expr,
1270                        parent: ExprParent::Collate,
1271                        side: OperandSide::Left,
1272                    });
1273                }
1274                Expr::IsNull { expr, not, .. } => {
1275                    if *not {
1276                        tasks.push(ExprWriteTask::Text(" IS NOT NULL"));
1277                    } else {
1278                        tasks.push(ExprWriteTask::Text(" IS NULL"));
1279                    }
1280                    tasks.push(ExprWriteTask::Operand {
1281                        expr,
1282                        parent: ExprParent::IsNull,
1283                        side: OperandSide::Left,
1284                    });
1285                }
1286                Expr::Raise {
1287                    action, message, ..
1288                } => {
1289                    write!(f, "RAISE({action}")?;
1290                    if let Some(message) = message {
1291                        write!(f, ", '{}'", message.replace('\'', "''"))?;
1292                    }
1293                    f.write_str(")")?;
1294                }
1295                Expr::JsonAccess {
1296                    expr, path, arrow, ..
1297                } => {
1298                    tasks.push(ExprWriteTask::Operand {
1299                        expr: path,
1300                        parent: ExprParent::Json,
1301                        side: OperandSide::Right,
1302                    });
1303                    match arrow {
1304                        JsonArrow::Arrow => tasks.push(ExprWriteTask::Text(" -> ")),
1305                        JsonArrow::DoubleArrow => tasks.push(ExprWriteTask::Text(" ->> ")),
1306                    }
1307                    tasks.push(ExprWriteTask::Operand {
1308                        expr,
1309                        parent: ExprParent::Json,
1310                        side: OperandSide::Left,
1311                    });
1312                }
1313                Expr::RowValue(exprs, _) => {
1314                    tasks.push(ExprWriteTask::Text(")"));
1315                    push_comma_separated_exprs(&mut tasks, exprs);
1316                    tasks.push(ExprWriteTask::Text("("));
1317                }
1318                Expr::Placeholder(placeholder, _) => {
1319                    tasks.push(ExprWriteTask::Placeholder(placeholder));
1320                }
1321            },
1322        }
1323    }
1324    #[cfg(test)]
1325    LAST_EXPR_WRITE_TASK_STACK_STATS.set(tasks.stats());
1326    Ok(())
1327}
1328
1329// ---------------------------------------------------------------------------
1330// Literal
1331// ---------------------------------------------------------------------------
1332
1333fn write_sqlite_float_literal(f: &mut fmt::Formatter<'_>, value: f64) -> fmt::Result {
1334    if value.is_nan() {
1335        // SQLite never surfaces NaN as a REAL value. Arithmetic and register
1336        // writes normalize it to SQL NULL.
1337        f.write_str("NULL")
1338    } else if value.is_infinite() {
1339        if value.is_sign_negative() {
1340            f.write_str("-9e999")
1341        } else {
1342            f.write_str("9e999")
1343        }
1344    // Ensure a finite integral float always has a decimal point.
1345    } else if value.fract() == 0.0 {
1346        write!(f, "{value:.1}")
1347    } else {
1348        write!(f, "{value}")
1349    }
1350}
1351
1352fn write_sql_string_literal(f: &mut fmt::Formatter<'_>, value: &str) -> fmt::Result {
1353    write!(f, "'{}'", value.replace('\'', "''"))
1354}
1355
1356fn write_sql_blob_literal(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
1357    f.write_str("X'")?;
1358    for byte in bytes {
1359        write!(f, "{byte:02X}")?;
1360    }
1361    f.write_str("'")
1362}
1363
1364fn write_sqlite_value_literal(f: &mut fmt::Formatter<'_>, value: &SqliteValue) -> fmt::Result {
1365    match value {
1366        SqliteValue::Null => f.write_str("NULL"),
1367        SqliteValue::Integer(value) => write!(f, "{value}"),
1368        SqliteValue::Float(value) => write_sqlite_float_literal(f, *value),
1369        SqliteValue::Text(value) => write_sql_string_literal(f, value.as_str()),
1370        SqliteValue::Blob(value) => write_sql_blob_literal(f, value),
1371    }
1372}
1373
1374impl fmt::Display for Literal {
1375    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1376        match self {
1377            Self::Integer(n) => write!(f, "{n}"),
1378            Self::Float(v) => write_sqlite_float_literal(f, *v),
1379            Self::String(s) => write_sql_string_literal(f, s),
1380            Self::Blob(bytes) => write_sql_blob_literal(f, bytes),
1381            Self::Null => f.write_str("NULL"),
1382            Self::True => f.write_str("TRUE"),
1383            Self::False => f.write_str("FALSE"),
1384            Self::CurrentTime => f.write_str("CURRENT_TIME"),
1385            Self::CurrentDate => f.write_str("CURRENT_DATE"),
1386            Self::CurrentTimestamp => f.write_str("CURRENT_TIMESTAMP"),
1387        }
1388    }
1389}
1390
1391// ---------------------------------------------------------------------------
1392// ColumnRef
1393// ---------------------------------------------------------------------------
1394
1395impl fmt::Display for ColumnRef {
1396    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1397        if let Some(ref s) = self.schema {
1398            write_ident(f, s)?;
1399            f.write_str(".")?;
1400        }
1401        if let Some(ref t) = self.table {
1402            write_ident(f, t)?;
1403            f.write_str(".")?;
1404        }
1405        write_ident(f, &self.column)
1406    }
1407}
1408
1409// ---------------------------------------------------------------------------
1410// TypeName
1411// ---------------------------------------------------------------------------
1412
1413impl fmt::Display for TypeName {
1414    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1415        f.write_str(&self.name)?;
1416        match (&self.arg1, &self.arg2) {
1417            (Some(a1), Some(a2)) => write!(f, "({a1}, {a2})"),
1418            (Some(a1), None) => write!(f, "({a1})"),
1419            _ => Ok(()),
1420        }
1421    }
1422}
1423
1424// ---------------------------------------------------------------------------
1425// PlaceholderType
1426// ---------------------------------------------------------------------------
1427
1428impl fmt::Display for PlaceholderType {
1429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1430        match self {
1431            Self::Anonymous => f.write_str("?"),
1432            Self::Numbered(n) => write!(f, "?{n}"),
1433            Self::ColonNamed(s) => write!(f, ":{s}"),
1434            Self::AtNamed(s) => write!(f, "@{s}"),
1435            Self::DollarNamed(s) => write!(f, "${s}"),
1436        }
1437    }
1438}
1439
1440// ---------------------------------------------------------------------------
1441// LikeOp
1442// ---------------------------------------------------------------------------
1443
1444impl fmt::Display for LikeOp {
1445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1446        f.write_str(match self {
1447            Self::Like => "LIKE",
1448            Self::Glob => "GLOB",
1449            Self::Match => "MATCH",
1450            Self::Regexp => "REGEXP",
1451        })
1452    }
1453}
1454
1455// ---------------------------------------------------------------------------
1456// RaiseAction
1457// ---------------------------------------------------------------------------
1458
1459impl fmt::Display for RaiseAction {
1460    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1461        f.write_str(match self {
1462            Self::Ignore => "IGNORE",
1463            Self::Rollback => "ROLLBACK",
1464            Self::Abort => "ABORT",
1465            Self::Fail => "FAIL",
1466        })
1467    }
1468}
1469
1470// ---------------------------------------------------------------------------
1471// Expr
1472// ---------------------------------------------------------------------------
1473
1474impl fmt::Display for Expr {
1475    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1476        write_expr(f, self)
1477    }
1478}
1479
1480// ---------------------------------------------------------------------------
1481// WindowSpec
1482// ---------------------------------------------------------------------------
1483
1484impl fmt::Display for WindowSpec {
1485    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1486        if let Some(WindowReference::Direct(name)) = &self.window_ref
1487            && self.partition_by.is_empty()
1488            && self.order_by.is_empty()
1489            && self.frame.is_none()
1490        {
1491            return write_ident(f, name);
1492        }
1493        f.write_str("(")?;
1494        let mut need_space = if let Some(window_ref) = &self.window_ref {
1495            write_ident(f, window_ref.name())?;
1496            true
1497        } else {
1498            false
1499        };
1500        if !self.partition_by.is_empty() {
1501            if need_space {
1502                f.write_str(" ")?;
1503            }
1504            f.write_str("PARTITION BY ")?;
1505            comma_list(f, &self.partition_by)?;
1506            need_space = true;
1507        }
1508        if !self.order_by.is_empty() {
1509            if need_space {
1510                f.write_str(" ")?;
1511            }
1512            f.write_str("ORDER BY ")?;
1513            comma_list(f, &self.order_by)?;
1514            need_space = true;
1515        }
1516        if let Some(frame) = &self.frame {
1517            if need_space {
1518                f.write_str(" ")?;
1519            }
1520            write!(f, "{frame}")?;
1521        }
1522        f.write_str(")")
1523    }
1524}
1525
1526// ---------------------------------------------------------------------------
1527// FrameSpec
1528// ---------------------------------------------------------------------------
1529
1530impl fmt::Display for FrameSpec {
1531    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1532        write!(f, "{}", self.frame_type)?;
1533        if let Some(ref end) = self.end {
1534            write!(f, " BETWEEN {} AND {end}", self.start)?;
1535        } else {
1536            write!(f, " {}", self.start)?;
1537        }
1538        if let Some(ref excl) = self.exclude {
1539            write!(f, " EXCLUDE {excl}")?;
1540        }
1541        Ok(())
1542    }
1543}
1544
1545impl fmt::Display for FrameType {
1546    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1547        f.write_str(match self {
1548            Self::Rows => "ROWS",
1549            Self::Range => "RANGE",
1550            Self::Groups => "GROUPS",
1551        })
1552    }
1553}
1554
1555impl fmt::Display for FrameBound {
1556    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1557        match self {
1558            Self::UnboundedPreceding => f.write_str("UNBOUNDED PRECEDING"),
1559            Self::Preceding(e) => write!(f, "{e} PRECEDING"),
1560            Self::CurrentRow => f.write_str("CURRENT ROW"),
1561            Self::Following(e) => write!(f, "{e} FOLLOWING"),
1562            Self::UnboundedFollowing => f.write_str("UNBOUNDED FOLLOWING"),
1563        }
1564    }
1565}
1566
1567impl fmt::Display for FrameExclude {
1568    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1569        f.write_str(match self {
1570            Self::NoOthers => "NO OTHERS",
1571            Self::CurrentRow => "CURRENT ROW",
1572            Self::Group => "GROUP",
1573            Self::Ties => "TIES",
1574        })
1575    }
1576}
1577
1578// ---------------------------------------------------------------------------
1579// OrderingTerm
1580// ---------------------------------------------------------------------------
1581
1582impl fmt::Display for OrderingTerm {
1583    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1584        write!(f, "{}", self.expr)?;
1585        if let Some(dir) = self.direction {
1586            write!(f, " {dir}")?;
1587        }
1588        if let Some(nulls) = self.nulls {
1589            write!(f, " {nulls}")?;
1590        }
1591        Ok(())
1592    }
1593}
1594
1595impl fmt::Display for SortDirection {
1596    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1597        f.write_str(match self {
1598            Self::Asc => "ASC",
1599            Self::Desc => "DESC",
1600        })
1601    }
1602}
1603
1604impl fmt::Display for NullsOrder {
1605    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1606        f.write_str(match self {
1607            Self::First => "NULLS FIRST",
1608            Self::Last => "NULLS LAST",
1609        })
1610    }
1611}
1612
1613// ---------------------------------------------------------------------------
1614// ResultColumn
1615// ---------------------------------------------------------------------------
1616
1617impl fmt::Display for ResultColumn {
1618    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1619        match self {
1620            Self::Star => f.write_str("*"),
1621            Self::TableStar(t) => {
1622                write_qualified_name(f, t)?;
1623                f.write_str(".*")
1624            }
1625            Self::Expr { expr, alias } => {
1626                write!(f, "{expr}")?;
1627                if let Some(a) = alias {
1628                    f.write_str(" AS ")?;
1629                    write_ident(f, a)?;
1630                }
1631                Ok(())
1632            }
1633        }
1634    }
1635}
1636
1637// ---------------------------------------------------------------------------
1638// SelectStatement
1639// ---------------------------------------------------------------------------
1640
1641impl fmt::Display for SelectStatement {
1642    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1643        write_select(f, self)
1644    }
1645}
1646
1647// ---------------------------------------------------------------------------
1648// WithClause / CTE
1649// ---------------------------------------------------------------------------
1650
1651impl fmt::Display for WithClause {
1652    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1653        f.write_str("WITH ")?;
1654        if self.recursive {
1655            f.write_str("RECURSIVE ")?;
1656        }
1657        comma_list(f, &self.ctes)
1658    }
1659}
1660
1661impl fmt::Display for Cte {
1662    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1663        write_ident(f, &self.name)?;
1664        if !self.columns.is_empty() {
1665            f.write_str("(")?;
1666            comma_list_fn(f, &self.columns, |col, f| write_ident(f, col))?;
1667            f.write_str(")")?;
1668        }
1669        f.write_str(" AS ")?;
1670        if let Some(mat) = self.materialized {
1671            write!(f, "{mat} ")?;
1672        }
1673        write!(f, "({})", self.query)
1674    }
1675}
1676
1677impl fmt::Display for CteMaterialized {
1678    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1679        f.write_str(match self {
1680            Self::Materialized => "MATERIALIZED",
1681            Self::NotMaterialized => "NOT MATERIALIZED",
1682        })
1683    }
1684}
1685
1686// ---------------------------------------------------------------------------
1687// SelectBody / SelectCore
1688// ---------------------------------------------------------------------------
1689
1690impl fmt::Display for SelectBody {
1691    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1692        write_select_body(f, self)
1693    }
1694}
1695
1696impl fmt::Display for CompoundOp {
1697    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1698        f.write_str(match self {
1699            Self::Union => "UNION",
1700            Self::UnionAll => "UNION ALL",
1701            Self::Intersect => "INTERSECT",
1702            Self::Except => "EXCEPT",
1703        })
1704    }
1705}
1706
1707impl fmt::Display for SelectCore {
1708    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1709        write_select_core(f, self)
1710    }
1711}
1712
1713// ---------------------------------------------------------------------------
1714// FromClause / TableOrSubquery
1715// ---------------------------------------------------------------------------
1716
1717impl fmt::Display for FromClause {
1718    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1719        write_from(f, self)
1720    }
1721}
1722
1723impl fmt::Display for TableOrSubquery {
1724    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1725        write_table(f, self)
1726    }
1727}
1728
1729impl fmt::Display for IndexHint {
1730    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1731        match self {
1732            Self::IndexedBy(name) => {
1733                f.write_str("INDEXED BY ")?;
1734                write_ident(f, name)
1735            }
1736            Self::NotIndexed => f.write_str("NOT INDEXED"),
1737        }
1738    }
1739}
1740
1741// ---------------------------------------------------------------------------
1742// Time-travel clause
1743// ---------------------------------------------------------------------------
1744
1745impl fmt::Display for TimeTravelClause {
1746    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1747        write!(f, "FOR SYSTEM_TIME AS OF {}", self.target)
1748    }
1749}
1750
1751impl fmt::Display for TimeTravelTarget {
1752    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1753        match self {
1754            Self::CommitSequence(seq) => write!(f, "COMMITSEQ {seq}"),
1755            Self::Timestamp(ts) => write!(f, "'{}'", ts.replace('\'', "''")),
1756        }
1757    }
1758}
1759
1760// ---------------------------------------------------------------------------
1761// JoinClause / JoinType
1762// ---------------------------------------------------------------------------
1763
1764impl fmt::Display for JoinClause {
1765    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1766        write_join(f, self)
1767    }
1768}
1769
1770impl fmt::Display for JoinType {
1771    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1772        if self.natural {
1773            f.write_str("NATURAL ")?;
1774        }
1775        write!(f, "{}", self.kind)?;
1776        f.write_str(" JOIN")
1777    }
1778}
1779
1780impl fmt::Display for JoinKind {
1781    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1782        f.write_str(match self {
1783            Self::Cross => "CROSS",
1784            Self::Inner => "INNER",
1785            Self::Left => "LEFT",
1786            Self::Right => "RIGHT",
1787            Self::Full => "FULL",
1788        })
1789    }
1790}
1791
1792impl fmt::Display for JoinConstraint {
1793    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1794        match self {
1795            Self::On(expr) => write!(f, "ON {expr}"),
1796            Self::Using(cols) => {
1797                f.write_str("USING (")?;
1798                comma_list_fn(f, cols, |col, f| write_ident(f, col))?;
1799                f.write_str(")")
1800            }
1801        }
1802    }
1803}
1804
1805// ---------------------------------------------------------------------------
1806// WindowDef
1807// ---------------------------------------------------------------------------
1808
1809impl fmt::Display for WindowDef {
1810    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1811        write_ident(f, &self.name)?;
1812        write!(f, " AS {}", self.spec)
1813    }
1814}
1815
1816// ---------------------------------------------------------------------------
1817// LimitClause
1818// ---------------------------------------------------------------------------
1819
1820impl fmt::Display for LimitClause {
1821    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1822        write!(f, "LIMIT {}", self.limit)?;
1823        if let Some(ref off) = self.offset {
1824            write!(f, " OFFSET {off}")?;
1825        }
1826        Ok(())
1827    }
1828}
1829
1830// ---------------------------------------------------------------------------
1831// ConflictAction
1832// ---------------------------------------------------------------------------
1833
1834impl fmt::Display for ConflictAction {
1835    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1836        f.write_str(match self {
1837            Self::Rollback => "ROLLBACK",
1838            Self::Abort => "ABORT",
1839            Self::Fail => "FAIL",
1840            Self::Ignore => "IGNORE",
1841            Self::Replace => "REPLACE",
1842        })
1843    }
1844}
1845
1846// ---------------------------------------------------------------------------
1847// InsertStatement
1848// ---------------------------------------------------------------------------
1849
1850impl fmt::Display for InsertStatement {
1851    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1852        if let Some(ref with) = self.with {
1853            write!(f, "{with} ")?;
1854        }
1855        if self.or_conflict == Some(ConflictAction::Replace) {
1856            f.write_str("REPLACE")?;
1857        } else {
1858            f.write_str("INSERT")?;
1859            if let Some(ref action) = self.or_conflict {
1860                write!(f, " OR {action}")?;
1861            }
1862        }
1863        write!(f, " INTO {}", self.table)?;
1864        if let Some(ref a) = self.alias {
1865            f.write_str(" AS ")?;
1866            write_ident(f, a)?;
1867        }
1868        if !self.columns.is_empty() {
1869            f.write_str(" (")?;
1870            comma_list_fn(f, &self.columns, |col, f| write_ident(f, col))?;
1871            f.write_str(")")?;
1872        }
1873        write!(f, " {}", self.source)?;
1874        for upsert in &self.upsert {
1875            write!(f, " {upsert}")?;
1876        }
1877        if !self.returning.is_empty() {
1878            f.write_str(" RETURNING ")?;
1879            comma_list(f, &self.returning)?;
1880        }
1881        Ok(())
1882    }
1883}
1884
1885impl fmt::Display for InsertSource {
1886    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1887        match self {
1888            Self::Values(rows) => {
1889                f.write_str("VALUES ")?;
1890                for (i, row) in rows.iter().enumerate() {
1891                    if i > 0 {
1892                        f.write_str(", ")?;
1893                    }
1894                    f.write_str("(")?;
1895                    comma_list(f, row)?;
1896                    f.write_str(")")?;
1897                }
1898                Ok(())
1899            }
1900            Self::Select(q) => write!(f, "{q}"),
1901            Self::DefaultValues => f.write_str("DEFAULT VALUES"),
1902        }
1903    }
1904}
1905
1906impl fmt::Display for UpsertClause {
1907    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1908        f.write_str("ON CONFLICT")?;
1909        if let Some(ref target) = self.target {
1910            f.write_str(" (")?;
1911            comma_list(f, &target.columns)?;
1912            f.write_str(")")?;
1913            if let Some(ref w) = target.where_clause {
1914                write!(f, " WHERE {w}")?;
1915            }
1916        }
1917        write!(f, " {}", self.action)
1918    }
1919}
1920
1921impl fmt::Display for IndexedColumn {
1922    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1923        write!(f, "{}", self.expr)?;
1924        if let Some(ref col) = self.collation {
1925            write!(f, " COLLATE {col}")?;
1926        }
1927        if let Some(dir) = self.direction {
1928            write!(f, " {dir}")?;
1929        }
1930        Ok(())
1931    }
1932}
1933
1934impl fmt::Display for UpsertAction {
1935    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1936        match self {
1937            Self::Nothing => f.write_str("DO NOTHING"),
1938            Self::Update {
1939                assignments,
1940                where_clause,
1941            } => {
1942                f.write_str("DO UPDATE SET ")?;
1943                comma_list(f, assignments)?;
1944                if let Some(w) = where_clause {
1945                    write!(f, " WHERE {w}")?;
1946                }
1947                Ok(())
1948            }
1949        }
1950    }
1951}
1952
1953impl fmt::Display for Assignment {
1954    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1955        write!(f, "{} = {}", self.target, self.value)
1956    }
1957}
1958
1959impl fmt::Display for AssignmentTarget {
1960    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1961        match self {
1962            Self::Column(name) => write_ident(f, name),
1963            Self::ColumnList(names) => {
1964                f.write_str("(")?;
1965                comma_list_fn(f, names, |col, f| write_ident(f, col))?;
1966                f.write_str(")")
1967            }
1968        }
1969    }
1970}
1971
1972// ---------------------------------------------------------------------------
1973// UpdateStatement
1974// ---------------------------------------------------------------------------
1975
1976impl fmt::Display for UpdateStatement {
1977    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1978        write_update(f, self)
1979    }
1980}
1981
1982impl fmt::Display for QualifiedTableRef {
1983    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1984        write!(f, "{}", self.name)?;
1985        if let Some(ref a) = self.alias {
1986            f.write_str(" AS ")?;
1987            write_ident(f, a)?;
1988        }
1989        if let Some(ref hint) = self.index_hint {
1990            write!(f, " {hint}")?;
1991        }
1992        if let Some(ref tt) = self.time_travel {
1993            write!(f, " {tt}")?;
1994        }
1995        Ok(())
1996    }
1997}
1998
1999// ---------------------------------------------------------------------------
2000// DeleteStatement
2001// ---------------------------------------------------------------------------
2002
2003impl fmt::Display for DeleteStatement {
2004    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2005        if let Some(ref with) = self.with {
2006            write!(f, "{with} ")?;
2007        }
2008        write!(f, "DELETE FROM {}", self.table)?;
2009        if let Some(ref w) = self.where_clause {
2010            write!(f, " WHERE {w}")?;
2011        }
2012        if !self.returning.is_empty() {
2013            f.write_str(" RETURNING ")?;
2014            comma_list(f, &self.returning)?;
2015        }
2016        if !self.order_by.is_empty() {
2017            f.write_str(" ORDER BY ")?;
2018            comma_list(f, &self.order_by)?;
2019        }
2020        if let Some(ref lim) = self.limit {
2021            write!(f, " {lim}")?;
2022        }
2023        Ok(())
2024    }
2025}
2026
2027// ---------------------------------------------------------------------------
2028// CreateTableStatement
2029// ---------------------------------------------------------------------------
2030
2031impl fmt::Display for CreateTableStatement {
2032    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2033        f.write_str("CREATE ")?;
2034        if self.temporary {
2035            f.write_str("TEMP ")?;
2036        }
2037        f.write_str("TABLE ")?;
2038        if self.if_not_exists {
2039            f.write_str("IF NOT EXISTS ")?;
2040        }
2041        write!(f, "{}", self.name)?;
2042        match &self.body {
2043            CreateTableBody::Columns {
2044                columns,
2045                constraints,
2046            } => {
2047                f.write_str(" (")?;
2048                for (i, col) in columns.iter().enumerate() {
2049                    if i > 0 {
2050                        f.write_str(", ")?;
2051                    }
2052                    write!(f, "{col}")?;
2053                }
2054                for constraint in constraints {
2055                    f.write_str(", ")?;
2056                    write!(f, "{constraint}")?;
2057                }
2058                f.write_str(")")?;
2059            }
2060            CreateTableBody::AsSelect(q) => {
2061                write!(f, " AS {q}")?;
2062            }
2063        }
2064        let mut table_options = Vec::new();
2065        if self.without_rowid {
2066            table_options.push("WITHOUT ROWID");
2067        }
2068        if self.strict {
2069            table_options.push("STRICT");
2070        }
2071        if !table_options.is_empty() {
2072            write!(f, " {}", table_options.join(", "))?;
2073        }
2074        Ok(())
2075    }
2076}
2077
2078impl fmt::Display for ColumnDef {
2079    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2080        write_ident(f, &self.name)?;
2081        if let Some(ref tn) = self.type_name {
2082            write!(f, " {tn}")?;
2083        }
2084        for c in &self.constraints {
2085            write!(f, " {c}")?;
2086        }
2087        Ok(())
2088    }
2089}
2090
2091impl fmt::Display for ColumnConstraint {
2092    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2093        if let Some(ref name) = self.name {
2094            f.write_str("CONSTRAINT ")?;
2095            write_ident(f, name)?;
2096            f.write_str(" ")?;
2097        }
2098        write!(f, "{}", self.kind)
2099    }
2100}
2101
2102impl fmt::Display for ColumnConstraintKind {
2103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2104        match self {
2105            Self::PrimaryKey {
2106                direction,
2107                conflict,
2108                autoincrement,
2109            } => {
2110                f.write_str("PRIMARY KEY")?;
2111                if let Some(dir) = direction {
2112                    write!(f, " {dir}")?;
2113                }
2114                if let Some(action) = conflict {
2115                    write!(f, " ON CONFLICT {action}")?;
2116                }
2117                if *autoincrement {
2118                    f.write_str(" AUTOINCREMENT")?;
2119                }
2120                Ok(())
2121            }
2122            Self::NotNull { conflict } => {
2123                f.write_str("NOT NULL")?;
2124                if let Some(action) = conflict {
2125                    write!(f, " ON CONFLICT {action}")?;
2126                }
2127                Ok(())
2128            }
2129            Self::Null => f.write_str("NULL"),
2130            Self::Unique { conflict } => {
2131                f.write_str("UNIQUE")?;
2132                if let Some(action) = conflict {
2133                    write!(f, " ON CONFLICT {action}")?;
2134                }
2135                Ok(())
2136            }
2137            Self::Check(expr) => write!(f, "CHECK ({expr})"),
2138            Self::Default(val) => {
2139                f.write_str("DEFAULT ")?;
2140                match val {
2141                    DefaultValue::Expr(e) => write!(f, "{e}"),
2142                    DefaultValue::ParenExpr(e) => write!(f, "({e})"),
2143                }
2144            }
2145            Self::Collate(name) => write!(f, "COLLATE {name}"),
2146            Self::ForeignKey(fk) => write!(f, "{fk}"),
2147            Self::Generated { expr, storage } => {
2148                write!(f, "GENERATED ALWAYS AS ({expr})")?;
2149                if let Some(s) = storage {
2150                    write!(f, " {s}")?;
2151                }
2152                Ok(())
2153            }
2154        }
2155    }
2156}
2157
2158impl fmt::Display for GeneratedStorage {
2159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2160        f.write_str(match self {
2161            Self::Stored => "STORED",
2162            Self::Virtual => "VIRTUAL",
2163        })
2164    }
2165}
2166
2167impl fmt::Display for TableConstraint {
2168    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2169        if let Some(ref name) = self.name {
2170            f.write_str("CONSTRAINT ")?;
2171            write_ident(f, name)?;
2172            f.write_str(" ")?;
2173        }
2174        write!(f, "{}", self.kind)
2175    }
2176}
2177
2178impl fmt::Display for TableConstraintKind {
2179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2180        match self {
2181            Self::PrimaryKey { columns, conflict } => {
2182                f.write_str("PRIMARY KEY (")?;
2183                comma_list(f, columns)?;
2184                f.write_str(")")?;
2185                if let Some(action) = conflict {
2186                    write!(f, " ON CONFLICT {action}")?;
2187                }
2188                Ok(())
2189            }
2190            Self::Unique { columns, conflict } => {
2191                f.write_str("UNIQUE (")?;
2192                comma_list(f, columns)?;
2193                f.write_str(")")?;
2194                if let Some(action) = conflict {
2195                    write!(f, " ON CONFLICT {action}")?;
2196                }
2197                Ok(())
2198            }
2199            Self::Check(expr) => write!(f, "CHECK ({expr})"),
2200            Self::ForeignKey { columns, clause } => {
2201                f.write_str("FOREIGN KEY (")?;
2202                comma_list_fn(f, columns, |col, f| write_ident(f, col))?;
2203                write!(f, ") {clause}")
2204            }
2205        }
2206    }
2207}
2208
2209impl fmt::Display for ForeignKeyClause {
2210    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2211        f.write_str("REFERENCES ")?;
2212        write_ident(f, &self.table)?;
2213        if !self.columns.is_empty() {
2214            // No space before the column-list paren: stock sqlite3 stores
2215            // `REFERENCES <table>(<cols>)` (verbatim, oracle-verified), and the
2216            // ALTER rewrite path relies on this to stay .schema-faithful.
2217            f.write_str("(")?;
2218            comma_list_fn(f, &self.columns, |col, f| write_ident(f, col))?;
2219            f.write_str(")")?;
2220        }
2221        for action in &self.actions {
2222            write!(f, " {action}")?;
2223        }
2224        if let Some(ref def) = self.deferrable {
2225            write!(f, " {def}")?;
2226        }
2227        Ok(())
2228    }
2229}
2230
2231impl fmt::Display for ForeignKeyAction {
2232    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2233        write!(f, "{} {}", self.trigger, self.action)
2234    }
2235}
2236
2237impl fmt::Display for ForeignKeyTrigger {
2238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2239        f.write_str(match self {
2240            Self::OnDelete => "ON DELETE",
2241            Self::OnUpdate => "ON UPDATE",
2242        })
2243    }
2244}
2245
2246impl fmt::Display for ForeignKeyActionType {
2247    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2248        f.write_str(match self {
2249            Self::SetNull => "SET NULL",
2250            Self::SetDefault => "SET DEFAULT",
2251            Self::Cascade => "CASCADE",
2252            Self::Restrict => "RESTRICT",
2253            Self::NoAction => "NO ACTION",
2254        })
2255    }
2256}
2257
2258impl fmt::Display for Deferrable {
2259    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2260        if self.not {
2261            f.write_str("NOT ")?;
2262        }
2263        f.write_str("DEFERRABLE")?;
2264        if let Some(init) = self.initially {
2265            write!(f, " {init}")?;
2266        }
2267        Ok(())
2268    }
2269}
2270
2271impl fmt::Display for DeferrableInitially {
2272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2273        f.write_str(match self {
2274            Self::Deferred => "INITIALLY DEFERRED",
2275            Self::Immediate => "INITIALLY IMMEDIATE",
2276        })
2277    }
2278}
2279
2280// ---------------------------------------------------------------------------
2281// CreateIndexStatement
2282// ---------------------------------------------------------------------------
2283
2284impl fmt::Display for CreateIndexStatement {
2285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2286        f.write_str("CREATE ")?;
2287        if self.unique {
2288            f.write_str("UNIQUE ")?;
2289        }
2290        f.write_str("INDEX ")?;
2291        if self.if_not_exists {
2292            f.write_str("IF NOT EXISTS ")?;
2293        }
2294        write!(f, "{} ON ", self.name)?;
2295        write_ident(f, &self.table)?;
2296        f.write_str("(")?;
2297        comma_list(f, &self.columns)?;
2298        f.write_str(")")?;
2299        if let Some(ref w) = self.where_clause {
2300            write!(f, " WHERE {w}")?;
2301        }
2302        Ok(())
2303    }
2304}
2305
2306// ---------------------------------------------------------------------------
2307// CreateViewStatement
2308// ---------------------------------------------------------------------------
2309
2310impl fmt::Display for CreateViewStatement {
2311    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2312        f.write_str("CREATE ")?;
2313        if self.temporary {
2314            f.write_str("TEMP ")?;
2315        }
2316        f.write_str("VIEW ")?;
2317        if self.if_not_exists {
2318            f.write_str("IF NOT EXISTS ")?;
2319        }
2320        write!(f, "{}", self.name)?;
2321        if !self.columns.is_empty() {
2322            f.write_str(" (")?;
2323            comma_list_fn(f, &self.columns, |col, f| write_ident(f, col))?;
2324            f.write_str(")")?;
2325        }
2326        write!(f, " AS {}", self.query)
2327    }
2328}
2329
2330// ---------------------------------------------------------------------------
2331// CreateTriggerStatement
2332// ---------------------------------------------------------------------------
2333
2334impl fmt::Display for CreateTriggerStatement {
2335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2336        write_create_trigger(f, self)
2337    }
2338}
2339
2340impl fmt::Display for TriggerTiming {
2341    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2342        f.write_str(match self {
2343            Self::Before => "BEFORE",
2344            Self::After => "AFTER",
2345            Self::InsteadOf => "INSTEAD OF",
2346        })
2347    }
2348}
2349
2350impl fmt::Display for TriggerEvent {
2351    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2352        match self {
2353            Self::Insert => f.write_str("INSERT"),
2354            Self::Delete => f.write_str("DELETE"),
2355            Self::Update(cols) => {
2356                f.write_str("UPDATE")?;
2357                if !cols.is_empty() {
2358                    f.write_str(" OF ")?;
2359                    comma_list_fn(f, cols, |col, f| write_ident(f, col))?;
2360                }
2361                Ok(())
2362            }
2363        }
2364    }
2365}
2366
2367// ---------------------------------------------------------------------------
2368// CreateVirtualTableStatement
2369// ---------------------------------------------------------------------------
2370
2371impl fmt::Display for CreateVirtualTableStatement {
2372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2373        f.write_str("CREATE VIRTUAL TABLE ")?;
2374        if self.if_not_exists {
2375            f.write_str("IF NOT EXISTS ")?;
2376        }
2377        write!(f, "{} USING ", self.name)?;
2378        write_ident(f, &self.module)?;
2379        if !self.args.is_empty() {
2380            f.write_str("(")?;
2381            for (i, arg) in self.args.iter().enumerate() {
2382                if i > 0 {
2383                    f.write_str(", ")?;
2384                }
2385                f.write_str(arg)?;
2386            }
2387            f.write_str(")")?;
2388        }
2389        Ok(())
2390    }
2391}
2392
2393// ---------------------------------------------------------------------------
2394// DropStatement
2395// ---------------------------------------------------------------------------
2396
2397impl fmt::Display for DropStatement {
2398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2399        write!(f, "DROP {}", self.object_type)?;
2400        if self.if_exists {
2401            f.write_str(" IF EXISTS")?;
2402        }
2403        write!(f, " {}", self.name)
2404    }
2405}
2406
2407impl fmt::Display for DropObjectType {
2408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2409        f.write_str(match self {
2410            Self::Table => "TABLE",
2411            Self::View => "VIEW",
2412            Self::Index => "INDEX",
2413            Self::Trigger => "TRIGGER",
2414        })
2415    }
2416}
2417
2418// ---------------------------------------------------------------------------
2419// AlterTableStatement
2420// ---------------------------------------------------------------------------
2421
2422impl fmt::Display for AlterTableStatement {
2423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2424        write!(f, "ALTER TABLE {} {}", self.table, self.action)
2425    }
2426}
2427
2428impl fmt::Display for AlterTableAction {
2429    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2430        match self {
2431            Self::RenameTo(name) => {
2432                f.write_str("RENAME TO ")?;
2433                write_ident(f, name)
2434            }
2435            Self::RenameColumn { old, new } => {
2436                f.write_str("RENAME COLUMN ")?;
2437                write_ident(f, old)?;
2438                f.write_str(" TO ")?;
2439                write_ident(f, new)
2440            }
2441            Self::AddColumn(col) => write!(f, "ADD COLUMN {col}"),
2442            Self::DropColumn(name) => {
2443                f.write_str("DROP COLUMN ")?;
2444                write_ident(f, name)
2445            }
2446        }
2447    }
2448}
2449
2450// ---------------------------------------------------------------------------
2451// Transaction control
2452// ---------------------------------------------------------------------------
2453
2454impl fmt::Display for BeginStatement {
2455    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2456        f.write_str("BEGIN")?;
2457        if let Some(mode) = self.mode {
2458            write!(f, " {mode}")?;
2459        }
2460        Ok(())
2461    }
2462}
2463
2464impl fmt::Display for TransactionMode {
2465    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2466        f.write_str(match self {
2467            Self::Deferred => "DEFERRED",
2468            Self::Immediate => "IMMEDIATE",
2469            Self::Exclusive => "EXCLUSIVE",
2470            Self::Concurrent => "CONCURRENT",
2471        })
2472    }
2473}
2474
2475impl fmt::Display for RollbackStatement {
2476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2477        f.write_str("ROLLBACK")?;
2478        if let Some(ref sp) = self.to_savepoint {
2479            f.write_str(" TO SAVEPOINT ")?;
2480            write_ident(f, sp)?;
2481        }
2482        Ok(())
2483    }
2484}
2485
2486// ---------------------------------------------------------------------------
2487// ATTACH / DETACH / PRAGMA / VACUUM
2488// ---------------------------------------------------------------------------
2489
2490impl fmt::Display for AttachStatement {
2491    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2492        write!(f, "ATTACH {} AS ", self.expr)?;
2493        write_ident(f, &self.schema)
2494    }
2495}
2496
2497impl fmt::Display for PragmaStatement {
2498    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2499        write!(f, "PRAGMA {}", self.name)?;
2500        if let Some(ref val) = self.value {
2501            match val {
2502                PragmaValue::Assign(e) => write!(f, " = {e}")?,
2503                PragmaValue::Call(e) => write!(f, "({e})")?,
2504            }
2505        }
2506        Ok(())
2507    }
2508}
2509
2510impl fmt::Display for VacuumStatement {
2511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2512        f.write_str("VACUUM")?;
2513        if let Some(ref s) = self.schema {
2514            f.write_str(" ")?;
2515            write_ident(f, s)?;
2516        }
2517        if let Some(ref expr) = self.into {
2518            write!(f, " INTO {expr}")?;
2519        }
2520        Ok(())
2521    }
2522}
2523
2524// ---------------------------------------------------------------------------
2525// Top-level Statement
2526// ---------------------------------------------------------------------------
2527
2528impl fmt::Display for Statement {
2529    #[allow(clippy::too_many_lines)]
2530    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2531        write_statement(f, self)
2532    }
2533}
2534
2535#[cfg(test)]
2536mod expr_display_tests {
2537    use super::*;
2538    use std::fmt::Write as _;
2539
2540    fn column(name: &str) -> Expr {
2541        Expr::Column(ColumnRef::bare(name), Span::ZERO)
2542    }
2543
2544    fn integer(value: i64) -> Expr {
2545        Expr::Literal(Literal::Integer(value), Span::ZERO)
2546    }
2547
2548    fn binary(left: Expr, op: BinaryOp, right: Expr) -> Expr {
2549        Expr::BinaryOp {
2550            left: Box::new(left),
2551            op,
2552            right: Box::new(right),
2553            span: Span::ZERO,
2554        }
2555    }
2556
2557    fn table_source(name: &str) -> TableOrSubquery {
2558        TableOrSubquery::Table {
2559            name: QualifiedName::bare(name),
2560            alias: None,
2561            index_hint: None,
2562            time_travel: None,
2563        }
2564    }
2565
2566    fn nested_from(height: usize) -> FromClause {
2567        let mut from = FromClause {
2568            source: table_source("leaf"),
2569            joins: Vec::new(),
2570        };
2571        for _ in 0..height {
2572            from = FromClause {
2573                source: TableOrSubquery::ParenJoin(Box::new(from)),
2574                joins: Vec::new(),
2575            };
2576        }
2577        from
2578    }
2579
2580    fn update_with_from(from: FromClause) -> UpdateStatement {
2581        UpdateStatement {
2582            with: None,
2583            or_conflict: None,
2584            table: QualifiedTableRef {
2585                name: QualifiedName::bare("target"),
2586                alias: None,
2587                index_hint: None,
2588                time_travel: None,
2589            },
2590            assignments: vec![Assignment {
2591                target: AssignmentTarget::Column("x".to_owned()),
2592                value: integer(1),
2593            }],
2594            from: Some(from),
2595            where_clause: None,
2596            returning: Vec::new(),
2597            order_by: Vec::new(),
2598            limit: None,
2599        }
2600    }
2601
2602    fn drop_table(name: &str) -> Statement {
2603        Statement::Drop(DropStatement {
2604            object_type: DropObjectType::Table,
2605            if_exists: false,
2606            name: QualifiedName::bare(name),
2607        })
2608    }
2609
2610    fn drop_from_iteratively(root: FromClause) {
2611        let mut from_clauses = vec![root];
2612        let mut tables = Vec::new();
2613        while let Some(FromClause { source, joins }) = from_clauses.pop() {
2614            tables.push(source);
2615            for join in joins {
2616                tables.push(join.table);
2617                drop(join.constraint);
2618            }
2619            while let Some(table) = tables.pop() {
2620                match table {
2621                    TableOrSubquery::ParenJoin(inner) => from_clauses.push(*inner),
2622                    leaf => drop(leaf),
2623                }
2624            }
2625        }
2626    }
2627
2628    fn drop_table_iteratively(table: TableOrSubquery) {
2629        match table {
2630            TableOrSubquery::ParenJoin(inner) => drop_from_iteratively(*inner),
2631            leaf => drop(leaf),
2632        }
2633    }
2634
2635    fn drop_select_core_iteratively(core: SelectCore) {
2636        match core {
2637            SelectCore::Select { from, .. } => {
2638                if let Some(from) = from {
2639                    drop_from_iteratively(from);
2640                }
2641            }
2642            SelectCore::Values(_) => {}
2643        }
2644    }
2645
2646    fn drop_select_body_iteratively(body: SelectBody) {
2647        drop_select_core_iteratively(body.select);
2648        for (_, core) in body.compounds {
2649            drop_select_core_iteratively(core);
2650        }
2651    }
2652
2653    fn drop_statement_iteratively(mut statement: Statement) {
2654        loop {
2655            match statement {
2656                Statement::Explain { stmt, .. } => statement = *stmt,
2657                Statement::CreateTrigger(mut trigger) if trigger.body.len() == 1 => {
2658                    statement = trigger
2659                        .body
2660                        .pop()
2661                        .expect("single trigger body statement must exist");
2662                }
2663                Statement::Update(mut update) => {
2664                    if let Some(from) = update.from.take() {
2665                        drop_from_iteratively(from);
2666                    }
2667                    break;
2668                }
2669                leaf => {
2670                    drop(leaf);
2671                    break;
2672                }
2673            }
2674        }
2675    }
2676
2677    fn drop_scalar_subquery_chain_iteratively(mut expr: Expr) {
2678        loop {
2679            match expr {
2680                Expr::Subquery(select, _) => {
2681                    let SelectStatement {
2682                        with,
2683                        body,
2684                        order_by,
2685                        limit,
2686                    } = *select;
2687                    assert!(with.is_none());
2688                    assert!(order_by.is_empty());
2689                    assert!(limit.is_none());
2690                    let SelectBody { select, compounds } = body;
2691                    assert!(compounds.is_empty());
2692                    let SelectCore::Select {
2693                        mut columns,
2694                        from,
2695                        where_clause,
2696                        group_by,
2697                        having,
2698                        windows,
2699                        ..
2700                    } = select
2701                    else {
2702                        panic!("scalar-subquery chain must contain SELECT cores");
2703                    };
2704                    assert!(from.is_none());
2705                    assert!(where_clause.is_none());
2706                    assert!(group_by.is_empty());
2707                    assert!(having.is_none());
2708                    assert!(windows.is_empty());
2709                    assert_eq!(columns.len(), 1);
2710                    let ResultColumn::Expr { expr: child, alias } = columns
2711                        .pop()
2712                        .expect("scalar-subquery SELECT must contain one column")
2713                    else {
2714                        panic!("scalar-subquery SELECT column must be an expression");
2715                    };
2716                    assert!(alias.is_none());
2717                    expr = child;
2718                }
2719                leaf => {
2720                    drop(leaf);
2721                    break;
2722                }
2723            }
2724        }
2725    }
2726
2727    fn format_statement_on_one_mib_stack(statement: Statement) -> String {
2728        let (rendered, statement) = std::thread::Builder::new()
2729            .stack_size(1024 * 1024)
2730            .spawn(move || (statement.to_string(), statement))
2731            .expect("1 MiB formatter thread must spawn")
2732            .join()
2733            .expect("formatting on a 1 MiB stack must not overflow");
2734        drop_statement_iteratively(statement);
2735        rendered
2736    }
2737
2738    #[test]
2739    fn expression_task_stack_uses_inline_boundary_and_preserves_lifo() {
2740        let mut stack = ExprWriteTaskStack::new(ExprWriteTask::Text("first"));
2741        stack.push(ExprWriteTask::Text("second"));
2742        stack.push(ExprWriteTask::Text("third"));
2743        assert!(matches!(stack.pop(), Some(ExprWriteTask::Text("third"))));
2744        assert!(matches!(stack.pop(), Some(ExprWriteTask::Text("second"))));
2745        assert!(matches!(stack.pop(), Some(ExprWriteTask::Text("first"))));
2746
2747        let mut boundary = ExprWriteTaskStack::new(ExprWriteTask::Text("inline"));
2748        for _ in 1..INLINE_EXPR_WRITE_TASKS {
2749            boundary.push(ExprWriteTask::Text("inline"));
2750        }
2751        assert!(!boundary.tasks.spilled());
2752        boundary.push(ExprWriteTask::Text("spill"));
2753        assert!(boundary.tasks.spilled());
2754        while boundary.pop().is_some() {}
2755        assert!(
2756            boundary.stats().spilled,
2757            "spill history must survive draining the task stack"
2758        );
2759    }
2760
2761    #[test]
2762    fn formatter_error_resets_task_stack_stats() {
2763        struct FailingWriter;
2764
2765        impl fmt::Write for FailingWriter {
2766            fn write_str(&mut self, _: &str) -> fmt::Result {
2767                Err(fmt::Error)
2768            }
2769        }
2770
2771        let mut deep = integer(1);
2772        for _ in 1..100 {
2773            deep = binary(deep, BinaryOp::Add, integer(1));
2774        }
2775        let _ = deep.to_string();
2776        assert!(
2777            LAST_EXPR_WRITE_TASK_STACK_STATS
2778                .with(std::cell::Cell::get)
2779                .spilled
2780        );
2781
2782        let mut writer = FailingWriter;
2783        write!(&mut writer, "{}", column("value"))
2784            .expect_err("the test writer must reject formatter output");
2785        assert_eq!(
2786            LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get),
2787            ExprWriteTaskStackStats::default(),
2788            "an early formatter error must not expose the previous call's task stats"
2789        );
2790    }
2791
2792    #[test]
2793    fn public_from_roots_format_height_1000_and_1001_on_one_mib_stack() {
2794        for height in [1000, 1001] {
2795            let from_root = nested_from(height);
2796            let table_root = TableOrSubquery::ParenJoin(Box::new(nested_from(height)));
2797            let join_root = JoinClause {
2798                join_type: JoinType {
2799                    natural: false,
2800                    kind: JoinKind::Inner,
2801                },
2802                table: TableOrSubquery::ParenJoin(Box::new(nested_from(height))),
2803                constraint: None,
2804            };
2805            let select_core_root = SelectCore::Select {
2806                distinct: Distinctness::All,
2807                columns: vec![ResultColumn::Star],
2808                from: Some(nested_from(height)),
2809                where_clause: None,
2810                group_by: Vec::new(),
2811                having: None,
2812                windows: Vec::new(),
2813            };
2814            let select_body_root = SelectBody {
2815                select: SelectCore::Select {
2816                    distinct: Distinctness::All,
2817                    columns: vec![ResultColumn::Star],
2818                    from: Some(nested_from(height)),
2819                    where_clause: None,
2820                    group_by: Vec::new(),
2821                    having: None,
2822                    windows: Vec::new(),
2823                },
2824                compounds: Vec::new(),
2825            };
2826            let update_root = Statement::Update(update_with_from(nested_from(height)));
2827
2828            let (rendered, roots) = std::thread::Builder::new()
2829                .stack_size(1024 * 1024)
2830                .spawn(move || {
2831                    let rendered = [
2832                        from_root.to_string(),
2833                        table_root.to_string(),
2834                        join_root.to_string(),
2835                        select_core_root.to_string(),
2836                        select_body_root.to_string(),
2837                        update_root.to_string(),
2838                    ];
2839                    (
2840                        rendered,
2841                        (
2842                            from_root,
2843                            table_root,
2844                            join_root,
2845                            select_core_root,
2846                            select_body_root,
2847                            update_root,
2848                        ),
2849                    )
2850                })
2851                .expect("1 MiB formatter thread must spawn")
2852                .join()
2853                .expect("all public FROM roots must format without stack overflow");
2854            let (
2855                from_root,
2856                table_root,
2857                JoinClause {
2858                    table: join_table, ..
2859                },
2860                select_core_root,
2861                select_body_root,
2862                update_root,
2863            ) = roots;
2864            drop_from_iteratively(from_root);
2865            drop_table_iteratively(table_root);
2866            drop_table_iteratively(join_table);
2867            drop_select_core_iteratively(select_core_root);
2868            drop_select_body_iteratively(select_body_root);
2869            drop_statement_iteratively(update_root);
2870
2871            let expected_parentheses = [height, height + 1, height + 1, height, height, height];
2872            for (sql, expected) in rendered.iter().zip(expected_parentheses) {
2873                assert_eq!(sql.matches('(').count(), expected);
2874                assert_eq!(sql.matches(')').count(), expected);
2875                assert!(sql.contains("leaf"));
2876            }
2877            assert!(rendered[2].starts_with("INNER JOIN "));
2878            assert!(rendered[3].starts_with("SELECT * FROM "));
2879            assert!(rendered[4].starts_with("SELECT * FROM "));
2880            assert!(rendered[5].starts_with("UPDATE target SET x = 1 FROM "));
2881        }
2882    }
2883
2884    #[test]
2885    fn nested_explain_height_1000_and_1001_formats_on_one_mib_stack() {
2886        for height in [1000, 1001] {
2887            let mut statement = drop_table("leaf");
2888            for level in 0..height {
2889                statement = Statement::Explain {
2890                    query_plan: level % 2 == 0,
2891                    stmt: Box::new(statement),
2892                };
2893            }
2894
2895            let rendered = format_statement_on_one_mib_stack(statement);
2896            let mut tail = rendered.as_str();
2897            for level in (0..height).rev() {
2898                let prefix = if level % 2 == 0 {
2899                    "EXPLAIN QUERY PLAN "
2900                } else {
2901                    "EXPLAIN "
2902                };
2903                tail = tail
2904                    .strip_prefix(prefix)
2905                    .expect("EXPLAIN wrappers must retain their exact order");
2906            }
2907            assert_eq!(tail, "DROP TABLE leaf");
2908        }
2909    }
2910
2911    #[test]
2912    fn nested_trigger_body_height_1000_and_1001_formats_on_one_mib_stack() {
2913        for height in [1000, 1001] {
2914            let mut statement = drop_table("leaf");
2915            for level in 0..height {
2916                statement = Statement::CreateTrigger(CreateTriggerStatement {
2917                    if_not_exists: false,
2918                    temporary: false,
2919                    name: QualifiedName::bare(format!("trigger_{level}")),
2920                    timing: TriggerTiming::After,
2921                    event: TriggerEvent::Insert,
2922                    table: "target".to_owned(),
2923                    for_each_row: false,
2924                    when: None,
2925                    body: vec![statement],
2926                });
2927            }
2928
2929            let rendered = format_statement_on_one_mib_stack(statement);
2930            assert_eq!(rendered.matches("CREATE TRIGGER ").count(), height);
2931            assert_eq!(rendered.matches("; END").count(), height);
2932            assert!(rendered.contains("DROP TABLE leaf"));
2933            assert!(rendered.ends_with("END"));
2934        }
2935    }
2936
2937    #[test]
2938    fn iterative_public_roots_preserve_shallow_sql() {
2939        let joined = FromClause {
2940            source: table_source("a"),
2941            joins: vec![JoinClause {
2942                join_type: JoinType {
2943                    natural: false,
2944                    kind: JoinKind::Inner,
2945                },
2946                table: table_source("b"),
2947                constraint: Some(JoinConstraint::On(binary(
2948                    column("a_id"),
2949                    BinaryOp::Eq,
2950                    column("b_id"),
2951                ))),
2952            }],
2953        };
2954        assert_eq!(joined.to_string(), "a INNER JOIN b ON a_id = b_id");
2955
2956        let body = SelectBody {
2957            select: SelectCore::Select {
2958                distinct: Distinctness::All,
2959                columns: vec![ResultColumn::Star],
2960                from: Some(joined),
2961                where_clause: None,
2962                group_by: Vec::new(),
2963                having: None,
2964                windows: Vec::new(),
2965            },
2966            compounds: Vec::new(),
2967        };
2968        assert_eq!(
2969            body.to_string(),
2970            "SELECT * FROM a INNER JOIN b ON a_id = b_id"
2971        );
2972
2973        let compounds = SelectBody {
2974            select: SelectCore::Values(vec![vec![integer(1)]].into()),
2975            compounds: vec![
2976                (
2977                    CompoundOp::UnionAll,
2978                    SelectCore::Values(vec![vec![integer(2)]].into()),
2979                ),
2980                (
2981                    CompoundOp::Except,
2982                    SelectCore::Values(vec![vec![integer(3)]].into()),
2983                ),
2984            ],
2985        };
2986        assert_eq!(
2987            compounds.to_string(),
2988            "VALUES (1) UNION ALL VALUES (2) EXCEPT VALUES (3)"
2989        );
2990
2991        let update = UpdateStatement {
2992            with: None,
2993            or_conflict: None,
2994            table: QualifiedTableRef {
2995                name: QualifiedName::bare("target"),
2996                alias: None,
2997                index_hint: None,
2998                time_travel: None,
2999            },
3000            assignments: vec![Assignment {
3001                target: AssignmentTarget::Column("x".to_owned()),
3002                value: integer(1),
3003            }],
3004            from: Some(FromClause {
3005                source: table_source("source"),
3006                joins: Vec::new(),
3007            }),
3008            where_clause: Some(binary(column("id"), BinaryOp::Eq, integer(7))),
3009            returning: vec![ResultColumn::Expr {
3010                expr: column("x"),
3011                alias: Some("updated".to_owned()),
3012            }],
3013            order_by: vec![OrderingTerm {
3014                expr: column("id"),
3015                direction: Some(SortDirection::Desc),
3016                nulls: Some(NullsOrder::Last),
3017            }],
3018            limit: Some(LimitClause {
3019                limit: integer(10),
3020                offset: Some(integer(2)),
3021            }),
3022        };
3023        assert_eq!(
3024            update.to_string(),
3025            "UPDATE target SET x = 1 FROM source WHERE id = 7 RETURNING x AS updated \
3026             ORDER BY id DESC NULLS LAST LIMIT 10 OFFSET 2"
3027        );
3028
3029        let explained = Statement::Explain {
3030            query_plan: true,
3031            stmt: Box::new(drop_table("old")),
3032        };
3033        assert_eq!(explained.to_string(), "EXPLAIN QUERY PLAN DROP TABLE old");
3034
3035        let trigger = CreateTriggerStatement {
3036            if_not_exists: false,
3037            temporary: false,
3038            name: QualifiedName::bare("tr"),
3039            timing: TriggerTiming::After,
3040            event: TriggerEvent::Insert,
3041            table: "target".to_owned(),
3042            for_each_row: false,
3043            when: None,
3044            body: vec![drop_table("old"), drop_table("older")],
3045        };
3046        assert_eq!(
3047            trigger.to_string(),
3048            "CREATE TRIGGER tr AFTER INSERT ON target BEGIN \
3049             DROP TABLE old; DROP TABLE older; END"
3050        );
3051    }
3052
3053    #[test]
3054    fn representative_rich_select_stays_in_inline_task_stack() {
3055        let cte_query = SelectStatement {
3056            with: None,
3057            body: SelectBody {
3058                select: SelectCore::Values(vec![vec![integer(1)]].into()),
3059                compounds: Vec::new(),
3060            },
3061            order_by: Vec::new(),
3062            limit: None,
3063        };
3064        let select = SelectStatement {
3065            with: Some(WithClause {
3066                recursive: false,
3067                ctes: vec![Cte {
3068                    name: "seed".to_owned(),
3069                    columns: vec!["id".to_owned()],
3070                    materialized: Some(CteMaterialized::NotMaterialized),
3071                    query: cte_query,
3072                }],
3073            }),
3074            body: SelectBody {
3075                select: SelectCore::Select {
3076                    distinct: Distinctness::Distinct,
3077                    columns: vec![
3078                        ResultColumn::Expr {
3079                            expr: column("a_id"),
3080                            alias: Some("id".to_owned()),
3081                        },
3082                        ResultColumn::Expr {
3083                            expr: column("b_value"),
3084                            alias: None,
3085                        },
3086                    ],
3087                    from: Some(FromClause {
3088                        source: table_source("a"),
3089                        joins: vec![JoinClause {
3090                            join_type: JoinType {
3091                                natural: false,
3092                                kind: JoinKind::Left,
3093                            },
3094                            table: table_source("b"),
3095                            constraint: Some(JoinConstraint::On(binary(
3096                                column("a_id"),
3097                                BinaryOp::Eq,
3098                                column("b_id"),
3099                            ))),
3100                        }],
3101                    }),
3102                    where_clause: Some(Box::new(binary(column("a_id"), BinaryOp::Gt, integer(0)))),
3103                    group_by: vec![column("a_id")],
3104                    having: Some(Box::new(binary(
3105                        column("b_value"),
3106                        BinaryOp::IsNot,
3107                        Expr::Literal(Literal::Null, Span::ZERO),
3108                    ))),
3109                    windows: vec![WindowDef {
3110                        name: "w".to_owned(),
3111                        spec: WindowSpec {
3112                            window_ref: None,
3113                            partition_by: vec![column("a_id")],
3114                            order_by: vec![OrderingTerm {
3115                                expr: column("b_value"),
3116                                direction: Some(SortDirection::Desc),
3117                                nulls: None,
3118                            }],
3119                            frame: None,
3120                        },
3121                    }],
3122                },
3123                compounds: Vec::new(),
3124            },
3125            order_by: vec![OrderingTerm {
3126                expr: column("a_id"),
3127                direction: Some(SortDirection::Asc),
3128                nulls: Some(NullsOrder::First),
3129            }],
3130            limit: Some(LimitClause {
3131                limit: integer(25),
3132                offset: Some(integer(5)),
3133            }),
3134        };
3135
3136        assert_eq!(
3137            select.to_string(),
3138            "WITH seed(id) AS NOT MATERIALIZED (VALUES (1)) \
3139             SELECT DISTINCT a_id AS id, b_value FROM a \
3140             LEFT JOIN b ON a_id = b_id WHERE a_id > 0 GROUP BY a_id \
3141             HAVING b_value IS NOT NULL WINDOW w AS \
3142             (PARTITION BY a_id ORDER BY b_value DESC) \
3143             ORDER BY a_id ASC NULLS FIRST LIMIT 25 OFFSET 5"
3144        );
3145        let task_stats = LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get);
3146        assert!(
3147            !task_stats.spilled,
3148            "representative rich SELECT should remain in the inline task stack"
3149        );
3150        assert!(task_stats.peak_len <= INLINE_EXPR_WRITE_TASKS);
3151    }
3152
3153    #[test]
3154    fn binary_operands_use_minimal_semantics_preserving_parentheses() {
3155        let tighter_right = binary(
3156            column("a"),
3157            BinaryOp::Add,
3158            binary(column("b"), BinaryOp::Multiply, integer(2)),
3159        );
3160        assert_eq!(tighter_right.to_string(), "a + b * 2");
3161
3162        let looser_right = binary(
3163            column("a"),
3164            BinaryOp::Multiply,
3165            binary(column("b"), BinaryOp::Add, column("c")),
3166        );
3167        assert_eq!(looser_right.to_string(), "a * (b + c)");
3168
3169        let left_associative = binary(
3170            binary(column("a"), BinaryOp::Subtract, column("b")),
3171            BinaryOp::Subtract,
3172            column("c"),
3173        );
3174        assert_eq!(left_associative.to_string(), "a - b - c");
3175
3176        let right_subtract = binary(
3177            column("a"),
3178            BinaryOp::Subtract,
3179            binary(column("b"), BinaryOp::Subtract, column("c")),
3180        );
3181        assert_eq!(right_subtract.to_string(), "a - (b - c)");
3182
3183        let right_divide = binary(
3184            column("a"),
3185            BinaryOp::Divide,
3186            binary(column("b"), BinaryOp::Divide, column("c")),
3187        );
3188        assert_eq!(right_divide.to_string(), "a / (b / c)");
3189
3190        let and_chain = binary(
3191            column("a"),
3192            BinaryOp::And,
3193            binary(column("b"), BinaryOp::And, column("c")),
3194        );
3195        assert_eq!(and_chain.to_string(), "a AND b AND c");
3196
3197        let or_chain = binary(
3198            column("a"),
3199            BinaryOp::Or,
3200            binary(column("b"), BinaryOp::Or, column("c")),
3201        );
3202        assert_eq!(or_chain.to_string(), "a OR b OR c");
3203    }
3204
3205    #[test]
3206    fn expression_display_height_1000_uses_bounded_work_stack() {
3207        let mut expr = Expr::Literal(Literal::Integer(1), Span::ZERO);
3208        for _ in 1..1000 {
3209            expr = Expr::BinaryOp {
3210                left: Box::new(expr),
3211                op: BinaryOp::Add,
3212                right: Box::new(Expr::Literal(Literal::Integer(1), Span::ZERO)),
3213                span: Span::ZERO,
3214            };
3215        }
3216
3217        let rendered = expr.to_string();
3218        let task_stats = LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get);
3219        assert!(
3220            task_stats.spilled,
3221            "height-1000 expression should exercise the heap spill path"
3222        );
3223        assert!(task_stats.peak_len > INLINE_EXPR_WRITE_TASKS);
3224        assert_eq!(rendered.matches('+').count(), 999);
3225        assert!(rendered.ends_with(" + 1"));
3226    }
3227
3228    #[test]
3229    fn scalar_subquery_display_height_1000_uses_one_mib_stack() {
3230        let mut expr = Expr::Literal(Literal::Integer(1), Span::ZERO);
3231        for _ in 1..1000 {
3232            expr = Expr::Subquery(
3233                Box::new(SelectStatement {
3234                    with: None,
3235                    body: SelectBody {
3236                        select: SelectCore::Select {
3237                            distinct: Distinctness::All,
3238                            columns: vec![ResultColumn::Expr { expr, alias: None }],
3239                            from: None,
3240                            where_clause: None,
3241                            group_by: Vec::new(),
3242                            having: None,
3243                            windows: Vec::new(),
3244                        },
3245                        compounds: Vec::new(),
3246                    },
3247                    order_by: Vec::new(),
3248                    limit: None,
3249                }),
3250                Span::ZERO,
3251            );
3252        }
3253
3254        let (rendered, expr) = std::thread::Builder::new()
3255            .stack_size(1024 * 1024)
3256            .spawn(move || {
3257                let rendered = expr.to_string();
3258                (rendered, expr)
3259            })
3260            .expect("1 MiB formatter thread must spawn")
3261            .join()
3262            .expect("height-1000 scalar subquery formatting must not overflow");
3263        drop_scalar_subquery_chain_iteratively(expr);
3264        assert_eq!(rendered.matches("(SELECT ").count(), 999);
3265        assert!(rendered.ends_with(&")".repeat(999)));
3266    }
3267
3268    #[test]
3269    fn negative_literal_operands_cannot_merge_into_sql_comments() {
3270        let integer = Expr::UnaryOp {
3271            op: UnaryOp::Negate,
3272            expr: Box::new(Expr::Literal(Literal::Integer(i64::MIN), Span::ZERO)),
3273            span: Span::ZERO,
3274        };
3275        assert_eq!(integer.to_string(), "-(-9223372036854775808)");
3276
3277        let negative_zero = Expr::UnaryOp {
3278            op: UnaryOp::Negate,
3279            expr: Box::new(Expr::Literal(Literal::Float(-0.0), Span::ZERO)),
3280            span: Span::ZERO,
3281        };
3282        assert_eq!(negative_zero.to_string(), "-(-0.0)");
3283
3284        let bound = Expr::UnaryOp {
3285            op: UnaryOp::Negate,
3286            expr: Box::new(Expr::BoundOuterValue {
3287                value: SqliteValue::Integer(-1),
3288                collation: BoundCollation::Unspecified,
3289                affinity: None,
3290                span: Span::ZERO,
3291            }),
3292            span: Span::ZERO,
3293        };
3294        assert_eq!(bound.to_string(), "-(-1)");
3295    }
3296
3297    #[test]
3298    fn like_escape_operand_preserves_comparison_grouping() {
3299        let expr = Expr::Like {
3300            expr: Box::new(column("value")),
3301            pattern: Box::new(column("pattern")),
3302            escape: Some(Box::new(binary(
3303                column("lower"),
3304                BinaryOp::Lt,
3305                column("upper"),
3306            ))),
3307            op: LikeOp::Like,
3308            not: false,
3309            span: Span::ZERO,
3310        };
3311        assert_eq!(
3312            expr.to_string(),
3313            "value LIKE pattern ESCAPE (lower < upper)"
3314        );
3315    }
3316
3317    #[test]
3318    fn bound_outer_values_render_as_safe_literals_without_metadata() {
3319        let bound = |value| Expr::BoundOuterValue {
3320            value,
3321            collation: BoundCollation::Named("NOCASE".to_owned()),
3322            affinity: Some(fsqlite_types::TypeAffinity::Text),
3323            span: Span::new(7, 19),
3324        };
3325
3326        assert_eq!(bound(SqliteValue::Null).to_string(), "NULL");
3327        assert_eq!(bound(SqliteValue::Integer(-7)).to_string(), "-7");
3328        assert_eq!(bound(SqliteValue::Float(1.0)).to_string(), "1.0");
3329        assert_eq!(
3330            bound(SqliteValue::Text("O'Brien".into())).to_string(),
3331            "'O''Brien'"
3332        );
3333        assert_eq!(
3334            bound(SqliteValue::Blob(vec![0x00, 0xAB].into())).to_string(),
3335            "X'00AB'"
3336        );
3337    }
3338
3339    #[test]
3340    fn infinite_float_literals_render_as_numeric_sql() {
3341        assert_eq!(Literal::Float(f64::INFINITY).to_string(), "9e999");
3342        assert_eq!(Literal::Float(f64::NEG_INFINITY).to_string(), "-9e999");
3343        assert_eq!(Literal::Float(f64::NAN).to_string(), "NULL");
3344        assert_eq!(Literal::Float(-f64::NAN).to_string(), "NULL");
3345    }
3346
3347    #[test]
3348    fn collation_names_use_identifier_quoting() {
3349        let expr = Expr::Collate {
3350            expr: Box::new(column("value")),
3351            collation: "my col".to_owned(),
3352            span: Span::ZERO,
3353        };
3354        assert_eq!(expr.to_string(), "value COLLATE \"my col\"");
3355    }
3356
3357    #[test]
3358    fn window_reference_form_is_preserved_exactly() {
3359        let extended = Expr::FunctionCall {
3360            name: "sum".to_owned(),
3361            args: FunctionArgs::List(vec![column("x")]),
3362            distinct: false,
3363            order_by: Vec::new(),
3364            filter: None,
3365            over: Some(WindowSpec {
3366                window_ref: Some(WindowReference::Base("base".to_owned())),
3367                partition_by: vec![column("p")],
3368                order_by: vec![OrderingTerm {
3369                    expr: column("y"),
3370                    direction: None,
3371                    nulls: None,
3372                }],
3373                frame: Some(FrameSpec {
3374                    frame_type: FrameType::Rows,
3375                    start: FrameBound::Preceding(Box::new(column("z"))),
3376                    end: Some(FrameBound::CurrentRow),
3377                    exclude: None,
3378                }),
3379            }),
3380            span: Span::ZERO,
3381        };
3382        assert_eq!(
3383            extended.to_string(),
3384            "sum(x) OVER (base PARTITION BY p ORDER BY y ROWS BETWEEN z PRECEDING AND CURRENT ROW)"
3385        );
3386        let task_stats = LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get);
3387        assert!(
3388            !task_stats.spilled,
3389            "representative window expression should remain in the inline task stack"
3390        );
3391        assert!(task_stats.peak_len <= INLINE_EXPR_WRITE_TASKS);
3392
3393        let bare = Expr::FunctionCall {
3394            name: "sum".to_owned(),
3395            args: FunctionArgs::List(vec![column("x")]),
3396            distinct: false,
3397            order_by: Vec::new(),
3398            filter: None,
3399            over: Some(WindowSpec {
3400                window_ref: Some(WindowReference::Direct("base".to_owned())),
3401                partition_by: Vec::new(),
3402                order_by: Vec::new(),
3403                frame: None,
3404            }),
3405            span: Span::ZERO,
3406        };
3407        assert_eq!(bare.to_string(), "sum(x) OVER base");
3408        let Expr::FunctionCall {
3409            over: Some(bare_window),
3410            ..
3411        } = &bare
3412        else {
3413            panic!("bare window function should carry a window");
3414        };
3415        assert_eq!(bare_window.to_string(), "base");
3416
3417        let parenthesized = Expr::FunctionCall {
3418            name: "sum".to_owned(),
3419            args: FunctionArgs::List(vec![column("x")]),
3420            distinct: false,
3421            order_by: Vec::new(),
3422            filter: None,
3423            over: Some(WindowSpec {
3424                window_ref: Some(WindowReference::Base("base".to_owned())),
3425                partition_by: Vec::new(),
3426                order_by: Vec::new(),
3427                frame: None,
3428            }),
3429            span: Span::ZERO,
3430        };
3431        assert_eq!(parenthesized.to_string(), "sum(x) OVER (base)");
3432        let Expr::FunctionCall {
3433            over: Some(parenthesized_window),
3434            ..
3435        } = &parenthesized
3436        else {
3437            panic!("parenthesized window function should carry a window");
3438        };
3439        assert_eq!(parenthesized_window.to_string(), "(base)");
3440    }
3441}