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