Skip to main content

squonk_ast/render/
mod.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! SQL rendering: the `Render` trait, `RenderCtx` / `RenderConfig` / `RenderMode`,
5//! the `Displayed` wrapper, and the Tier-1 canonical renderer.
6//!
7//! Nodes are non-self-describing: an `Ident` is a `Symbol`, a literal is a byte
8//! range. So `std::fmt::Display`, whose `fmt` takes no extra
9//! arguments, cannot reach the resolver or source needed to spell a node. The
10//! renderer therefore threads a [`RenderCtx`] through a dedicated [`Render`]
11//! trait, and [`Displayed`] re-enables `Display` at any boundary by pairing a
12//! node with its context.
13//!
14//! That canonical path is exact but strict: it assumes the resolver and source
15//! match the node's parse, and panics on a symbol they cannot back. For the narrow
16//! case of debugging a *detached* node whose context may not match, [`RenderCtx::debug`]
17//! and the [`DebugSql`] wrapper (via [`RenderExt::debug_sql`]) add an opt-in,
18//! resolver-explicit path that tolerates unresolvable symbols and spans with a
19//! placeholder instead of panicking — the debug-SQL mitigation, realized as
20//! an explicit argument rather than a hidden thread-local, so
21//! normal rendering keeps no hidden global behaviour.
22
23use crate::ast::{DataType, Expr, Extension, Query, Statement};
24use crate::dialect::FeatureSet;
25use crate::precedence::BindingPower;
26use crate::vocab::{NodeId, Resolver, Span, Symbol};
27use std::fmt;
28
29mod dyn_ext;
30mod nodes;
31
32pub use dyn_ext::{DynAstExt, DynExt};
33pub use nodes::{render_extension_infix, render_extension_prefix};
34
35#[cfg(test)]
36mod tests;
37
38/// Render an AST node to SQL text using the resolver, source, and config carried
39/// by `ctx`.
40pub trait Render {
41    /// Return the render for this value.
42    fn render(&self, ctx: &RenderCtx<'_>, f: &mut fmt::Formatter<'_>) -> fmt::Result;
43
44    /// The binding power this node contributes when it appears as an operand, or
45    /// `None` (the default) for a self-delimiting node — an atom, call, or
46    /// constructor — that never needs parentheses.
47    ///
48    /// An extension operator node (the `X` inside [`Expr::Other`]) overrides this to
49    /// report its own precedence, so a custom-operator tree is parenthesized by the
50    /// same binding-power rule as the built-in operators and round-trips through the
51    /// renderer. The value MUST equal the binding power the dialect's
52    /// `peek_infix_operator_hook` reported for the same operator, so parse-time
53    /// climbing and render-time grouping stay one source of truth.
54    ///
55    /// [`Expr::Other`]: crate::ast::Expr::Other
56    fn operand_binding_power(&self) -> Option<BindingPower> {
57        None
58    }
59}
60
61/// Fallible render result used by Tier-2 dialect-target renderers.
62pub type RenderResult<T> = Result<T, RenderError>;
63
64/// Why a fallible render operation failed.
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct RenderError {
67    kind: RenderErrorKind,
68    span: Option<Span>,
69    message: String,
70}
71
72/// Stable category for render failures.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum RenderErrorKind {
75    /// The target dialect cannot express the AST construct being rendered.
76    Unsupported,
77    /// The underlying formatter reported an error.
78    Format,
79}
80
81impl RenderError {
82    /// Build an unsupported-target diagnostic.
83    pub fn unsupported(span: Option<Span>, message: impl Into<String>) -> Self {
84        Self {
85            kind: RenderErrorKind::Unsupported,
86            span,
87            message: message.into(),
88        }
89    }
90
91    /// The coarse render error category.
92    pub fn kind(&self) -> RenderErrorKind {
93        self.kind
94    }
95
96    /// The source span of the unsupported construct, when known.
97    pub fn span(&self) -> Option<Span> {
98        self.span
99    }
100
101    /// Human-readable diagnostic text.
102    pub fn message(&self) -> &str {
103        &self.message
104    }
105}
106
107impl fmt::Display for RenderError {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        match self.span {
110            Some(span) if !span.is_synthetic() => {
111                write!(
112                    f,
113                    "{} at bytes {}..{}",
114                    self.message,
115                    span.start(),
116                    span.end()
117                )
118            }
119            _ => f.write_str(&self.message),
120        }
121    }
122}
123
124impl std::error::Error for RenderError {}
125
126impl From<fmt::Error> for RenderError {
127    fn from(_error: fmt::Error) -> Self {
128        Self {
129            kind: RenderErrorKind::Format,
130            span: None,
131            message: "render formatter failed".to_owned(),
132        }
133    }
134}
135
136/// How a [`Render`] impl spells a node.
137#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
138pub enum RenderMode {
139    /// Round-trippable SQL with the minimal parentheses the binding-power table
140    /// requires.
141    #[default]
142    Canonical,
143    /// Every binary/unary subexpression fully parenthesized: the precedence
144    /// oracle used by testing.
145    Parenthesized,
146    /// Identifier and literal *content* replaced by fixed placeholders — `id` for
147    /// every identifier, `?` for every literal — while query *shape* is preserved,
148    /// yielding a stable, PII-free query fingerprint.
149    ///
150    /// Two statements produce the **same** redacted string exactly when they differ
151    /// only along a dimension masking erases:
152    /// - identifier spelling: every name (column, table, alias, function, and each
153    ///   qualified part) becomes `id`, so `a` and `b` coincide;
154    /// - identifier quoting: the delimiters are dropped before the symbol is
155    ///   resolved, so `a`, `"a"`, `` `a` ``, and `[a]` all become `id` (a keyword
156    ///   spelled as an identifier masks the same way);
157    /// - literal value: every literal becomes `?`, so `1` and `999`, `'x'` and a
158    ///   secret string, `TRUE` and `NULL` coincide;
159    /// - keyword casing: canonical rendering upper-cases keywords, so `select` and
160    ///   `SELECT` coincide.
161    ///
162    /// Query shape is **kept**, so the fingerprint still separates statements that
163    /// differ in clause structure, projection/list arity, qualified-name arity
164    /// (`id` vs `id.id`), operators or keywords (`=` vs `<`, `AND` vs `OR`, `SELECT`
165    /// vs `SELECT DISTINCT`), or the parentheses the binding-power table requires.
166    ///
167    /// The result is a fingerprint, **not** guaranteed re-parseable — only
168    /// [`Canonical`](RenderMode::Canonical) round-trips. A masked literal renders as
169    /// `?`, the anonymous-parameter sigil, which only lexes under a dialect that
170    /// enables it (neither ANSI nor PostgreSQL do) and, even then, would re-parse as
171    /// a *parameter* rather than the literal it replaced. An identifier-only
172    /// redaction happens to re-parse, but that is incidental, not a promise.
173    Redacted,
174}
175
176/// How renderable surface tags are spelled.
177#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
178pub enum RenderSpelling {
179    /// Preserve the AST's source spelling tags while emitting normalized uppercase SQL.
180    #[default]
181    PreserveSource,
182    /// Prefer spellings for the explicit target [`FeatureSet`].
183    TargetDialect,
184}
185
186/// Tunable rendering options.
187#[derive(Clone, Debug, PartialEq, Eq)]
188pub struct RenderConfig {
189    /// Mode selected by this syntax.
190    pub mode: RenderMode,
191    /// Object targeted by this syntax.
192    pub target: FeatureSet,
193    /// Exact source spelling retained for faithful rendering.
194    pub spelling: RenderSpelling,
195}
196
197impl RenderConfig {
198    /// The canonical default, as a `const` so a borrow of it (`&RenderConfig::DEFAULT`)
199    /// promotes to `'static` (rvalue static promotion) instead of living only as long
200    /// as some local binding. [`RenderExt::debug_sql`] needs exactly that: it builds a
201    /// [`RenderCtx`] with no caller-supplied config to borrow, and the [`DebugSql`] it
202    /// returns must keep that ctx alive past the constructing call, so a local
203    /// `RenderConfig::default()` temporary would not outlive the borrow.
204    const DEFAULT: RenderConfig = RenderConfig {
205        mode: RenderMode::Canonical,
206        target: FeatureSet::ANSI,
207        spelling: RenderSpelling::PreserveSource,
208    };
209}
210
211impl Default for RenderConfig {
212    fn default() -> Self {
213        Self::DEFAULT
214    }
215}
216
217/// Placeholder spelled for a [`Symbol`] the debug renderer cannot resolve.
218///
219/// Deliberately not a valid bare identifier (the angle brackets are operators), so
220/// a foreign or unknown symbol reads as an obvious debug artifact rather than a
221/// plausible-but-wrong name. Only the opt-in [`RenderCtx::debug`] path emits it;
222/// canonical rendering panics on an unresolvable symbol instead.
223const UNRESOLVED_SYMBOL: &str = "<unresolved>";
224
225/// Whether a [`RenderCtx`] takes the canonical render path or the opt-in debug path.
226///
227/// The distinction is confined to [`RenderCtx::resolve`] and [`RenderCtx::slice`]:
228/// every [`Render`] body is written once and is unaware of it. Canonical is the
229/// default; the debug path is reached only through [`RenderCtx::debug`] /
230/// [`RenderExt::debug_sql`], so normal rendering keeps no hidden global behaviour.
231#[derive(Clone, Copy, PartialEq, Eq)]
232enum ContextKind {
233    /// Canonical rendering: resolution is strict — a symbol foreign to
234    /// the resolver is a caller bug, so it panics — and literals slice their exact
235    /// source spelling.
236    Canonical,
237    /// Opt-in debug rendering: resolution is tolerant — a symbol the resolver does
238    /// not know renders the `UNRESOLVED_SYMBOL` placeholder instead of panicking — and literals
239    /// never slice source (a detached node's spans may index a *different* string),
240    /// so they fall back to their kind-based spelling.
241    Debug,
242}
243
244/// Resolver, source, and config threaded through every [`Render`] call.
245///
246/// The [`Resolver`] trait (not the concrete interner) lives in this crate, so the
247/// renderer stays independent of parser internals. `config` is *borrowed*, not
248/// owned: a multi-statement render builds one [`RenderCtx`] per statement (or per
249/// render — see the parser crate's `Parsed::to_sql`), and `RenderConfig` is large
250/// enough — it embeds the whole target [`FeatureSet`], keyword/precedence tables and
251/// all — that owning a fresh copy each time was a real per-statement memcpy of data
252/// that never changes across a render.
253pub struct RenderCtx<'a> {
254    resolver: &'a dyn Resolver,
255    source: &'a str,
256    config: &'a RenderConfig,
257    kind: ContextKind,
258}
259
260impl<'a> RenderCtx<'a> {
261    /// Build a canonical context from a resolver, the original source text, and
262    /// config.
263    ///
264    /// This is the canonical render path: the resolver and source must
265    /// come from the *same* parse as the node, because resolution is strict (a
266    /// foreign symbol panics) and literals slice their exact source spelling. Prefer
267    /// it — and the [`Displayed`] wrapper or the parser crate's directly-`Display`
268    /// `Parsed` root over it — whenever the matched context is in hand. For a
269    /// *detached* node whose context may not match, reach for the tolerant
270    /// [`debug`](Self::debug) sibling instead.
271    ///
272    /// `config` is borrowed, so building a ctx per statement (a multi-statement
273    /// render's loop) costs copying a pointer, never a `RenderConfig` copy.
274    pub fn new(resolver: &'a dyn Resolver, source: &'a str, config: &'a RenderConfig) -> Self {
275        Self {
276            resolver,
277            source,
278            config,
279            kind: ContextKind::Canonical,
280        }
281    }
282
283    /// Build the opt-in *debug* context: an explicitly-supplied resolver with
284    /// tolerant resolution and no source slicing (the debug-SQL mitigation).
285    ///
286    /// Use this — or the [`RenderExt::debug_sql`] convenience over it — to render a
287    /// *detached* or *synthesized* node for debugging, where the canonical
288    /// [`new`](Self::new) path's guarantees may not hold:
289    ///
290    /// - The resolver is an **explicit argument**, never a hidden global or default,
291    ///   so debug rendering can never *silently* pick the wrong resolver — the choice
292    ///   is always visible at the call site. (A resolver from a *different* parse can
293    ///   still mis-resolve a numerically-colliding symbol; only the canonical path,
294    ///   which travels with its own resolver, rules that out. See [`debug_sql`].)
295    /// - A symbol the resolver does not know renders the `UNRESOLVED_SYMBOL` placeholder instead of
296    ///   panicking, so a partially-detached tree renders to completion.
297    /// - Source is deliberately **not** an argument: debug rendering never slices it,
298    ///   so a literal always uses its kind-based spelling and can never emit
299    ///   unrelated bytes from a mismatched source. For exact literal text, use the
300    ///   canonical path, which owns the matched source.
301    ///
302    /// [`debug_sql`]: RenderExt::debug_sql
303    pub fn debug(resolver: &'a dyn Resolver, config: &'a RenderConfig) -> Self {
304        Self {
305            resolver,
306            // Debug rendering never slices source (see `slice`); the field is unused
307            // on this path, so an empty borrow keeps source out of the debug API.
308            source: "",
309            config,
310            kind: ContextKind::Debug,
311        }
312    }
313
314    /// The resolver used to turn [`Symbol`]s back into identifier text.
315    pub fn resolver(&self) -> &dyn Resolver {
316        self.resolver
317    }
318
319    /// The original source text that literals are sliced from.
320    pub fn source(&self) -> &str {
321        self.source
322    }
323
324    /// The active render configuration.
325    pub fn config(&self) -> &RenderConfig {
326        self.config
327    }
328
329    /// The active render mode.
330    pub fn mode(&self) -> RenderMode {
331        self.config.mode
332    }
333
334    /// The active target dialect feature set.
335    pub fn target(&self) -> &FeatureSet {
336        &self.config.target
337    }
338
339    /// The active spelling policy.
340    pub fn spelling(&self) -> RenderSpelling {
341        self.config.spelling
342    }
343
344    /// Resolve `sym` to its identifier text.
345    ///
346    /// # Panics
347    ///
348    /// On the canonical path ([`new`](Self::new)), panics if `sym` was not produced
349    /// by this context's resolver; a node and its resolver must come from the same
350    /// parse. The debug path ([`debug`](Self::debug)) never panics — an unknown
351    /// symbol resolves to the `UNRESOLVED_SYMBOL` placeholder instead.
352    fn resolve(&self, sym: Symbol) -> &str {
353        match self.kind {
354            ContextKind::Canonical => self.resolver.resolve(sym),
355            ContextKind::Debug => self.resolver.try_resolve(sym).unwrap_or(UNRESOLVED_SYMBOL),
356        }
357    }
358
359    /// Slice the source covered by `span`, or `None` when there is no backing text.
360    ///
361    /// Returns `None` for a synthetic / out-of-range span (a detached or
362    /// rewrite-synthesized node has no source text), and always for the debug path
363    /// ([`debug`](Self::debug)), which never trusts a detached node's spans against a
364    /// possibly-mismatched source. A `None` result is total: [`Literal`] falls back
365    /// to a kind-based spelling rather than panicking.
366    ///
367    /// [`Literal`]: crate::ast::Literal
368    fn slice(&self, span: Span) -> Option<&str> {
369        if self.kind == ContextKind::Debug || span.is_synthetic() {
370            return None;
371        }
372        self.source.get(span.start() as usize..span.end() as usize)
373    }
374}
375
376/// `Display` adapter pairing a node with its [`RenderCtx`] so `format!`,
377/// `to_string`, and `{}` work at any boundary.
378pub struct Displayed<'a, T: Render>(&'a T, &'a RenderCtx<'a>);
379
380impl<T: Render> fmt::Display for Displayed<'_, T> {
381    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
382        self.0.render(self.1, f)
383    }
384}
385
386/// `Display` adapter for the opt-in debug path: pairs a node with a resolver-only,
387/// tolerant [`RenderCtx`] so a *detached* node can be printed for debugging without
388/// building a full context (the debug-SQL mitigation).
389///
390/// Unlike [`Displayed`], this owns its context (built from the resolver alone), so
391/// [`RenderExt::debug_sql`] can return it directly. Rendering is tolerant: an
392/// unknown symbol becomes the `UNRESOLVED_SYMBOL` placeholder and literals use their kind-based
393/// spelling, so it never panics on a symbol or span the context cannot back. See
394/// [`debug_sql`](RenderExt::debug_sql) for when to prefer the canonical path.
395pub struct DebugSql<'a, T: Render> {
396    node: &'a T,
397    ctx: RenderCtx<'a>,
398}
399
400impl<T: Render> fmt::Display for DebugSql<'_, T> {
401    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402        self.node.render(&self.ctx, f)
403    }
404}
405
406/// Ergonomic constructors for the [`Displayed`] and [`DebugSql`] adapters,
407/// blanket-impl'd for every [`Render`] type.
408pub trait RenderExt: Render {
409    /// Pair this node with an explicit canonical [`RenderCtx`] so `format!`,
410    /// `to_string`, and `{}` render it. This is the canonical path: the
411    /// `ctx`'s resolver and source must match the node's parse.
412    fn displayed<'a>(&'a self, ctx: &'a RenderCtx<'a>) -> Displayed<'a, Self>
413    where
414        Self: Sized;
415
416    /// Render this node for **debugging** against an explicitly-supplied `resolver`
417    /// (the debug-SQL mitigation), returning a `Display` adapter.
418    ///
419    /// Reach for this only for a *detached* or *synthesized* node — one not behind a
420    /// `Parsed` root and possibly holding symbols the `resolver` on hand does not
421    /// own. **Prefer the canonical path first:** the `Parsed` root's `Display` and
422    /// [`displayed`](Self::displayed) render exact SQL because they travel with the
423    /// matched source and resolver, and cannot mismatch.
424    ///
425    /// This helper trades exactness for safety on a detached node:
426    ///
427    /// - The `resolver` is an **explicit argument** — never a hidden thread-local,
428    ///   global, or default — so it can never *silently* render with the wrong
429    ///   resolver: the choice is always written at the call site.
430    /// - A symbol the `resolver` does not know renders as `<unresolved>` rather than
431    ///   panicking, so a partially-detached tree still prints to completion.
432    /// - Literals use their kind-based spelling (`0`, `''`, `NULL`, …): debug
433    ///   rendering never slices source, so it cannot emit unrelated bytes.
434    ///
435    /// **Limitation.** An *explicit* argument cannot detect a resolver from a
436    /// *different* parse: a symbol whose numeric id happens to collide with an entry
437    /// in that resolver resolves to the *other* parse's text. The helper prevents a
438    /// silent *choice* of the wrong resolver and turns *unknown* symbols into a
439    /// visible placeholder, but only the canonical path — which owns its own matched
440    /// resolver — rules a colliding foreign resolver out entirely.
441    fn debug_sql<'a>(&'a self, resolver: &'a dyn Resolver) -> DebugSql<'a, Self>
442    where
443        Self: Sized,
444    {
445        DebugSql {
446            node: self,
447            // `&RenderConfig::DEFAULT` promotes to `'static` (it is a bare `const`
448            // path, the case `rustc` always promotes), which is what lets an owned
449            // `RenderCtx<'a>` borrow a config here with no caller-supplied place to
450            // borrow one from.
451            ctx: RenderCtx::debug(resolver, &RenderConfig::DEFAULT),
452        }
453    }
454}
455
456impl<T: Render> RenderExt for T {
457    fn displayed<'a>(&'a self, ctx: &'a RenderCtx<'a>) -> Displayed<'a, Self> {
458        Displayed(self, ctx)
459    }
460}
461
462/// The audited allowlist of node kinds that render to a **self-contained** SQL
463/// fragment — a marker trait sealed against outside impls so the set stays a
464/// closed, reviewable list.
465///
466/// Every AST node has a [`Render`] impl, but most render only a *piece* of a larger
467/// construct that is meaningless on its own: a bare `JoinConstraint` renders `ON a =
468/// b` (no join), a `SelectItem` renders `a AS x` (no `SELECT`), an `OrderByExpr`
469/// renders `a DESC` (no `ORDER BY`). Handing those to a fragment API would emit
470/// plausible-looking but context-dependent SQL a consumer cannot re-parse. The
471/// fragment entry points ([`Parsed::render_fragment`]) therefore accept only the
472/// kinds in this list, which each stand alone:
473///
474/// - [`Expr`] — a complete scalar expression;
475/// - [`Query`] — a complete `SELECT` / set-operation / `VALUES` query;
476/// - [`Statement`] — a complete statement;
477/// - [`DataType`] — a complete type spelling (as it appears after `CAST(x AS …)` or
478///   in a column definition).
479///
480/// The gate is compile-time: a call site can only pass one of these types, so a
481/// context-dependent node is rejected by the type checker rather than silently
482/// rendered. The runtime sibling for language facades, which hold node handles by
483/// id, is [`Parsed::render_fragment_by_id`]; it enforces the *same* allowlist
484/// dynamically.
485///
486/// [`Parsed::render_fragment`]: https://docs.rs/squonk
487/// [`Parsed::render_fragment_by_id`]: https://docs.rs/squonk
488pub trait FragmentRender: Render + fragment_sealed::Sealed {
489    /// This node's [`NodeId`], used by the by-id fragment lookup to match a node
490    /// handle held by a language facade. Implementation detail of the fragment API —
491    /// prefer the node's own [`Meta`](crate::vocab::Meta) `node_id` field directly.
492    #[doc(hidden)]
493    fn fragment_node_id(&self) -> NodeId;
494}
495
496/// Seals [`FragmentRender`] so the standalone-renderable allowlist cannot be widened
497/// from outside this crate — only the audited node kinds below implement `Sealed`.
498mod fragment_sealed {
499    pub trait Sealed {}
500}
501
502impl<X: Extension + Render> fragment_sealed::Sealed for Query<X> {}
503impl<X: Extension + Render> FragmentRender for Query<X> {
504    fn fragment_node_id(&self) -> NodeId {
505        self.meta.node_id
506    }
507}
508
509// `Statement` is `#[non_exhaustive]`, so a cross-crate match would need a `_` arm
510// that could not report a new variant's id. Extracting every fragment id here also
511// keeps the `Expr` and `DataType` matches exhaustive, so adding any fragment variant
512// is a compile error to update rather than a silently unrenderable node (the
513// completeness guarantee the generated `NodeIdWalk` relies on for the same reason).
514impl<X: Extension + Render> fragment_sealed::Sealed for Statement<X> {}
515impl<X: Extension + Render> FragmentRender for Statement<X> {
516    fn fragment_node_id(&self) -> NodeId {
517        match self {
518            Statement::Query { meta, .. }
519            | Statement::CreateTable { meta, .. }
520            | Statement::AlterTable { meta, .. }
521            | Statement::Drop { meta, .. }
522            | Statement::CreateSchema { meta, .. }
523            | Statement::CreateView { meta, .. }
524            | Statement::RefreshMaterializedView { meta, .. }
525            | Statement::CreateColocationGroup { meta, .. }
526            | Statement::DropColocationGroup { meta, .. }
527            | Statement::AlterView { meta, .. }
528            | Statement::CreateIndex { meta, .. }
529            | Statement::CreateFunction { meta, .. }
530            | Statement::CreateProcedure { meta, .. }
531            | Statement::AlterRoutine { meta, .. }
532            | Statement::CreateEvent { meta, .. }
533            | Statement::AlterEvent { meta, .. }
534            | Statement::DropEvent { meta, .. }
535            | Statement::DropDatabase { meta, .. }
536            | Statement::DropIndex { meta, .. }
537            | Statement::CreateDatabase { meta, .. }
538            | Statement::DropRoutine { meta, .. }
539            | Statement::DropTransform { meta, .. }
540            | Statement::Truncate { meta, .. }
541            | Statement::CommentOn { meta, .. }
542            | Statement::Insert { meta, .. }
543            | Statement::Update { meta, .. }
544            | Statement::Delete { meta, .. }
545            | Statement::Merge { meta, .. }
546            | Statement::Transaction { meta, .. }
547            | Statement::Xa { meta, .. }
548            | Statement::Session { meta, .. }
549            | Statement::AccessControl { meta, .. }
550            | Statement::Copy { meta, .. }
551            | Statement::CopyInto { meta, .. }
552            | Statement::Export { meta, .. }
553            | Statement::Import { meta, .. }
554            | Statement::Explain { meta, .. }
555            | Statement::Describe { meta, .. }
556            | Statement::Show { meta, .. }
557            | Statement::Kill { meta, .. }
558            | Statement::Handler { meta, .. }
559            | Statement::Install { meta, .. }
560            | Statement::Uninstall { meta, .. }
561            | Statement::Shutdown { meta, .. }
562            | Statement::Restart { meta, .. }
563            | Statement::Clone { meta, .. }
564            | Statement::ImportTable { meta, .. }
565            | Statement::Help { meta, .. }
566            | Statement::Binlog { meta, .. }
567            | Statement::Pragma { meta, .. }
568            | Statement::Attach { meta, .. }
569            | Statement::Detach { meta, .. }
570            | Statement::Checkpoint { meta, .. }
571            | Statement::Load { meta, .. }
572            | Statement::LoadData { meta, .. }
573            | Statement::UpdateExtensions { meta, .. }
574            | Statement::Vacuum { meta, .. }
575            | Statement::Reindex { meta, .. }
576            | Statement::Analyze { meta, .. }
577            | Statement::Use { meta, .. }
578            | Statement::CreateTrigger { meta, .. }
579            | Statement::CreateStoredTrigger { meta, .. }
580            | Statement::CreateMacro { meta, .. }
581            | Statement::CreateSecret { meta, .. }
582            | Statement::DropSecret { meta, .. }
583            | Statement::CreateType { meta, .. }
584            | Statement::CreateVirtualTable { meta, .. }
585            | Statement::CreateSequence { meta, .. }
586            | Statement::CreateExtension { meta, .. }
587            | Statement::AlterExtension { meta, .. }
588            | Statement::CreateTablespace { meta, .. }
589            | Statement::AlterTablespace { meta, .. }
590            | Statement::DropTablespace { meta, .. }
591            | Statement::CreateLogfileGroup { meta, .. }
592            | Statement::AlterLogfileGroup { meta, .. }
593            | Statement::DropLogfileGroup { meta, .. }
594            | Statement::AlterObjectDepends { meta, .. }
595            | Statement::AlterSystem { meta, .. }
596            | Statement::AlterDatabase { meta, .. }
597            | Statement::AlterDatabaseOptions { meta, .. }
598            | Statement::CreateServer { meta, .. }
599            | Statement::AlterServer { meta, .. }
600            | Statement::DropServer { meta, .. }
601            | Statement::AlterInstance { meta, .. }
602            | Statement::CreateSpatialReferenceSystem { meta, .. }
603            | Statement::DropSpatialReferenceSystem { meta, .. }
604            | Statement::CreateResourceGroup { meta, .. }
605            | Statement::AlterResourceGroup { meta, .. }
606            | Statement::DropResourceGroup { meta, .. }
607            | Statement::AlterSequence { meta, .. }
608            | Statement::AlterObjectSchema { meta, .. }
609            | Statement::Pivot { meta, .. }
610            | Statement::Unpivot { meta, .. }
611            | Statement::ShowRef { meta, .. }
612            | Statement::Prepare { meta, .. }
613            | Statement::Execute { meta, .. }
614            | Statement::PrepareFrom { meta, .. }
615            | Statement::ExecuteUsing { meta, .. }
616            | Statement::Deallocate { meta, .. }
617            | Statement::Call { meta, .. }
618            | Statement::Do { meta, .. }
619            | Statement::DoExpressions { meta, .. }
620            | Statement::LockTables { meta, .. }
621            | Statement::UnlockTables { meta, .. }
622            | Statement::InstanceLock { meta, .. }
623            | Statement::Compound { meta, .. }
624            | Statement::If { meta, .. }
625            | Statement::Case { meta, .. }
626            | Statement::Loop { meta, .. }
627            | Statement::While { meta, .. }
628            | Statement::Repeat { meta, .. }
629            | Statement::Leave { meta, .. }
630            | Statement::Iterate { meta, .. }
631            | Statement::Return { meta, .. }
632            | Statement::OpenCursor { meta, .. }
633            | Statement::FetchCursor { meta, .. }
634            | Statement::CloseCursor { meta, .. }
635            | Statement::TableMaintenance { meta, .. }
636            | Statement::CacheIndex { meta, .. }
637            | Statement::LoadIndex { meta, .. }
638            | Statement::Rename { meta, .. }
639            | Statement::Flush { meta, .. }
640            | Statement::Purge { meta, .. }
641            | Statement::Replication { meta, .. }
642            | Statement::CreateUser { meta, .. }
643            | Statement::AlterUser { meta, .. }
644            | Statement::UserRoleList { meta, .. }
645            | Statement::Signal { meta, .. }
646            | Statement::Resignal { meta, .. }
647            | Statement::GetDiagnostics { meta, .. }
648            | Statement::Other { meta, .. } => meta.node_id,
649        }
650    }
651}
652
653impl<X: Extension + Render> fragment_sealed::Sealed for Expr<X> {}
654impl<X: Extension + Render> FragmentRender for Expr<X> {
655    fn fragment_node_id(&self) -> NodeId {
656        match self {
657            Expr::Column { meta, .. }
658            | Expr::Literal { meta, .. }
659            | Expr::BinaryOp { meta, .. }
660            | Expr::UnaryOp { meta, .. }
661            | Expr::Function { meta, .. }
662            | Expr::Case { meta, .. }
663            | Expr::Extract { meta, .. }
664            | Expr::Cast { meta, .. }
665            | Expr::IsNull { meta, .. }
666            | Expr::IsTruth { meta, .. }
667            | Expr::IsNormalized { meta, .. }
668            | Expr::Between { meta, .. }
669            | Expr::Like { meta, .. }
670            | Expr::InList { meta, .. }
671            | Expr::InSubquery { meta, .. }
672            | Expr::InExpr { meta, .. }
673            | Expr::Exists { meta, .. }
674            | Expr::QuantifiedComparison { meta, .. }
675            | Expr::QuantifiedList { meta, .. }
676            | Expr::QuantifiedLike { meta, .. }
677            | Expr::Subquery { meta, .. }
678            | Expr::Parameter { meta, .. }
679            | Expr::PositionalColumn { meta, .. }
680            | Expr::SessionVariable { meta, .. }
681            | Expr::Subscript { meta, .. }
682            | Expr::SemiStructuredAccess { meta, .. }
683            | Expr::Collate { meta, .. }
684            | Expr::AtTimeZone { meta, .. }
685            | Expr::Interval { meta, .. }
686            | Expr::Array { meta, .. }
687            | Expr::Struct { meta, .. }
688            | Expr::StructConstructor { meta, .. }
689            | Expr::Map { meta, .. }
690            | Expr::Row { meta, .. }
691            | Expr::FieldSelection { meta, .. }
692            | Expr::NamedOperator { meta, .. }
693            | Expr::PrefixOperator { meta, .. }
694            | Expr::PostfixOperator { meta, .. }
695            | Expr::Lambda { meta, .. }
696            | Expr::Columns { meta, .. }
697            | Expr::SpecialFunction { meta, .. }
698            | Expr::JsonFunc { meta, .. }
699            | Expr::JsonObject { meta, .. }
700            | Expr::JsonArray { meta, .. }
701            | Expr::JsonAggregate { meta, .. }
702            | Expr::JsonConstructor { meta, .. }
703            | Expr::IsJson { meta, .. }
704            | Expr::XmlFunc { meta, .. }
705            | Expr::IsDocument { meta, .. }
706            | Expr::StringFunc { meta, .. }
707            | Expr::Other { meta, .. } => meta.node_id,
708        }
709    }
710}
711
712impl<X: Extension + Render> fragment_sealed::Sealed for DataType<X> {}
713impl<X: Extension + Render> FragmentRender for DataType<X> {
714    fn fragment_node_id(&self) -> NodeId {
715        match self {
716            DataType::Boolean { meta, .. }
717            | DataType::TinyInt { meta, .. }
718            | DataType::SmallInt { meta, .. }
719            | DataType::MediumInt { meta, .. }
720            | DataType::Integer { meta, .. }
721            | DataType::BigInt { meta, .. }
722            | DataType::Decimal { meta, .. }
723            | DataType::Float { meta, .. }
724            | DataType::Real { meta, .. }
725            | DataType::Double { meta, .. }
726            | DataType::Text { meta, .. }
727            | DataType::Blob { meta, .. }
728            | DataType::Character { meta, .. }
729            | DataType::Binary { meta, .. }
730            | DataType::Bit { meta, .. }
731            | DataType::Json { meta, .. }
732            | DataType::Uuid { meta, .. }
733            | DataType::Date { meta, .. }
734            | DataType::Time { meta, .. }
735            | DataType::Timestamp { meta, .. }
736            | DataType::Interval { meta, .. }
737            | DataType::Enum { meta, .. }
738            | DataType::Set { meta, .. }
739            | DataType::NumericModifier { meta, .. }
740            | DataType::Array { meta, .. }
741            | DataType::Struct { meta, .. }
742            | DataType::Union { meta, .. }
743            | DataType::Map { meta, .. }
744            | DataType::Wrapped { meta, .. }
745            | DataType::FixedString { meta, .. }
746            | DataType::DateTime64 { meta, .. }
747            | DataType::Nested { meta, .. }
748            | DataType::FixedWidthInt { meta, .. }
749            | DataType::UserDefined { meta, .. }
750            | DataType::Liberal { meta, .. }
751            | DataType::Other { meta, .. } => meta.node_id,
752        }
753    }
754}