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            // No space before the column-list paren: stock sqlite3 stores
2197            // `REFERENCES <table>(<cols>)` (verbatim, oracle-verified), and the
2198            // ALTER rewrite path relies on this to stay .schema-faithful.
2199            f.write_str("(")?;
2200            comma_list_fn(f, &self.columns, |col, f| write_ident(f, col))?;
2201            f.write_str(")")?;
2202        }
2203        for action in &self.actions {
2204            write!(f, " {action}")?;
2205        }
2206        if let Some(ref def) = self.deferrable {
2207            write!(f, " {def}")?;
2208        }
2209        Ok(())
2210    }
2211}
2212
2213impl fmt::Display for ForeignKeyAction {
2214    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2215        write!(f, "{} {}", self.trigger, self.action)
2216    }
2217}
2218
2219impl fmt::Display for ForeignKeyTrigger {
2220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2221        f.write_str(match self {
2222            Self::OnDelete => "ON DELETE",
2223            Self::OnUpdate => "ON UPDATE",
2224        })
2225    }
2226}
2227
2228impl fmt::Display for ForeignKeyActionType {
2229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2230        f.write_str(match self {
2231            Self::SetNull => "SET NULL",
2232            Self::SetDefault => "SET DEFAULT",
2233            Self::Cascade => "CASCADE",
2234            Self::Restrict => "RESTRICT",
2235            Self::NoAction => "NO ACTION",
2236        })
2237    }
2238}
2239
2240impl fmt::Display for Deferrable {
2241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2242        if self.not {
2243            f.write_str("NOT ")?;
2244        }
2245        f.write_str("DEFERRABLE")?;
2246        if let Some(init) = self.initially {
2247            write!(f, " {init}")?;
2248        }
2249        Ok(())
2250    }
2251}
2252
2253impl fmt::Display for DeferrableInitially {
2254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2255        f.write_str(match self {
2256            Self::Deferred => "INITIALLY DEFERRED",
2257            Self::Immediate => "INITIALLY IMMEDIATE",
2258        })
2259    }
2260}
2261
2262// ---------------------------------------------------------------------------
2263// CreateIndexStatement
2264// ---------------------------------------------------------------------------
2265
2266impl fmt::Display for CreateIndexStatement {
2267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2268        f.write_str("CREATE ")?;
2269        if self.unique {
2270            f.write_str("UNIQUE ")?;
2271        }
2272        f.write_str("INDEX ")?;
2273        if self.if_not_exists {
2274            f.write_str("IF NOT EXISTS ")?;
2275        }
2276        write!(f, "{} ON ", self.name)?;
2277        write_ident(f, &self.table)?;
2278        f.write_str("(")?;
2279        comma_list(f, &self.columns)?;
2280        f.write_str(")")?;
2281        if let Some(ref w) = self.where_clause {
2282            write!(f, " WHERE {w}")?;
2283        }
2284        Ok(())
2285    }
2286}
2287
2288// ---------------------------------------------------------------------------
2289// CreateViewStatement
2290// ---------------------------------------------------------------------------
2291
2292impl fmt::Display for CreateViewStatement {
2293    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2294        f.write_str("CREATE ")?;
2295        if self.temporary {
2296            f.write_str("TEMP ")?;
2297        }
2298        f.write_str("VIEW ")?;
2299        if self.if_not_exists {
2300            f.write_str("IF NOT EXISTS ")?;
2301        }
2302        write!(f, "{}", self.name)?;
2303        if !self.columns.is_empty() {
2304            f.write_str(" (")?;
2305            comma_list_fn(f, &self.columns, |col, f| write_ident(f, col))?;
2306            f.write_str(")")?;
2307        }
2308        write!(f, " AS {}", self.query)
2309    }
2310}
2311
2312// ---------------------------------------------------------------------------
2313// CreateTriggerStatement
2314// ---------------------------------------------------------------------------
2315
2316impl fmt::Display for CreateTriggerStatement {
2317    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2318        write_create_trigger(f, self)
2319    }
2320}
2321
2322impl fmt::Display for TriggerTiming {
2323    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2324        f.write_str(match self {
2325            Self::Before => "BEFORE",
2326            Self::After => "AFTER",
2327            Self::InsteadOf => "INSTEAD OF",
2328        })
2329    }
2330}
2331
2332impl fmt::Display for TriggerEvent {
2333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2334        match self {
2335            Self::Insert => f.write_str("INSERT"),
2336            Self::Delete => f.write_str("DELETE"),
2337            Self::Update(cols) => {
2338                f.write_str("UPDATE")?;
2339                if !cols.is_empty() {
2340                    f.write_str(" OF ")?;
2341                    comma_list_fn(f, cols, |col, f| write_ident(f, col))?;
2342                }
2343                Ok(())
2344            }
2345        }
2346    }
2347}
2348
2349// ---------------------------------------------------------------------------
2350// CreateVirtualTableStatement
2351// ---------------------------------------------------------------------------
2352
2353impl fmt::Display for CreateVirtualTableStatement {
2354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2355        f.write_str("CREATE VIRTUAL TABLE ")?;
2356        if self.if_not_exists {
2357            f.write_str("IF NOT EXISTS ")?;
2358        }
2359        write!(f, "{} USING ", self.name)?;
2360        write_ident(f, &self.module)?;
2361        if !self.args.is_empty() {
2362            f.write_str("(")?;
2363            for (i, arg) in self.args.iter().enumerate() {
2364                if i > 0 {
2365                    f.write_str(", ")?;
2366                }
2367                f.write_str(arg)?;
2368            }
2369            f.write_str(")")?;
2370        }
2371        Ok(())
2372    }
2373}
2374
2375// ---------------------------------------------------------------------------
2376// DropStatement
2377// ---------------------------------------------------------------------------
2378
2379impl fmt::Display for DropStatement {
2380    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2381        write!(f, "DROP {}", self.object_type)?;
2382        if self.if_exists {
2383            f.write_str(" IF EXISTS")?;
2384        }
2385        write!(f, " {}", self.name)
2386    }
2387}
2388
2389impl fmt::Display for DropObjectType {
2390    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2391        f.write_str(match self {
2392            Self::Table => "TABLE",
2393            Self::View => "VIEW",
2394            Self::Index => "INDEX",
2395            Self::Trigger => "TRIGGER",
2396        })
2397    }
2398}
2399
2400// ---------------------------------------------------------------------------
2401// AlterTableStatement
2402// ---------------------------------------------------------------------------
2403
2404impl fmt::Display for AlterTableStatement {
2405    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2406        write!(f, "ALTER TABLE {} {}", self.table, self.action)
2407    }
2408}
2409
2410impl fmt::Display for AlterTableAction {
2411    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2412        match self {
2413            Self::RenameTo(name) => {
2414                f.write_str("RENAME TO ")?;
2415                write_ident(f, name)
2416            }
2417            Self::RenameColumn { old, new } => {
2418                f.write_str("RENAME COLUMN ")?;
2419                write_ident(f, old)?;
2420                f.write_str(" TO ")?;
2421                write_ident(f, new)
2422            }
2423            Self::AddColumn(col) => write!(f, "ADD COLUMN {col}"),
2424            Self::DropColumn(name) => {
2425                f.write_str("DROP COLUMN ")?;
2426                write_ident(f, name)
2427            }
2428        }
2429    }
2430}
2431
2432// ---------------------------------------------------------------------------
2433// Transaction control
2434// ---------------------------------------------------------------------------
2435
2436impl fmt::Display for BeginStatement {
2437    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2438        f.write_str("BEGIN")?;
2439        if let Some(mode) = self.mode {
2440            write!(f, " {mode}")?;
2441        }
2442        Ok(())
2443    }
2444}
2445
2446impl fmt::Display for TransactionMode {
2447    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2448        f.write_str(match self {
2449            Self::Deferred => "DEFERRED",
2450            Self::Immediate => "IMMEDIATE",
2451            Self::Exclusive => "EXCLUSIVE",
2452            Self::Concurrent => "CONCURRENT",
2453        })
2454    }
2455}
2456
2457impl fmt::Display for RollbackStatement {
2458    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2459        f.write_str("ROLLBACK")?;
2460        if let Some(ref sp) = self.to_savepoint {
2461            f.write_str(" TO SAVEPOINT ")?;
2462            write_ident(f, sp)?;
2463        }
2464        Ok(())
2465    }
2466}
2467
2468// ---------------------------------------------------------------------------
2469// ATTACH / DETACH / PRAGMA / VACUUM
2470// ---------------------------------------------------------------------------
2471
2472impl fmt::Display for AttachStatement {
2473    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2474        write!(f, "ATTACH {} AS ", self.expr)?;
2475        write_ident(f, &self.schema)
2476    }
2477}
2478
2479impl fmt::Display for PragmaStatement {
2480    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2481        write!(f, "PRAGMA {}", self.name)?;
2482        if let Some(ref val) = self.value {
2483            match val {
2484                PragmaValue::Assign(e) => write!(f, " = {e}")?,
2485                PragmaValue::Call(e) => write!(f, "({e})")?,
2486            }
2487        }
2488        Ok(())
2489    }
2490}
2491
2492impl fmt::Display for VacuumStatement {
2493    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2494        f.write_str("VACUUM")?;
2495        if let Some(ref s) = self.schema {
2496            f.write_str(" ")?;
2497            write_ident(f, s)?;
2498        }
2499        if let Some(ref expr) = self.into {
2500            write!(f, " INTO {expr}")?;
2501        }
2502        Ok(())
2503    }
2504}
2505
2506// ---------------------------------------------------------------------------
2507// Top-level Statement
2508// ---------------------------------------------------------------------------
2509
2510impl fmt::Display for Statement {
2511    #[allow(clippy::too_many_lines)]
2512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2513        write_statement(f, self)
2514    }
2515}
2516
2517#[cfg(test)]
2518mod expr_display_tests {
2519    use super::*;
2520    use std::fmt::Write as _;
2521
2522    fn column(name: &str) -> Expr {
2523        Expr::Column(ColumnRef::bare(name), Span::ZERO)
2524    }
2525
2526    fn integer(value: i64) -> Expr {
2527        Expr::Literal(Literal::Integer(value), Span::ZERO)
2528    }
2529
2530    fn binary(left: Expr, op: BinaryOp, right: Expr) -> Expr {
2531        Expr::BinaryOp {
2532            left: Box::new(left),
2533            op,
2534            right: Box::new(right),
2535            span: Span::ZERO,
2536        }
2537    }
2538
2539    fn table_source(name: &str) -> TableOrSubquery {
2540        TableOrSubquery::Table {
2541            name: QualifiedName::bare(name),
2542            alias: None,
2543            index_hint: None,
2544            time_travel: None,
2545        }
2546    }
2547
2548    fn nested_from(height: usize) -> FromClause {
2549        let mut from = FromClause {
2550            source: table_source("leaf"),
2551            joins: Vec::new(),
2552        };
2553        for _ in 0..height {
2554            from = FromClause {
2555                source: TableOrSubquery::ParenJoin(Box::new(from)),
2556                joins: Vec::new(),
2557            };
2558        }
2559        from
2560    }
2561
2562    fn update_with_from(from: FromClause) -> UpdateStatement {
2563        UpdateStatement {
2564            with: None,
2565            or_conflict: None,
2566            table: QualifiedTableRef {
2567                name: QualifiedName::bare("target"),
2568                alias: None,
2569                index_hint: None,
2570                time_travel: None,
2571            },
2572            assignments: vec![Assignment {
2573                target: AssignmentTarget::Column("x".to_owned()),
2574                value: integer(1),
2575            }],
2576            from: Some(from),
2577            where_clause: None,
2578            returning: Vec::new(),
2579            order_by: Vec::new(),
2580            limit: None,
2581        }
2582    }
2583
2584    fn drop_table(name: &str) -> Statement {
2585        Statement::Drop(DropStatement {
2586            object_type: DropObjectType::Table,
2587            if_exists: false,
2588            name: QualifiedName::bare(name),
2589        })
2590    }
2591
2592    fn drop_from_iteratively(root: FromClause) {
2593        let mut from_clauses = vec![root];
2594        let mut tables = Vec::new();
2595        while let Some(FromClause { source, joins }) = from_clauses.pop() {
2596            tables.push(source);
2597            for join in joins {
2598                tables.push(join.table);
2599                drop(join.constraint);
2600            }
2601            while let Some(table) = tables.pop() {
2602                match table {
2603                    TableOrSubquery::ParenJoin(inner) => from_clauses.push(*inner),
2604                    leaf => drop(leaf),
2605                }
2606            }
2607        }
2608    }
2609
2610    fn drop_table_iteratively(table: TableOrSubquery) {
2611        match table {
2612            TableOrSubquery::ParenJoin(inner) => drop_from_iteratively(*inner),
2613            leaf => drop(leaf),
2614        }
2615    }
2616
2617    fn drop_select_core_iteratively(core: SelectCore) {
2618        match core {
2619            SelectCore::Select { from, .. } => {
2620                if let Some(from) = from {
2621                    drop_from_iteratively(from);
2622                }
2623            }
2624            SelectCore::Values(_) => {}
2625        }
2626    }
2627
2628    fn drop_select_body_iteratively(body: SelectBody) {
2629        drop_select_core_iteratively(body.select);
2630        for (_, core) in body.compounds {
2631            drop_select_core_iteratively(core);
2632        }
2633    }
2634
2635    fn drop_statement_iteratively(mut statement: Statement) {
2636        loop {
2637            match statement {
2638                Statement::Explain { stmt, .. } => statement = *stmt,
2639                Statement::CreateTrigger(mut trigger) if trigger.body.len() == 1 => {
2640                    statement = trigger
2641                        .body
2642                        .pop()
2643                        .expect("single trigger body statement must exist");
2644                }
2645                Statement::Update(mut update) => {
2646                    if let Some(from) = update.from.take() {
2647                        drop_from_iteratively(from);
2648                    }
2649                    break;
2650                }
2651                leaf => {
2652                    drop(leaf);
2653                    break;
2654                }
2655            }
2656        }
2657    }
2658
2659    fn drop_scalar_subquery_chain_iteratively(mut expr: Expr) {
2660        loop {
2661            match expr {
2662                Expr::Subquery(select, _) => {
2663                    let SelectStatement {
2664                        with,
2665                        body,
2666                        order_by,
2667                        limit,
2668                    } = *select;
2669                    assert!(with.is_none());
2670                    assert!(order_by.is_empty());
2671                    assert!(limit.is_none());
2672                    let SelectBody { select, compounds } = body;
2673                    assert!(compounds.is_empty());
2674                    let SelectCore::Select {
2675                        mut columns,
2676                        from,
2677                        where_clause,
2678                        group_by,
2679                        having,
2680                        windows,
2681                        ..
2682                    } = select
2683                    else {
2684                        panic!("scalar-subquery chain must contain SELECT cores");
2685                    };
2686                    assert!(from.is_none());
2687                    assert!(where_clause.is_none());
2688                    assert!(group_by.is_empty());
2689                    assert!(having.is_none());
2690                    assert!(windows.is_empty());
2691                    assert_eq!(columns.len(), 1);
2692                    let ResultColumn::Expr { expr: child, alias } = columns
2693                        .pop()
2694                        .expect("scalar-subquery SELECT must contain one column")
2695                    else {
2696                        panic!("scalar-subquery SELECT column must be an expression");
2697                    };
2698                    assert!(alias.is_none());
2699                    expr = child;
2700                }
2701                leaf => {
2702                    drop(leaf);
2703                    break;
2704                }
2705            }
2706        }
2707    }
2708
2709    fn format_statement_on_one_mib_stack(statement: Statement) -> String {
2710        let (rendered, statement) = std::thread::Builder::new()
2711            .stack_size(1024 * 1024)
2712            .spawn(move || (statement.to_string(), statement))
2713            .expect("1 MiB formatter thread must spawn")
2714            .join()
2715            .expect("formatting on a 1 MiB stack must not overflow");
2716        drop_statement_iteratively(statement);
2717        rendered
2718    }
2719
2720    #[test]
2721    fn expression_task_stack_uses_inline_boundary_and_preserves_lifo() {
2722        let mut stack = ExprWriteTaskStack::new(ExprWriteTask::Text("first"));
2723        stack.push(ExprWriteTask::Text("second"));
2724        stack.push(ExprWriteTask::Text("third"));
2725        assert!(matches!(stack.pop(), Some(ExprWriteTask::Text("third"))));
2726        assert!(matches!(stack.pop(), Some(ExprWriteTask::Text("second"))));
2727        assert!(matches!(stack.pop(), Some(ExprWriteTask::Text("first"))));
2728
2729        let mut boundary = ExprWriteTaskStack::new(ExprWriteTask::Text("inline"));
2730        for _ in 1..INLINE_EXPR_WRITE_TASKS {
2731            boundary.push(ExprWriteTask::Text("inline"));
2732        }
2733        assert!(!boundary.tasks.spilled());
2734        boundary.push(ExprWriteTask::Text("spill"));
2735        assert!(boundary.tasks.spilled());
2736        while boundary.pop().is_some() {}
2737        assert!(
2738            boundary.stats().spilled,
2739            "spill history must survive draining the task stack"
2740        );
2741    }
2742
2743    #[test]
2744    fn formatter_error_resets_task_stack_stats() {
2745        struct FailingWriter;
2746
2747        impl fmt::Write for FailingWriter {
2748            fn write_str(&mut self, _: &str) -> fmt::Result {
2749                Err(fmt::Error)
2750            }
2751        }
2752
2753        let mut deep = integer(1);
2754        for _ in 1..100 {
2755            deep = binary(deep, BinaryOp::Add, integer(1));
2756        }
2757        let _ = deep.to_string();
2758        assert!(
2759            LAST_EXPR_WRITE_TASK_STACK_STATS
2760                .with(std::cell::Cell::get)
2761                .spilled
2762        );
2763
2764        let mut writer = FailingWriter;
2765        write!(&mut writer, "{}", column("value"))
2766            .expect_err("the test writer must reject formatter output");
2767        assert_eq!(
2768            LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get),
2769            ExprWriteTaskStackStats::default(),
2770            "an early formatter error must not expose the previous call's task stats"
2771        );
2772    }
2773
2774    #[test]
2775    fn public_from_roots_format_height_1000_and_1001_on_one_mib_stack() {
2776        for height in [1000, 1001] {
2777            let from_root = nested_from(height);
2778            let table_root = TableOrSubquery::ParenJoin(Box::new(nested_from(height)));
2779            let join_root = JoinClause {
2780                join_type: JoinType {
2781                    natural: false,
2782                    kind: JoinKind::Inner,
2783                },
2784                table: TableOrSubquery::ParenJoin(Box::new(nested_from(height))),
2785                constraint: None,
2786            };
2787            let select_core_root = SelectCore::Select {
2788                distinct: Distinctness::All,
2789                columns: vec![ResultColumn::Star],
2790                from: Some(nested_from(height)),
2791                where_clause: None,
2792                group_by: Vec::new(),
2793                having: None,
2794                windows: Vec::new(),
2795            };
2796            let select_body_root = SelectBody {
2797                select: SelectCore::Select {
2798                    distinct: Distinctness::All,
2799                    columns: vec![ResultColumn::Star],
2800                    from: Some(nested_from(height)),
2801                    where_clause: None,
2802                    group_by: Vec::new(),
2803                    having: None,
2804                    windows: Vec::new(),
2805                },
2806                compounds: Vec::new(),
2807            };
2808            let update_root = Statement::Update(update_with_from(nested_from(height)));
2809
2810            let (rendered, roots) = std::thread::Builder::new()
2811                .stack_size(1024 * 1024)
2812                .spawn(move || {
2813                    let rendered = [
2814                        from_root.to_string(),
2815                        table_root.to_string(),
2816                        join_root.to_string(),
2817                        select_core_root.to_string(),
2818                        select_body_root.to_string(),
2819                        update_root.to_string(),
2820                    ];
2821                    (
2822                        rendered,
2823                        (
2824                            from_root,
2825                            table_root,
2826                            join_root,
2827                            select_core_root,
2828                            select_body_root,
2829                            update_root,
2830                        ),
2831                    )
2832                })
2833                .expect("1 MiB formatter thread must spawn")
2834                .join()
2835                .expect("all public FROM roots must format without stack overflow");
2836            let (
2837                from_root,
2838                table_root,
2839                JoinClause {
2840                    table: join_table, ..
2841                },
2842                select_core_root,
2843                select_body_root,
2844                update_root,
2845            ) = roots;
2846            drop_from_iteratively(from_root);
2847            drop_table_iteratively(table_root);
2848            drop_table_iteratively(join_table);
2849            drop_select_core_iteratively(select_core_root);
2850            drop_select_body_iteratively(select_body_root);
2851            drop_statement_iteratively(update_root);
2852
2853            let expected_parentheses = [height, height + 1, height + 1, height, height, height];
2854            for (sql, expected) in rendered.iter().zip(expected_parentheses) {
2855                assert_eq!(sql.matches('(').count(), expected);
2856                assert_eq!(sql.matches(')').count(), expected);
2857                assert!(sql.contains("leaf"));
2858            }
2859            assert!(rendered[2].starts_with("INNER JOIN "));
2860            assert!(rendered[3].starts_with("SELECT * FROM "));
2861            assert!(rendered[4].starts_with("SELECT * FROM "));
2862            assert!(rendered[5].starts_with("UPDATE target SET x = 1 FROM "));
2863        }
2864    }
2865
2866    #[test]
2867    fn nested_explain_height_1000_and_1001_formats_on_one_mib_stack() {
2868        for height in [1000, 1001] {
2869            let mut statement = drop_table("leaf");
2870            for level in 0..height {
2871                statement = Statement::Explain {
2872                    query_plan: level % 2 == 0,
2873                    stmt: Box::new(statement),
2874                };
2875            }
2876
2877            let rendered = format_statement_on_one_mib_stack(statement);
2878            let mut tail = rendered.as_str();
2879            for level in (0..height).rev() {
2880                let prefix = if level % 2 == 0 {
2881                    "EXPLAIN QUERY PLAN "
2882                } else {
2883                    "EXPLAIN "
2884                };
2885                tail = tail
2886                    .strip_prefix(prefix)
2887                    .expect("EXPLAIN wrappers must retain their exact order");
2888            }
2889            assert_eq!(tail, "DROP TABLE leaf");
2890        }
2891    }
2892
2893    #[test]
2894    fn nested_trigger_body_height_1000_and_1001_formats_on_one_mib_stack() {
2895        for height in [1000, 1001] {
2896            let mut statement = drop_table("leaf");
2897            for level in 0..height {
2898                statement = Statement::CreateTrigger(CreateTriggerStatement {
2899                    if_not_exists: false,
2900                    temporary: false,
2901                    name: QualifiedName::bare(format!("trigger_{level}")),
2902                    timing: TriggerTiming::After,
2903                    event: TriggerEvent::Insert,
2904                    table: "target".to_owned(),
2905                    for_each_row: false,
2906                    when: None,
2907                    body: vec![statement],
2908                });
2909            }
2910
2911            let rendered = format_statement_on_one_mib_stack(statement);
2912            assert_eq!(rendered.matches("CREATE TRIGGER ").count(), height);
2913            assert_eq!(rendered.matches("; END").count(), height);
2914            assert!(rendered.contains("DROP TABLE leaf"));
2915            assert!(rendered.ends_with("END"));
2916        }
2917    }
2918
2919    #[test]
2920    fn iterative_public_roots_preserve_shallow_sql() {
2921        let joined = FromClause {
2922            source: table_source("a"),
2923            joins: vec![JoinClause {
2924                join_type: JoinType {
2925                    natural: false,
2926                    kind: JoinKind::Inner,
2927                },
2928                table: table_source("b"),
2929                constraint: Some(JoinConstraint::On(binary(
2930                    column("a_id"),
2931                    BinaryOp::Eq,
2932                    column("b_id"),
2933                ))),
2934            }],
2935        };
2936        assert_eq!(joined.to_string(), "a INNER JOIN b ON a_id = b_id");
2937
2938        let body = SelectBody {
2939            select: SelectCore::Select {
2940                distinct: Distinctness::All,
2941                columns: vec![ResultColumn::Star],
2942                from: Some(joined),
2943                where_clause: None,
2944                group_by: Vec::new(),
2945                having: None,
2946                windows: Vec::new(),
2947            },
2948            compounds: Vec::new(),
2949        };
2950        assert_eq!(
2951            body.to_string(),
2952            "SELECT * FROM a INNER JOIN b ON a_id = b_id"
2953        );
2954
2955        let compounds = SelectBody {
2956            select: SelectCore::Values(vec![vec![integer(1)]].into()),
2957            compounds: vec![
2958                (
2959                    CompoundOp::UnionAll,
2960                    SelectCore::Values(vec![vec![integer(2)]].into()),
2961                ),
2962                (
2963                    CompoundOp::Except,
2964                    SelectCore::Values(vec![vec![integer(3)]].into()),
2965                ),
2966            ],
2967        };
2968        assert_eq!(
2969            compounds.to_string(),
2970            "VALUES (1) UNION ALL VALUES (2) EXCEPT VALUES (3)"
2971        );
2972
2973        let update = UpdateStatement {
2974            with: None,
2975            or_conflict: None,
2976            table: QualifiedTableRef {
2977                name: QualifiedName::bare("target"),
2978                alias: None,
2979                index_hint: None,
2980                time_travel: None,
2981            },
2982            assignments: vec![Assignment {
2983                target: AssignmentTarget::Column("x".to_owned()),
2984                value: integer(1),
2985            }],
2986            from: Some(FromClause {
2987                source: table_source("source"),
2988                joins: Vec::new(),
2989            }),
2990            where_clause: Some(binary(column("id"), BinaryOp::Eq, integer(7))),
2991            returning: vec![ResultColumn::Expr {
2992                expr: column("x"),
2993                alias: Some("updated".to_owned()),
2994            }],
2995            order_by: vec![OrderingTerm {
2996                expr: column("id"),
2997                direction: Some(SortDirection::Desc),
2998                nulls: Some(NullsOrder::Last),
2999            }],
3000            limit: Some(LimitClause {
3001                limit: integer(10),
3002                offset: Some(integer(2)),
3003            }),
3004        };
3005        assert_eq!(
3006            update.to_string(),
3007            "UPDATE target SET x = 1 FROM source WHERE id = 7 RETURNING x AS updated \
3008             ORDER BY id DESC NULLS LAST LIMIT 10 OFFSET 2"
3009        );
3010
3011        let explained = Statement::Explain {
3012            query_plan: true,
3013            stmt: Box::new(drop_table("old")),
3014        };
3015        assert_eq!(explained.to_string(), "EXPLAIN QUERY PLAN DROP TABLE old");
3016
3017        let trigger = CreateTriggerStatement {
3018            if_not_exists: false,
3019            temporary: false,
3020            name: QualifiedName::bare("tr"),
3021            timing: TriggerTiming::After,
3022            event: TriggerEvent::Insert,
3023            table: "target".to_owned(),
3024            for_each_row: false,
3025            when: None,
3026            body: vec![drop_table("old"), drop_table("older")],
3027        };
3028        assert_eq!(
3029            trigger.to_string(),
3030            "CREATE TRIGGER tr AFTER INSERT ON target BEGIN \
3031             DROP TABLE old; DROP TABLE older; END"
3032        );
3033    }
3034
3035    #[test]
3036    fn representative_rich_select_stays_in_inline_task_stack() {
3037        let cte_query = SelectStatement {
3038            with: None,
3039            body: SelectBody {
3040                select: SelectCore::Values(vec![vec![integer(1)]].into()),
3041                compounds: Vec::new(),
3042            },
3043            order_by: Vec::new(),
3044            limit: None,
3045        };
3046        let select = SelectStatement {
3047            with: Some(WithClause {
3048                recursive: false,
3049                ctes: vec![Cte {
3050                    name: "seed".to_owned(),
3051                    columns: vec!["id".to_owned()],
3052                    materialized: Some(CteMaterialized::NotMaterialized),
3053                    query: cte_query,
3054                }],
3055            }),
3056            body: SelectBody {
3057                select: SelectCore::Select {
3058                    distinct: Distinctness::Distinct,
3059                    columns: vec![
3060                        ResultColumn::Expr {
3061                            expr: column("a_id"),
3062                            alias: Some("id".to_owned()),
3063                        },
3064                        ResultColumn::Expr {
3065                            expr: column("b_value"),
3066                            alias: None,
3067                        },
3068                    ],
3069                    from: Some(FromClause {
3070                        source: table_source("a"),
3071                        joins: vec![JoinClause {
3072                            join_type: JoinType {
3073                                natural: false,
3074                                kind: JoinKind::Left,
3075                            },
3076                            table: table_source("b"),
3077                            constraint: Some(JoinConstraint::On(binary(
3078                                column("a_id"),
3079                                BinaryOp::Eq,
3080                                column("b_id"),
3081                            ))),
3082                        }],
3083                    }),
3084                    where_clause: Some(Box::new(binary(column("a_id"), BinaryOp::Gt, integer(0)))),
3085                    group_by: vec![column("a_id")],
3086                    having: Some(Box::new(binary(
3087                        column("b_value"),
3088                        BinaryOp::IsNot,
3089                        Expr::Literal(Literal::Null, Span::ZERO),
3090                    ))),
3091                    windows: vec![WindowDef {
3092                        name: "w".to_owned(),
3093                        spec: WindowSpec {
3094                            window_ref: None,
3095                            partition_by: vec![column("a_id")],
3096                            order_by: vec![OrderingTerm {
3097                                expr: column("b_value"),
3098                                direction: Some(SortDirection::Desc),
3099                                nulls: None,
3100                            }],
3101                            frame: None,
3102                        },
3103                    }],
3104                },
3105                compounds: Vec::new(),
3106            },
3107            order_by: vec![OrderingTerm {
3108                expr: column("a_id"),
3109                direction: Some(SortDirection::Asc),
3110                nulls: Some(NullsOrder::First),
3111            }],
3112            limit: Some(LimitClause {
3113                limit: integer(25),
3114                offset: Some(integer(5)),
3115            }),
3116        };
3117
3118        assert_eq!(
3119            select.to_string(),
3120            "WITH seed(id) AS NOT MATERIALIZED (VALUES (1)) \
3121             SELECT DISTINCT a_id AS id, b_value FROM a \
3122             LEFT JOIN b ON a_id = b_id WHERE a_id > 0 GROUP BY a_id \
3123             HAVING b_value IS NOT NULL WINDOW w AS \
3124             (PARTITION BY a_id ORDER BY b_value DESC) \
3125             ORDER BY a_id ASC NULLS FIRST LIMIT 25 OFFSET 5"
3126        );
3127        let task_stats = LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get);
3128        assert!(
3129            !task_stats.spilled,
3130            "representative rich SELECT should remain in the inline task stack"
3131        );
3132        assert!(task_stats.peak_len <= INLINE_EXPR_WRITE_TASKS);
3133    }
3134
3135    #[test]
3136    fn binary_operands_use_minimal_semantics_preserving_parentheses() {
3137        let tighter_right = binary(
3138            column("a"),
3139            BinaryOp::Add,
3140            binary(column("b"), BinaryOp::Multiply, integer(2)),
3141        );
3142        assert_eq!(tighter_right.to_string(), "a + b * 2");
3143
3144        let looser_right = binary(
3145            column("a"),
3146            BinaryOp::Multiply,
3147            binary(column("b"), BinaryOp::Add, column("c")),
3148        );
3149        assert_eq!(looser_right.to_string(), "a * (b + c)");
3150
3151        let left_associative = binary(
3152            binary(column("a"), BinaryOp::Subtract, column("b")),
3153            BinaryOp::Subtract,
3154            column("c"),
3155        );
3156        assert_eq!(left_associative.to_string(), "a - b - c");
3157
3158        let right_subtract = binary(
3159            column("a"),
3160            BinaryOp::Subtract,
3161            binary(column("b"), BinaryOp::Subtract, column("c")),
3162        );
3163        assert_eq!(right_subtract.to_string(), "a - (b - c)");
3164
3165        let right_divide = binary(
3166            column("a"),
3167            BinaryOp::Divide,
3168            binary(column("b"), BinaryOp::Divide, column("c")),
3169        );
3170        assert_eq!(right_divide.to_string(), "a / (b / c)");
3171
3172        let and_chain = binary(
3173            column("a"),
3174            BinaryOp::And,
3175            binary(column("b"), BinaryOp::And, column("c")),
3176        );
3177        assert_eq!(and_chain.to_string(), "a AND b AND c");
3178
3179        let or_chain = binary(
3180            column("a"),
3181            BinaryOp::Or,
3182            binary(column("b"), BinaryOp::Or, column("c")),
3183        );
3184        assert_eq!(or_chain.to_string(), "a OR b OR c");
3185    }
3186
3187    #[test]
3188    fn expression_display_height_1000_uses_bounded_work_stack() {
3189        let mut expr = Expr::Literal(Literal::Integer(1), Span::ZERO);
3190        for _ in 1..1000 {
3191            expr = Expr::BinaryOp {
3192                left: Box::new(expr),
3193                op: BinaryOp::Add,
3194                right: Box::new(Expr::Literal(Literal::Integer(1), Span::ZERO)),
3195                span: Span::ZERO,
3196            };
3197        }
3198
3199        let rendered = expr.to_string();
3200        let task_stats = LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get);
3201        assert!(
3202            task_stats.spilled,
3203            "height-1000 expression should exercise the heap spill path"
3204        );
3205        assert!(task_stats.peak_len > INLINE_EXPR_WRITE_TASKS);
3206        assert_eq!(rendered.matches('+').count(), 999);
3207        assert!(rendered.ends_with(" + 1"));
3208    }
3209
3210    #[test]
3211    fn scalar_subquery_display_height_1000_uses_one_mib_stack() {
3212        let mut expr = Expr::Literal(Literal::Integer(1), Span::ZERO);
3213        for _ in 1..1000 {
3214            expr = Expr::Subquery(
3215                Box::new(SelectStatement {
3216                    with: None,
3217                    body: SelectBody {
3218                        select: SelectCore::Select {
3219                            distinct: Distinctness::All,
3220                            columns: vec![ResultColumn::Expr { expr, alias: None }],
3221                            from: None,
3222                            where_clause: None,
3223                            group_by: Vec::new(),
3224                            having: None,
3225                            windows: Vec::new(),
3226                        },
3227                        compounds: Vec::new(),
3228                    },
3229                    order_by: Vec::new(),
3230                    limit: None,
3231                }),
3232                Span::ZERO,
3233            );
3234        }
3235
3236        let (rendered, expr) = std::thread::Builder::new()
3237            .stack_size(1024 * 1024)
3238            .spawn(move || {
3239                let rendered = expr.to_string();
3240                (rendered, expr)
3241            })
3242            .expect("1 MiB formatter thread must spawn")
3243            .join()
3244            .expect("height-1000 scalar subquery formatting must not overflow");
3245        drop_scalar_subquery_chain_iteratively(expr);
3246        assert_eq!(rendered.matches("(SELECT ").count(), 999);
3247        assert!(rendered.ends_with(&")".repeat(999)));
3248    }
3249
3250    #[test]
3251    fn negative_literal_operands_cannot_merge_into_sql_comments() {
3252        let integer = Expr::UnaryOp {
3253            op: UnaryOp::Negate,
3254            expr: Box::new(Expr::Literal(Literal::Integer(i64::MIN), Span::ZERO)),
3255            span: Span::ZERO,
3256        };
3257        assert_eq!(integer.to_string(), "-(-9223372036854775808)");
3258
3259        let negative_zero = Expr::UnaryOp {
3260            op: UnaryOp::Negate,
3261            expr: Box::new(Expr::Literal(Literal::Float(-0.0), Span::ZERO)),
3262            span: Span::ZERO,
3263        };
3264        assert_eq!(negative_zero.to_string(), "-(-0.0)");
3265
3266        let bound = Expr::UnaryOp {
3267            op: UnaryOp::Negate,
3268            expr: Box::new(Expr::BoundOuterValue {
3269                value: SqliteValue::Integer(-1),
3270                collation: BoundCollation::Unspecified,
3271                affinity: None,
3272                span: Span::ZERO,
3273            }),
3274            span: Span::ZERO,
3275        };
3276        assert_eq!(bound.to_string(), "-(-1)");
3277    }
3278
3279    #[test]
3280    fn like_escape_operand_preserves_comparison_grouping() {
3281        let expr = Expr::Like {
3282            expr: Box::new(column("value")),
3283            pattern: Box::new(column("pattern")),
3284            escape: Some(Box::new(binary(
3285                column("lower"),
3286                BinaryOp::Lt,
3287                column("upper"),
3288            ))),
3289            op: LikeOp::Like,
3290            not: false,
3291            span: Span::ZERO,
3292        };
3293        assert_eq!(
3294            expr.to_string(),
3295            "value LIKE pattern ESCAPE (lower < upper)"
3296        );
3297    }
3298
3299    #[test]
3300    fn bound_outer_values_render_as_safe_literals_without_metadata() {
3301        let bound = |value| Expr::BoundOuterValue {
3302            value,
3303            collation: BoundCollation::Named("NOCASE".to_owned()),
3304            affinity: Some(fsqlite_types::TypeAffinity::Text),
3305            span: Span::new(7, 19),
3306        };
3307
3308        assert_eq!(bound(SqliteValue::Null).to_string(), "NULL");
3309        assert_eq!(bound(SqliteValue::Integer(-7)).to_string(), "-7");
3310        assert_eq!(bound(SqliteValue::Float(1.0)).to_string(), "1.0");
3311        assert_eq!(
3312            bound(SqliteValue::Text("O'Brien".into())).to_string(),
3313            "'O''Brien'"
3314        );
3315        assert_eq!(
3316            bound(SqliteValue::Blob(vec![0x00, 0xAB].into())).to_string(),
3317            "X'00AB'"
3318        );
3319    }
3320
3321    #[test]
3322    fn infinite_float_literals_render_as_numeric_sql() {
3323        assert_eq!(Literal::Float(f64::INFINITY).to_string(), "9e999");
3324        assert_eq!(Literal::Float(f64::NEG_INFINITY).to_string(), "-9e999");
3325        assert_eq!(Literal::Float(f64::NAN).to_string(), "NULL");
3326        assert_eq!(Literal::Float(-f64::NAN).to_string(), "NULL");
3327    }
3328
3329    #[test]
3330    fn collation_names_use_identifier_quoting() {
3331        let expr = Expr::Collate {
3332            expr: Box::new(column("value")),
3333            collation: "my col".to_owned(),
3334            span: Span::ZERO,
3335        };
3336        assert_eq!(expr.to_string(), "value COLLATE \"my col\"");
3337    }
3338
3339    #[test]
3340    fn window_reference_form_is_preserved_exactly() {
3341        let extended = Expr::FunctionCall {
3342            name: "sum".to_owned(),
3343            args: FunctionArgs::List(vec![column("x")]),
3344            distinct: false,
3345            order_by: Vec::new(),
3346            filter: None,
3347            over: Some(WindowSpec {
3348                window_ref: Some(WindowReference::Base("base".to_owned())),
3349                partition_by: vec![column("p")],
3350                order_by: vec![OrderingTerm {
3351                    expr: column("y"),
3352                    direction: None,
3353                    nulls: None,
3354                }],
3355                frame: Some(FrameSpec {
3356                    frame_type: FrameType::Rows,
3357                    start: FrameBound::Preceding(Box::new(column("z"))),
3358                    end: Some(FrameBound::CurrentRow),
3359                    exclude: None,
3360                }),
3361            }),
3362            span: Span::ZERO,
3363        };
3364        assert_eq!(
3365            extended.to_string(),
3366            "sum(x) OVER (base PARTITION BY p ORDER BY y ROWS BETWEEN z PRECEDING AND CURRENT ROW)"
3367        );
3368        let task_stats = LAST_EXPR_WRITE_TASK_STACK_STATS.with(std::cell::Cell::get);
3369        assert!(
3370            !task_stats.spilled,
3371            "representative window expression should remain in the inline task stack"
3372        );
3373        assert!(task_stats.peak_len <= INLINE_EXPR_WRITE_TASKS);
3374
3375        let bare = Expr::FunctionCall {
3376            name: "sum".to_owned(),
3377            args: FunctionArgs::List(vec![column("x")]),
3378            distinct: false,
3379            order_by: Vec::new(),
3380            filter: None,
3381            over: Some(WindowSpec {
3382                window_ref: Some(WindowReference::Direct("base".to_owned())),
3383                partition_by: Vec::new(),
3384                order_by: Vec::new(),
3385                frame: None,
3386            }),
3387            span: Span::ZERO,
3388        };
3389        assert_eq!(bare.to_string(), "sum(x) OVER base");
3390        let Expr::FunctionCall {
3391            over: Some(bare_window),
3392            ..
3393        } = &bare
3394        else {
3395            panic!("bare window function should carry a window");
3396        };
3397        assert_eq!(bare_window.to_string(), "base");
3398
3399        let parenthesized = Expr::FunctionCall {
3400            name: "sum".to_owned(),
3401            args: FunctionArgs::List(vec![column("x")]),
3402            distinct: false,
3403            order_by: Vec::new(),
3404            filter: None,
3405            over: Some(WindowSpec {
3406                window_ref: Some(WindowReference::Base("base".to_owned())),
3407                partition_by: Vec::new(),
3408                order_by: Vec::new(),
3409                frame: None,
3410            }),
3411            span: Span::ZERO,
3412        };
3413        assert_eq!(parenthesized.to_string(), "sum(x) OVER (base)");
3414        let Expr::FunctionCall {
3415            over: Some(parenthesized_window),
3416            ..
3417        } = &parenthesized
3418        else {
3419            panic!("parenthesized window function should carry a window");
3420        };
3421        assert_eq!(parenthesized_window.to_string(), "(base)");
3422    }
3423}