Skip to main content

squonk_ast/ast/
util.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! Utility-statement AST nodes (ADR-0012): COPY, EXPLAIN, DESCRIBE, KILL, PRAGMA, ATTACH, DETACH, CHECKPOINT, LOAD, VACUUM, REINDEX.
5
6use super::{
7    DataType, Expr, Extension, Ident, Limit, Literal, NoExt, ObjectName, SelectItem, SetAssignment,
8    SetParameterValue, Statement, UpdateAssignment, ValuesItem,
9};
10use crate::vocab::Meta;
11use thin_vec::ThinVec;
12
13/// A PostgreSQL `COPY` bulk data-transfer statement.
14///
15/// Moves rows between an external location and either a table or the result of a
16/// query: `COPY {<table> [(cols)] | (<query>)} {FROM | TO} {'file' | STDIN | STDOUT
17/// | PROGRAM 'cmd'} [ [WITH] (options) | <legacy options> ]`. The two row sources
18/// ride [`source`](Self::source); the query source makes this node generic over the
19/// extension `X`, since it embeds a [`Statement`].
20///
21/// One canonical shape: the legacy un-parenthesized option list and the
22/// modern parenthesized list share one [`options`](Self::options) list, with the
23/// [`parenthesized`](Self::parenthesized) surface tag recording which spelling was
24/// written so the construct round-trips. `FROM`/`TO` is recorded by
25/// [`direction`](Self::direction) (always `TO` for the query source, which
26/// PostgreSQL forbids `FROM` on); the `STDIN`/`STDOUT` keyword that is only
27/// semantically valid for one direction is preserved verbatim rather than gated
28/// (PostgreSQL accepts either with either direction).
29///
30/// A handful of legacy table-only surfaces ride dedicated fields rather than the
31/// generic option list: the very-old `opt_binary` prefix ([`binary`](Self::binary)),
32/// the `[USING] DELIMITERS '<str>'` clause ([`delimiters`](Self::delimiters)), and
33/// the `COPY <table> FROM '<file>' WHERE <predicate>` filter ([`filter`](Self::filter),
34/// `FROM`-only — PostgreSQL rejects it on `COPY TO`). None are valid on the query
35/// source, which the parser enforces.
36#[derive(Clone, Debug, PartialEq, Eq, Hash)]
37#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
38#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
39pub struct CopyStatement<X: Extension = NoExt> {
40    /// The very-old `opt_binary` prefix (`COPY BINARY <table> ...`), distinct from
41    /// the `BINARY` [option](CopyOption). Table source only. `true` renders the
42    /// leading `BINARY` keyword.
43    pub binary: bool,
44    /// Input source for this syntax.
45    pub source: CopySource<X>,
46    /// Whether the copy is `FROM` (load) or `TO` (export); see [`CopyDirection`].
47    pub direction: CopyDirection,
48    /// Object targeted by this syntax.
49    pub target: CopyTarget,
50    /// The legacy `[USING] DELIMITERS '<str>'` clause (PostgreSQL `copy_delimiter`),
51    /// written between the endpoint and the options; `None` when absent. Only the
52    /// delimiter string is kept: the optional `USING` is non-load-bearing
53    /// (PostgreSQL `opt_using`) and canonicalizes away like the `WITH` before the
54    /// options and the `AS` in `DELIMITER AS '<str>'`.
55    pub delimiters: Option<Literal>,
56    /// Whether the parenthesized `WITH (opt, ...)` option-list spelling was used
57    /// (the always-present surface tag); `false` is the legacy
58    /// space-separated list (`CSV`, `DELIMITER ','`). Irrelevant when
59    /// [`options`](Self::options) is empty, where neither spelling renders.
60    pub parenthesized: bool,
61    /// Options supplied in source order.
62    pub options: ThinVec<CopyOption>,
63    /// The `WHERE <predicate>` row filter of a `COPY <table> FROM <source>`; `None`
64    /// when absent. PostgreSQL admits it for `COPY FROM` only (it errors "WHERE
65    /// clause not allowed with COPY TO"), so the parser only ever populates this on
66    /// a `FROM` table source.
67    pub filter: Option<Expr<X>>,
68    /// Source location and node identity.
69    pub meta: Meta,
70}
71
72/// The row source of a [`CopyStatement`]: a table (with an optional column list) or
73/// a parenthesized query.
74///
75/// PostgreSQL's query source is a parenthesized `PreparableStmt` — a `SELECT`,
76/// `INSERT`, `UPDATE`, `DELETE`, or `MERGE` (the parser gates the inner statement to
77/// the kinds it models). It is only valid with `TO`, never `FROM`.
78#[derive(Clone, Debug, PartialEq, Eq, Hash)]
79#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
80#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
81pub enum CopySource<X: Extension = NoExt> {
82    /// `COPY <table> [(col, ...)]`: the named relation and its optional column list
83    /// (empty when none was written).
84    Table {
85        /// Table referenced by this syntax.
86        table: ObjectName,
87        /// Columns in source order.
88        columns: ThinVec<Ident>,
89        /// Source location and node identity.
90        meta: Meta,
91    },
92    /// A parenthesized query source (`COPY (<query>) TO …`), valid only with `TO`.
93    Query {
94        /// Query governed by this node.
95        query: Box<Statement<X>>,
96        /// Source location and node identity.
97        meta: Meta,
98    },
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
103#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
104/// The SQL copy direction forms represented by the AST.
105pub enum CopyDirection {
106    /// `COPY … FROM` — load data into the table.
107    From,
108    /// `COPY … TO` — export the table's data.
109    To,
110}
111
112/// The external endpoint of a `COPY`: a file, the client stream, or a subprocess.
113///
114/// `STDIN`/`STDOUT` are split so each renders the exact keyword written.
115/// PostgreSQL's grammar admits either with either direction (the `TO STDIN` /
116/// `FROM STDOUT` mismatch is a later semantic check, not a parse error), so the
117/// keyword the parser saw is recorded as-is rather than normalized by direction.
118#[derive(Clone, Debug, PartialEq, Eq, Hash)]
119#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
120#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
121pub enum CopyTarget {
122    /// A server-side file path (`COPY … '<path>'`).
123    File {
124        /// Path supplied by this syntax.
125        path: Literal,
126        /// Source location and node identity.
127        meta: Meta,
128    },
129    /// The client input stream (`FROM STDIN`).
130    Stdin {
131        /// Source location and node identity.
132        meta: Meta,
133    },
134    /// The client output stream (`TO STDOUT`).
135    Stdout {
136        /// Source location and node identity.
137        meta: Meta,
138    },
139    /// A subprocess whose stdio is piped (`COPY … PROGRAM '<cmd>'`).
140    Program {
141        /// Command text supplied by this syntax.
142        command: Literal,
143        /// Source location and node identity.
144        meta: Meta,
145    },
146}
147
148/// One option in a `COPY` option list, e.g. `FORMAT csv`, `DELIMITER ','`, a bare
149/// `HEADER`, DuckDB's `ROW_GROUP_SIZE 100000`, or `FORCE_QUOTE (a, b)`.
150///
151/// The generic key/value escape hatch of the COPY option axis: PostgreSQL's generic
152/// option grammar is `ColLabel copy_generic_opt_arg`, so the [`name`](Self::name)
153/// admits any word (including keywords such as `NULL`) and the argument is optional.
154/// This one `name` + optional [`CopyOptionValue`] shape is a deliberate reshape
155/// (ADR-0011) of sqlparser-rs's fixed per-option enums (`CopyOption::Format(Ident)`,
156/// `ForceQuote(Vec<Ident>)`, …): rather than enumerate PostgreSQL's known option
157/// keywords as variants, every option — PostgreSQL, DuckDB, or an unknown dialect
158/// word — rides the same generic pair, so a dialect's format/option vocabulary
159/// (DuckDB `COPY … (FORMAT PARQUET, COMPRESSION 'zstd', PARTITION_BY (y, m))`) is
160/// carried as typed data without a per-keyword variant. The value *shapes*
161/// PostgreSQL's `copy_generic_opt_arg` admits are the [`CopyOptionValue`] axis.
162#[derive(Clone, Debug, PartialEq, Eq, Hash)]
163#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
164#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
165pub struct CopyOption {
166    /// Name referenced by this syntax.
167    pub name: Ident,
168    /// Value supplied by this syntax.
169    pub value: Option<CopyOptionValue>,
170    /// Source location and node identity.
171    pub meta: Meta,
172}
173
174/// The argument shapes a [`CopyOption`] can carry — the extensible axis of the COPY
175/// option machinery.
176///
177/// Mirrors PostgreSQL's `copy_generic_opt_arg` alternatives (`opt_boolean_or_string`,
178/// `NumericOnly`, `'*'`, and `'(' copy_generic_opt_arg_list ')'`), which pg_query
179/// accepts under the plain `copy` gate, plus the legacy [`Force`](Self::Force)
180/// compound. DuckDB's and the Snowflake-adjacent parity target's format/option values
181/// are a subset of these same shapes, so no widening feature flag is needed. This
182/// enum is the pre-staged extension point for the `COPY INTO` sibling: a keyed nested
183/// option list (Snowflake `FILE_FORMAT = (TYPE = CSV …)`) becomes an *additive*
184/// variant here (a `ThinVec<CopyOption>` payload), riding the same
185/// [`CopyOption`]/[`CopyStatement`] machinery rather than reshaping it.
186#[derive(Clone, Debug, PartialEq, Eq, Hash)]
187#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
188#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
189pub enum CopyOptionValue {
190    /// A bareword argument kept as its source word (`FORMAT csv`, `HEADER true`,
191    /// DuckDB `FORMAT PARQUET`). PostgreSQL `opt_boolean_or_string`.
192    Word {
193        /// Identifier-form value.
194        word: Ident,
195        /// Source location and node identity.
196        meta: Meta,
197    },
198    /// A string argument (`DELIMITER ','`, `NULL ''`, DuckDB `COMPRESSION 'zstd'`).
199    String {
200        /// Value supplied by this syntax.
201        value: Literal,
202        /// Source location and node identity.
203        meta: Meta,
204    },
205    /// A numeric argument (`HEADER 1`, DuckDB `ROW_GROUP_SIZE 100000`), including a
206    /// sign-folded negative (`-1`) or decimal (`1.5`). PostgreSQL `NumericOnly`; the
207    /// sign is folded into the literal's span so it round-trips whole.
208    Number {
209        /// Value supplied by this syntax.
210        value: Literal,
211        /// Source location and node identity.
212        meta: Meta,
213    },
214    /// The bare `*` argument of a generic parenthesized option (`FORCE_QUOTE *`).
215    /// PostgreSQL `'*'`; distinct from the legacy [`Force`](Self::Force) all-columns
216    /// form, which spells `*` as an empty column list.
217    Star {
218        /// Source location and node identity.
219        meta: Meta,
220    },
221    /// A parenthesized argument list (`FORCE_QUOTE (a, b)`, DuckDB `PARTITION_BY (y,
222    /// m)`). PostgreSQL `'(' copy_generic_opt_arg_list ')'`. The parser only ever
223    /// emits [`Word`](Self::Word)/[`String`](Self::String)/[`Number`](Self::Number)
224    /// list items (PostgreSQL's list items are `opt_boolean_or_string`; DuckDB adds
225    /// numerics); the recursive type keeps one axis rather than a parallel item enum.
226    List {
227        /// Values in source order.
228        values: ThinVec<CopyOptionValue>,
229        /// Source location and node identity.
230        meta: Meta,
231    },
232    /// The `{QUOTE | NULL | NOT NULL} {<column-list> | *}` payload of a legacy
233    /// `FORCE` option (PostgreSQL `copy_opt_item`), whose compound keyword the
234    /// generic `<name> [<value>]` shape cannot carry: the [`name`](CopyOption::name)
235    /// is the leading `FORCE` word and this value holds the sub-keyword (which
236    /// `kind` distinguishes) and its target. An empty
237    /// `columns` list is the `*` (all-columns) form (PostgreSQL
238    /// `A_Star`) — unambiguous because PostgreSQL's `columnList` is never empty.
239    Force {
240        /// Whether the force applies to all columns or a named list; see [`ForceKind`].
241        kind: ForceKind,
242        /// Columns in source order.
243        columns: ThinVec<Ident>,
244        /// Source location and node identity.
245        meta: Meta,
246    },
247    /// A keyed nested option list — the space-separated `<key> = <value>` pairs
248    /// inside a Snowflake `FILE_FORMAT = (TYPE = CSV FIELD_DELIMITER = ',')` (or a
249    /// `FORMAT_NAME = '...'`) argument. Each element is a [`CopyOption`], so the
250    /// nested option vocabulary rides the same machinery as the outer list rather
251    /// than a parallel node — the additive extension point ADR-0011 pre-staged on
252    /// this enum. Distinct from [`List`](Self::List), whose elements are bare
253    /// comma-separated values (`FORCE_QUOTE (a, b)`): an `OptionList` element is a
254    /// `key = value` pair and the elements are space-separated. Only
255    /// [`CopyIntoStatement`] emits this (via its `= (...)` option grammar);
256    /// PostgreSQL/DuckDB `COPY` never does.
257    OptionList {
258        /// Options supplied in source order.
259        options: ThinVec<CopyOption>,
260        /// Source location and node identity.
261        meta: Meta,
262    },
263}
264
265/// Which legacy `FORCE` option a [`CopyOptionValue::Force`] carries.
266///
267/// A tag (no `meta`): the sub-keyword's span is subsumed by the enclosing
268/// [`CopyOptionValue::Force`], exactly as [`CopyDirection`]/[`ExplainFormat`] ride
269/// their parent's span.
270#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
271#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
272#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
273pub enum ForceKind {
274    /// `FORCE QUOTE` — force-quote the listed columns (`COPY TO`, CSV).
275    Quote,
276    /// `FORCE NULL` — match the null string on the listed columns (`COPY FROM`, CSV).
277    Null,
278    /// `FORCE NOT NULL` — never match the null string on the listed columns
279    /// (`COPY FROM`, CSV).
280    NotNull,
281}
282
283/// A Snowflake `COPY INTO` bulk load/unload statement.
284///
285/// Moves rows between a table and a location: `COPY INTO <target> FROM <source>
286/// [<option> = <value> ...]`. Unlike PostgreSQL's [`CopyStatement`] — which is a
287/// `{FROM | TO} <endpoint>` transfer with comma-separated options — the direction
288/// is fixed (`INTO <target> FROM <source>`) and the load-vs-unload sense is carried
289/// by *which side* is the table: a table [`target`](Self::target) with a location
290/// [`source`](Self::source) loads, a location target with a table/query source
291/// unloads. It is a sibling statement rather than a variant of [`CopyStatement`]
292/// because the two share only the `COPY` keyword: the endpoint model, the option
293/// spelling (`KEY = VALUE`, space-separated, with a nested
294/// [`OptionList`](CopyOptionValue::OptionList) for `FILE_FORMAT`), and the clause set
295/// (`FILES`, `PATTERN`, `VALIDATION_MODE`) are disjoint. This mirrors sqlparser-rs's
296/// separate `CopyIntoSnowflake` node.
297///
298/// Every trailing clause — `FILE_FORMAT = (...)`, `FILES = (...)`, `PATTERN = '...'`,
299/// `VALIDATION_MODE = ...`, and the copy options (`ON_ERROR = ...`, `FORCE = TRUE`, …)
300/// — rides the one generic [`options`](Self::options) list as a [`CopyOption`] whose
301/// value shape captures its argument, exactly the ADR-0011 reshape
302/// [`CopyStatement`] uses for PostgreSQL/DuckDB options: no per-keyword field.
303///
304/// The `@<stage>` internal/named-stage source-target sigil is not modelled yet — it
305/// needs a tokenizer stage-reference surface (Snowflake enables no `@` lexer path
306/// today) — so [`CopyIntoTarget`]/[`CopyIntoSource`] carry the table and external
307/// string-location forms (and the query source), with the stage sigil a tracked
308/// follow-up.
309#[derive(Clone, Debug, PartialEq, Eq, Hash)]
310#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
311#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
312pub struct CopyIntoStatement<X: Extension = NoExt> {
313    /// Object targeted by this syntax.
314    pub target: CopyIntoTarget,
315    /// Input source for this syntax.
316    pub source: CopyIntoSource<X>,
317    /// The trailing `<key> = <value>` clauses (`FILE_FORMAT`, `FILES`, `PATTERN`,
318    /// `VALIDATION_MODE`, and the copy options), space-separated in source. Each is a
319    /// [`CopyOption`] whose [`value`](CopyOption::value) captures the argument shape;
320    /// `FILE_FORMAT`'s nested list rides [`CopyOptionValue::OptionList`], `FILES`'s
321    /// comma list rides [`CopyOptionValue::List`]. Empty when none were written.
322    pub options: ThinVec<CopyOption>,
323    /// Source location and node identity.
324    pub meta: Meta,
325}
326
327/// The `INTO <target>` destination of a [`CopyIntoStatement`]: a table (loading) or
328/// an external location string (unloading).
329#[derive(Clone, Debug, PartialEq, Eq, Hash)]
330#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
331#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
332pub enum CopyIntoTarget {
333    /// `COPY INTO [<db>.<schema>.]<table> [(col, ...)]`: the loaded relation and its
334    /// optional column list (empty when none was written).
335    Table {
336        /// Table referenced by this syntax.
337        table: ObjectName,
338        /// Columns in source order.
339        columns: ThinVec<Ident>,
340        /// Source location and node identity.
341        meta: Meta,
342    },
343    /// `COPY INTO '<url>'`: an external location string (`'s3://bucket/path/'`).
344    External {
345        /// External location referenced by this syntax.
346        location: Literal,
347        /// Source location and node identity.
348        meta: Meta,
349    },
350    /// `COPY INTO @stage[/path]`: a Snowflake stage reference (token span text).
351    Stage {
352        /// Stage reference used by this syntax.
353        reference: Ident,
354        /// Source location and node identity.
355        meta: Meta,
356    },
357}
358
359/// The `FROM <source>` origin of a [`CopyIntoStatement`]: a table, an external
360/// location string, or a parenthesized transformation query.
361#[derive(Clone, Debug, PartialEq, Eq, Hash)]
362#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
363#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
364pub enum CopyIntoSource<X: Extension = NoExt> {
365    /// `FROM [<db>.<schema>.]<table>`: an unloaded relation.
366    Table {
367        /// Table referenced by this syntax.
368        table: ObjectName,
369        /// Source location and node identity.
370        meta: Meta,
371    },
372    /// `FROM '<url>'`: an external location string to load from.
373    External {
374        /// External location referenced by this syntax.
375        location: Literal,
376        /// Source location and node identity.
377        meta: Meta,
378    },
379    /// `FROM @stage[/path]`: a Snowflake stage reference.
380    Stage {
381        /// Stage reference used by this syntax.
382        reference: Ident,
383        /// Source location and node identity.
384        meta: Meta,
385    },
386    /// `FROM ( <query> )`: a transformation query (Snowflake's `COPY INTO <table>
387    /// FROM (SELECT ... )` load form). The inner is gated to the query kinds we
388    /// model, like [`CopySource::Query`].
389    Query {
390        /// Query governed by this node.
391        query: Box<Statement<X>>,
392        /// Source location and node identity.
393        meta: Meta,
394    },
395}
396
397/// A DuckDB `EXPORT DATABASE ['<db>' TO] '<path>' [<copy-options>]` statement: write
398/// the catalogue as a directory of `CREATE`/`INSERT`/`COPY` scripts plus data files.
399///
400/// The two grammar forms (DuckDB `ExportStmt`) are `EXPORT DATABASE '<path>'
401/// <copy_options>` and `EXPORT DATABASE <db> TO '<path>' <copy_options>` — the
402/// named-database form threads the catalogue name through a *required* `TO` before the
403/// path (`EXPORT DATABASE db '<path>'` without `TO` is a parser error, probed on
404/// 1.5.4). The presence of [`database`](Self::database) therefore reconstructs the `TO`
405/// on render — a `Some` is the `<db> TO '<path>'` spelling, a `None` the bare
406/// `'<path>'` — so no separate `to` surface tag is carried.
407///
408/// The options reuse the PostgreSQL `COPY` option axis verbatim: DuckDB's grammar
409/// gives `ExportStmt` the same `copy_options` production `COPY` uses (a legacy
410/// space-separated `copy_opt_list` *or* a parenthesized generic list), so the
411/// [`options`](Self::options) list is [`CopyOption`]s and [`parenthesized`](Self::parenthesized)
412/// records which spelling was written — exactly [`CopyStatement`]'s pair. Unlike `COPY`
413/// there is no leading `WITH` (`EXPORT DATABASE '<path>' WITH (...)` is a parser error,
414/// probed on 1.5.4), so the parenthesized form renders bare `(...)`. Non-generic: the
415/// path is a string literal and [`CopyOption`] carries no extension nodes, so — like
416/// [`DetachStatement`] — this node needs no `X`. Gated together with its
417/// [`ImportStatement`] inverse by
418/// [`UtilitySyntax::export_import_database`](crate::dialect::UtilitySyntax).
419#[derive(Clone, Debug, PartialEq, Eq, Hash)]
420#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
421#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
422pub struct ExportStatement {
423    /// The catalogue (database) name of the `EXPORT DATABASE <db> TO '<path>'` form;
424    /// `None` is the bare `EXPORT DATABASE '<path>'` form. A `Some` reconstructs the
425    /// required `TO` on render.
426    pub database: Option<Ident>,
427    /// The destination directory path — a string literal (DuckDB `Sconst`).
428    pub path: Literal,
429    /// Whether the parenthesized `(opt, ...)` option-list spelling was used (the
430    /// surface tag); `false` is the legacy space-separated list (`FORMAT` is not in
431    /// that fixed set, but `HEADER`/`CSV`/`DELIMITER '...'` are). Irrelevant when
432    /// [`options`](Self::options) is empty, where neither spelling renders — mirroring
433    /// [`CopyStatement::parenthesized`].
434    pub parenthesized: bool,
435    /// Copy options in source order; see [`CopyOption`]. Empty when none were written.
436    pub options: ThinVec<CopyOption>,
437    /// Source location and node identity.
438    pub meta: Meta,
439}
440
441/// A DuckDB `IMPORT DATABASE '<path>'` statement: replay the `CREATE`/`INSERT`/`COPY`
442/// scripts an [`ExportStatement`] wrote to a directory, reconstructing the catalogue.
443///
444/// The other half of DuckDB's database export/import round-trip, sharing the
445/// [`UtilitySyntax::export_import_database`](crate::dialect::UtilitySyntax) gate (one
446/// dialect unit — a dialect that exports databases imports them). The grammar (DuckDB
447/// `ImportStmt`) is exactly `IMPORT DATABASE '<path>'`: a single string path and no
448/// options (`IMPORT DATABASE '<path>' (...)` is a parser error, probed on 1.5.4).
449/// Non-generic, like [`DetachStatement`]: a path literal carries no expressions or
450/// extension nodes.
451#[derive(Clone, Debug, PartialEq, Eq, Hash)]
452#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
453#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
454pub struct ImportStatement {
455    /// The source directory path — a string literal (DuckDB `Sconst`).
456    pub path: Literal,
457    /// Source location and node identity.
458    pub meta: Meta,
459}
460
461/// An `EXPLAIN` statement: report the planner's query plan for an inner statement.
462///
463/// Covers both option spellings: the legacy `EXPLAIN [ANALYZE] [VERBOSE]
464/// <statement>` keyword prefix and the modern parenthesized list `EXPLAIN ( option
465/// [, ...] ) <statement>`. They share one [`options`](Self::options) list; the
466/// [`parenthesized`](Self::parenthesized) surface tag records which
467/// spelling was written so the construct round-trips, since the legacy prefix can
468/// only carry [`Analyze`](ExplainOption::Analyze)/[`Verbose`](ExplainOption::Verbose).
469///
470/// MySQL spells the same query-plan statement `EXPLAIN`, `DESCRIBE`, or `DESC`
471/// interchangeably ([`DESCRIBE SELECT 1`](ExplainKeyword) is `EXPLAIN SELECT 1`), so the
472/// leading keyword rides the [`spelling`](Self::spelling) surface tag; PostgreSQL only
473/// has `EXPLAIN`, so its parser always sets [`Explain`](ExplainKeyword::Explain). The
474/// separate MySQL *table*-metadata overload (`DESCRIBE <table>`) is a different shape —
475/// a table name rather than an inner statement — and rides its own
476/// [`DescribeStatement`], not this node.
477///
478/// Which inner statements PostgreSQL actually allows after `EXPLAIN` (a query,
479/// `INSERT`/`UPDATE`/`DELETE`, `CREATE TABLE AS`, …) is a semantic restriction left
480/// to a later pass; the grammar accepts any [`Statement`].
481#[derive(Clone, Debug, PartialEq, Eq, Hash)]
482#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
483#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
484pub struct ExplainStatement<X: Extension = NoExt> {
485    /// Which leading keyword spelled the statement (`EXPLAIN`/`DESCRIBE`/`DESC`);
486    /// always [`Explain`](ExplainKeyword::Explain) outside MySQL.
487    pub spelling: ExplainKeyword,
488    /// Whether the parenthesized form was present in the source.
489    pub parenthesized: bool,
490    /// Options supplied in source order.
491    pub options: ThinVec<ExplainOption>,
492    /// Statement governed by this node.
493    pub statement: Box<Statement<X>>,
494    /// Source location and node identity.
495    pub meta: Meta,
496}
497
498/// One option of an [`ExplainStatement`].
499///
500/// `ANALYZE`/`VERBOSE` carry an optional boolean/word argument (`ANALYZE`,
501/// `ANALYZE true`) that is always absent in the legacy keyword-prefix spelling;
502/// `FORMAT` names an output format; any other PostgreSQL option (`COSTS`,
503/// `BUFFERS`, `SETTINGS`, `WAL`, `TIMING`, `SUMMARY`, …) rides
504/// [`Other`](Self::Other) with its optional argument.
505#[derive(Clone, Debug, PartialEq, Eq, Hash)]
506#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
507#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
508pub enum ExplainOption {
509    /// `ANALYZE` — actually run the statement and report real row counts and timings.
510    Analyze {
511        /// Value supplied by this syntax.
512        value: Option<Ident>,
513        /// Source location and node identity.
514        meta: Meta,
515    },
516    /// `VERBOSE` — include additional plan detail.
517    Verbose {
518        /// Value supplied by this syntax.
519        value: Option<Ident>,
520        /// Source location and node identity.
521        meta: Meta,
522    },
523    /// `FORMAT <fmt>` — select the plan output format.
524    Format {
525        /// The output format (`TEXT`/`XML`/`JSON`/`YAML`); see [`ExplainFormat`].
526        format: ExplainFormat,
527        /// Source location and node identity.
528        meta: Meta,
529    },
530    /// Any other named option, with its optional bareword/boolean argument.
531    Other {
532        /// Name referenced by this syntax.
533        name: Ident,
534        /// Value supplied by this syntax.
535        value: Option<Ident>,
536        /// Source location and node identity.
537        meta: Meta,
538    },
539}
540
541#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
542#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
543#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
544/// The SQL explain format forms represented by the AST.
545pub enum ExplainFormat {
546    /// `TEXT` — human-readable plan output (the default).
547    Text,
548    /// `XML` plan output.
549    Xml,
550    /// `JSON` plan output.
551    Json,
552    /// `YAML` plan output.
553    Yaml,
554}
555
556/// Which leading keyword spelled an EXPLAIN-family statement: the canonical `EXPLAIN`,
557/// or MySQL's `DESCRIBE`/`DESC` synonyms.
558///
559/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
560/// [`ExplainStatement`]/[`DescribeStatement`], exactly as [`ExplainFormat`] rides its
561/// parent's span. MySQL accepts all three spellings for both the query-plan form
562/// ([`ExplainStatement`]) and the table-metadata form ([`DescribeStatement`]);
563/// PostgreSQL has only `EXPLAIN`, so its parser produces only
564/// [`Explain`](Self::Explain).
565#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
566#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
567#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
568pub enum ExplainKeyword {
569    /// The canonical `EXPLAIN` keyword (the only spelling PostgreSQL accepts).
570    Explain,
571    /// MySQL's `DESCRIBE` synonym.
572    Describe,
573    /// MySQL's abbreviated `DESC` synonym.
574    Desc,
575}
576
577/// A MySQL `{DESCRIBE | DESC | EXPLAIN} <table> [<column> | '<pattern>']` table-metadata
578/// statement (MySQL-specific; gated by
579/// [`ShowSyntax::describe`](crate::dialect::UtilitySyntax)).
580///
581/// MySQL overloads the EXPLAIN-family keywords: followed by an explainable statement they
582/// plan a query ([`ExplainStatement`]); followed by a table name they instead report that
583/// table's column metadata (the `SHOW COLUMNS FROM <table>` information), which is this
584/// node. All three keyword spellings reach both forms, so the leading keyword rides the
585/// [`keyword`](Self::keyword) [`ExplainKeyword`] surface tag for round-trip fidelity, as
586/// [`ExplainStatement`] carries it. The optional trailing argument narrows the output to
587/// one column or a `LIKE`-pattern set ([`column`](Self::column)).
588///
589/// This form has no PostgreSQL analogue and carries a table name rather than an inner
590/// [`Statement`], so it is a distinct node, not a spelling folded onto
591/// [`ExplainStatement`]. Non-generic, like [`DetachStatement`]: a table name and an
592/// optional column/pattern carry no expressions or extension nodes.
593#[derive(Clone, Debug, PartialEq, Eq, Hash)]
594#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
595#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
596pub struct DescribeStatement {
597    /// Which leading keyword was written (`DESCRIBE`/`DESC`/`EXPLAIN`).
598    pub keyword: ExplainKeyword,
599    /// The table whose columns are described (`table_ident`, possibly schema-qualified —
600    /// `DESCRIBE db.t`).
601    pub table: ObjectName,
602    /// The optional trailing `<column> | '<pattern>'` narrowing; `None` describes every
603    /// column.
604    pub column: Option<DescribeColumn>,
605    /// Source location and node identity.
606    pub meta: Meta,
607}
608
609/// The optional trailing argument of a [`DescribeStatement`]: a single column name or a
610/// `LIKE`-style wildcard pattern.
611///
612/// MySQL's `opt_describe_column` is `ident | text_string`: a bare identifier names one
613/// column (`DESCRIBE t col`), a string is a pattern the column list is filtered against
614/// (`DESCRIBE t 'a%'`). A dotted name, a `*`, and a second argument are all MySQL syntax
615/// errors, so only these two shapes are modelled.
616#[derive(Clone, Debug, PartialEq, Eq, Hash)]
617#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
618#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
619pub enum DescribeColumn {
620    /// A specific column name to describe.
621    Name {
622        /// Name referenced by this syntax.
623        name: Ident,
624        /// Source location and node identity.
625        meta: Meta,
626    },
627    /// A `LIKE` wildcard pattern selecting columns to describe.
628    Wild {
629        /// Pattern matched by this syntax.
630        pattern: Literal,
631        /// Source location and node identity.
632        meta: Meta,
633    },
634}
635
636/// A MySQL `KILL [CONNECTION | QUERY] <id>` statement: terminate a server thread or the
637/// statement it is running (MySQL-specific; gated by
638/// [`UtilitySyntax::kill`](crate::dialect::UtilitySyntax)).
639///
640/// The optional [`CONNECTION`/`QUERY`](KillTarget) keyword selects whether the whole
641/// connection or just its current query is killed; bare `KILL <id>` defaults to
642/// `CONNECTION`, and the [`target`](Self::target) tag keeps the three spellings distinct
643/// so they round-trip. The thread id is a full expression in MySQL's grammar
644/// (`KILL @id`, `KILL 1 + 1`, and a string `KILL '5'` all prepare), so it rides an
645/// [`Expr`], which makes this node generic over the extension `X`.
646#[derive(Clone, Debug, PartialEq, Eq, Hash)]
647#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
648#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
649pub struct KillStatement<X: Extension = NoExt> {
650    /// Which thread scope the `CONNECTION`/`QUERY` keyword selected, or that none was
651    /// written (the bare form).
652    pub target: KillTarget,
653    /// The thread/connection id expression.
654    pub id: Expr<X>,
655    /// Source location and node identity.
656    pub meta: Meta,
657}
658
659/// The optional scope keyword of a [`KillStatement`].
660///
661/// A surface tag (no `meta`), riding the parent's span like [`CopyDirection`].
662#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
663#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
664#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
665pub enum KillTarget {
666    /// Bare `KILL <id>` — no keyword written (MySQL defaults it to `CONNECTION`).
667    Unspecified,
668    /// `KILL CONNECTION <id>` — terminate the whole connection.
669    Connection,
670    /// `KILL QUERY <id>` — terminate only the connection's current statement.
671    Query,
672}
673
674/// A MySQL `INSTALL` server-administration statement (gated by
675/// [`UtilitySyntax::plugin_component_statements`](crate::dialect::UtilitySyntax)) — one of the
676/// two forms of `sql_yacc.yy` `install_stmt`. The inverse [`UninstallStatement`] shares the
677/// same `plugin_component_statements` gate (an install/uninstall pair, like `ATTACH`/`DETACH`).
678///
679/// Generic over `X` because the `COMPONENT` form's optional `SET` tail carries [`Expr`]s.
680/// MySQL grammar-accepts every well-formed shape but cannot *prepare* the `COMPONENT` forms
681/// over the binary protocol (`ER_UNSUPPORTED_PS` 1295) — a bind-time verdict the parse layer
682/// does not model; the `PLUGIN` form does prepare.
683#[derive(Clone, Debug, PartialEq, Eq, Hash)]
684#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
685#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
686pub enum InstallStatement<X: Extension = NoExt> {
687    /// `INSTALL PLUGIN <name> SONAME <soname>` — load a single plugin from a shared-library
688    /// file. The name is a bare/back-quoted [`Ident`] (a quoted-string name is
689    /// `ER_PARSE_ERROR` on mysql:8), the `SONAME` a required string [`Literal`]. Exactly one
690    /// plugin — a comma list is rejected.
691    Plugin {
692        /// The plugin name (`ident`, not a quoted string).
693        name: Ident,
694        /// The `SONAME` shared-library file, a string literal.
695        soname: Literal,
696        /// Source location and node identity.
697        meta: Meta,
698    },
699    /// `INSTALL COMPONENT <urn> [, <urn> …] [SET <var-assignment> [, …]]` — load one or more
700    /// components by URN, with an optional `SET` tail of scoped configuration-variable
701    /// assignments. Each URN is a string [`Literal`] (`TEXT_STRING_sys_list`); the list is
702    /// non-empty.
703    Component {
704        /// The component URNs, a non-empty list of string literals.
705        urns: ThinVec<Literal>,
706        /// The optional `SET` tail, empty when none was written; see
707        /// [`InstallComponentSetElement`].
708        set: ThinVec<InstallComponentSetElement<X>>,
709        /// Source location and node identity.
710        meta: Meta,
711    },
712}
713
714/// A MySQL `UNINSTALL` server-administration statement (gated by
715/// [`UtilitySyntax::plugin_component_statements`](crate::dialect::UtilitySyntax)) — the
716/// `sql_yacc.yy` `uninstall` rule, the inverse of [`InstallStatement`]. Neither form carries
717/// an expression, so — unlike its install counterpart — this node is not generic (the
718/// [`PragmaStatement`] precedent).
719#[derive(Clone, Debug, PartialEq, Eq, Hash)]
720#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
721#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
722pub enum UninstallStatement {
723    /// `UNINSTALL PLUGIN <name>` — unload a single plugin by name (a bare/back-quoted
724    /// [`Ident`]; exactly one, a comma list is `ER_PARSE_ERROR` on mysql:8).
725    Plugin {
726        /// The plugin name (`ident`, not a quoted string).
727        name: Ident,
728        /// Source location and node identity.
729        meta: Meta,
730    },
731    /// `UNINSTALL COMPONENT <urn> [, <urn> …]` — unload one or more components by URN. Unlike
732    /// `INSTALL COMPONENT` there is no `SET` tail.
733    Component {
734        /// The component URNs, a non-empty list of string literals.
735        urns: ThinVec<Literal>,
736        /// Source location and node identity.
737        meta: Meta,
738    },
739}
740
741/// One assignment in an `INSTALL COMPONENT … SET` tail (`sql_yacc.yy` `install_set_value`):
742/// `[GLOBAL | PERSIST] <name> {= | :=} <value>`. A strictly narrower grammar than the general
743/// MySQL `SET` — the scope is only the `install_option_type` set (`GLOBAL`/`PERSIST`, default
744/// `GLOBAL`; `SESSION`/`LOCAL`/`PERSIST_ONLY` and `@`/`@@` sigils are `ER_PARSE_ERROR` on
745/// mysql:8), and the value is only `install_set_rvalue` (`ON` or an [`Expr`]; `DEFAULT` and the
746/// other `SET` sentinels reject). The name and assignment operator do reuse the general `SET`
747/// machinery (`lvalue_variable`, [`SetAssignment`]).
748#[derive(Clone, Debug, PartialEq, Eq, Hash)]
749#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
750#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
751pub struct InstallComponentSetElement<X: Extension = NoExt> {
752    /// The scope keyword, or `None` for the implicit default (which MySQL treats as `GLOBAL`);
753    /// see [`InstallComponentSetScope`].
754    pub scope: Option<InstallComponentSetScope>,
755    /// The variable name (`lvalue_variable`: a one- or two-part `name[.name]`).
756    pub name: ObjectName,
757    /// The `=` / `:=` assignment operator (the two are synonyms).
758    pub assignment: SetAssignment,
759    /// The assigned value; see [`InstallComponentSetValue`].
760    pub value: InstallComponentSetValue<X>,
761    /// Source location and node identity.
762    pub meta: Meta,
763}
764
765/// The scope keyword of an [`InstallComponentSetElement`] — the `install_option_type` set. A
766/// surface tag (no `meta`), riding the parent's span like [`KillTarget`]. `None` at the parent
767/// (not a variant here) is the implicit default, which MySQL resolves to `GLOBAL`.
768#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
769#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
770#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
771pub enum InstallComponentSetScope {
772    /// `GLOBAL` — set the global system variable.
773    Global,
774    /// `PERSIST` — set and persist the global system variable.
775    Persist,
776}
777
778/// The value of an [`InstallComponentSetElement`] — `sql_yacc.yy` `install_set_rvalue`, exactly
779/// `ON` or an expression (the general `SET`'s `DEFAULT`/`ALL`/`BINARY`/`ROW`/`SYSTEM` sentinels
780/// are not admitted here).
781#[derive(Clone, Debug, PartialEq, Eq, Hash)]
782#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
783#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
784pub enum InstallComponentSetValue<X: Extension = NoExt> {
785    /// The `ON` keyword (folded to the string `"ON"` by the server; kept as a tag so it
786    /// round-trips).
787    On {
788        /// Source location and node identity.
789        meta: Meta,
790    },
791    /// A value expression.
792    Expr {
793        /// The value expression.
794        expr: Expr<X>,
795        /// Source location and node identity.
796        meta: Meta,
797    },
798}
799
800/// A MySQL `HANDLER` low-level cursor statement (gated by
801/// [`UtilitySyntax::handler_statements`](crate::dialect::UtilitySyntax)) — direct,
802/// index-level read access to a table's storage engine that bypasses the optimizer
803/// (`sql_yacc.yy` `handler_stmt`).
804///
805/// One node covers the three verbs on a single opened table, distinguished by
806/// [`operation`](Self::operation): `HANDLER <t> OPEN [[AS] alias]`, `HANDLER <t> READ …`,
807/// and `HANDLER <t> CLOSE`. The table's name arity differs by verb and is a parse-time
808/// constraint, not a field: `OPEN` takes a possibly schema-qualified `table_ident`
809/// (`HANDLER db.t OPEN`), while `READ`/`CLOSE` take a bare unqualified `ident` (`HANDLER
810/// db.t CLOSE` is `ER_PARSE_ERROR` on mysql:8), so the parser admits a dotted
811/// [`table`](Self::table) only under `OPEN`. Generic over `X`: the `READ` key list and
812/// `WHERE` filter carry [`Expr`]s.
813///
814/// MySQL grammar-accepts every well-formed shape but cannot *prepare* a `HANDLER` over the
815/// binary protocol (`ER_UNSUPPORTED_PS` 1295; a bare-connection `HANDLER … OPEN` against no
816/// default database is `ER_NO_DB_ERROR` 1046) — a bind-time verdict the parse layer does
817/// not model.
818#[derive(Clone, Debug, PartialEq, Eq, Hash)]
819#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
820#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
821pub struct HandlerStatement<X: Extension = NoExt> {
822    /// The handler's table. Schema-qualified only under the `OPEN` verb (parse-enforced).
823    pub table: ObjectName,
824    /// Which verb this statement performs; see [`HandlerOperation`].
825    pub operation: HandlerOperation<X>,
826    /// Source location and node identity.
827    pub meta: Meta,
828}
829
830/// The verb of a [`HandlerStatement`]: `OPEN`, `CLOSE`, or a `READ` with its selector and
831/// shared `WHERE`/`LIMIT` tail.
832#[derive(Clone, Debug, PartialEq, Eq, Hash)]
833#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
834#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
835pub enum HandlerOperation<X: Extension = NoExt> {
836    /// `HANDLER <t> OPEN [ [AS] <alias> ]` — open a handler on the table, optionally
837    /// aliased. [`as_keyword`](Self::Open::as_keyword) records whether the optional `AS`
838    /// was written (meaningful only when [`alias`](Self::Open::alias) is `Some`), so the
839    /// two spellings round-trip.
840    Open {
841        /// The optional handler alias; `None` for a bare `HANDLER <t> OPEN`.
842        alias: Option<Ident>,
843        /// Whether the optional `AS` keyword preceded the alias.
844        as_keyword: bool,
845        /// Source location and node identity.
846        meta: Meta,
847    },
848    /// `HANDLER <t> CLOSE` — close an open handler on the table.
849    Close {
850        /// Source location and node identity.
851        meta: Meta,
852    },
853    /// `HANDLER <t> READ <selector> [WHERE <expr>] [LIMIT …]` — fetch rows through the
854    /// handler. The `WHERE` filter and `LIMIT` are shared across every read shape (see
855    /// [`selector`](Self::Read::selector)); each is `None` when the source writes none.
856    Read {
857        /// Which rows this read selects; see [`HandlerReadSelector`].
858        selector: HandlerReadSelector<X>,
859        /// The optional `WHERE` filter, applied after the storage-engine read.
860        selection: Option<Expr<X>>,
861        /// The optional `LIMIT` clause (`LIMIT n`, `LIMIT off, n`, `LIMIT n OFFSET off` — the
862        /// `LimitOffset`/`CommaOffset` shapes of [`Limit`]).
863        limit: Option<Limit<X>>,
864        /// Source location and node identity.
865        meta: Meta,
866    },
867}
868
869/// The row-selection shape of a `HANDLER … READ` (`sql_yacc.yy` `handler_scan_function` /
870/// `handler_rkey_function` / `handler_rkey_mode`), independent of the shared `WHERE`/`LIMIT`
871/// tail carried by [`HandlerOperation::Read`].
872#[derive(Clone, Debug, PartialEq, Eq, Hash)]
873#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
874#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
875pub enum HandlerReadSelector<X: Extension = NoExt> {
876    /// `READ { FIRST | NEXT }` — a full scan in storage order with no index named.
877    /// Restricted to `FIRST`/`NEXT` ([`HandlerScanDirection`]); `PREV`/`LAST` require a
878    /// named index (`HANDLER t READ PREV` is `ER_PARSE_ERROR` on mysql:8).
879    Scan {
880        /// The scan direction (`FIRST`/`NEXT` only).
881        direction: HandlerScanDirection,
882        /// Source location and node identity.
883        meta: Meta,
884    },
885    /// `READ <index> { FIRST | NEXT | PREV | LAST }` — traverse a named index in the given
886    /// direction ([`HandlerIndexDirection`], the wider `PREV`/`LAST` set). A `PRIMARY` key
887    /// is spelled with the reserved word quoted (`` READ `PRIMARY` ``).
888    Index {
889        /// The index name.
890        index: Ident,
891        /// The traversal direction (`FIRST`/`NEXT`/`PREV`/`LAST`).
892        direction: HandlerIndexDirection,
893        /// Source location and node identity.
894        meta: Meta,
895    },
896    /// `READ <index> <op> ( <value> [, …] )` — seek a named index by key: position at the
897    /// first row whose index value satisfies [`comparison`](Self::Key::comparison) against
898    /// the key tuple. The key is a non-empty `expr_or_default` list ([`ValuesItem`], so a
899    /// bare `DEFAULT` element is admitted like the INSERT values path); `( )` is
900    /// `ER_PARSE_ERROR` on mysql:8.
901    Key {
902        /// The index name.
903        index: Ident,
904        /// The key comparison operator; see [`HandlerKeyComparison`].
905        comparison: HandlerKeyComparison,
906        /// The key tuple, a non-empty list of value expressions (`DEFAULT` admitted).
907        key: ThinVec<ValuesItem<X>>,
908        /// Source location and node identity.
909        meta: Meta,
910    },
911}
912
913/// The traversal direction of an indexless `HANDLER … READ { FIRST | NEXT }` scan
914/// ([`HandlerReadSelector::Scan`]) — the narrow `handler_scan_function` set. A surface tag
915/// (no `meta`), riding the parent's span like [`KillTarget`].
916#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
917#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
918#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
919pub enum HandlerScanDirection {
920    /// `FIRST` — the first row in storage order.
921    First,
922    /// `NEXT` — the next row after the handler's current position.
923    Next,
924}
925
926/// The traversal direction of an indexed `HANDLER … READ <index> { FIRST | NEXT | PREV |
927/// LAST }` ([`HandlerReadSelector::Index`]) — the `handler_rkey_function` set, which adds
928/// `PREV`/`LAST` over [`HandlerScanDirection`]. A surface tag (no `meta`).
929#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
930#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
931#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
932pub enum HandlerIndexDirection {
933    /// `FIRST` — the first row by the index's key order.
934    First,
935    /// `NEXT` — the next row in index order.
936    Next,
937    /// `PREV` — the previous row in index order.
938    Prev,
939    /// `LAST` — the last row by the index's key order.
940    Last,
941}
942
943/// The key comparison of a `HANDLER … READ <index> <op> (…)` seek
944/// ([`HandlerReadSelector::Key`]) — `sql_yacc.yy` `handler_rkey_mode`. A surface tag (no
945/// `meta`). The set is exactly `= >= <= > <`; `<>`/`!=` are `ER_PARSE_ERROR` on mysql:8.
946#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
947#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
948#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
949pub enum HandlerKeyComparison {
950    /// `=` — the first row whose index value equals the key.
951    Eq,
952    /// `>=` — the first row whose index value is greater than or equal to the key.
953    GreaterOrEqual,
954    /// `<=` — the last row whose index value is less than or equal to the key.
955    LessOrEqual,
956    /// `>` — the first row whose index value is strictly greater than the key.
957    Greater,
958    /// `<` — the last row whose index value is strictly less than the key.
959    Less,
960}
961
962/// A MySQL `CLONE` statement (gated by [`UtilitySyntax::clone`](crate::dialect::UtilitySyntax))
963/// — provision a data directory from either the running server or a remote donor
964/// (`sql_yacc.yy` `clone_stmt`).
965///
966/// The single `CLONE` leading keyword splits into two grammar-disjoint forms, so they ride the
967/// axis as variants rather than one option-soup node: [`Local`](Self::Local) copies the current
968/// instance's data into a new directory, and [`Instance`](Self::Instance) streams a remote
969/// donor's data over the wire. Non-generic — every operand is an account name, a string
970/// literal, or a port number, never an embedded expression.
971///
972/// Neither form is preparable over the binary protocol (live mysql:8.4.10: both are
973/// `ER_UNSUPPORTED_PS` 1295, grammar-*positive* but PS-declined) — a bind-time verdict the
974/// parse layer does not model.
975#[derive(Clone, Debug, PartialEq, Eq, Hash)]
976#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
977#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
978pub enum CloneStatement {
979    /// `CLONE LOCAL DATA DIRECTORY [=] '<dir>'` — clone the running instance into a new local
980    /// directory. The `DATA DIRECTORY` clause is mandatory (a bare `CLONE LOCAL '<dir>'` is
981    /// `ER_PARSE_ERROR` on mysql:8).
982    Local {
983        /// The mandatory target `DATA DIRECTORY [=] '<dir>'`.
984        data_directory: CloneDataDirectory,
985        /// Source location and node identity.
986        meta: Meta,
987    },
988    /// `CLONE INSTANCE FROM <user>[@<host>]:<port> IDENTIFIED BY '<pw>' [DATA DIRECTORY [=]
989    /// '<dir>'] [REQUIRE [NO] SSL]` — clone a remote donor instance. The `:<port>` is
990    /// mandatory and must abut the donor account with no surrounding whitespace (a space on
991    /// either side of the `:` is `ER_PARSE_ERROR` on mysql:8, a raw-offset adjacency check in
992    /// the grammar).
993    Instance {
994        /// The donor account — MySQL's full `user` axis (`<user>[@<host>]` or `CURRENT_USER`),
995        /// so the shared [`AccountName`] node is reused.
996        source: AccountName,
997        /// The donor port; a `ulong_num` integer literal, spelling preserved.
998        port: Literal,
999        /// The donor password from `IDENTIFIED BY '<pw>'` — a string literal.
1000        password: Literal,
1001        /// The optional target `DATA DIRECTORY [=] '<dir>'`; `None` when omitted.
1002        data_directory: Option<CloneDataDirectory>,
1003        /// The `REQUIRE [NO] SSL` transport requirement; see [`CloneSsl`].
1004        ssl: CloneSsl,
1005        /// Source location and node identity.
1006        meta: Meta,
1007    },
1008}
1009
1010/// The `DATA DIRECTORY [=] '<dir>'` target of a [`CloneStatement`] — mandatory in the `LOCAL`
1011/// form, optional in the `INSTANCE` form.
1012#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1013#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1014#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1015pub struct CloneDataDirectory {
1016    /// Whether the optional `=` was written between `DATA DIRECTORY` and the path, so the two
1017    /// spellings round-trip.
1018    pub equals: bool,
1019    /// The directory path — a string literal (`TEXT_STRING_filesystem`).
1020    pub path: Literal,
1021    /// Source location and node identity.
1022    pub meta: Meta,
1023}
1024
1025/// The `REQUIRE [NO] SSL` transport requirement of a `CLONE INSTANCE` statement (`sql_yacc.yy`
1026/// `opt_ssl`) — a surface tag (no `meta`), riding the parent's span like [`KillTarget`].
1027#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1028#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1029#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1030pub enum CloneSsl {
1031    /// No `REQUIRE` clause was written (`SSL_TYPE_NOT_SPECIFIED`).
1032    Unspecified,
1033    /// `REQUIRE SSL` — encrypted transport is required.
1034    Require,
1035    /// `REQUIRE NO SSL` — encrypted transport is disabled.
1036    RequireNo,
1037}
1038
1039/// A MySQL `IMPORT TABLE FROM '<file>' [, '<file>' …]` statement (gated by
1040/// [`UtilitySyntax::import_table`](crate::dialect::UtilitySyntax)) — recreate tables from
1041/// serialized `.sdi` metadata files written by a discarded tablespace (`sql_yacc.yy`
1042/// `import_stmt`).
1043///
1044/// The operand is a non-empty comma-separated list of **string** literals
1045/// (`TEXT_STRING_sys_list`); a bare identifier is `ER_PARSE_ERROR` on mysql:8, so
1046/// [`files`](Self::files) is a [`Literal`] list, not a name list. Distinct from DuckDB's
1047/// `IMPORT DATABASE '<dir>'` ([`ImportStatement`]) — both spell the leading `IMPORT`, but the
1048/// second keyword (`TABLE` vs `DATABASE`) and their separate gates keep them apart. Not
1049/// preparable over the binary protocol (live mysql:8.4.10: `ER_UNSUPPORTED_PS` 1295).
1050#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1051#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1052#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1053pub struct ImportTableStatement {
1054    /// The non-empty list of `.sdi` metadata file paths — string literals in source order.
1055    pub files: ThinVec<Literal>,
1056    /// Source location and node identity.
1057    pub meta: Meta,
1058}
1059
1060/// A MySQL `HELP '<topic>'` statement (gated by
1061/// [`UtilitySyntax::help_statement`](crate::dialect::UtilitySyntax)) — look up a term in the
1062/// server's help tables (`sql_yacc.yy` `help`).
1063///
1064/// The operand is a single `ident_or_text` (`sql_yacc.yy`), so a bare identifier (`HELP
1065/// contents`) and a quoted string (`HELP 'contents'`) are both accepted and fold to one
1066/// [`Ident`] whose quote style round-trips from its span — exactly the [`AccountName`] name
1067/// treatment. Exactly one operand: a bare `HELP` and a two-operand `HELP 'a' 'b'` are both
1068/// `ER_PARSE_ERROR` on mysql:8. Not preparable over the binary protocol (live mysql:8.4.10:
1069/// `ER_UNSUPPORTED_PS` 1295).
1070#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1071#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1072#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1073pub struct HelpStatement {
1074    /// The help topic — a bare-or-quoted `ident_or_text`, folded to an [`Ident`].
1075    pub topic: Ident,
1076    /// Source location and node identity.
1077    pub meta: Meta,
1078}
1079
1080/// A MySQL `BINLOG '<base64-event>'` statement (gated by
1081/// [`UtilitySyntax::binlog`](crate::dialect::UtilitySyntax)) — replay a base64-encoded binary
1082/// log event, the format `mysqlbinlog --base64-output` emits (`sql_yacc.yy`
1083/// `binlog_base64_event`).
1084///
1085/// The operand is a single string literal (`TEXT_STRING_sys`); a bare identifier is
1086/// `ER_PARSE_ERROR` on mysql:8. Unlike the other server-administration families, `BINLOG` **is**
1087/// preparable over the binary protocol (live mysql:8.4.10: `PREPARE` accepts a grammar-valid
1088/// payload; the base64 decode and event application happen only at *execution*, which the
1089/// parse/prepare layer never reaches), so the family evidence records a `Prepared` outcome, not
1090/// `ER_UNSUPPORTED_PS`.
1091#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1092#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1093#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1094pub struct BinlogStatement {
1095    /// The base64-encoded event payload — a string literal.
1096    pub event: Literal,
1097    /// Source location and node identity.
1098    pub meta: Meta,
1099}
1100
1101/// A SQLite `PRAGMA [<schema> .] <name> [= <value> | (<value>)]` configuration
1102/// statement (SQLite-specific; gated by
1103/// [`UtilitySyntax::pragma`](crate::dialect::UtilitySyntax)).
1104///
1105/// One canonical shape covers all three surface forms: a bare read
1106/// (`PRAGMA user_version`) is `value: None`, and the assignment (`= <value>`) and
1107/// call (`(<value>)`) spellings share one [`value`](Self::value) slot with the
1108/// [`parenthesized`](Self::parenthesized) surface tag recording which was written —
1109/// the [`CopyStatement::parenthesized`] pattern, never a second node. The value
1110/// grammar is SQLite's `signed-number | name | string-literal`, exactly the shape
1111/// [`SetParameterValue`] models for `SET` (SQLite's `PRAGMA` is its session-config
1112/// surface), so that node is reused rather than minting a parallel one. A general
1113/// expression is *not* admitted (`PRAGMA cache_size = 1 + 2` is a SQLite syntax
1114/// error), which is why the slot is not an [`Expr`].
1115#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1116#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1117#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1118pub struct PragmaStatement {
1119    /// Name referenced by this syntax.
1120    pub name: ObjectName,
1121    /// The written value; `None` for the bare interrogative form.
1122    pub value: Option<SetParameterValue>,
1123    /// Whether the value was written in the call form `(<value>)` (`true`) or the
1124    /// assignment form `= <value>` (`false`); irrelevant when [`value`](Self::value)
1125    /// is `None`, where neither spelling renders.
1126    pub parenthesized: bool,
1127    /// Source location and node identity.
1128    pub meta: Meta,
1129}
1130
1131/// A SQLite `ATTACH [DATABASE] <expr> AS <schema>` statement (SQLite-specific;
1132/// gated — together with its [`DetachStatement`] inverse — by
1133/// [`UtilitySyntax::attach`](crate::dialect::UtilitySyntax)).
1134///
1135/// The database source is a full expression in SQLite's grammar (usually a string
1136/// literal, but `ATTACH 'a' || '.db' AS x` is legal), which makes this node generic
1137/// over the extension `X`. The optional `DATABASE` noise keyword is
1138/// round-trip-significant, so it rides the
1139/// [`database_keyword`](Self::database_keyword) surface tag. The schema
1140/// alias is modelled as an [`Ident`] — SQLite's grammar technically admits an
1141/// expression there too (`AS 'aux'`), a permissiveness we deliberately do not model.
1142#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1143#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1144#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1145pub struct AttachStatement<X: Extension = NoExt> {
1146    /// Whether the optional `DATABASE` keyword was written; `true` renders it.
1147    pub database_keyword: bool,
1148    /// Object targeted by this syntax.
1149    pub target: Expr<X>,
1150    /// The attached/detached schema (database) name.
1151    pub schema: Ident,
1152    /// Source location and node identity.
1153    pub meta: Meta,
1154}
1155
1156/// A `DETACH [DATABASE] [IF EXISTS] <schema>` statement — the [`AttachStatement`]
1157/// inverse, sharing its [`UtilitySyntax::attach`](crate::dialect::UtilitySyntax)
1158/// gate (one dialect unit) and its `DATABASE` surface tag. Non-generic, like
1159/// [`CommentOnStatement`](super::CommentOnStatement): a schema name carries no
1160/// expressions or extension nodes.
1161///
1162/// The `IF EXISTS` guard is a DuckDB extension gated by
1163/// [`UtilitySyntax::detach_if_exists`](crate::dialect::UtilitySyntax) (SQLite's
1164/// `DETACH` has no such guard). DuckDB admits it only *after* the `DATABASE`
1165/// keyword — `DETACH DATABASE IF EXISTS x` parses but `DETACH IF EXISTS x` is a
1166/// parser error (probed on 1.5.4) — so [`if_exists`](Self::if_exists) is `true`
1167/// only alongside [`database_keyword`](Self::database_keyword).
1168#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1169#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1170#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1171pub struct DetachStatement {
1172    /// Whether the optional `DATABASE` keyword was written; `true` renders it.
1173    pub database_keyword: bool,
1174    /// Whether an `IF EXISTS` guard was written (DuckDB; requires
1175    /// [`database_keyword`](Self::database_keyword)). SQLite always leaves this `false`.
1176    pub if_exists: bool,
1177    /// The attached/detached schema (database) name.
1178    pub schema: Ident,
1179    /// Source location and node identity.
1180    pub meta: Meta,
1181}
1182
1183/// A `[FORCE] CHECKPOINT [<database>]` write-ahead-log flush statement.
1184///
1185/// PostgreSQL has the bare `CHECKPOINT` (no operands, gated by
1186/// [`MaintenanceSyntax::checkpoint`](crate::dialect::UtilitySyntax)); DuckDB extends it
1187/// with an optional `FORCE` modifier and an optional single database name, both gated
1188/// by [`MaintenanceSyntax::checkpoint_database`](crate::dialect::UtilitySyntax). The
1189/// database is a single bare [`Ident`] — DuckDB rejects a dotted `CHECKPOINT a.b` and
1190/// a quoted-string operand at parse time (probed on 1.5.4). Non-generic, like
1191/// [`DetachStatement`]: no expressions or extension nodes.
1192#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1193#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1194#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1195pub struct CheckpointStatement {
1196    /// Whether the `FORCE` modifier preceded `CHECKPOINT` (DuckDB).
1197    pub force: bool,
1198    /// The database to checkpoint; `None` for the bare form. A single bare name, not a
1199    /// dotted [`ObjectName`] (DuckDB rejects `CHECKPOINT a.b`).
1200    pub database: Option<Ident>,
1201    /// Source location and node identity.
1202    pub meta: Meta,
1203}
1204
1205/// A `LOAD <extension>` extension/shared-library load statement.
1206///
1207/// PostgreSQL loads a shared library by string path (`LOAD 'plpgsql'`); DuckDB loads
1208/// an extension by a string (`LOAD 'tpch'`, `LOAD 'path/e.duckdb_extension'`) *or* a
1209/// bare name (`LOAD tpch`). [`UtilitySyntax::load_extension`](crate::dialect::UtilitySyntax)
1210/// gates the string form (both dialects);
1211/// [`UtilitySyntax::load_bare_name`](crate::dialect::UtilitySyntax) admits the DuckDB
1212/// bare-identifier argument. Non-generic: the argument is a name or a string literal,
1213/// never a general expression (DuckDB rejects `LOAD 1 + 1`).
1214#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1215#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1216#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1217pub struct LoadStatement {
1218    /// Object targeted by this syntax.
1219    pub target: LoadTarget,
1220    /// Source location and node identity.
1221    pub meta: Meta,
1222}
1223
1224/// The argument of a [`LoadStatement`]: a bare extension name or a string path.
1225///
1226/// The two forms round-trip distinctly — `LOAD tpch` keeps its bare spelling and
1227/// `LOAD 'tpch'` its quoted one — so the parser records which was written rather than
1228/// canonicalizing to one.
1229#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1230#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1231#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1232pub enum LoadTarget {
1233    /// A bare identifier extension name (DuckDB `LOAD tpch`).
1234    Name {
1235        /// Name referenced by this syntax.
1236        name: Ident,
1237        /// Source location and node identity.
1238        meta: Meta,
1239    },
1240    /// A string-literal path or name (`LOAD 'plpgsql'`, `LOAD 'path/e.duckdb_extension'`).
1241    Path {
1242        /// Path supplied by this syntax.
1243        path: Literal,
1244        /// Source location and node identity.
1245        meta: Meta,
1246    },
1247}
1248
1249/// A DuckDB `UPDATE EXTENSIONS [( <name>, ... )]` extension-refresh statement
1250/// (DuckDB-specific; gated by
1251/// [`UtilitySyntax::update_extensions`](crate::dialect::UtilitySyntax)).
1252///
1253/// Refreshes installed extensions from their repository; the optional parenthesized
1254/// list restricts the refresh to the named extensions, and its absence updates every
1255/// installed extension. Non-generic, like [`ReindexStatement`]: the operands are bare
1256/// extension names — DuckDB's `opt_column_list` (`ColId` list), a quoted or unquoted
1257/// identifier, never a string, dotted name, or general expression (all engine-probed
1258/// rejects on 1.5.4). `extensions` is empty for the bare `UPDATE EXTENSIONS`; when the
1259/// list is written it carries at least one name (`UPDATE EXTENSIONS ()` is a DuckDB
1260/// parser error), so an empty vector round-trips as the bare form unambiguously.
1261#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1262#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1263#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1264pub struct UpdateExtensionsStatement {
1265    /// The extensions to refresh; empty for the bare `UPDATE EXTENSIONS` (all
1266    /// installed). When non-empty, the written parenthesized `( <name>, ... )` list.
1267    pub extensions: ThinVec<Ident>,
1268    /// Source location and node identity.
1269    pub meta: Meta,
1270}
1271
1272/// A `VACUUM …` database-maintenance statement — SQLite's `VACUUM [<schema>] [INTO
1273/// <expr>]` compaction *and* DuckDB's `VACUUM [ANALYZE] [<table> [(<col>, …)]]`
1274/// statistics/compaction, one node with each dialect's operands on their own gated
1275/// fields (the `DetachStatement`/`CheckpointStatement` precedent: extend the shared
1276/// node with dialect-gated fields rather than mint a dialect-named variant).
1277///
1278/// The three SQLite utility maintenance statements (`VACUUM`/`REINDEX`/`ANALYZE`) each
1279/// ride their own leading-keyword gate rather than one shared flag: they are
1280/// independent statements, not an inverse pair like `ATTACH`/`DETACH`, so they follow
1281/// the `copy`/`comment_on` precedent (separate flags even though every shipped dialect
1282/// toggles them together). The leading `VACUUM` is dispatched under
1283/// [`MaintenanceSyntax::vacuum`](crate::dialect::MaintenanceSyntax) (SQLite) *or*
1284/// [`MaintenanceSyntax::vacuum_analyze`](crate::dialect::MaintenanceSyntax) (DuckDB).
1285///
1286/// The two dialects' operands are disjoint and each rides its own gate, so at most one
1287/// dialect's fields are populated by a given parse — under every preset, *including*
1288/// the permissive union with both gates on. With both gates on the accepted language is
1289/// the exact union of the two grammars, not their cross product: engine-measured
1290/// (SQLite 3.x + DuckDB 1.5.4), every hybrid tail (`VACUUM ANALYZE … INTO`, a column
1291/// list or dotted name before `INTO`) is rejected by BOTH engines, so the parser admits
1292/// the `INTO` tail only on a SQLite-shaped prefix and a taken `INTO`'s single-part name
1293/// populates [`schema`](Self::schema), never [`table`](Self::table).
1294/// - **SQLite** ([`schema`](Self::schema) + [`into`](Self::into)): the grammar
1295///   (`parse.y`: `cmd ::= VACUUM nm INTO expr`) admits an optional single database name
1296///   — not a dotted [`ObjectName`], since `VACUUM main.t` is a SQLite syntax error — and
1297///   an optional `INTO <filename>` whose target is a full expression (`VACUUM INTO 'a' ||
1298///   '.db'` is legal), which makes this node generic over `X`.
1299/// - **DuckDB** ([`analyze`](Self::analyze) + [`table`](Self::table) +
1300///   [`columns`](Self::columns)): an optional `ANALYZE` option — the surviving VACUUM
1301///   option, spelled either bare (`VACUUM ANALYZE`) or as a parenthesized option list
1302///   (`VACUUM (ANALYZE)`), the two tracked distinctly on [`VacuumAnalyze`] for
1303///   round-trip fidelity — an optional *qualified* table (`VACUUM db.t` is legal, unlike
1304///   SQLite), and an optional parenthesized column list (present only alongside a table,
1305///   always non-empty — `VACUUM t ()` is a parser error). `ANALYZE` is the *only* option
1306///   the grammar admits at either layer: 1.5.4's parser rejects `NOWAIT`/`SKIP_TOAST`/any
1307///   unknown option and the boolean-argument form `(ANALYZE true)`, and its transform
1308///   throws `NotImplementedException` on `FULL`/`FREEZE`/`VERBOSE`/`disable_page_skipping`
1309///   — so those never parse and never prepare (engine-measured; see the parser's
1310///   `parse_vacuum_statement`).
1311///
1312/// The invariant is a *parser* guarantee, not a structural one: the representable
1313/// invalid states (both [`schema`](Self::schema) and [`table`](Self::table) populated,
1314/// [`columns`](Self::columns) without a table, an empty column list) are deliberately
1315/// unguarded against manual construction and serde deserialization. This crate has no
1316/// node-level structural-validation hook, and sibling nodes' shape claims (e.g.
1317/// [`AnalyzeStatement`]'s "always non-empty" column list) carry the same parse-output
1318/// scope; a bespoke debug assert on this one node would be an inconsistent one-off. The
1319/// renderer emits whatever is populated, in the fixed `ANALYZE`, name, columns, `INTO`
1320/// order.
1321#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1322#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1323#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1324pub struct VacuumStatement<X: Extension = NoExt> {
1325    /// SQLite: the database (schema) name to compact; `None` for the bare `VACUUM`. A
1326    /// single name, not schema-qualified (SQLite rejects `VACUUM main.t`). Always `None`
1327    /// under DuckDB (whose table operand rides [`table`](Self::table)).
1328    pub schema: Option<Ident>,
1329    /// SQLite: the `INTO <filename>` target expression; `None` when the clause is absent.
1330    /// A full expression (SQLite `INTO expr`), not merely a string literal. Always `None`
1331    /// under DuckDB (no `INTO` form).
1332    pub into: Option<Expr<X>>,
1333    /// DuckDB: the `ANALYZE` option and how it was spelled (`VACUUM ANALYZE` vs the
1334    /// parenthesized `VACUUM (ANALYZE)`), the one vacuum option 1.5.4 admits; `None` when
1335    /// absent. See [`VacuumAnalyze`]. Always `None` under SQLite.
1336    pub analyze: Option<VacuumAnalyze>,
1337    /// DuckDB: the *qualified* table to vacuum (`VACUUM db.t`); `None` when absent. A
1338    /// dotted [`ObjectName`], unlike SQLite's single-ident [`schema`](Self::schema).
1339    /// Always `None` under SQLite.
1340    pub table: Option<ObjectName>,
1341    /// DuckDB: the parenthesized column list restricting the vacuum/analyze; `None` when
1342    /// the clause is absent. Present only alongside [`table`](Self::table), and always
1343    /// non-empty (`VACUUM t ()` is a parser error). Always `None` under SQLite.
1344    pub columns: Option<ThinVec<Ident>>,
1345    /// Source location and node identity.
1346    pub meta: Meta,
1347}
1348
1349/// How DuckDB's `ANALYZE` vacuum option was spelled — a round-trip spelling tag, like
1350/// [`TableKeyword`]. DuckDB 1.5.4 accepts the option two ways with identical meaning
1351/// (both set the engine's single analyze flag), so the distinction is purely syntactic
1352/// and exists only to render the input back verbatim.
1353///
1354/// `ANALYZE` is the sole option either spelling admits: the parenthesized list rejects
1355/// every other option keyword and the boolean-argument form (`(ANALYZE true)`) at the
1356/// parser, and `FULL`/`FREEZE`/`VERBOSE`/`disable_page_skipping` at the transform
1357/// (engine-measured on libduckdb 1.5.4). A list of repeated `ANALYZE`
1358/// (`VACUUM (ANALYZE, ANALYZE)`) is accepted and canonicalized to the single
1359/// [`Parenthesized`](Self::Parenthesized) form — the repeats are semantically
1360/// idempotent, so no count is preserved.
1361#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1362#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1363#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1364pub enum VacuumAnalyze {
1365    /// The bare keyword form, `VACUUM ANALYZE`.
1366    Keyword,
1367    /// The parenthesized option-list form, `VACUUM (ANALYZE)`.
1368    Parenthesized,
1369}
1370
1371/// A SQLite `REINDEX [<collation> | [<schema> .] <table-or-index>]`
1372/// index-rebuild statement (SQLite-specific; gated by
1373/// [`MaintenanceSyntax::reindex`](crate::dialect::UtilitySyntax)).
1374///
1375/// The optional target is a possibly schema-qualified name (SQLite `REINDEX nm dbnm` —
1376/// a collation, table, or index name; the three are disambiguated by catalogue
1377/// lookup, not syntax, so one [`ObjectName`] slot covers all). Non-generic, like
1378/// [`DetachStatement`]: a name carries no expressions. `None` is the bare `REINDEX`
1379/// (rebuild everything).
1380#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1381#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1382#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1383pub struct ReindexStatement {
1384    /// Object targeted by this syntax.
1385    pub target: Option<ObjectName>,
1386    /// Source location and node identity.
1387    pub meta: Meta,
1388}
1389
1390/// An `ANALYZE …` statistics-gathering statement — SQLite's `ANALYZE [<schema> |
1391/// [<schema> .] <table-or-index>]` *and* DuckDB's `ANALYZE [<table> [(<col>, …)]]`,
1392/// gated by [`MaintenanceSyntax::analyze`](crate::dialect::MaintenanceSyntax).
1393///
1394/// Structurally the [`ReindexStatement`] shape — an optional possibly-qualified name
1395/// (SQLite `ANALYZE nm dbnm`; DuckDB `ANALYZE qualified_name`) shared on
1396/// [`target`](Self::target) — but a distinct statement with its own gate and node (the
1397/// `Vacuum`/`Reindex`/`Analyze` trio are three separate statements, not one parametric
1398/// family). `None` [`target`](Self::target) is the bare `ANALYZE` (analyze the whole
1399/// database).
1400///
1401/// DuckDB extends the target with an optional parenthesized column list
1402/// ([`columns`](Self::columns), gated by
1403/// [`MaintenanceSyntax::analyze_columns`](crate::dialect::MaintenanceSyntax)); DuckDB's
1404/// `VERBOSE` option never parses (1.5.4's transform throws
1405/// `NotImplementedException`), so no verbose surface is modelled.
1406#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1407#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1408#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1409pub struct AnalyzeStatement {
1410    /// Object targeted by this syntax.
1411    pub target: Option<ObjectName>,
1412    /// DuckDB: the parenthesized column list restricting the analyze; `None` when the
1413    /// clause is absent. Present only alongside a [`target`](Self::target), and always
1414    /// non-empty (`ANALYZE t ()` is a parser error). Always `None` under SQLite.
1415    pub columns: Option<ThinVec<Ident>>,
1416    /// Source location and node identity.
1417    pub meta: Meta,
1418}
1419
1420/// A MySQL table-administration maintenance statement — one of the five admin-table
1421/// verbs `{ANALYZE | CHECK | CHECKSUM | OPTIMIZE | REPAIR} {TABLE | TABLES} <table-list>
1422/// [options]` (gated by
1423/// [`MaintenanceSyntax::table_maintenance`](crate::dialect::MaintenanceSyntax)).
1424///
1425/// The five verbs share one shape — a verb, an optional `NO_WRITE_TO_BINLOG | LOCAL`
1426/// binlog-suppression prefix (ANALYZE/OPTIMIZE/REPAIR only), the `TABLE`/`TABLES`
1427/// keyword, a comma-separated table list, and per-verb trailing options. That shared
1428/// spine (the [`table_keyword`](Self::table_keyword) synonym tag and the
1429/// [`tables`](Self::tables) list) is hoisted here once, and the verb rides the
1430/// [`kind`](Self::kind) axis carrying only what differs, rather than five bespoke
1431/// statement nodes. Non-generic: every operand is a name, keyword flag, column list, or
1432/// integer literal — no embedded expression.
1433#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1434#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1435#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1436pub struct TableMaintenanceStatement {
1437    /// The verb and its per-verb options; see [`TableMaintenanceKind`].
1438    pub kind: TableMaintenanceKind,
1439    /// Whether the shared table keyword was written `TABLE` or its `TABLES` synonym.
1440    pub table_keyword: TableKeyword,
1441    /// The comma-separated target table list (each schema-qualifiable).
1442    pub tables: ThinVec<ObjectName>,
1443    /// Source location and node identity.
1444    pub meta: Meta,
1445}
1446
1447/// The verb of a [`TableMaintenanceStatement`] and its per-verb options — the axis on
1448/// which the five MySQL admin-table verbs differ.
1449///
1450/// The `NO_WRITE_TO_BINLOG | LOCAL` prefix is carried only by the verbs whose grammar
1451/// admits it (`ANALYZE`/`OPTIMIZE`/`REPAIR`; `CHECK`/`CHECKSUM` have none). `CHECK` and
1452/// `REPAIR` take an order-preserving, repeatable option list (MySQL OR's the flags but
1453/// the written order/repeats round-trip from the [`ThinVec`]); `CHECKSUM` takes a single
1454/// mutually-exclusive option; `OPTIMIZE` takes none.
1455#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1456#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1457#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1458pub enum TableMaintenanceKind {
1459    /// `ANALYZE [NO_WRITE_TO_BINLOG | LOCAL] {TABLE | TABLES} <list> [histogram-tail]`.
1460    Analyze {
1461        /// The optional binlog-suppression prefix.
1462        no_write_to_binlog: Option<NoWriteToBinlog>,
1463        /// The optional `{UPDATE | DROP} HISTOGRAM ON <cols>` tail.
1464        histogram: Option<AnalyzeHistogram>,
1465        /// Source location and node identity (the whole statement span; the hoisted
1466        /// spine sits between the prefix and the tail, so the verb payload has no
1467        /// contiguous sub-span of its own — the `VACUUM` dual-`meta` convention).
1468        meta: Meta,
1469    },
1470    /// `CHECK {TABLE | TABLES} <list> [<check-option> ...]`.
1471    Check {
1472        /// The repeatable, order-preserving check-type option list.
1473        options: ThinVec<CheckTableOption>,
1474        /// Source location and node identity.
1475        meta: Meta,
1476    },
1477    /// `CHECKSUM {TABLE | TABLES} <list> [QUICK | EXTENDED]`.
1478    Checksum {
1479        /// The single optional `QUICK`/`EXTENDED` mode.
1480        option: Option<ChecksumTableOption>,
1481        /// Source location and node identity.
1482        meta: Meta,
1483    },
1484    /// `OPTIMIZE [NO_WRITE_TO_BINLOG | LOCAL] {TABLE | TABLES} <list>`.
1485    Optimize {
1486        /// The optional binlog-suppression prefix.
1487        no_write_to_binlog: Option<NoWriteToBinlog>,
1488        /// Source location and node identity.
1489        meta: Meta,
1490    },
1491    /// `REPAIR [NO_WRITE_TO_BINLOG | LOCAL] {TABLE | TABLES} <list> [<repair-option> ...]`.
1492    Repair {
1493        /// The optional binlog-suppression prefix.
1494        no_write_to_binlog: Option<NoWriteToBinlog>,
1495        /// The repeatable, order-preserving repair-type option list.
1496        options: ThinVec<RepairTableOption>,
1497        /// Source location and node identity.
1498        meta: Meta,
1499    },
1500}
1501
1502/// `TABLE` vs its interchangeable `TABLES` synonym in the admin-table verbs — a
1503/// round-trip spelling tag (MySQL's `table_or_tables`).
1504#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1505#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1506#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1507pub enum TableKeyword {
1508    /// The singular `TABLE` spelling.
1509    Table,
1510    /// The plural `TABLES` spelling.
1511    Tables,
1512}
1513
1514/// The MySQL `NO_WRITE_TO_BINLOG | LOCAL` binlog-suppression prefix on
1515/// `ANALYZE`/`OPTIMIZE`/`REPAIR TABLE`.
1516///
1517/// `LOCAL` is an exact synonym of `NO_WRITE_TO_BINLOG` (both set the same
1518/// don't-replicate flag), so the written spelling rides this tag for round-trip.
1519#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1520#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1521#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1522pub enum NoWriteToBinlog {
1523    /// The `NO_WRITE_TO_BINLOG` spelling.
1524    NoWriteToBinlog,
1525    /// The `LOCAL` synonym spelling.
1526    Local,
1527}
1528
1529/// The `ANALYZE TABLE ... {UPDATE | DROP} HISTOGRAM ON <cols>` histogram-management tail.
1530///
1531/// Only one tail may appear. The 8.4 extensions to the `UPDATE` form — `{AUTO | MANUAL}
1532/// UPDATE` and `USING DATA '<json>'` — are deliberately not modelled here; the measured
1533/// grammar this node covers is `UPDATE HISTOGRAM ON <cols> [WITH <n> BUCKETS]` and `DROP
1534/// HISTOGRAM ON <cols>`.
1535#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1536#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1537#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1538pub enum AnalyzeHistogram {
1539    /// `UPDATE HISTOGRAM ON <cols> [WITH <n> BUCKETS]`.
1540    Update {
1541        /// The columns whose histogram is (re)built.
1542        columns: ThinVec<Ident>,
1543        /// The optional `WITH <n> BUCKETS` bucket count (an unsigned integer literal).
1544        buckets: Option<Literal>,
1545        /// Source location and node identity.
1546        meta: Meta,
1547    },
1548    /// `DROP HISTOGRAM ON <cols>`.
1549    Drop {
1550        /// The columns whose histogram is dropped.
1551        columns: ThinVec<Ident>,
1552        /// Source location and node identity.
1553        meta: Meta,
1554    },
1555}
1556
1557/// A `CHECK TABLE` check-type option (`opt_mi_check_types`).
1558///
1559/// Grammatically repeatable and order-free (MySQL OR's the flags); the parser preserves
1560/// the written order and repeats in the option list.
1561#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1562#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1563#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1564pub enum CheckTableOption {
1565    /// `FOR UPGRADE`.
1566    ForUpgrade,
1567    /// `QUICK`.
1568    Quick,
1569    /// `FAST`.
1570    Fast,
1571    /// `MEDIUM`.
1572    Medium,
1573    /// `EXTENDED`.
1574    Extended,
1575    /// `CHANGED`.
1576    Changed,
1577}
1578
1579/// A `CHECKSUM TABLE` option (`opt_checksum_type`) — single and mutually exclusive.
1580#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1581#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1582#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1583pub enum ChecksumTableOption {
1584    /// `QUICK`.
1585    Quick,
1586    /// `EXTENDED`.
1587    Extended,
1588}
1589
1590/// A `REPAIR TABLE` repair-type option (`mi_repair_type`).
1591///
1592/// Grammatically repeatable and order-free (MySQL OR's the flags); the parser preserves
1593/// the written order and repeats in the option list.
1594#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1595#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1596#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1597pub enum RepairTableOption {
1598    /// `QUICK`.
1599    Quick,
1600    /// `EXTENDED`.
1601    Extended,
1602    /// `USE_FRM`.
1603    UseFrm,
1604}
1605
1606/// A MySQL `CACHE INDEX` statement — assign a table's indexes to a named key cache (gated by
1607/// [`UtilitySyntax::key_cache_statements`](crate::dialect::UtilitySyntax)).
1608///
1609/// The MyISAM-era key-cache management pair, with [`LoadIndexStatement`]. Two grammar arms
1610/// share the `<table> [<key-list>]` per-table shape but are mutually exclusive on the
1611/// partition axis (`sql_yacc.yy` `keycache_stmt`), so they ride the [`CacheIndexTargets`]
1612/// enum rather than an optional-partition field that could pair a partition with a table
1613/// list: `CACHE INDEX <t> [<keys>][, <t> [<keys>] ...] IN <cache>` (the multi-table
1614/// [`Tables`](CacheIndexTargets::Tables) arm, no partition) and `CACHE INDEX <t> PARTITION
1615/// (...) [<keys>] IN <cache>` (the single-table [`Partition`](CacheIndexTargets::Partition)
1616/// arm). Measured on mysql:8.4.10: a table list with a `PARTITION` clause, and a `PARTITION`
1617/// written after the key list, both `ER_PARSE_ERROR`. Carries no [`Expr`], so it is not
1618/// generic over `X`.
1619#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1620#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1621#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1622pub struct CacheIndexStatement {
1623    /// The table(s) whose indexes are assigned, and — in the partitioned arm — the selected
1624    /// partitions; see [`CacheIndexTargets`].
1625    pub targets: CacheIndexTargets,
1626    /// The destination key cache named by the trailing `IN <cache>`; see [`KeyCacheName`].
1627    pub cache: KeyCacheName,
1628    /// Source location and node identity.
1629    pub meta: Meta,
1630}
1631
1632/// The table target(s) of a [`CacheIndexStatement`] — the list-vs-partition axis of MySQL's
1633/// `keycache_stmt`.
1634///
1635/// The two arms are mutually exclusive: the multi-table list carries no partition, and the
1636/// partitioned form is restricted to a single table. Modelling them as one enum keeps the
1637/// invalid "table list + partition" state unrepresentable.
1638#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1639#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1640#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1641pub enum CacheIndexTargets {
1642    /// `<t> [<keys>][, <t> [<keys>] ...]` — one or more tables, each with an optional key
1643    /// list, and no partitioning (the `keycache_list` arm; a single unpartitioned table is
1644    /// the one-element list).
1645    Tables {
1646        /// The per-table assignments in source order (always non-empty).
1647        tables: ThinVec<CacheIndexTable>,
1648        /// Source location and node identity.
1649        meta: Meta,
1650    },
1651    /// `<t> PARTITION (...) [<keys>]` — a single table restricted to the named partitions,
1652    /// with an optional key list written *after* the partition clause (the `adm_partition`
1653    /// arm; the grammar admits no table list here).
1654    Partition {
1655        /// The single target table.
1656        table: ObjectName,
1657        /// The `PARTITION (ALL | <names>)` selection; see [`PartitionSelection`].
1658        partition: PartitionSelection,
1659        /// The optional trailing `{INDEX | KEY} (<keys>)` list; see [`CacheIndexKeyList`].
1660        keys: Option<CacheIndexKeyList>,
1661        /// Source location and node identity.
1662        meta: Meta,
1663    },
1664}
1665
1666/// One `<table> [{INDEX | KEY} (<keys>)]` assignment in a [`CacheIndexTargets::Tables`] list
1667/// (MySQL's `assign_to_keycache`).
1668#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1669#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1670#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1671pub struct CacheIndexTable {
1672    /// The (schema-qualifiable) table name.
1673    pub table: ObjectName,
1674    /// The optional `{INDEX | KEY} (<keys>)` list; see [`CacheIndexKeyList`].
1675    pub keys: Option<CacheIndexKeyList>,
1676    /// Source location and node identity.
1677    pub meta: Meta,
1678}
1679
1680/// A MySQL `LOAD INDEX INTO CACHE` statement — preload a table's index blocks into its key
1681/// cache (gated by
1682/// [`UtilitySyntax::key_cache_statements`](crate::dialect::UtilitySyntax)).
1683///
1684/// The preload half of the key-cache pair with [`CacheIndexStatement`]; it takes no `IN
1685/// <cache>` (the index is loaded into the table's already-assigned cache). Same list-vs-
1686/// partition exclusivity (`sql_yacc.yy` `preload_stmt`), plus a per-table `IGNORE LEAVES`
1687/// flag written *after* the key list — see [`LoadIndexTargets`]. Measured on mysql:8.4.10:
1688/// `IGNORE LEAVES` before the key list, a partition with a table list, and a trailing `IN
1689/// <cache>` all `ER_PARSE_ERROR`. Carries no [`Expr`], so it is not generic over `X`.
1690#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1691#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1692#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1693pub struct LoadIndexStatement {
1694    /// The table(s) whose indexes are preloaded, and — in the partitioned arm — the selected
1695    /// partitions; see [`LoadIndexTargets`].
1696    pub targets: LoadIndexTargets,
1697    /// Source location and node identity.
1698    pub meta: Meta,
1699}
1700
1701/// The table target(s) of a [`LoadIndexStatement`] — the list-vs-partition axis of MySQL's
1702/// `preload_stmt`, mirroring [`CacheIndexTargets`] with a per-table `IGNORE LEAVES` flag.
1703#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1704#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1705#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1706pub enum LoadIndexTargets {
1707    /// `<t> [<keys>] [IGNORE LEAVES][, ...]` — one or more tables, each with an optional key
1708    /// list and `IGNORE LEAVES` flag, and no partitioning (the `preload_list` arm).
1709    Tables {
1710        /// The per-table preloads in source order (always non-empty).
1711        tables: ThinVec<LoadIndexTable>,
1712        /// Source location and node identity.
1713        meta: Meta,
1714    },
1715    /// `<t> PARTITION (...) [<keys>] [IGNORE LEAVES]` — a single table restricted to the
1716    /// named partitions (the partitioned `preload_stmt` arm; no table list here).
1717    Partition {
1718        /// The single target table.
1719        table: ObjectName,
1720        /// The `PARTITION (ALL | <names>)` selection; see [`PartitionSelection`].
1721        partition: PartitionSelection,
1722        /// The optional trailing `{INDEX | KEY} (<keys>)` list; see [`CacheIndexKeyList`].
1723        keys: Option<CacheIndexKeyList>,
1724        /// Whether the trailing `IGNORE LEAVES` flag (skip non-leaf index blocks) was written.
1725        ignore_leaves: bool,
1726        /// Source location and node identity.
1727        meta: Meta,
1728    },
1729}
1730
1731/// One `<table> [{INDEX | KEY} (<keys>)] [IGNORE LEAVES]` preload in a
1732/// [`LoadIndexTargets::Tables`] list (MySQL's `preload_keys`).
1733#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1734#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1735#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1736pub struct LoadIndexTable {
1737    /// The (schema-qualifiable) table name.
1738    pub table: ObjectName,
1739    /// The optional `{INDEX | KEY} (<keys>)` list; see [`CacheIndexKeyList`].
1740    pub keys: Option<CacheIndexKeyList>,
1741    /// Whether the trailing `IGNORE LEAVES` flag was written (skip non-leaf index blocks).
1742    pub ignore_leaves: bool,
1743    /// Source location and node identity.
1744    pub meta: Meta,
1745}
1746
1747/// The optional `{INDEX | KEY} (<key>[, ...])` list shared by the key-cache statements
1748/// (MySQL's `opt_cache_key_list` in its non-empty form).
1749///
1750/// `INDEX` and `KEY` are exact synonyms — the written spelling rides
1751/// [`keyword`](Self::keyword) for round-trip. The parenthesized list may be empty
1752/// (`INDEX ()`); the parentheses are always written, so an empty [`keys`](Self::keys) is
1753/// distinct from the clause's absence (the enclosing `Option<CacheIndexKeyList>` being
1754/// `None`). Each key is an index name, or the `PRIMARY` keyword naming the primary key —
1755/// admitted here unquoted (`key_usage_element: ident | PRIMARY_SYM`) and preserved as an
1756/// unquoted [`Ident`] (a backtick-quoted `` `PRIMARY` `` round-trips as a quoted name).
1757#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1758#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1759#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1760pub struct CacheIndexKeyList {
1761    /// Whether the list was introduced with `INDEX` or its `KEY` synonym.
1762    pub keyword: CacheIndexKeyword,
1763    /// The index names in source order; possibly empty (the `INDEX ()` form).
1764    pub keys: ThinVec<Ident>,
1765    /// Source location and node identity.
1766    pub meta: Meta,
1767}
1768
1769/// The `INDEX` vs `KEY` spelling of a [`CacheIndexKeyList`] — exact synonyms, recorded so the
1770/// written keyword round-trips. A surface tag (no `meta`, like [`TableKeyword`]).
1771#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1772#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1773#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1774pub enum CacheIndexKeyword {
1775    /// The `INDEX` spelling.
1776    Index,
1777    /// The `KEY` spelling.
1778    Key,
1779}
1780
1781/// The destination key cache of a [`CacheIndexStatement`]'s `IN <cache>` clause (MySQL's
1782/// `key_cache_name`): a named cache or the `DEFAULT` server key cache.
1783#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1784#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1785#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1786pub enum KeyCacheName {
1787    /// `IN <cache_name>` — a named key cache.
1788    Named {
1789        /// The key-cache name identifier.
1790        name: Ident,
1791        /// Source location and node identity.
1792        meta: Meta,
1793    },
1794    /// `IN DEFAULT` — the server's built-in default key cache (`default_key_cache_base`).
1795    Default {
1796        /// Source location and node identity.
1797        meta: Meta,
1798    },
1799}
1800
1801/// The `PARTITION (ALL | <name>[, ...])` selection of a partitioned key-cache statement
1802/// (MySQL's `adm_partition` / `all_or_alt_part_name_list`): every partition, or a named set.
1803#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1804#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1805#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1806pub enum PartitionSelection {
1807    /// `PARTITION (ALL)` — every partition of the table.
1808    All {
1809        /// Source location and node identity.
1810        meta: Meta,
1811    },
1812    /// `PARTITION (<name>[, ...])` — the named partitions (always non-empty).
1813    Names {
1814        /// The partition-name identifiers in source order.
1815        names: ThinVec<Ident>,
1816        /// Source location and node identity.
1817        meta: Meta,
1818    },
1819}
1820
1821/// A MySQL standalone object-rename statement — `RENAME TABLE <a> TO <b>[, ...]` or
1822/// `RENAME USER <u> TO <v>[, ...]` (gated by
1823/// [`UtilitySyntax::rename_statement`](crate::dialect::UtilitySyntax)).
1824///
1825/// The two forms share the `RENAME <keyword> <old> TO <new>[, ...]` rename-list shape but
1826/// carry different element types — schema-qualifiable table names vs. MySQL account names
1827/// — so they ride the axis as distinct variants rather than one option-soup list. This is
1828/// the *standalone* `RENAME` statement; the `ALTER TABLE ... RENAME TO` sub-clause is a
1829/// separate construct.
1830#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1831#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1832#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1833pub enum RenameStatement {
1834    /// `RENAME {TABLE | TABLES} <from> TO <to>[, <from> TO <to> ...]`.
1835    Table {
1836        /// Whether the keyword was written `TABLE` or its `TABLES` synonym.
1837        table_keyword: TableKeyword,
1838        /// The `<from> TO <to>` table-rename mappings in source order.
1839        renames: ThinVec<TableRename>,
1840        /// Source location and node identity.
1841        meta: Meta,
1842    },
1843    /// `RENAME USER <from> TO <to>[, <from> TO <to> ...]`.
1844    User {
1845        /// The `<from> TO <to>` user-rename mappings in source order.
1846        renames: ThinVec<UserRename>,
1847        /// Source location and node identity.
1848        meta: Meta,
1849    },
1850}
1851
1852/// One `<from> TO <to>` table-rename mapping in a `RENAME TABLE` statement.
1853#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1854#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1855#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1856pub struct TableRename {
1857    /// The existing table name.
1858    pub from: ObjectName,
1859    /// The new table name.
1860    pub to: ObjectName,
1861    /// Source location and node identity.
1862    pub meta: Meta,
1863}
1864
1865/// One `<from> TO <to>` account-rename mapping in a `RENAME USER` statement.
1866#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1867#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1868#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1869pub struct UserRename {
1870    /// The existing account name.
1871    pub from: AccountName,
1872    /// The new account name.
1873    pub to: AccountName,
1874    /// Source location and node identity.
1875    pub meta: Meta,
1876}
1877
1878/// A MySQL account name — the full `user` grammar axis: a named `<user>[@<host>]` account
1879/// or the `CURRENT_USER [()]` self-reference.
1880///
1881/// This is the shared account-reference node the whole MySQL user/role surface rides —
1882/// `RENAME USER`, `CREATE`/`ALTER`/`DROP USER`, `CREATE`/`DROP ROLE`, and (via the same
1883/// spellings) role lists. Each name part of a named account is a MySQL `ident_or_text` (a
1884/// bare/backtick identifier or a quoted `'…'`/`"…"` string), folded to an [`Ident`] whose
1885/// quote style round-trips from its span (`'u'@'localhost'`, `` u@`localhost` ``,
1886/// `u@localhost`). A bare user with no `@host` leaves [`host`](AccountName::Account::host)
1887/// `None` — the server reads that as `@'%'`, but the absent host is preserved as written
1888/// rather than materialised.
1889///
1890/// [`Definer`](super::Definer) is the parallel *minimal* account reference a routine header
1891/// carries (`DEFINER = <user>`); it mirrors this node's `Account`/`CurrentUser` split and is
1892/// slated to fold into this shared node once the routine/trigger/event landings that consume
1893/// it converge.
1894#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1895#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1896#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1897pub enum AccountName {
1898    /// `<user>[@<host>]` — a named account. Both parts round-trip their source spelling
1899    /// (quote style included) from their [`Ident`] spans.
1900    Account {
1901        /// The user-name part (before any `@`).
1902        user: Ident,
1903        /// The optional `@<host>` part; `None` for a bare user name.
1904        host: Option<Ident>,
1905        /// Source location and node identity.
1906        meta: Meta,
1907    },
1908    /// `CURRENT_USER [()]` — the session's current account. Never valid as a *role* name, but
1909    /// accepted anywhere the grammar's `user` non-terminal is (user lists, `DEFAULT ROLE`
1910    /// targets bind it as an account, not a role).
1911    CurrentUser {
1912        /// Whether the empty `()` call-style parentheses were written.
1913        parens: bool,
1914        /// Source location and node identity.
1915        meta: Meta,
1916    },
1917}
1918
1919/// A MySQL `FLUSH [NO_WRITE_TO_BINLOG | LOCAL] <target>` server-administration statement
1920/// (gated by [`UtilitySyntax::flush`](crate::dialect::UtilitySyntax)).
1921///
1922/// `FLUSH` reloads or clears one of the server's internal caches or logs. The optional
1923/// `NO_WRITE_TO_BINLOG | LOCAL` prefix — shared with the admin-table verbs, so it reuses
1924/// [`NoWriteToBinlog`] — suppresses binary-log replication of the flush. *What* is flushed
1925/// rides the [`FlushTarget`] axis: MySQL's `flush_options` grammar splits into the
1926/// `{TABLE | TABLES} [<list>] [WITH READ LOCK | FOR EXPORT]` form and a comma-separated list
1927/// of keyword targets, and the two are mutually exclusive — `FLUSH TABLES, LOGS` and
1928/// `FLUSH LOGS, TABLES` are both `ER_PARSE_ERROR` on mysql:8.4.10, `TABLES` never joining the
1929/// list. Non-generic: every operand is a table name, keyword flag, or channel string — no
1930/// embedded expression.
1931#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1932#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1933#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1934pub struct FlushStatement {
1935    /// The optional `NO_WRITE_TO_BINLOG | LOCAL` binlog-suppression prefix.
1936    pub no_write_to_binlog: Option<NoWriteToBinlog>,
1937    /// What is flushed; see [`FlushTarget`].
1938    pub target: FlushTarget,
1939    /// Source location and node identity.
1940    pub meta: Meta,
1941}
1942
1943/// What a [`FlushStatement`] flushes — MySQL's mutually-exclusive `flush_options` split.
1944///
1945/// The `{TABLE | TABLES}` form ([`Tables`](Self::Tables)) and the comma-separated keyword
1946/// list ([`Options`](Self::Options)) are distinct grammar alternatives, not two shapes of one
1947/// list: `TABLES` cannot appear inside the option list and the list members cannot follow
1948/// `TABLES` (both `ER_PARSE_ERROR` on mysql:8.4.10), so they ride the axis as two variants
1949/// rather than one option-soup list.
1950#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1951#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1952#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1953pub enum FlushTarget {
1954    /// `{TABLE | TABLES} [<tbl>[, ...]] [WITH READ LOCK | FOR EXPORT]`.
1955    ///
1956    /// The table list may be empty (`FLUSH TABLES`, `FLUSH TABLES WITH READ LOCK`). `FOR
1957    /// EXPORT` *requires* a non-empty list — `FLUSH TABLES FOR EXPORT` is `ER_PARSE_ERROR`
1958    /// (`ER_NO_TABLES_USED`) on mysql:8.4.10 — while `WITH READ LOCK` admits the empty list;
1959    /// the parser enforces that boundary and never produces [`ForExport`](FlushTablesLock::ForExport)
1960    /// with an empty [`tables`](Self::Tables::tables). The `TABLE`/`TABLES` spelling reuses
1961    /// the shared [`TableKeyword`] synonym tag.
1962    Tables {
1963        /// Whether the keyword was written `TABLE` or its `TABLES` synonym.
1964        table_keyword: TableKeyword,
1965        /// The comma-separated target table list (each schema-qualifiable); empty when no
1966        /// list was written.
1967        tables: ThinVec<ObjectName>,
1968        /// The optional trailing `WITH READ LOCK | FOR EXPORT` lock clause.
1969        lock: Option<FlushTablesLock>,
1970        /// Source location and node identity.
1971        meta: Meta,
1972    },
1973    /// A comma-separated list of keyword flush targets (`FLUSH LOGS`, `FLUSH PRIVILEGES,
1974    /// STATUS`); see [`FlushOption`]. Always non-empty — the grammar's `flush_options_list`
1975    /// carries at least one member.
1976    Options {
1977        /// The keyword targets in source order.
1978        options: ThinVec<FlushOption>,
1979        /// Source location and node identity.
1980        meta: Meta,
1981    },
1982}
1983
1984/// The trailing lock clause on the `FLUSH TABLES` form — MySQL's `opt_flush_lock`.
1985#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1986#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1987#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1988pub enum FlushTablesLock {
1989    /// `WITH READ LOCK` — flush and hold a global read lock (accepted with or without a
1990    /// table list).
1991    WithReadLock,
1992    /// `FOR EXPORT` — flush and lock the named tables for a transportable-tablespace export
1993    /// (requires a non-empty table list; see [`FlushTarget::Tables`]).
1994    ForExport,
1995}
1996
1997/// One keyword target in a [`FlushTarget::Options`] list — MySQL's `flush_option`.
1998///
1999/// Every member is a bare keyword or keyword pair except [`RelayLogs`](Self::RelayLogs),
2000/// which carries an optional `FOR CHANNEL '<name>'` qualifier. Measured against MySQL 8.4.10
2001/// (`sql_yacc.yy` `flush_option`): the pre-8.0 `QUERY CACHE`, `DES_KEY_FILE`, and `HOSTS`
2002/// targets are gone (all `ER_PARSE_ERROR`), and `USER_RESOURCES` is the sole accepted
2003/// spelling of the user-resource reset — bare `RESOURCES` is `ER_PARSE_ERROR` even though the
2004/// yacc token is *named* `RESOURCES`, because the lexer maps only the `USER_RESOURCES` keyword
2005/// text onto it. A spanned enum (each variant carries its own [`Meta`], like [`ShowTarget`]):
2006/// [`RelayLogs`](Self::RelayLogs) holds a spanned channel [`Literal`], so every sibling
2007/// records its own list-element span too.
2008#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2009#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2010#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2011pub enum FlushOption {
2012    /// `PRIVILEGES` — reload the grant tables.
2013    Privileges {
2014        /// Source location and node identity.
2015        meta: Meta,
2016    },
2017    /// `LOGS` — close and reopen all log files.
2018    Logs {
2019        /// Source location and node identity.
2020        meta: Meta,
2021    },
2022    /// `BINARY LOGS` — rotate the binary log.
2023    BinaryLogs {
2024        /// Source location and node identity.
2025        meta: Meta,
2026    },
2027    /// `ENGINE LOGS` — flush the storage-engine logs.
2028    EngineLogs {
2029        /// Source location and node identity.
2030        meta: Meta,
2031    },
2032    /// `ERROR LOGS` — close and reopen the error log.
2033    ErrorLogs {
2034        /// Source location and node identity.
2035        meta: Meta,
2036    },
2037    /// `GENERAL LOGS` — close and reopen the general query log.
2038    GeneralLogs {
2039        /// Source location and node identity.
2040        meta: Meta,
2041    },
2042    /// `SLOW LOGS` — close and reopen the slow query log.
2043    SlowLogs {
2044        /// Source location and node identity.
2045        meta: Meta,
2046    },
2047    /// `RELAY LOGS [FOR CHANNEL '<name>']` — rotate the relay log, optionally for one named
2048    /// replication channel (`opt_channel`).
2049    RelayLogs {
2050        /// The optional `FOR CHANNEL '<name>'` qualifier (a string literal); `None` when no
2051        /// channel was named.
2052        channel: Option<Literal>,
2053        /// Source location and node identity.
2054        meta: Meta,
2055    },
2056    /// `STATUS` — reset the session status counters.
2057    Status {
2058        /// Source location and node identity.
2059        meta: Meta,
2060    },
2061    /// `USER_RESOURCES` — reset the per-account resource counters.
2062    UserResources {
2063        /// Source location and node identity.
2064        meta: Meta,
2065    },
2066    /// `OPTIMIZER_COSTS` — reload the optimizer cost constants.
2067    OptimizerCosts {
2068        /// Source location and node identity.
2069        meta: Meta,
2070    },
2071}
2072
2073/// A MySQL `PURGE BINARY LOGS {TO '<log>' | BEFORE <datetime>}` binary-log purge statement
2074/// (gated by [`UtilitySyntax::purge_binary_logs`](crate::dialect::UtilitySyntax)).
2075///
2076/// Deletes binary-log files up to a named file or a cutoff time. The `BINARY` keyword is
2077/// fixed: MySQL 8.4 removed the deprecated `MASTER` synonym (`PURGE MASTER LOGS` is
2078/// `ER_PARSE_ERROR` on mysql:8.4.10), so there is no spelling axis. Exactly one target clause
2079/// is required — a bare `PURGE BINARY LOGS` is `ER_PARSE_ERROR` — riding the [`PurgeTarget`]
2080/// axis. The `BEFORE` form takes a full datetime *expression* (`BEFORE NOW() - INTERVAL 3 DAY`
2081/// parses), which makes this node generic over the extension `X`.
2082#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2083#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2084#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2085pub struct PurgeStatement<X: Extension = NoExt> {
2086    /// The required `TO '<log>'` or `BEFORE <datetime>` target clause; see [`PurgeTarget`].
2087    pub target: PurgeTarget<X>,
2088    /// Source location and node identity.
2089    pub meta: Meta,
2090}
2091
2092/// The required target clause of a [`PurgeStatement`] — MySQL's `purge_option`.
2093#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2094#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2095#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2096pub enum PurgeTarget<X: Extension = NoExt> {
2097    /// `TO '<log>'` — purge every binary log up to (not including) the named file.
2098    To {
2099        /// The binary-log file name (a string literal, `TEXT_STRING_sys`).
2100        log: Literal,
2101        /// Source location and node identity.
2102        meta: Meta,
2103    },
2104    /// `BEFORE <datetime>` — purge every binary log written before the cutoff time.
2105    Before {
2106        /// The cutoff datetime expression (`'2000-01-01 00:00:00'`, `NOW() - INTERVAL 3 DAY`,
2107        /// ...).
2108        datetime: Expr<X>,
2109        /// Source location and node identity.
2110        meta: Meta,
2111    },
2112}
2113
2114/// A `USE <schema>` (MySQL) / `USE <catalog> [. <schema>]` (DuckDB) catalog/schema-switch
2115/// statement (gated by
2116/// [`UtilitySyntax::use_statement`](crate::dialect::UtilitySyntax)).
2117///
2118/// Sets the default catalog and schema for subsequent unqualified names — DuckDB's
2119/// engine implements it as a `SET schema`, MySQL's as `SQLCOM_CHANGE_DB`, but syntactically
2120/// it is its own statement. The name arity is dialect data gated by
2121/// [`UtilitySyntax::use_qualified_name`](crate::dialect::UtilitySyntax): DuckDB accepts
2122/// `USE db` and `USE db.schema` but rejects a three-part `USE a.b.c` at parse time
2123/// (`Expected "USE database" or "USE database.schema"`), while MySQL's `USE ident` takes a
2124/// single unqualified schema and `ER_PARSE_ERROR`s any dotted name (engine-measured on
2125/// mysql:8) — so this holds an [`ObjectName`] of one or two [`Ident`]s and the parser
2126/// enforces whichever bound the dialect does. Non-generic, like [`DetachStatement`]: a
2127/// qualified name carries no expressions or extension nodes.
2128#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2129#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2130#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2131pub struct UseStatement {
2132    /// Name referenced by this syntax.
2133    pub name: ObjectName,
2134    /// Source location and node identity.
2135    pub meta: Meta,
2136}
2137
2138/// A `PREPARE <name> [ ( <type> [, ...] ) ] AS <statement>` prepared-statement
2139/// definition (DuckDB; gated by
2140/// [`UtilitySyntax::prepared_statements`](crate::dialect::UtilitySyntax)).
2141///
2142/// Binds a session-scoped name to a parameterized statement (`PREPARE v1 AS SELECT
2143/// 'Test' LIMIT ?`). The body embeds a [`Statement`], which makes this node generic
2144/// over the extension `X`, and follows the [`ExplainStatement`] contract:
2145/// the grammar accepts *any* [`Statement`] and leaves the preparable-kind restriction
2146/// to a later pass — DuckDB rejects a non-preparable body at bind, not parse.
2147///
2148/// The prepared-statement name is a bare [`Ident`], not a dotted [`ObjectName`]: the
2149/// name lives in a flat session namespace, not the catalogue. DuckDB rejects the
2150/// PostgreSQL `PREPARE name ( <type> [, ...] ) AS ...` argument-type list ("Prepared
2151/// statement argument types are not supported, use CAST"), so
2152/// [`parameter_types`](Self::parameter_types) is gated by its own
2153/// [`prepare_typed_parameters`](crate::dialect::UtilitySyntax::prepare_typed_parameters)
2154/// flag, independent of the base `PREPARE`/`EXECUTE`/`DEALLOCATE` dispatch.
2155#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2156#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2157#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2158pub struct PrepareStatement<X: Extension = NoExt> {
2159    /// Name referenced by this syntax.
2160    pub name: Ident,
2161    /// The PostgreSQL parenthesized parameter-type list (`PREPARE p(int, text) AS
2162    /// ...`), in source order; empty when the parentheses were not written.
2163    /// PostgreSQL rejects an empty written `()`, so an empty list unambiguously means
2164    /// "clause absent" and needs no separate surface tag — the same
2165    /// absent-vs-empty-not-written equivalence [`ExecuteStatement::args`] uses.
2166    pub parameter_types: ThinVec<DataType<X>>,
2167    /// The statement bound to the name; any [`Statement`] parses (the
2168    /// [`ExplainStatement`] "grammar accepts any statement" contract).
2169    pub statement: Box<Statement<X>>,
2170    /// Source location and node identity.
2171    pub meta: Meta,
2172}
2173
2174/// A MySQL `PREPARE <name> FROM {'<text>' | @<var>}` prepared-statement definition (gated by
2175/// [`UtilitySyntax::prepared_statements_from`](crate::dialect::UtilitySyntax)).
2176///
2177/// A *different shape on the same `PREPARE` keyword* from DuckDB/PostgreSQL's typed
2178/// [`PrepareStatement`]: MySQL binds the name to a statement *source* — an opaque string
2179/// literal or a user-variable reference read at prepare time (`sql_yacc.yy` `prepare_src`) —
2180/// **not** an inline-parsed [`Statement`], and takes neither the `AS` keyword nor a
2181/// parameter-type list. The source text is never re-parsed here (the placeholders `?` it
2182/// carries are a run-time protocol concern), so this node holds no expression or statement
2183/// children and is non-generic, like [`DeallocateStatement`]. The two `PREPARE` behaviours
2184/// never coexist in one preset (each arms at most one gate), the split mirroring the
2185/// [`DoStatement`]/[`DoExpressionsStatement`] `DO`-keyword split.
2186///
2187/// MySQL grammar-accepts every well-formed source shape but cannot *prepare* the outer
2188/// `PREPARE` itself over the binary protocol (`ER_UNSUPPORTED_PS` 1295) — a bind-time
2189/// verdict the parse layer does not model, exactly the [`PrepareStatement`]
2190/// "grammar accepts, bind restricts" contract.
2191#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2192#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2193#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2194pub struct PrepareFromStatement {
2195    /// Name referenced by this syntax.
2196    pub name: Ident,
2197    /// The statement source bound to the name; see [`PrepareSource`].
2198    pub source: PrepareSource,
2199    /// Source location and node identity.
2200    pub meta: Meta,
2201}
2202
2203/// The source of a MySQL [`PrepareFromStatement`] — `sql_yacc.yy` `prepare_src`, either an
2204/// inline string literal or a user-variable reference. A spanned enum node: each arm
2205/// round-trips its own spelling, so the two are recorded as written rather than folded to a
2206/// single string.
2207#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2208#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2209#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2210pub enum PrepareSource {
2211    /// `PREPARE name FROM '<text>'` — the opaque statement source string (`TEXT_STRING_sys`),
2212    /// kept verbatim and never re-parsed here (like [`DoArg::Body`]). Its spelling — quote
2213    /// style and escapes — round-trips from the [`Literal`].
2214    Text {
2215        /// The statement-source [`Literal`] (a [`LiteralKind::String`](crate::ast::LiteralKind)).
2216        source: Literal,
2217        /// Source location and node identity.
2218        meta: Meta,
2219    },
2220    /// `PREPARE name FROM @<var>` — the source read from a user variable at prepare time
2221    /// (`'@' ident_or_text`, MySQL's `prepared_stmt_code_is_varref`). The variable's name is
2222    /// held without the `@` sigil, its quote style preserved for round-trip; `@@`-prefixed
2223    /// system variables are rejected by the parser (`ER_PARSE_ERROR` on mysql:8).
2224    Variable {
2225        /// The user-variable name (without the `@` sigil).
2226        name: Ident,
2227        /// Source location and node identity.
2228        meta: Meta,
2229    },
2230}
2231
2232/// An `EXECUTE <name> [ ( <arg> [, ...] ) ]` prepared-statement invocation (DuckDB;
2233/// gated by [`UtilitySyntax::prepared_statements`](crate::dialect::UtilitySyntax)).
2234///
2235/// Runs a [`PrepareStatement`]-bound name, supplying its parameters positionally
2236/// (`EXECUTE v1(1)`). The arguments are full expressions, which makes this node generic
2237/// over the extension `X`. A bare `EXECUTE v1` (no argument list) leaves
2238/// [`args`](Self::args) empty; DuckDB rejects an empty `EXECUTE v1()` as a syntax error,
2239/// so an empty list unambiguously means "no parentheses written" and needs no separate
2240/// surface tag — one shape covers the absent-list form, and the parser refuses the empty
2241/// `()` that would otherwise collide with it.
2242#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2243#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2244#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2245pub struct ExecuteStatement<X: Extension = NoExt> {
2246    /// Name referenced by this syntax.
2247    pub name: Ident,
2248    /// The positional argument expressions, in source order; empty for the bare
2249    /// `EXECUTE v1` form (no argument list). Never an empty written `()`, which DuckDB
2250    /// rejects.
2251    pub args: ThinVec<Expr<X>>,
2252    /// Source location and node identity.
2253    pub meta: Meta,
2254}
2255
2256/// A MySQL `EXECUTE <name> [USING @<var> [, ...]]` prepared-statement invocation (gated by
2257/// [`UtilitySyntax::prepared_statements_from`](crate::dialect::UtilitySyntax)).
2258///
2259/// A *different argument surface on the same `EXECUTE` keyword* from DuckDB's parenthesized
2260/// positional-expression [`ExecuteStatement`]: MySQL supplies parameters through a `USING`
2261/// clause whose members are strictly *user-variable references* (`sql_yacc.yy`
2262/// `execute_var_list` → `execute_var_ident: '@' ident_or_text`), never arbitrary
2263/// expressions — `EXECUTE s USING 1` and `EXECUTE s USING @@sys` are both `ER_PARSE_ERROR`
2264/// on mysql:8, and there is no `EXECUTE s(...)` parenthesized form. So this node holds a
2265/// list of variable-name [`Ident`]s, not [`Expr`]s, and is non-generic. A bare `EXECUTE s`
2266/// (no `USING`) leaves [`using`](Self::using) empty; MySQL has no empty-`USING` spelling, so
2267/// an empty list unambiguously means the clause was absent.
2268///
2269/// Like [`PrepareFromStatement`], the statement grammar-parses but cannot be prepared over
2270/// the binary protocol (`ER_UNSUPPORTED_PS` 1295) — a bind verdict the parse layer leaves
2271/// untouched.
2272#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2273#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2274#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2275pub struct ExecuteUsingStatement {
2276    /// Name referenced by this syntax.
2277    pub name: Ident,
2278    /// The `USING` user-variable references, in source order; each is a variable name held
2279    /// without its `@` sigil (quote style preserved for round-trip). Empty for the bare
2280    /// `EXECUTE name` form (no `USING` clause).
2281    pub using: ThinVec<Ident>,
2282    /// Source location and node identity.
2283    pub meta: Meta,
2284}
2285
2286/// A `CALL <name> [ ( [ <arg> [, ...] ] ) ]` routine invocation (DuckDB, MySQL; gated by
2287/// [`UtilitySyntax::call`](crate::dialect::UtilitySyntax)).
2288///
2289/// Invokes a table function or stored procedure as a top-level statement
2290/// (`CALL pragma_table_info('t')`, `CALL my_proc(1, 2)`). The arguments are full
2291/// expressions, which makes this node generic over the extension `X`.
2292///
2293/// The parenthesized argument list is *mandatory* for DuckDB — it rejects a bare
2294/// `CALL pragma_version` (syntax error) but accepts an empty `CALL pragma_version()` — and
2295/// *optional* for MySQL, whose grammar (`CALL_SYM sp_name opt_paren_expr_list`) admits a
2296/// bare `CALL my_proc` with no argument list at all (verified on mysql:8.4.10 — the bare
2297/// form resolves to ER_SP_DOES_NOT_EXIST, a grammar-positive binding reject). The
2298/// [`parenthesized`](Self::parenthesized) surface flag distinguishes the two written forms
2299/// so the source round-trips: `false` is the MySQL bare form (`CALL p`, always empty
2300/// [`args`](Self::args)), `true` is the parenthesized form (`CALL p()` / `CALL p(1, 2)`,
2301/// which is the only DuckDB shape). The bare form is gated by
2302/// [`UtilitySyntax::call_bare_name`](crate::dialect::UtilitySyntax).
2303#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2304#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2305#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2306pub struct CallStatement<X: Extension = NoExt> {
2307    /// Name referenced by this syntax.
2308    pub name: ObjectName,
2309    /// The argument expressions, in source order; may be empty (`CALL f()`) when
2310    /// [`parenthesized`](Self::parenthesized) is `true`, and is always empty for the bare
2311    /// MySQL form (`CALL f`).
2312    pub args: ThinVec<Expr<X>>,
2313    /// Whether a parenthesized argument list was written. `true` renders the `(...)` (empty
2314    /// or not); `false` is the MySQL bare `CALL name` form with no argument list, which
2315    /// renders just the name. Always `true` for DuckDB, whose parentheses are mandatory.
2316    pub parenthesized: bool,
2317    /// Source location and node identity.
2318    pub meta: Meta,
2319}
2320
2321/// A `{DEALLOCATE | DROP} [PREPARE] <name>` prepared-statement release (DuckDB, gated by
2322/// [`UtilitySyntax::prepared_statements`](crate::dialect::UtilitySyntax); MySQL, gated by
2323/// [`UtilitySyntax::prepared_statements_from`](crate::dialect::UtilitySyntax)).
2324///
2325/// Frees a [`PrepareStatement`]/[`PrepareFromStatement`]-bound name. Non-generic, like
2326/// [`DetachStatement`]: a name plus two surface flags carries no expressions or extension
2327/// nodes. Neither dialect has a `DEALLOCATE ALL` (DuckDB rejects it: "DEALLOCATE requires a
2328/// name"; MySQL's grammar takes a single `ident`), so the target is always a single
2329/// [`Ident`], never an all-marker.
2330///
2331/// The `PREPARE` keyword's role differs by dialect and is a parse-time constraint, not a
2332/// node field: DuckDB's `DEALLOCATE [PREPARE] name` makes it optional (round-tripped by
2333/// [`prepare_keyword`](Self::prepare_keyword)), while MySQL's `deallocate_or_drop PREPARE
2334/// ident` makes it *mandatory* (a bare `DEALLOCATE name` is `ER_PARSE_ERROR` on mysql:8),
2335/// so the MySQL parser always sets the flag. The leading-verb spelling
2336/// ([`keyword`](Self::keyword)) is MySQL's `deallocate_or_drop` synonym choice; DuckDB has
2337/// only the `DEALLOCATE` spelling.
2338#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2339#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2340#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2341pub struct DeallocateStatement {
2342    /// Which leading verb spelled this release: MySQL's `deallocate_or_drop` accepts
2343    /// `DEALLOCATE PREPARE name` and the `DROP PREPARE name` synonym interchangeably, and
2344    /// the spelling round-trips. Always [`DeallocateKeyword::Deallocate`] for DuckDB, whose
2345    /// grammar has no `DROP PREPARE` form.
2346    pub keyword: DeallocateKeyword,
2347    /// Whether the `PREPARE` keyword was written (`DEALLOCATE PREPARE v1` vs `DEALLOCATE
2348    /// v1`); a round-trip surface tag. Optional for DuckDB (both forms accepted), always
2349    /// `true` for MySQL (the keyword is mandatory there).
2350    pub prepare_keyword: bool,
2351    /// Name referenced by this syntax.
2352    pub name: Ident,
2353    /// Source location and node identity.
2354    pub meta: Meta,
2355}
2356
2357/// The leading verb of a [`DeallocateStatement`] — MySQL's `deallocate_or_drop` synonym
2358/// choice. A round-trip surface tag: `DROP PREPARE name` and `DEALLOCATE PREPARE name` are
2359/// the same statement on mysql:8, differing only in this spelling.
2360#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2361#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2362#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2363pub enum DeallocateKeyword {
2364    /// The `DEALLOCATE` spelling (DuckDB and MySQL).
2365    Deallocate,
2366    /// MySQL's `DROP` synonym (`DROP PREPARE name`).
2367    Drop,
2368}
2369
2370/// A PostgreSQL `DO [LANGUAGE <lang>] '<body>'` anonymous code block (gated by
2371/// [`UtilitySyntax::do_statement`](crate::dialect::UtilitySyntax)).
2372///
2373/// Runs an inline procedural-language block without defining a routine. The body is an
2374/// opaque source string in the target language ([`LiteralKind::String`](crate::ast::LiteralKind)
2375/// — dollar-quoted in practice), never re-parsed here: like
2376/// [`FunctionOption::As`](crate::ast::FunctionOption) the body's grammar is the target
2377/// language, not SQL, so the PL text is not smuggled into the AST.
2378///
2379/// The shape mirrors [`CreateFunction`](crate::ast::CreateFunction)'s option cluster
2380/// rather than a fixed `{ language, body }` pair, because PostgreSQL's raw grammar is the
2381/// same free option list: `DO dostmt_opt_list`, where `dostmt_opt_list` is a *non-empty*
2382/// sequence of items each either an `Sconst` body or `LANGUAGE <word>`, in any order and
2383/// with no arity limit. The parser (`makeDefElem("as"/"language", …)`) accepts a repeated
2384/// or missing body and a repeated language — `DO LANGUAGE plpgsql` (language, no body),
2385/// `DO $$a$$ $$b$$` (two bodies), and `DO 'x' LANGUAGE a LANGUAGE b` (two languages) all
2386/// parse — and defers the "exactly one body, at most one language" check to execution
2387/// (`ExecuteDoStmt`), exactly as the [`PrepareStatement`] "grammar accepts any statement,
2388/// bind restricts the kind" contract defers its own semantic check. Modelling the list
2389/// faithfully is what keeps raw-parse accept/reject parity with libpg_query; collapsing it
2390/// to a single body/language pair would over-reject those three forms. The list is
2391/// non-empty (a bare `DO` is a syntax error), which the parser enforces.
2392#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2393#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2394#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2395pub struct DoStatement {
2396    /// The `dostmt_opt_list` items in source order; always non-empty.
2397    pub args: ThinVec<DoArg>,
2398    /// Source location and node identity.
2399    pub meta: Meta,
2400}
2401
2402/// One item of a [`DoStatement`]'s `dostmt_opt_list` — a body string or a `LANGUAGE`
2403/// clause. The two forms and their source order round-trip, so the parser records each as
2404/// written rather than folding them into a canonical `{ language, body }` pair.
2405#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2406#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2407#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2408pub enum DoArg {
2409    /// An inline-body `Sconst` item — PostgreSQL's `makeDefElem("as", …)`. The body is
2410    /// the opaque source [`Literal`] (kept, not re-parsed, like
2411    /// [`FunctionOption::As`](crate::ast::FunctionOption)); there is no `AS` keyword in the
2412    /// `DO` syntax, so the variant is named for the block body it carries.
2413    Body {
2414        /// Statement or query body governed by this node.
2415        body: Literal,
2416        /// Source location and node identity.
2417        meta: Meta,
2418    },
2419    /// A `LANGUAGE <name>` item — PostgreSQL's `makeDefElem("language", …)`. The name is a
2420    /// `NonReservedWord_or_Sconst` ([`LanguageName`]): a bare word or a string constant, as
2421    /// in the `CREATE FUNCTION` `LANGUAGE` clause. A reserved word (a bit/hex/national string
2422    /// constant, since those are not `Sconst`) is the syntax error PostgreSQL reports.
2423    Language {
2424        /// Name referenced by this syntax.
2425        name: LanguageName,
2426        /// Source location and node identity.
2427        meta: Meta,
2428    },
2429}
2430
2431/// A routine `LANGUAGE <name>` operand (PostgreSQL's `NonReservedWord_or_Sconst`): a bare
2432/// non-reserved word or a string constant. Shared by the two positions that spell it the same
2433/// way — the [`DoStatement`] `DO … LANGUAGE <name>` argument and the `CREATE FUNCTION`
2434/// [`FunctionOption::Language`](crate::ast::FunctionOption) clause. PostgreSQL folds both
2435/// spellings to the same language name internally, but the surface form is kept distinct so it
2436/// round-trips — the same `NonReservedWord_or_Sconst` shape as
2437/// [`ExtensionVersion`](crate::ast::ExtensionVersion).
2438///
2439/// The string arm admits only an `Sconst` (a plain, `E'...'`, `U&'...'`, or dollar-quoted
2440/// constant); a bit-string (`b'...'`/`x'...'`) or national (`N'...'`) constant is not an
2441/// `Sconst`, so — like the code-block body — it is the syntax error PostgreSQL reports. The
2442/// string spelling is a PostgreSQL surface: MySQL's routine `LANGUAGE` admits only the bare
2443/// word `SQL` (`LANGUAGE 'SQL'` is `ER_PARSE_ERROR` on mysql:8), so the parser gates the string
2444/// arm on [`IndexAlterSyntax::routine_language_string`](crate::dialect::IndexAlterSyntax::routine_language_string).
2445#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2446#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2447#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2448pub enum LanguageName {
2449    /// A bare non-reserved word (`LANGUAGE plpgsql`).
2450    Word {
2451        /// Identifier-form language name.
2452        word: Ident,
2453        /// Source location and node identity.
2454        meta: Meta,
2455    },
2456    /// A string constant (`LANGUAGE 'plpgsql'`).
2457    String {
2458        /// Value supplied by this syntax.
2459        value: Literal,
2460        /// Source location and node identity.
2461        meta: Meta,
2462    },
2463}
2464
2465/// A MySQL `DO <expr> [, <expr> ...]` evaluate-and-discard statement (gated by
2466/// [`UtilitySyntax::do_expression_list`](crate::dialect::UtilitySyntax)).
2467///
2468/// A *different behaviour on the same `DO` keyword* from PostgreSQL's anonymous code block
2469/// ([`DoStatement`]): MySQL's `DO` evaluates a list of expressions purely for their side
2470/// effects and throws the results away (`DO SLEEP(1)`, `DO @x := 1`), whereas PostgreSQL's
2471/// `DO` runs an opaque procedural-language body string. The two never coexist in one dialect
2472/// (each preset arms exactly one gate), the split mirroring the transaction-`BEGIN`
2473/// vs compound-block-`BEGIN` dialect-gated arms.
2474///
2475/// The grammar is literally `DO select_item_list` (mysql `sql_yacc.yy` `do_stmt`), so the
2476/// items are [`SelectItem`]s, not bare [`Expr`]s: MySQL grammar-accepts a select alias
2477/// (`DO 1 AS x` PREPAREs) and a wildcard (`DO *`, `DO t.*`) here exactly as in a projection.
2478/// Reusing the projection-item node keeps raw-parse acceptance aligned with the engine; the
2479/// wildcard forms bind-reject (`DO *` is `ER_NO_TABLES_USED`), but that is a resolver verdict
2480/// the parse layer does not model, not a syntax reject. The list is non-empty (a bare `DO` is
2481/// `ER_PARSE_ERROR`), which the parser enforces.
2482#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2483#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2484#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2485pub struct DoExpressionsStatement<X: Extension = NoExt> {
2486    /// The evaluated expression list in source order; always non-empty.
2487    pub items: ThinVec<SelectItem<X>>,
2488    /// Source location and node identity.
2489    pub meta: Meta,
2490}
2491
2492/// A MySQL `LOCK {TABLES | TABLE} <tbl> [[AS] <alias>] <lock-kind> [, ...]` explicit
2493/// table-locking statement (gated by
2494/// [`UtilitySyntax::lock_tables`](crate::dialect::UtilitySyntax)).
2495///
2496/// This is *one of two distinct behaviours a dialect can attach to the leading `LOCK`
2497/// keyword*, the split modelled the [`DoExpressionsStatement`] / [`DoStatement`] way (a
2498/// behaviour-named gate per reading, never both armed in one preset). MySQL's `LOCK TABLES`
2499/// names a per-table lock **kind** (`READ`, `READ LOCAL`, `WRITE`) on each table in a list —
2500/// the shape captured here. PostgreSQL's `LOCK TABLE`, by contrast, takes a single
2501/// statement-level lock **mode** clause (`IN ACCESS SHARE MODE`, `NOWAIT`, …) over a relation
2502/// list; that reading is not implemented, but when it is it takes its own node behind its own
2503/// `LOCK`-keyword gate, so the two never collide. The `MySql`/`Lenient` presets arm
2504/// [`lock_tables`](crate::dialect::UtilitySyntax::lock_tables); every other preset leaves the
2505/// leading `LOCK` keyword undispatched (an unknown statement).
2506///
2507/// The grammar is `LOCK table_or_tables table_lock_list` (mysql `sql_yacc.yy` `lock` /
2508/// `table_lock_list`), so the `TABLES` (plural) and `TABLE` (singular) spellings are
2509/// interchangeable and preserved on [`plural`](Self::plural) to round-trip. Each
2510/// [`TableLock`] carries a mandatory lock kind — a bare `LOCK TABLES t1` with no kind is
2511/// `ER_PARSE_ERROR` (engine-measured on mysql:8.4.10), which the parser enforces. The list is
2512/// non-empty. Non-generic, like [`UseStatement`]: a table lock carries no expressions or
2513/// extension nodes.
2514#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2515#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2516#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2517pub struct LockTablesStatement {
2518    /// The keyword spelling: `true` for `LOCK TABLES` (plural), `false` for `LOCK TABLE`
2519    /// (singular). Both are grammar-equal; preserved so the exact spelling round-trips.
2520    pub plural: bool,
2521    /// The locked tables in source order; always non-empty.
2522    pub tables: ThinVec<TableLock>,
2523    /// Source location and node identity.
2524    pub meta: Meta,
2525}
2526
2527/// One entry of a MySQL [`LockTablesStatement`] table list: a table name, an optional alias,
2528/// and its mandatory [`TableLockKind`] (`table_ident opt_table_alias lock_option` in mysql
2529/// `sql_yacc.yy` `table_lock`). The alias is a single identifier (`opt_as ident`); whether it
2530/// was written with the `AS` keyword is not modelled because MySQL discards it, so the
2531/// renderer emits the canonical `AS`-less spelling.
2532#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2533#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2534#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2535pub struct TableLock {
2536    /// The table being locked.
2537    pub name: ObjectName,
2538    /// The optional table alias (`[AS] <alias>`).
2539    pub alias: Option<Ident>,
2540    /// The lock kind acquired on the table (mandatory).
2541    pub kind: TableLockKind,
2542    /// Source location and node identity.
2543    pub meta: Meta,
2544}
2545
2546/// The lock kind a MySQL [`TableLock`] acquires (`lock_option` in mysql `sql_yacc.yy`). Only
2547/// these three spellings are grammar-valid on mysql:8.4.10 — the historical (pre-8.0)
2548/// `LOW_PRIORITY WRITE` modifier is `ER_PARSE_ERROR` there (engine-measured), so it is
2549/// deliberately not a variant.
2550#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2551#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2552#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2553pub enum TableLockKind {
2554    /// `READ` — a shared read lock.
2555    Read,
2556    /// `READ LOCAL` — a shared read lock permitting concurrent non-conflicting inserts.
2557    ReadLocal,
2558    /// `WRITE` — an exclusive write lock.
2559    Write,
2560}
2561
2562/// A MySQL `UNLOCK {TABLES | TABLE}` statement releasing all table locks held by the session
2563/// (gated by [`UtilitySyntax::lock_tables`](crate::dialect::UtilitySyntax), the release
2564/// counterpart of [`LockTablesStatement`]).
2565///
2566/// Carries no table list — MySQL's `UNLOCK` releases everything the session holds — only the
2567/// `TABLES`/`TABLE` spelling, preserved on [`plural`](Self::plural) to round-trip
2568/// (`unlock: UNLOCK_SYM table_or_tables` in mysql `sql_yacc.yy`).
2569#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2570#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2571#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2572pub struct UnlockTablesStatement {
2573    /// The keyword spelling: `true` for `UNLOCK TABLES` (plural), `false` for `UNLOCK TABLE`
2574    /// (singular). Both are grammar-equal; preserved so the exact spelling round-trips.
2575    pub plural: bool,
2576    /// Source location and node identity.
2577    pub meta: Meta,
2578}
2579
2580/// A MySQL instance-wide backup lock statement — `LOCK INSTANCE FOR BACKUP` (acquire) or
2581/// `UNLOCK INSTANCE` (release) — gated by
2582/// [`UtilitySyntax::lock_instance`](crate::dialect::UtilitySyntax).
2583///
2584/// One node for the pair, distinguished by [`acquire`](Self::acquire), because neither side
2585/// carries any other payload (mysql `sql_yacc.yy` `lock`/`unlock`: both alternatives build a
2586/// bare `Sql_cmd_{lock,unlock}_instance`): a two-struct split would add an empty node for the
2587/// release half. Distinct from [`LockTablesStatement`]/[`UnlockTablesStatement`], which take a
2588/// per-table lock-kind list — the instance lock is a single server-wide DDL-blocking lock
2589/// with fixed spelling on both sides, so there is nothing else to model. Both spellings are
2590/// grammar-positive on mysql:8.4.10 (`ER_UNSUPPORTED_PS` under the PREPARE oracle — parsed,
2591/// then declined by the PREPARE protocol, never `ER_PARSE_ERROR`).
2592#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2593#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2594#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2595pub struct InstanceLockStatement {
2596    /// `true` for `LOCK INSTANCE FOR BACKUP` (acquire), `false` for `UNLOCK INSTANCE`
2597    /// (release).
2598    pub acquire: bool,
2599    /// Source location and node identity.
2600    pub meta: Meta,
2601}
2602
2603/// A MySQL `LOAD {DATA | XML} … INFILE … INTO TABLE …` bulk-import statement (gated by
2604/// [`UtilitySyntax::load_data`](crate::dialect::UtilitySyntax)).
2605///
2606/// One node covers both the `DATA` (delimited text) and `XML` readings, distinguished by
2607/// [`format`](Self::format): the mysql `sql_yacc.yy` `load_stmt` rule is a *single*
2608/// `data_or_xml` production whose whole clause train is grammatically shared, so at the parse
2609/// layer the two forms differ only in the `DATA`/`XML` keyword. The clauses MySQL restricts to
2610/// one reading (`FIELDS`/`LINES` are meaningless under `XML`, `ROWS IDENTIFIED BY` under
2611/// `DATA`) are *semantic* restrictions the server enforces only after it has parsed the whole
2612/// statement and resolved the table — every clause parses under either format (engine-measured
2613/// on mysql:8.4.10: a `LOAD XML … FIELDS TERMINATED BY ','` reaches `ER_NO_SUCH_TABLE` 1146,
2614/// not `ER_PARSE_ERROR` 1064), so this node does not gate them by format and the parse layer
2615/// leaves that binding verdict untouched (the [`PrepareStatement`] "grammar accepts, bind
2616/// restricts" contract).
2617///
2618/// The clause train is strictly *order-sensitive* (engine-measured: any out-of-order clause is
2619/// `ER_PARSE_ERROR` 1064), so the node's field order is the grammar's canonical order and the
2620/// renderer emits that order. The optional clauses are `None`/empty when absent. Generic over
2621/// the extension `X` because the [`set`](Self::set) assignments carry full expressions.
2622///
2623/// Scope boundary: the MySQL 8.4 secondary-engine bulk-load extension clauses on the same
2624/// `load_stmt` rule — the `FROM` keyword, `URL`/`S3` source types, `COUNT n`,
2625/// `IN PRIMARY KEY ORDER`, `COMPRESSION`, `PARALLEL`, `MEMORY`, and `ALGORITHM = BULK` — are a
2626/// distinct cloud-bulk-load feature family and are intentionally not modelled here (they take
2627/// their own follow-up; see the ticket close note). This node is the classic documented
2628/// `LOAD DATA` / `LOAD XML` surface.
2629#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2630#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2631#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2632pub struct LoadDataStatement<X: Extension = NoExt> {
2633    /// Whether the source is delimited text (`LOAD DATA`) or XML (`LOAD XML`); see
2634    /// [`LoadDataFormat`].
2635    pub format: LoadDataFormat,
2636    /// The optional table-lock concurrency modifier (`LOW_PRIORITY` / `CONCURRENT`); see
2637    /// [`LoadDataConcurrency`]. `None` for the default lock (`load_data_lock` %empty).
2638    pub concurrency: Option<LoadDataConcurrency>,
2639    /// `true` when the `LOCAL` keyword was written (the file is read from the client rather
2640    /// than the server host).
2641    pub local: bool,
2642    /// The `INFILE '<path>'` source-file string literal (`TEXT_STRING_filesystem`); only the
2643    /// `INFILE` source type is modelled (see the type-level scope note).
2644    pub file: Literal,
2645    /// The optional duplicate-key handling (`REPLACE` / `IGNORE`); see [`LoadDataDuplicate`].
2646    /// `None` for the default (error on duplicate).
2647    pub on_duplicate: Option<LoadDataDuplicate>,
2648    /// The `INTO TABLE <name>` destination table.
2649    pub table: ObjectName,
2650    /// The `PARTITION (<name> [, ...])` partition list; empty when the clause is absent. The
2651    /// list is never a written empty `()` — MySQL requires at least one partition name.
2652    pub partitions: ThinVec<Ident>,
2653    /// The `CHARACTER SET <name>` charset override (`opt_load_data_charset`); `None` when
2654    /// absent. Held as a single [`Ident`] (a charset name, not a dotted [`ObjectName`]).
2655    pub charset: Option<Ident>,
2656    /// The `ROWS IDENTIFIED BY '<tag>'` row element tag (`opt_xml_rows_identified_by`); `None`
2657    /// when absent. Grammar-shared by both formats (see the type-level note) though only
2658    /// meaningful under `XML`.
2659    pub rows_identified_by: Option<Literal>,
2660    /// The `{FIELDS | COLUMNS} …` field-format clause; `None` when absent. See
2661    /// [`LoadDataFields`].
2662    pub fields: Option<LoadDataFields>,
2663    /// The `LINES …` line-format clause; `None` when absent. See [`LoadDataLines`].
2664    pub lines: Option<LoadDataLines>,
2665    /// The `IGNORE <n> {LINES | ROWS}` header-skip clause; `None` when absent. See
2666    /// [`LoadDataIgnoreRows`].
2667    pub ignore_rows: Option<LoadDataIgnoreRows>,
2668    /// The parenthesized `(col_or_var [, ...])` target list; empty when absent (or a written
2669    /// empty `()`, which MySQL folds to absent — `'(' ')'` yields the same nullptr as an
2670    /// omitted list). See [`LoadDataFieldOrVar`].
2671    pub columns: ThinVec<LoadDataFieldOrVar>,
2672    /// The `SET col = {expr | DEFAULT} [, ...]` post-load assignments; empty when absent.
2673    /// Reuses [`UpdateAssignment`] exactly as `INSERT … SET` does (mysql `load_data_set_elem`
2674    /// is `simple_ident_nospvar equal expr_or_default`, the single-column-assignment shape); a
2675    /// tuple assignment is not grammar-valid here, and the fitted `MySql` preset never emits
2676    /// one (its `multi_column_assignment` gate is off).
2677    pub set: ThinVec<UpdateAssignment<X>>,
2678    /// Source location and node identity.
2679    pub meta: Meta,
2680}
2681
2682/// Whether a [`LoadDataStatement`] reads delimited text (`LOAD DATA`) or XML (`LOAD XML`) —
2683/// the mysql `sql_yacc.yy` `data_or_xml` production.
2684#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2685#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2686#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2687pub enum LoadDataFormat {
2688    /// `LOAD DATA` — a delimited-text file (`FILETYPE_CSV`).
2689    Data,
2690    /// `LOAD XML` — an XML file (`FILETYPE_XML`).
2691    Xml,
2692}
2693
2694/// The optional table-lock concurrency modifier of a [`LoadDataStatement`] (mysql
2695/// `sql_yacc.yy` `load_data_lock`). The default (no keyword) is a plain write lock and is
2696/// modelled as `None` on the statement, so this enum carries only the two written spellings.
2697#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2698#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2699#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2700pub enum LoadDataConcurrency {
2701    /// `LOW_PRIORITY` — defer the load until no clients are reading the table.
2702    LowPriority,
2703    /// `CONCURRENT` — allow other clients to read the table during the load.
2704    Concurrent,
2705}
2706
2707/// The optional duplicate-key handling of a [`LoadDataStatement`] (mysql `sql_yacc.yy`
2708/// `opt_duplicate` / `duplicate`). The default (no keyword) errors on a duplicate and is
2709/// modelled as `None`, so this enum carries only the two written spellings. `REPLACE` and
2710/// `IGNORE` are mutually exclusive — writing both is `ER_PARSE_ERROR` (engine-measured).
2711#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2712#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2713#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2714pub enum LoadDataDuplicate {
2715    /// `REPLACE` — replace existing rows that collide on a unique key.
2716    Replace,
2717    /// `IGNORE` — skip input rows that collide on a unique key.
2718    Ignore,
2719}
2720
2721/// The `{FIELDS | COLUMNS} …` field-format clause of a [`LoadDataStatement`] (mysql
2722/// `sql_yacc.yy` `opt_field_term` / `field_term_list`).
2723///
2724/// The `FIELDS` and `COLUMNS` keywords are interchangeable synonyms; the written spelling
2725/// rides [`spelling`](Self::spelling) so it round-trips. At least one of the three sub-clauses
2726/// is present — a bare `FIELDS` with no sub-clause is `ER_PARSE_ERROR` (engine-measured), which
2727/// the parser enforces. Each sub-clause may appear in any order and the parser folds it onto
2728/// the matching field (a repeat is last-wins, mirroring the grammar's `merge_field_separators`);
2729/// the renderer emits the canonical `TERMINATED` / `[OPTIONALLY] ENCLOSED` / `ESCAPED` order.
2730#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2731#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2732#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2733pub struct LoadDataFields {
2734    /// The interchangeable keyword spelling (`FIELDS` vs `COLUMNS`); see
2735    /// [`LoadFieldsSpelling`].
2736    pub spelling: LoadFieldsSpelling,
2737    /// `TERMINATED BY '<string>'` — the field separator; `None` when absent.
2738    pub terminated_by: Option<Literal>,
2739    /// `[OPTIONALLY] ENCLOSED BY '<char>'` — the field quoting; `None` when absent. See
2740    /// [`LoadDataEnclosed`] (the `OPTIONALLY` modifier rides it, so an `OPTIONALLY` without an
2741    /// `ENCLOSED BY` is unrepresentable).
2742    pub enclosed_by: Option<LoadDataEnclosed>,
2743    /// `ESCAPED BY '<char>'` — the escape character; `None` when absent.
2744    pub escaped_by: Option<Literal>,
2745    /// Source location and node identity.
2746    pub meta: Meta,
2747}
2748
2749/// The interchangeable keyword spelling of a [`LoadDataFields`] clause (mysql `sql_yacc.yy`:
2750/// `opt_field_term` spells the same clause `COLUMNS`, while the documented form is `FIELDS`).
2751#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2752#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2753#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2754pub enum LoadFieldsSpelling {
2755    /// The `FIELDS` spelling (the documented keyword).
2756    Fields,
2757    /// The `COLUMNS` spelling (the grammar synonym).
2758    Columns,
2759}
2760
2761/// The `[OPTIONALLY] ENCLOSED BY '<char>'` sub-clause of a [`LoadDataFields`] clause (mysql
2762/// `sql_yacc.yy` `field_term`: the `ENCLOSED BY` and `OPTIONALLY ENCLOSED BY` alternatives).
2763/// Bundling the `OPTIONALLY` modifier with its value makes an `OPTIONALLY` with no `ENCLOSED
2764/// BY` unrepresentable.
2765#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2766#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2767#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2768pub struct LoadDataEnclosed {
2769    /// `true` when the `OPTIONALLY` modifier was written (`OPTIONALLY ENCLOSED BY`).
2770    pub optionally: bool,
2771    /// The enclosing-character string literal.
2772    pub value: Literal,
2773    /// Source location and node identity.
2774    pub meta: Meta,
2775}
2776
2777/// The `LINES …` line-format clause of a [`LoadDataStatement`] (mysql `sql_yacc.yy`
2778/// `opt_line_term` / `line_term_list`).
2779///
2780/// At least one of the two sub-clauses is present — a bare `LINES` with no sub-clause is
2781/// `ER_PARSE_ERROR` (engine-measured), which the parser enforces. Each sub-clause may appear in
2782/// any order (a repeat is last-wins, mirroring `merge_line_separators`); the renderer emits the
2783/// canonical `STARTING` / `TERMINATED` order.
2784#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2785#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2786#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2787pub struct LoadDataLines {
2788    /// `STARTING BY '<string>'` — the common line prefix to strip; `None` when absent.
2789    pub starting_by: Option<Literal>,
2790    /// `TERMINATED BY '<string>'` — the line separator; `None` when absent.
2791    pub terminated_by: Option<Literal>,
2792    /// Source location and node identity.
2793    pub meta: Meta,
2794}
2795
2796/// The `IGNORE <n> {LINES | ROWS}` header-skip clause of a [`LoadDataStatement`] (mysql
2797/// `sql_yacc.yy` `opt_ignore_lines` / `lines_or_rows`). The `LINES` and `ROWS` keywords are
2798/// interchangeable; the written spelling rides [`unit`](Self::unit) so it round-trips.
2799#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2800#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2801#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2802pub struct LoadDataIgnoreRows {
2803    /// The number of leading rows to skip (a `NUM` token), kept as an unsigned-integer
2804    /// [`Literal`] so the exact spelling round-trips.
2805    pub count: Literal,
2806    /// The interchangeable unit keyword (`LINES` vs `ROWS`); see [`LoadDataIgnoreUnit`].
2807    pub unit: LoadDataIgnoreUnit,
2808    /// Source location and node identity.
2809    pub meta: Meta,
2810}
2811
2812/// The interchangeable unit keyword of a [`LoadDataIgnoreRows`] clause (mysql `sql_yacc.yy`
2813/// `lines_or_rows`).
2814#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2815#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2816#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2817pub enum LoadDataIgnoreUnit {
2818    /// The `LINES` spelling.
2819    Lines,
2820    /// The `ROWS` spelling.
2821    Rows,
2822}
2823
2824/// One entry of a [`LoadDataStatement`] target list (mysql `sql_yacc.yy` `field_or_var`): a
2825/// destination column name, or a user variable (`@name`) that captures the raw field value for
2826/// use in the `SET` clause. A spanned enum so each spelling round-trips its own form and a
2827/// column is never confused with a variable.
2828#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2829#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2830#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2831pub enum LoadDataFieldOrVar {
2832    /// A destination column (`simple_ident_nospvar`).
2833    Column {
2834        /// The column name.
2835        name: Ident,
2836        /// Source location and node identity.
2837        meta: Meta,
2838    },
2839    /// A user variable (`@name`) capturing the field's raw value; the name is held without the
2840    /// `@` sigil, its quote style preserved for round-trip.
2841    Variable {
2842        /// The user-variable name (without the `@` sigil).
2843        name: Ident,
2844        /// Source location and node identity.
2845        meta: Meta,
2846    },
2847}
2848
2849/// A typed `SHOW TABLES` utility statement (MySQL/DuckDB; gated by
2850/// [`ShowSyntax::show_tables`](crate::dialect::UtilitySyntax)).
2851///
2852/// Distinct from the generic session `SHOW <var>`
2853/// ([`SessionStatement::Show`](crate::ast::SessionStatement)), which reads one
2854/// configuration parameter, and from the parenthesized `(SHOW <name>)` table source
2855/// ([`TableFactor::ShowRef`](crate::ast::TableFactor)), which only appears inside a
2856/// `FROM (…)` and produces a relation. `SHOW TABLES` is a top-level catalogue-listing
2857/// *statement*, so it is its own node — the MECE split spelled out on
2858/// [`show_tables`](crate::dialect::ShowSyntax::show_tables).
2859///
2860/// This is the opener of the typed-`SHOW` family (`SHOW COLUMNS`, `SHOW CREATE TABLE`,
2861/// `SHOW FUNCTIONS` join it): the listed thing rides the [`ShowTarget`] axis. That axis
2862/// is an enum with *per-variant* fields — the [`SessionStatement`](crate::ast::SessionStatement)
2863/// / [`ShowRefTarget`](crate::ast::ShowRefTarget) idiom, **not** a flat `kind` tag like
2864/// [`ApplyKind`](crate::ast::ApplyKind): the `SHOW` subforms carry genuinely different
2865/// payloads (`TABLES` takes a `FROM`/filter, `CREATE TABLE` a table name, `FUNCTIONS` a
2866/// scope keyword, schema qualifier, and name-or-regex filter), so a shared tag would
2867/// force an option-soup lowest common denominator. A sibling is then a new [`ShowTarget`]
2868/// variant plus its own gate, leaving the existing variants untouched.
2869#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2870#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2871#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2872pub struct ShowStatement<X: Extension = NoExt> {
2873    /// Object targeted by this syntax.
2874    pub target: ShowTarget<X>,
2875    /// Source location and node identity.
2876    pub meta: Meta,
2877}
2878
2879/// What a [`ShowStatement`] lists — the extensible axis of the typed-`SHOW` family.
2880///
2881/// The cross-dialect subforms [`Tables`](Self::Tables), [`Columns`](Self::Columns),
2882/// [`Functions`](Self::Functions), and [`RoutineStatus`](Self::RoutineStatus) each carry a
2883/// genuinely distinct payload (see [`ShowStatement`] for why this is a per-variant enum
2884/// rather than a flat `kind` tag). The MySQL server-administration / catalogue family folds
2885/// its ~40 near-identical sub-commands onto data axes instead: [`Listing`](Self::Listing)
2886/// and [`Bare`](Self::Bare) carry a sub-command discriminator, [`Create`](Self::Create) a
2887/// [`ShowCreateKind`], and [`Index`](Self::Index), [`Engine`](Self::Engine),
2888/// [`ReplicaStatus`](Self::ReplicaStatus), [`Diagnostics`](Self::Diagnostics), and
2889/// [`RoutineCode`](Self::RoutineCode) the handful with their own operands. The account /
2890/// diagnostics remainder rides its own operand-bearing variants: [`Grants`](Self::Grants) and
2891/// [`CreateUser`](Self::CreateUser) (the shared [`AccountName`] `user` grammar),
2892/// [`Profile`](Self::Profile) (a resource-type list plus `FOR QUERY` / `LIMIT`), and
2893/// [`LogEvents`](Self::LogEvents) (the `BINLOG` / `RELAYLOG` event dump).
2894#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2895#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2896#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2897pub enum ShowTarget<X: Extension = NoExt> {
2898    /// `SHOW [EXTENDED] [FULL] [ALL] TABLES [{FROM | IN} <db>] [LIKE '<pat>' | WHERE <expr>]`.
2899    ///
2900    /// The leading modifiers are dialect-split unions: `EXTENDED`/`FULL` are MySQL's
2901    /// (`SHOW FULL TABLES` adds a table-type column), `ALL` is DuckDB's (`SHOW ALL TABLES`
2902    /// lists every attached database's tables). Each is an independent optional keyword,
2903    /// so they ride separate bools rather than one axis; no shipped dialect mixes them.
2904    /// The `{FROM | IN} <db>` qualifier and the `LIKE`/`WHERE` filter are MySQL's (DuckDB
2905    /// accepts only `FROM <schema>`); the single [`show_tables`](crate::dialect::ShowSyntax::show_tables)
2906    /// gate accepts the union permissively, the DESCRIBE/PRAGMA single-flag-utility precedent.
2907    Tables {
2908        /// MySQL `EXTENDED` — also list hidden tables left by a failed `ALTER`.
2909        extended: bool,
2910        /// MySQL `FULL` — add the `Table_type` column.
2911        full: bool,
2912        /// DuckDB `ALL` — list tables across every attached database.
2913        all: bool,
2914        /// The optional `{FROM | IN} <db>` schema qualifier.
2915        from: Option<ShowFrom>,
2916        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
2917        filter: Option<ShowFilter<X>>,
2918        /// Source location and node identity.
2919        meta: Meta,
2920    },
2921    /// `SHOW [EXTENDED] [FULL] {COLUMNS | FIELDS} {FROM | IN} <tbl> [{FROM | IN} <db>]
2922    /// [LIKE '<pat>' | WHERE <expr>]` (MySQL; gated by
2923    /// [`show_columns`](crate::dialect::ShowSyntax::show_columns)).
2924    ///
2925    /// Unlike [`Tables`](Self::Tables), the `{FROM | IN}` qualifier is *mandatory* — it
2926    /// names the table whose columns are listed — and the grammar has a *second*, optional
2927    /// `{FROM | IN} <db>` naming the database (equivalent to writing `db.tbl` in the
2928    /// [`table`](Self::Columns::table) slot). There is no `ALL` modifier here; DuckDB has
2929    /// no `SHOW COLUMNS` grammar at all (engine-probed reject on 1.5.4), so this is a
2930    /// MySQL-only subform. `FIELDS` is an exact synonym of `COLUMNS`; the written spelling
2931    /// rides the [`ShowColumnsSpelling`] surface tag so the statement round-trips.
2932    Columns {
2933        /// MySQL `EXTENDED` — also list hidden columns MySQL maintains internally.
2934        extended: bool,
2935        /// MySQL `FULL` — add the collation, privileges, and comment columns.
2936        full: bool,
2937        /// Which keyword named the listing: `COLUMNS` or its `FIELDS` synonym.
2938        spelling: ShowColumnsSpelling,
2939        /// The mandatory `{FROM | IN} <tbl>` qualifier naming the target table.
2940        table: ShowFrom,
2941        /// The optional second `{FROM | IN} <db>` qualifier naming the database.
2942        database: Option<ShowFrom>,
2943        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
2944        filter: Option<ShowFilter<X>>,
2945        /// Source location and node identity.
2946        meta: Meta,
2947    },
2948    /// `SHOW CREATE {TABLE | VIEW | DATABASE [IF NOT EXISTS] | EVENT | PROCEDURE | FUNCTION
2949    /// | TRIGGER} <name>` — the DDL that would recreate the named object (MySQL). The
2950    /// `TABLE` spelling is gated by
2951    /// [`show_create_table`](crate::dialect::ShowSyntax::show_create_table); every other
2952    /// object kind is gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin).
2953    ///
2954    /// The object kind rides the [`kind`](Self::Create::kind) axis as DATA rather than
2955    /// forcing one bespoke variant per keyword — the `SHOW CREATE …` subforms are
2956    /// structurally identical (two fixed keywords plus one schema-qualifiable name), so a
2957    /// per-keyword variant would be pure duplication. `IF NOT EXISTS` is a `DATABASE`-only
2958    /// guard ([`if_not_exists`](Self::Create::if_not_exists), always `false` for the other
2959    /// kinds). `SHOW CREATE USER` is deliberately excluded: its operand is a MySQL user
2960    /// specification (`'user'@'host'`), not an [`ObjectName`], so it rides its own
2961    /// [`CreateUser`](Self::CreateUser) variant over the shared [`AccountName`] grammar.
2962    Create {
2963        /// Which object kind followed `CREATE`; see [`ShowCreateKind`].
2964        kind: ShowCreateKind,
2965        /// The target object name (schema-qualifiable).
2966        name: ObjectName,
2967        /// The `DATABASE`-only `IF NOT EXISTS` guard; `false` for every other kind.
2968        if_not_exists: bool,
2969        /// Source location and node identity.
2970        meta: Meta,
2971    },
2972    /// `SHOW [{USER | SYSTEM | ALL}] FUNCTIONS [{FROM | IN} <schema>]
2973    /// [[LIKE] {<function_name> | '<regex>'}]` (Spark / Databricks; gated by
2974    /// [`show_functions`](crate::dialect::ShowSyntax::show_functions)).
2975    ///
2976    /// The only shipped engine with a bare `SHOW FUNCTIONS` listing is Spark/Databricks,
2977    /// and it carries the full grammar: an optional [`kind`](Self::Functions::kind) scope
2978    /// keyword (`USER`/`SYSTEM`/`ALL`) *before* `FUNCTIONS`, an optional `{FROM | IN}`
2979    /// schema qualifier, and an optional trailing name-or-regex narrowing whose `LIKE`
2980    /// keyword is itself optional. MySQL's `SHOW FUNCTION STATUS` is a *different*
2981    /// statement (a routine catalogue over `mysql.proc`, not a bare `SHOW FUNCTIONS`) —
2982    /// modelled by its own [`RoutineStatus`](Self::RoutineStatus) variant; DuckDB has no
2983    /// `SHOW FUNCTIONS` grammar (`SHOW <name>` there is a `DESCRIBE` alias — `SHOW
2984    /// functions` describes a table named `functions`, engine-probed on 1.5.4).
2985    Functions {
2986        /// The optional `USER` / `SYSTEM` / `ALL` scope keyword written before `FUNCTIONS`.
2987        kind: Option<ShowFunctionsScope>,
2988        /// The optional `{FROM | IN} <schema>` schema qualifier.
2989        from: Option<ShowFrom>,
2990        /// The optional trailing `[LIKE] {<function_name> | '<regex>'}` narrowing.
2991        filter: Option<ShowFunctionsFilter>,
2992        /// Source location and node identity.
2993        meta: Meta,
2994    },
2995    /// `SHOW {FUNCTION | PROCEDURE} STATUS [LIKE '<pat>' | WHERE <expr>]` — the stored-routine
2996    /// catalogue listing (MySQL; gated by
2997    /// [`show_routine_status`](crate::dialect::ShowSyntax::show_routine_status)).
2998    ///
2999    /// A *different* statement from the Spark/Databricks [`Functions`](Self::Functions)
3000    /// listing: different keywords (the singular `FUNCTION`/`PROCEDURE` plus a mandatory
3001    /// `STATUS`, not a bare plural `FUNCTIONS`) and a different payload (a row per stored
3002    /// routine from `information_schema.routines`, not the bare function names). It carries
3003    /// no scope keyword and no `{FROM | IN}` qualifier — `SHOW FUNCTION STATUS FROM db` is
3004    /// `ER_PARSE_ERROR` on mysql:8 (engine-probed) — only the optional `LIKE`/`WHERE`
3005    /// narrowing, which reuses the shared [`ShowFilter`] (MySQL's mutually-exclusive `LIKE
3006    /// '<pat>' | WHERE <expr>`, exactly as `SHOW TABLES`/`SHOW COLUMNS`). The `FUNCTION`
3007    /// vs `PROCEDURE` object keyword rides the [`ShowRoutineKind`] surface tag so the
3008    /// statement round-trips.
3009    RoutineStatus {
3010        /// Which stored-routine kind was named: `FUNCTION` or `PROCEDURE`.
3011        kind: ShowRoutineKind,
3012        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
3013        filter: Option<ShowFilter<X>>,
3014        /// Source location and node identity.
3015        meta: Meta,
3016    },
3017    /// A MySQL catalogue/server listing that admits the shared `[LIKE '<pat>' | WHERE
3018    /// <expr>]` tail — `SHOW DATABASES`, `SHOW EVENTS`, `SHOW [GLOBAL | SESSION] STATUS`,
3019    /// `SHOW TRIGGERS`, and their siblings (gated by
3020    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3021    ///
3022    /// The sub-command rides the [`kind`](Self::Listing::kind) axis as DATA: every member
3023    /// shares the trailing [`ShowFilter`], the members that accept an optional `{FROM | IN}
3024    /// <db>` qualifier share [`from`](Self::Listing::from), and each sub-command's own
3025    /// small scalar payload (a `GLOBAL`/`SESSION` scope, a `FULL` flag, a spelling bit)
3026    /// rides the [`ShowListing`] discriminator, so the many near-identical listings collapse
3027    /// into one node instead of one bespoke variant each. The single [`from`](Self::Listing::from)
3028    /// admits the qualifier permissively (`SHOW DATABASES`/`SHOW STATUS` take none — the
3029    /// parser leaves those `None`), the DESCRIBE/PRAGMA single-field precedent.
3030    Listing {
3031        /// Which listing was named, plus its sub-command-specific scalar payload; see
3032        /// [`ShowListing`].
3033        kind: ShowListing,
3034        /// The optional `{FROM | IN} <db>` schema qualifier (only `EVENTS`, `TABLE STATUS`,
3035        /// `OPEN TABLES`, and `TRIGGERS` accept one; `None` for every other member).
3036        from: Option<ShowFrom>,
3037        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
3038        filter: Option<ShowFilter<X>>,
3039        /// Source location and node identity.
3040        meta: Meta,
3041    },
3042    /// A MySQL `SHOW` sub-command that takes no filter and no name operand — `SHOW PLUGINS`,
3043    /// `SHOW [STORAGE] ENGINES`, `SHOW PRIVILEGES`, `SHOW [FULL] PROCESSLIST`, `SHOW BINARY
3044    /// LOGS`, and their siblings (gated by
3045    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3046    ///
3047    /// The sub-command rides the [`kind`](Self::Bare::kind) axis as DATA; the only payloads
3048    /// any member carries are single leading-keyword flags (`STORAGE` before `ENGINES`,
3049    /// `FULL` before `PROCESSLIST`), folded into the [`ShowBare`] variant.
3050    Bare {
3051        /// Which bare sub-command was named; see [`ShowBare`].
3052        kind: ShowBare,
3053        /// Source location and node identity.
3054        meta: Meta,
3055    },
3056    /// `SHOW GRANTS [FOR <user> [USING <role> [, …]]]` — the privilege listing for an account
3057    /// (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3058    ///
3059    /// Bare `SHOW GRANTS` (the session's own privileges) leaves [`user`](Self::Grants::user)
3060    /// `None` and [`using_roles`](Self::Grants::using_roles) empty. `FOR <user>` names the
3061    /// account whose grants are shown; the optional `USING <role list>` (only valid after
3062    /// `FOR` — `SHOW GRANTS USING …` is `ER_PARSE_ERROR` on mysql:8.4.10) restricts the
3063    /// listing to the named active roles. Both the user and each role are the shared
3064    /// [`AccountName`] `user` grammar (a named `'u'@'host'` account or `CURRENT_USER [()]`),
3065    /// so this reuses the account-reference axis the DCL landings build. `using_roles` is
3066    /// non-empty only when `USING` was written, which the grammar allows only when
3067    /// [`user`](Self::Grants::user) is `Some`.
3068    Grants {
3069        /// The `FOR <user>` account, or `None` for bare `SHOW GRANTS`.
3070        user: Option<AccountName>,
3071        /// The `USING <role> [, …]` active-role restriction; empty when no `USING` was
3072        /// written (which the grammar permits only when [`user`](Self::Grants::user) is set).
3073        using_roles: ThinVec<AccountName>,
3074        /// Source location and node identity.
3075        meta: Meta,
3076    },
3077    /// `SHOW CREATE USER <user>` — the `CREATE USER` statement that would recreate an account
3078    /// (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3079    ///
3080    /// A sibling of [`Create`](Self::Create) held apart because its operand is the shared
3081    /// [`AccountName`] `user` specification (`'u'@'host'` or `CURRENT_USER [()]`), not an
3082    /// [`ObjectName`] — the exact reason the SHOW-family landing deferred it to the user-spec
3083    /// grammar (see [`ShowCreateKind`], where `USER` is absent).
3084    CreateUser {
3085        /// The account to recreate (the shared `user` grammar).
3086        user: AccountName,
3087        /// Source location and node identity.
3088        meta: Meta,
3089    },
3090    /// `SHOW PROFILE [<type> [, …]] [FOR QUERY <n>] [LIMIT …]` — the per-statement resource
3091    /// profile (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3092    ///
3093    /// The optional [`types`](Self::Profile::types) list selects which resource columns to
3094    /// report (MySQL's `profile_defs`; empty when none written — the server defaults to a
3095    /// standard set). `FOR QUERY <n>` ([`query`](Self::Profile::query)) profiles a specific
3096    /// entry from `SHOW PROFILES` rather than the last statement, and the shared
3097    /// [`ShowLimit`] tail narrows the row set. The three clauses are order-fixed:
3098    /// `SHOW PROFILE ALL FOR QUERY 1` parses but `SHOW PROFILE FOR QUERY 1 ALL` and
3099    /// `SHOW PROFILE LIMIT 5 FOR QUERY 1` are both `ER_PARSE_ERROR` on mysql:8.4.10.
3100    /// Distinct from the bare [`ShowBare::Profiles`] catalogue listing (`SHOW PROFILES`).
3101    Profile {
3102        /// The `profile_defs` resource-type list, in source order; empty when none written.
3103        types: ThinVec<ShowProfileType>,
3104        /// The `FOR QUERY <n>` query-id selector (an integer [`Literal`]); `None` when absent.
3105        query: Option<Literal>,
3106        /// The optional trailing `LIMIT …` narrowing.
3107        limit: Option<ShowLimit>,
3108        /// Source location and node identity.
3109        meta: Meta,
3110    },
3111    /// `SHOW {BINLOG | RELAYLOG} EVENTS [IN '<log>'] [FROM <pos>] [LIMIT …] [FOR CHANNEL
3112    /// '<channel>']` — the binary- / relay-log event dump (MySQL; gated by
3113    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3114    ///
3115    /// The two spellings share one payload — an optional `IN '<log>'` log-file name, an
3116    /// optional `FROM <pos>` start position, and the shared [`ShowLimit`] tail — so they ride
3117    /// this one variant with the spelling as DATA on [`relay`](Self::LogEvents::relay), the
3118    /// SHOW family's fold-near-identical-sub-commands precedent. The sole grammar difference
3119    /// is the trailing `FOR CHANNEL '<channel>'`: it is `RELAYLOG`-only, so
3120    /// [`channel`](Self::LogEvents::channel) is `Some` only when [`relay`](Self::LogEvents::relay)
3121    /// is `true` (`SHOW BINLOG EVENTS FOR CHANNEL …` is `ER_PARSE_ERROR` on mysql:8.4.10). The
3122    /// clause order is fixed: `SHOW BINLOG EVENTS FROM 4 IN '<log>'` is `ER_PARSE_ERROR` (the
3123    /// `IN` must precede the `FROM`).
3124    LogEvents {
3125        /// The log spelling: `false` for `BINLOG`, `true` for `RELAYLOG`.
3126        relay: bool,
3127        /// The `IN '<log>'` log-file name (a string [`Literal`]); `None` when absent.
3128        log_name: Option<Literal>,
3129        /// The `FROM <pos>` start position (an integer [`Literal`]); `None` when absent.
3130        position: Option<Literal>,
3131        /// The optional trailing `LIMIT …` narrowing.
3132        limit: Option<ShowLimit>,
3133        /// The `FOR CHANNEL '<channel>'` replication-channel qualifier (a string [`Literal`]);
3134        /// `Some` only for `RELAYLOG` (see [`relay`](Self::LogEvents::relay)).
3135        channel: Option<Literal>,
3136        /// Source location and node identity.
3137        meta: Meta,
3138    },
3139    /// `SHOW [EXTENDED] {INDEX | INDEXES | KEYS} {FROM | IN} <tbl> [{FROM | IN} <db>]
3140    /// [WHERE <expr>]` — the index-listing statement (MySQL; gated by
3141    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3142    ///
3143    /// Structurally the `{FROM | IN}`-qualified sibling of [`Columns`](Self::Columns): a
3144    /// mandatory table qualifier, an optional database qualifier, and a filter — but the
3145    /// filter here is `WHERE`-only (the MySQL grammar admits no `LIKE` on `SHOW INDEX`), so
3146    /// the parser only ever builds a [`ShowFilter::Where`]. `KEYS`/`INDEX`/`INDEXES` are
3147    /// exact synonyms whose written spelling rides the [`ShowIndexSpelling`] tag.
3148    Index {
3149        /// Which keyword named the listing: `INDEX`, `INDEXES`, or `KEYS`.
3150        spelling: ShowIndexSpelling,
3151        /// MySQL `EXTENDED` — also list hidden indexes MySQL maintains internally.
3152        extended: bool,
3153        /// The mandatory `{FROM | IN} <tbl>` qualifier naming the target table.
3154        table: ShowFrom,
3155        /// The optional second `{FROM | IN} <db>` qualifier naming the database.
3156        database: Option<ShowFrom>,
3157        /// The optional trailing `WHERE <expr>` narrowing (never `LIKE`).
3158        filter: Option<ShowFilter<X>>,
3159        /// Source location and node identity.
3160        meta: Meta,
3161    },
3162    /// `SHOW ENGINE {<name> | ALL} {STATUS | MUTEX | LOGS}` — a storage-engine diagnostic
3163    /// dump (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3164    ///
3165    /// The engine operand ([`engine`](Self::Engine::engine)) is a named engine, or `None`
3166    /// for the `ALL` wildcard; the requested artefact ([`artifact`](Self::Engine::artifact))
3167    /// is one of the three fixed report keywords. Distinct from the bare
3168    /// [`ShowBare::Engines`] catalogue listing, which takes no operand.
3169    Engine {
3170        /// The named storage engine, or `None` for the `ALL` wildcard.
3171        engine: Option<Ident>,
3172        /// Which per-engine report was requested; see [`ShowEngineArtifact`].
3173        artifact: ShowEngineArtifact,
3174        /// Source location and node identity.
3175        meta: Meta,
3176    },
3177    /// `SHOW REPLICA STATUS [FOR CHANNEL '<channel>']` — the replication-applier status
3178    /// (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3179    ///
3180    /// The optional [`channel`](Self::ReplicaStatus::channel) names a replication channel
3181    /// (`FOR CHANNEL '<name>'`). MySQL 8.4 removed the deprecated `SHOW SLAVE STATUS`
3182    /// terminology, so there is no spelling axis here.
3183    ReplicaStatus {
3184        /// The optional `FOR CHANNEL '<channel>'` qualifier.
3185        channel: Option<Literal>,
3186        /// Source location and node identity.
3187        meta: Meta,
3188    },
3189    /// `SHOW {WARNINGS | ERRORS} [LIMIT [<offset>,] <row_count>]` and `SHOW COUNT(*)
3190    /// {WARNINGS | ERRORS}` — the diagnostics-area readouts (MySQL; gated by
3191    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3192    ///
3193    /// The `WARNINGS` vs `ERRORS` choice rides [`kind`](Self::Diagnostics::kind).
3194    /// [`count`](Self::Diagnostics::count) records the `COUNT(*)` cardinality form, which is
3195    /// mutually exclusive with a [`limit`](Self::Diagnostics::limit) in the grammar — the
3196    /// parser never sets both.
3197    Diagnostics {
3198        /// Which diagnostics list was named: `WARNINGS` or `ERRORS`.
3199        kind: ShowDiagnosticKind,
3200        /// Whether this is the `SHOW COUNT(*) …` cardinality form (no `LIMIT`).
3201        count: bool,
3202        /// The optional `LIMIT [<offset>,] <row_count>` narrowing (never set when
3203        /// [`count`](Self::Diagnostics::count) is `true`).
3204        limit: Option<ShowLimit>,
3205        /// Source location and node identity.
3206        meta: Meta,
3207    },
3208    /// `SHOW {PROCEDURE | FUNCTION} CODE <name>` — the compiled stored-routine instruction
3209    /// dump (MySQL debug builds; gated by
3210    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3211    ///
3212    /// Shares the [`ShowRoutineKind`] object-keyword axis with
3213    /// [`RoutineStatus`](Self::RoutineStatus); the operand is the (schema-qualifiable)
3214    /// routine name.
3215    RoutineCode {
3216        /// Which stored-routine kind was named: `FUNCTION` or `PROCEDURE`.
3217        kind: ShowRoutineKind,
3218        /// The (optionally schema-qualified) routine name.
3219        name: ObjectName,
3220        /// Source location and node identity.
3221        meta: Meta,
3222    },
3223}
3224
3225/// Which stored-routine kind a [`ShowTarget::RoutineStatus`] listing named: `FUNCTION` or
3226/// `PROCEDURE` (MySQL `SHOW {FUNCTION | PROCEDURE} STATUS`).
3227///
3228/// A surface tag (no `meta`, like [`ShowColumnsSpelling`]): the keyword's span is subsumed
3229/// by the enclosing [`ShowStatement`]. Recorded only so the written object keyword
3230/// round-trips.
3231#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3232#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3233#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3234pub enum ShowRoutineKind {
3235    /// `FUNCTION` — list stored functions.
3236    Function,
3237    /// `PROCEDURE` — list stored procedures.
3238    Procedure,
3239}
3240
3241/// The optional scope keyword before `FUNCTIONS` in a [`ShowTarget::Functions`] listing:
3242/// `USER`, `SYSTEM`, or `ALL` (Spark / Databricks).
3243///
3244/// A surface tag (no `meta`, like [`ShowColumnsSpelling`]): the keyword's span is subsumed
3245/// by the enclosing [`ShowStatement`]. Recorded only so the written scope round-trips.
3246#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3247#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3248#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3249pub enum ShowFunctionsScope {
3250    /// `USER` — user-defined functions only.
3251    User,
3252    /// `SYSTEM` — system (built-in) functions only.
3253    System,
3254    /// `ALL` — both user and system functions.
3255    All,
3256}
3257
3258/// The optional trailing narrowing of a [`ShowTarget::Functions`] listing:
3259/// `[LIKE] {<function_name> | '<regex_pattern>'}` (Spark / Databricks).
3260///
3261/// A distinct type from [`ShowFilter`] because that models MySQL's mutually-exclusive
3262/// `LIKE '<pat>' | WHERE <expr>` (a mandatory keyword, no bare-name form, and a `WHERE`
3263/// predicate `SHOW FUNCTIONS` does not accept). Here the `LIKE` keyword is *optional* and
3264/// the operand is either a bare (optionally qualified) function name or a quoted regex
3265/// string; [`like`](Self::Name::like) records whether the keyword was written so the
3266/// statement round-trips.
3267#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3268#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3269#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3270pub enum ShowFunctionsFilter {
3271    /// `[LIKE] <function_name>` — a bare, optionally-qualified function name.
3272    Name {
3273        /// Whether the optional `LIKE` keyword preceded the name.
3274        like: bool,
3275        /// The (optionally qualified) function name to match.
3276        name: ObjectName,
3277        /// Source location and node identity.
3278        meta: Meta,
3279    },
3280    /// `[LIKE] '<regex_pattern>'` — a quoted regex-pattern string.
3281    Regex {
3282        /// Whether the optional `LIKE` keyword preceded the pattern.
3283        like: bool,
3284        /// The quoted regex-pattern string literal.
3285        pattern: Literal,
3286        /// Source location and node identity.
3287        meta: Meta,
3288    },
3289}
3290
3291/// Which keyword named a [`ShowTarget::Columns`] listing: `COLUMNS` or its exact `FIELDS`
3292/// synonym (MySQL).
3293///
3294/// A surface tag (no `meta`, like [`ShowFromKeyword`]): the keyword's span is subsumed by
3295/// the enclosing [`ShowStatement`]. Recorded only so the written spelling round-trips.
3296#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3297#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3298#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3299pub enum ShowColumnsSpelling {
3300    /// Source used the `COLUMNS` spelling.
3301    Columns,
3302    /// Source used the `FIELDS` spelling.
3303    Fields,
3304}
3305
3306/// A `{FROM | IN} <name>` object qualifier in the typed-`SHOW` family.
3307///
3308/// Used for the [`ShowTarget::Tables`] database qualifier and for both the mandatory
3309/// table and optional database qualifiers of [`ShowTarget::Columns`], so [`name`](Self::name)
3310/// is a generic [`ObjectName`] rather than a database- or table-specific field. MySQL
3311/// accepts either keyword interchangeably; DuckDB accepts only `FROM`. The
3312/// [`keyword`](Self::keyword) surface tag records which was written so the statement
3313/// round-trips.
3314#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3315#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3316#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3317pub struct ShowFrom {
3318    /// Whether `FROM` or `IN` introduced the database; see [`ShowFromKeyword`].
3319    pub keyword: ShowFromKeyword,
3320    /// Name referenced by this syntax.
3321    pub name: ObjectName,
3322    /// Source location and node identity.
3323    pub meta: Meta,
3324}
3325
3326/// Which keyword introduced a [`ShowFrom`]: `FROM` or its `IN` synonym.
3327///
3328/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3329/// [`ShowFrom`], as [`ExplainKeyword`] rides its parent's span.
3330#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3331#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3332#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3333pub enum ShowFromKeyword {
3334    /// `SHOW … FROM <db>`.
3335    From,
3336    /// `SHOW … IN <db>` — a synonym for `FROM`.
3337    In,
3338}
3339
3340/// The optional trailing narrowing of a [`ShowTarget::Tables`] or [`ShowTarget::Columns`]:
3341/// a `LIKE` name pattern or a `WHERE` predicate (MySQL).
3342///
3343/// The two are mutually exclusive in the grammar. `LIKE` takes a string pattern
3344/// ([`Literal`]); `WHERE` takes a general predicate ([`Expr`]) over the result columns,
3345/// which is why this enum — and thus [`ShowTarget`]/[`ShowStatement`] — is generic over
3346/// the extension `X`.
3347#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3348#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3349#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3350pub enum ShowFilter<X: Extension = NoExt> {
3351    /// A `LIKE '<pattern>'` name filter.
3352    Like {
3353        /// Pattern matched by this syntax.
3354        pattern: Literal,
3355        /// Source location and node identity.
3356        meta: Meta,
3357    },
3358    /// A `WHERE <predicate>` filter (MySQL).
3359    Where {
3360        /// Predicate that controls this clause.
3361        predicate: Expr<X>,
3362        /// Source location and node identity.
3363        meta: Meta,
3364    },
3365}
3366
3367/// Which object kind a [`ShowTarget::Create`] recreates: `TABLE`, `VIEW`, `DATABASE`,
3368/// `EVENT`, `PROCEDURE`, `FUNCTION`, or `TRIGGER` (MySQL `SHOW CREATE <kind> <name>`).
3369///
3370/// A surface tag (no `meta`, like [`ShowRoutineKind`]): the keyword's span is subsumed by
3371/// the enclosing [`ShowStatement`]. `USER` is absent — its operand is a user specification,
3372/// not an [`ObjectName`], so `SHOW CREATE USER` rides its own
3373/// [`ShowTarget::CreateUser`] variant over the shared [`AccountName`] grammar.
3374#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3375#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3376#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3377pub enum ShowCreateKind {
3378    /// `SHOW CREATE TABLE <tbl>`.
3379    Table,
3380    /// `SHOW CREATE VIEW <view>`.
3381    View,
3382    /// `SHOW CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] <db>`.
3383    Database {
3384        /// Whether the `SCHEMA` synonym spelling was written in place of `DATABASE`.
3385        schema: bool,
3386    },
3387    /// `SHOW CREATE EVENT <event>`.
3388    Event,
3389    /// `SHOW CREATE PROCEDURE <proc>`.
3390    Procedure,
3391    /// `SHOW CREATE FUNCTION <func>`.
3392    Function,
3393    /// `SHOW CREATE TRIGGER <trigger>`.
3394    Trigger,
3395}
3396
3397/// Which catalogue/server listing a [`ShowTarget::Listing`] named, plus that sub-command's
3398/// own small *scalar* payload (MySQL). Every member admits the shared `[LIKE | WHERE]` tail
3399/// and the optional `{FROM | IN} <db>` qualifier carried by the enclosing
3400/// [`ShowTarget::Listing`].
3401///
3402/// A surface discriminator (no `meta`, `Copy` like [`ShowScope`]): the `{FROM | IN}`
3403/// qualifier — the only spanned payload — lives on the enclosing variant, leaving this enum
3404/// a pure keyword tag plus scalar flags whose spans are subsumed by the [`ShowStatement`].
3405#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3406#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3407#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3408pub enum ShowListing {
3409    /// `SHOW {DATABASES | SCHEMAS}` (takes no `{FROM | IN}` qualifier).
3410    Databases {
3411        /// Whether the `SCHEMAS` synonym spelling was written in place of `DATABASES`.
3412        schemas: bool,
3413    },
3414    /// `SHOW {CHARACTER SET | CHARSET}` (takes no `{FROM | IN}` qualifier).
3415    CharacterSet {
3416        /// Whether the one-word `CHARSET` spelling was written in place of `CHARACTER SET`.
3417        charset: bool,
3418    },
3419    /// `SHOW COLLATION` (takes no `{FROM | IN}` qualifier).
3420    Collation,
3421    /// `SHOW [GLOBAL | SESSION | LOCAL] STATUS` (takes no `{FROM | IN}` qualifier).
3422    Status {
3423        /// The optional `GLOBAL`/`SESSION`/`LOCAL` scope; see [`ShowScope`].
3424        scope: Option<ShowScope>,
3425    },
3426    /// `SHOW [GLOBAL | SESSION | LOCAL] VARIABLES` (takes no `{FROM | IN}` qualifier).
3427    Variables {
3428        /// The optional `GLOBAL`/`SESSION`/`LOCAL` scope; see [`ShowScope`].
3429        scope: Option<ShowScope>,
3430    },
3431    /// `SHOW EVENTS [{FROM | IN} <db>]`.
3432    Events,
3433    /// `SHOW TABLE STATUS [{FROM | IN} <db>]`.
3434    TableStatus,
3435    /// `SHOW OPEN TABLES [{FROM | IN} <db>]`.
3436    OpenTables,
3437    /// `SHOW [FULL] TRIGGERS [{FROM | IN} <db>]`.
3438    Triggers {
3439        /// MySQL `FULL` — add the `sql_mode`, definer, and character-set columns.
3440        full: bool,
3441    },
3442}
3443
3444/// The optional `GLOBAL`/`SESSION`/`LOCAL` scope keyword on `SHOW … STATUS` / `SHOW …
3445/// VARIABLES` (MySQL `opt_var_type`).
3446///
3447/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3448/// [`ShowStatement`]. `LOCAL` is an exact synonym of `SESSION` in MySQL, kept distinct here
3449/// only so the written spelling round-trips.
3450#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3451#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3452#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3453pub enum ShowScope {
3454    /// `GLOBAL` — the server-wide value.
3455    Global,
3456    /// `SESSION` — the current session's value.
3457    Session,
3458    /// `LOCAL` — a synonym for `SESSION`.
3459    Local,
3460}
3461
3462/// A MySQL `SHOW` sub-command taking no filter and no name operand; the discriminant of
3463/// [`ShowTarget::Bare`]. The only payloads are single leading-keyword flags.
3464///
3465/// A surface tag (no `meta`): each keyword's span is subsumed by the enclosing
3466/// [`ShowStatement`].
3467#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3468#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3469#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3470pub enum ShowBare {
3471    /// `SHOW PLUGINS`.
3472    Plugins,
3473    /// `SHOW [STORAGE] ENGINES`.
3474    Engines {
3475        /// Whether the optional `STORAGE` keyword preceded `ENGINES`.
3476        storage: bool,
3477    },
3478    /// `SHOW PRIVILEGES`.
3479    Privileges,
3480    /// `SHOW PROFILES`.
3481    Profiles,
3482    /// `SHOW [FULL] PROCESSLIST`.
3483    Processlist {
3484        /// MySQL `FULL` — show the full `Info` column instead of truncating it.
3485        full: bool,
3486    },
3487    /// `SHOW BINARY LOGS`.
3488    BinaryLogs,
3489    /// `SHOW REPLICAS`.
3490    Replicas,
3491    /// `SHOW BINARY LOG STATUS`.
3492    BinaryLogStatus,
3493}
3494
3495/// Which keyword named a [`ShowTarget::Index`] listing: `INDEX`, `INDEXES`, or `KEYS`
3496/// (MySQL `keys_or_index`).
3497///
3498/// A surface tag (no `meta`, like [`ShowColumnsSpelling`]): the three are exact synonyms,
3499/// recorded only so the written spelling round-trips.
3500#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3501#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3502#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3503pub enum ShowIndexSpelling {
3504    /// Source used the singular `INDEX` spelling.
3505    Index,
3506    /// Source used the plural `INDEXES` spelling.
3507    Indexes,
3508    /// Source used the `KEYS` spelling.
3509    Keys,
3510}
3511
3512/// Which per-engine report a [`ShowTarget::Engine`] dump requested: `STATUS`, `MUTEX`, or
3513/// `LOGS` (MySQL).
3514///
3515/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3516/// [`ShowStatement`].
3517#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3518#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3519#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3520pub enum ShowEngineArtifact {
3521    /// `SHOW ENGINE <e> STATUS`.
3522    Status,
3523    /// `SHOW ENGINE <e> MUTEX`.
3524    Mutex,
3525    /// `SHOW ENGINE <e> LOGS`.
3526    Logs,
3527}
3528
3529/// Which diagnostics list a [`ShowTarget::Diagnostics`] readout named: `WARNINGS` or
3530/// `ERRORS` (MySQL).
3531///
3532/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3533/// [`ShowStatement`].
3534#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3535#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3536#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3537pub enum ShowDiagnosticKind {
3538    /// `SHOW WARNINGS` / `SHOW COUNT(*) WARNINGS`.
3539    Warnings,
3540    /// `SHOW ERRORS` / `SHOW COUNT(*) ERRORS`.
3541    Errors,
3542}
3543
3544/// One resource-type selector in a [`ShowTarget::Profile`] `profile_defs` list (MySQL).
3545///
3546/// A surface tag (no `meta`, `Copy` like [`ShowScope`]): every member is a pure keyword or
3547/// keyword pair with no operand, so the list's span is subsumed by the enclosing
3548/// [`ShowStatement`]. MySQL folds duplicates into a bitmask at bind time; the parser keeps
3549/// the written list order and multiplicity so the statement round-trips.
3550#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3551#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3552#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3553pub enum ShowProfileType {
3554    /// `ALL` — every available profile column.
3555    All,
3556    /// `BLOCK IO` — block input/output counts.
3557    BlockIo,
3558    /// `CONTEXT SWITCHES` — voluntary and involuntary context switches.
3559    ContextSwitches,
3560    /// `CPU` — user and system CPU time.
3561    Cpu,
3562    /// `IPC` — messages sent and received.
3563    Ipc,
3564    /// `MEMORY` — memory usage (currently unimplemented server-side, still grammar-valid).
3565    Memory,
3566    /// `PAGE FAULTS` — major and minor page faults.
3567    PageFaults,
3568    /// `SOURCE` — the source-file function names, files, and line numbers.
3569    Source,
3570    /// `SWAPS` — swap counts.
3571    Swaps,
3572}
3573
3574/// The shared `LIMIT` narrowing on the MySQL `SHOW` family — MySQL's `opt_limit_clause`
3575/// (`SHOW {WARNINGS | ERRORS}`, `SHOW PROFILE`, `SHOW {BINLOG | RELAYLOG} EVENTS`).
3576///
3577/// Models all three surface forms of `limit_options`:
3578///
3579/// * `LIMIT <row_count>` — no offset ([`offset`](Self::offset) `None`).
3580/// * `LIMIT <offset>, <row_count>` — the comma form, offset written first
3581///   ([`offset_keyword`](Self::offset_keyword) `false`).
3582/// * `LIMIT <row_count> OFFSET <offset>` — the `OFFSET`-keyword form
3583///   ([`offset_keyword`](Self::offset_keyword) `true`).
3584///
3585/// The two offset spellings are semantically identical (`LIMIT 2, 5` == `LIMIT 5 OFFSET 2`);
3586/// [`offset_keyword`](Self::offset_keyword) records which was written so the statement
3587/// round-trips, and is always `false` when [`offset`](Self::offset) is `None`. Both operands
3588/// are integer [`Literal`]s (MySQL's `limit_option` also admits a `?` param-marker or a
3589/// user-variable, deferred — the whole `SHOW` family parses only integer limits today).
3590#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3591#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3592#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3593pub struct ShowLimit {
3594    /// The optional `<offset>` — present for both the comma and `OFFSET`-keyword forms.
3595    pub offset: Option<Literal>,
3596    /// When [`offset`](Self::offset) is present, whether it was written with the `OFFSET`
3597    /// keyword (`LIMIT <row_count> OFFSET <offset>`) rather than the comma form
3598    /// (`LIMIT <offset>, <row_count>`); always `false` when there is no offset.
3599    pub offset_keyword: bool,
3600    /// The `<row_count>` — the sole operand, the second operand of the comma form, or the
3601    /// first operand of the `OFFSET`-keyword form.
3602    pub row_count: Literal,
3603    /// Source location and node identity.
3604    pub meta: Meta,
3605}