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 table to vacuum (`VACUUM db.t` or `VACUUM 'table name'`); `None`
1338    /// when absent. A possibly dotted [`ObjectName`], unlike SQLite's single-ident
1339    /// [`schema`](Self::schema). 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. Under
2127/// [`UtilitySyntax::use_string_literal_name`](crate::dialect::UtilitySyntax) DuckDB also
2128/// admits a single-part Sconst target (`USE 'n'` / `E'n'` / `$$n$$`), folded into a
2129/// single-quoted [`Ident`] so the quotes round-trip. Non-generic, like [`DetachStatement`]:
2130/// a qualified name carries no expressions or extension nodes.
2131#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2132#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2133#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2134pub struct UseStatement {
2135    /// Name referenced by this syntax.
2136    pub name: ObjectName,
2137    /// Source location and node identity.
2138    pub meta: Meta,
2139}
2140
2141/// A `PREPARE <name> [ ( <type> [, ...] ) ] AS <statement>` prepared-statement
2142/// definition (DuckDB; gated by
2143/// [`UtilitySyntax::prepared_statements`](crate::dialect::UtilitySyntax)).
2144///
2145/// Binds a session-scoped name to a parameterized statement (`PREPARE v1 AS SELECT
2146/// 'Test' LIMIT ?`). The body embeds a [`Statement`], which makes this node generic
2147/// over the extension `X`, and follows the [`ExplainStatement`] contract:
2148/// the grammar accepts *any* [`Statement`] and leaves the preparable-kind restriction
2149/// to a later pass — DuckDB rejects a non-preparable body at bind, not parse.
2150///
2151/// The prepared-statement name is a bare [`Ident`], not a dotted [`ObjectName`]: the
2152/// name lives in a flat session namespace, not the catalogue. DuckDB rejects the
2153/// PostgreSQL `PREPARE name ( <type> [, ...] ) AS ...` argument-type list ("Prepared
2154/// statement argument types are not supported, use CAST"), so
2155/// [`parameter_types`](Self::parameter_types) is gated by its own
2156/// [`prepare_typed_parameters`](crate::dialect::UtilitySyntax::prepare_typed_parameters)
2157/// flag, independent of the base `PREPARE`/`EXECUTE`/`DEALLOCATE` dispatch.
2158#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2159#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2160#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2161pub struct PrepareStatement<X: Extension = NoExt> {
2162    /// Name referenced by this syntax.
2163    pub name: Ident,
2164    /// The PostgreSQL parenthesized parameter-type list (`PREPARE p(int, text) AS
2165    /// ...`), in source order; empty when the parentheses were not written.
2166    /// PostgreSQL rejects an empty written `()`, so an empty list unambiguously means
2167    /// "clause absent" and needs no separate surface tag — the same
2168    /// absent-vs-empty-not-written equivalence [`ExecuteStatement::args`] uses.
2169    pub parameter_types: ThinVec<DataType<X>>,
2170    /// The statement bound to the name; any [`Statement`] parses (the
2171    /// [`ExplainStatement`] "grammar accepts any statement" contract).
2172    pub statement: Box<Statement<X>>,
2173    /// Source location and node identity.
2174    pub meta: Meta,
2175}
2176
2177/// A MySQL `PREPARE <name> FROM {'<text>' | @<var>}` prepared-statement definition (gated by
2178/// [`UtilitySyntax::prepared_statements_from`](crate::dialect::UtilitySyntax)).
2179///
2180/// A *different shape on the same `PREPARE` keyword* from DuckDB/PostgreSQL's typed
2181/// [`PrepareStatement`]: MySQL binds the name to a statement *source* — an opaque string
2182/// literal or a user-variable reference read at prepare time (`sql_yacc.yy` `prepare_src`) —
2183/// **not** an inline-parsed [`Statement`], and takes neither the `AS` keyword nor a
2184/// parameter-type list. The source text is never re-parsed here (the placeholders `?` it
2185/// carries are a run-time protocol concern), so this node holds no expression or statement
2186/// children and is non-generic, like [`DeallocateStatement`]. The two `PREPARE` behaviours
2187/// never coexist in one preset (each arms at most one gate), the split mirroring the
2188/// [`DoStatement`]/[`DoExpressionsStatement`] `DO`-keyword split.
2189///
2190/// MySQL grammar-accepts every well-formed source shape but cannot *prepare* the outer
2191/// `PREPARE` itself over the binary protocol (`ER_UNSUPPORTED_PS` 1295) — a bind-time
2192/// verdict the parse layer does not model, exactly the [`PrepareStatement`]
2193/// "grammar accepts, bind restricts" contract.
2194#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2195#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2196#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2197pub struct PrepareFromStatement {
2198    /// Name referenced by this syntax.
2199    pub name: Ident,
2200    /// The statement source bound to the name; see [`PrepareSource`].
2201    pub source: PrepareSource,
2202    /// Source location and node identity.
2203    pub meta: Meta,
2204}
2205
2206/// The source of a MySQL [`PrepareFromStatement`] — `sql_yacc.yy` `prepare_src`, either an
2207/// inline string literal or a user-variable reference. A spanned enum node: each arm
2208/// round-trips its own spelling, so the two are recorded as written rather than folded to a
2209/// single string.
2210#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2211#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2212#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2213pub enum PrepareSource {
2214    /// `PREPARE name FROM '<text>'` — the opaque statement source string (`TEXT_STRING_sys`),
2215    /// kept verbatim and never re-parsed here (like [`DoArg::Body`]). Its spelling — quote
2216    /// style and escapes — round-trips from the [`Literal`].
2217    Text {
2218        /// The statement-source [`Literal`] (a [`LiteralKind::String`](crate::ast::LiteralKind)).
2219        source: Literal,
2220        /// Source location and node identity.
2221        meta: Meta,
2222    },
2223    /// `PREPARE name FROM @<var>` — the source read from a user variable at prepare time
2224    /// (`'@' ident_or_text`, MySQL's `prepared_stmt_code_is_varref`). The variable's name is
2225    /// held without the `@` sigil, its quote style preserved for round-trip; `@@`-prefixed
2226    /// system variables are rejected by the parser (`ER_PARSE_ERROR` on mysql:8).
2227    Variable {
2228        /// The user-variable name (without the `@` sigil).
2229        name: Ident,
2230        /// Source location and node identity.
2231        meta: Meta,
2232    },
2233}
2234
2235/// An `EXECUTE <name> [ ( <arg> [, ...] ) ]` prepared-statement invocation (DuckDB;
2236/// gated by [`UtilitySyntax::prepared_statements`](crate::dialect::UtilitySyntax)).
2237///
2238/// Runs a [`PrepareStatement`]-bound name, supplying its parameters positionally
2239/// (`EXECUTE v1(1)`). The arguments are full expressions, which makes this node generic
2240/// over the extension `X`. A bare `EXECUTE v1` (no argument list) leaves
2241/// [`args`](Self::args) empty; DuckDB rejects an empty `EXECUTE v1()` as a syntax error,
2242/// so an empty list unambiguously means "no parentheses written" and needs no separate
2243/// surface tag — one shape covers the absent-list form, and the parser refuses the empty
2244/// `()` that would otherwise collide with it.
2245#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2246#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2247#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2248pub struct ExecuteStatement<X: Extension = NoExt> {
2249    /// Name referenced by this syntax.
2250    pub name: Ident,
2251    /// The positional argument expressions, in source order; empty for the bare
2252    /// `EXECUTE v1` form (no argument list). Never an empty written `()`, which DuckDB
2253    /// rejects.
2254    pub args: ThinVec<Expr<X>>,
2255    /// Source location and node identity.
2256    pub meta: Meta,
2257}
2258
2259/// A MySQL `EXECUTE <name> [USING @<var> [, ...]]` prepared-statement invocation (gated by
2260/// [`UtilitySyntax::prepared_statements_from`](crate::dialect::UtilitySyntax)).
2261///
2262/// A *different argument surface on the same `EXECUTE` keyword* from DuckDB's parenthesized
2263/// positional-expression [`ExecuteStatement`]: MySQL supplies parameters through a `USING`
2264/// clause whose members are strictly *user-variable references* (`sql_yacc.yy`
2265/// `execute_var_list` → `execute_var_ident: '@' ident_or_text`), never arbitrary
2266/// expressions — `EXECUTE s USING 1` and `EXECUTE s USING @@sys` are both `ER_PARSE_ERROR`
2267/// on mysql:8, and there is no `EXECUTE s(...)` parenthesized form. So this node holds a
2268/// list of variable-name [`Ident`]s, not [`Expr`]s, and is non-generic. A bare `EXECUTE s`
2269/// (no `USING`) leaves [`using`](Self::using) empty; MySQL has no empty-`USING` spelling, so
2270/// an empty list unambiguously means the clause was absent.
2271///
2272/// Like [`PrepareFromStatement`], the statement grammar-parses but cannot be prepared over
2273/// the binary protocol (`ER_UNSUPPORTED_PS` 1295) — a bind verdict the parse layer leaves
2274/// untouched.
2275#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2276#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2277#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2278pub struct ExecuteUsingStatement {
2279    /// Name referenced by this syntax.
2280    pub name: Ident,
2281    /// The `USING` user-variable references, in source order; each is a variable name held
2282    /// without its `@` sigil (quote style preserved for round-trip). Empty for the bare
2283    /// `EXECUTE name` form (no `USING` clause).
2284    pub using: ThinVec<Ident>,
2285    /// Source location and node identity.
2286    pub meta: Meta,
2287}
2288
2289/// A `CALL <name> [ ( [ <arg> [, ...] ] ) ]` routine invocation (DuckDB, MySQL; gated by
2290/// [`UtilitySyntax::call`](crate::dialect::UtilitySyntax)).
2291///
2292/// Invokes a table function or stored procedure as a top-level statement
2293/// (`CALL pragma_table_info('t')`, `CALL my_proc(1, 2)`). The arguments are full
2294/// expressions, which makes this node generic over the extension `X`.
2295///
2296/// The parenthesized argument list is *mandatory* for DuckDB — it rejects a bare
2297/// `CALL pragma_version` (syntax error) but accepts an empty `CALL pragma_version()` — and
2298/// *optional* for MySQL, whose grammar (`CALL_SYM sp_name opt_paren_expr_list`) admits a
2299/// bare `CALL my_proc` with no argument list at all (verified on mysql:8.4.10 — the bare
2300/// form resolves to ER_SP_DOES_NOT_EXIST, a grammar-positive binding reject). The
2301/// [`parenthesized`](Self::parenthesized) surface flag distinguishes the two written forms
2302/// so the source round-trips: `false` is the MySQL bare form (`CALL p`, always empty
2303/// [`args`](Self::args)), `true` is the parenthesized form (`CALL p()` / `CALL p(1, 2)`,
2304/// which is the only DuckDB shape). The bare form is gated by
2305/// [`UtilitySyntax::call_bare_name`](crate::dialect::UtilitySyntax).
2306#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2307#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2308#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2309pub struct CallStatement<X: Extension = NoExt> {
2310    /// Name referenced by this syntax.
2311    pub name: ObjectName,
2312    /// The argument expressions, in source order; may be empty (`CALL f()`) when
2313    /// [`parenthesized`](Self::parenthesized) is `true`, and is always empty for the bare
2314    /// MySQL form (`CALL f`).
2315    pub args: ThinVec<Expr<X>>,
2316    /// Whether a parenthesized argument list was written. `true` renders the `(...)` (empty
2317    /// or not); `false` is the MySQL bare `CALL name` form with no argument list, which
2318    /// renders just the name. Always `true` for DuckDB, whose parentheses are mandatory.
2319    pub parenthesized: bool,
2320    /// Source location and node identity.
2321    pub meta: Meta,
2322}
2323
2324/// A `{DEALLOCATE | DROP} [PREPARE] <name>` prepared-statement release (DuckDB, gated by
2325/// [`UtilitySyntax::prepared_statements`](crate::dialect::UtilitySyntax); MySQL, gated by
2326/// [`UtilitySyntax::prepared_statements_from`](crate::dialect::UtilitySyntax)).
2327///
2328/// Frees a [`PrepareStatement`]/[`PrepareFromStatement`]-bound name. Non-generic, like
2329/// [`DetachStatement`]: a name plus two surface flags carries no expressions or extension
2330/// nodes. Neither dialect has a `DEALLOCATE ALL` (DuckDB rejects it: "DEALLOCATE requires a
2331/// name"; MySQL's grammar takes a single `ident`), so the target is always a single
2332/// [`Ident`], never an all-marker.
2333///
2334/// The `PREPARE` keyword's role differs by dialect and is a parse-time constraint, not a
2335/// node field: DuckDB's `DEALLOCATE [PREPARE] name` makes it optional (round-tripped by
2336/// [`prepare_keyword`](Self::prepare_keyword)), while MySQL's `deallocate_or_drop PREPARE
2337/// ident` makes it *mandatory* (a bare `DEALLOCATE name` is `ER_PARSE_ERROR` on mysql:8),
2338/// so the MySQL parser always sets the flag. The leading-verb spelling
2339/// ([`keyword`](Self::keyword)) is MySQL's `deallocate_or_drop` synonym choice; DuckDB has
2340/// only the `DEALLOCATE` spelling.
2341#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2342#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2343#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2344pub struct DeallocateStatement {
2345    /// Which leading verb spelled this release: MySQL's `deallocate_or_drop` accepts
2346    /// `DEALLOCATE PREPARE name` and the `DROP PREPARE name` synonym interchangeably, and
2347    /// the spelling round-trips. Always [`DeallocateKeyword::Deallocate`] for DuckDB, whose
2348    /// grammar has no `DROP PREPARE` form.
2349    pub keyword: DeallocateKeyword,
2350    /// Whether the `PREPARE` keyword was written (`DEALLOCATE PREPARE v1` vs `DEALLOCATE
2351    /// v1`); a round-trip surface tag. Optional for DuckDB (both forms accepted), always
2352    /// `true` for MySQL (the keyword is mandatory there).
2353    pub prepare_keyword: bool,
2354    /// Name referenced by this syntax.
2355    pub name: Ident,
2356    /// Source location and node identity.
2357    pub meta: Meta,
2358}
2359
2360/// The leading verb of a [`DeallocateStatement`] — MySQL's `deallocate_or_drop` synonym
2361/// choice. A round-trip surface tag: `DROP PREPARE name` and `DEALLOCATE PREPARE name` are
2362/// the same statement on mysql:8, differing only in this spelling.
2363#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2364#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2365#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2366pub enum DeallocateKeyword {
2367    /// The `DEALLOCATE` spelling (DuckDB and MySQL).
2368    Deallocate,
2369    /// MySQL's `DROP` synonym (`DROP PREPARE name`).
2370    Drop,
2371}
2372
2373/// A PostgreSQL `DO [LANGUAGE <lang>] '<body>'` anonymous code block (gated by
2374/// [`UtilitySyntax::do_statement`](crate::dialect::UtilitySyntax)).
2375///
2376/// Runs an inline procedural-language block without defining a routine. The body is an
2377/// opaque source string in the target language ([`LiteralKind::String`](crate::ast::LiteralKind)
2378/// — dollar-quoted in practice), never re-parsed here: like
2379/// [`FunctionOption::As`](crate::ast::FunctionOption) the body's grammar is the target
2380/// language, not SQL, so the PL text is not smuggled into the AST.
2381///
2382/// The shape mirrors [`CreateFunction`](crate::ast::CreateFunction)'s option cluster
2383/// rather than a fixed `{ language, body }` pair, because PostgreSQL's raw grammar is the
2384/// same free option list: `DO dostmt_opt_list`, where `dostmt_opt_list` is a *non-empty*
2385/// sequence of items each either an `Sconst` body or `LANGUAGE <word>`, in any order and
2386/// with no arity limit. The parser (`makeDefElem("as"/"language", …)`) accepts a repeated
2387/// or missing body and a repeated language — `DO LANGUAGE plpgsql` (language, no body),
2388/// `DO $$a$$ $$b$$` (two bodies), and `DO 'x' LANGUAGE a LANGUAGE b` (two languages) all
2389/// parse — and defers the "exactly one body, at most one language" check to execution
2390/// (`ExecuteDoStmt`), exactly as the [`PrepareStatement`] "grammar accepts any statement,
2391/// bind restricts the kind" contract defers its own semantic check. Modelling the list
2392/// faithfully is what keeps raw-parse accept/reject parity with libpg_query; collapsing it
2393/// to a single body/language pair would over-reject those three forms. The list is
2394/// non-empty (a bare `DO` is a syntax error), which the parser enforces.
2395#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2396#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2397#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2398pub struct DoStatement {
2399    /// The `dostmt_opt_list` items in source order; always non-empty.
2400    pub args: ThinVec<DoArg>,
2401    /// Source location and node identity.
2402    pub meta: Meta,
2403}
2404
2405/// One item of a [`DoStatement`]'s `dostmt_opt_list` — a body string or a `LANGUAGE`
2406/// clause. The two forms and their source order round-trip, so the parser records each as
2407/// written rather than folding them into a canonical `{ language, body }` pair.
2408#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2409#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2410#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2411pub enum DoArg {
2412    /// An inline-body `Sconst` item — PostgreSQL's `makeDefElem("as", …)`. The body is
2413    /// the opaque source [`Literal`] (kept, not re-parsed, like
2414    /// [`FunctionOption::As`](crate::ast::FunctionOption)); there is no `AS` keyword in the
2415    /// `DO` syntax, so the variant is named for the block body it carries.
2416    Body {
2417        /// Statement or query body governed by this node.
2418        body: Literal,
2419        /// Source location and node identity.
2420        meta: Meta,
2421    },
2422    /// A `LANGUAGE <name>` item — PostgreSQL's `makeDefElem("language", …)`. The name is a
2423    /// `NonReservedWord_or_Sconst` ([`LanguageName`]): a bare word or a string constant, as
2424    /// in the `CREATE FUNCTION` `LANGUAGE` clause. A reserved word (a bit/hex/national string
2425    /// constant, since those are not `Sconst`) is the syntax error PostgreSQL reports.
2426    Language {
2427        /// Name referenced by this syntax.
2428        name: LanguageName,
2429        /// Source location and node identity.
2430        meta: Meta,
2431    },
2432}
2433
2434/// A routine `LANGUAGE <name>` operand (PostgreSQL's `NonReservedWord_or_Sconst`): a bare
2435/// non-reserved word or a string constant. Shared by the two positions that spell it the same
2436/// way — the [`DoStatement`] `DO … LANGUAGE <name>` argument and the `CREATE FUNCTION`
2437/// [`FunctionOption::Language`](crate::ast::FunctionOption) clause. PostgreSQL folds both
2438/// spellings to the same language name internally, but the surface form is kept distinct so it
2439/// round-trips — the same `NonReservedWord_or_Sconst` shape as
2440/// [`ExtensionVersion`](crate::ast::ExtensionVersion).
2441///
2442/// The string arm admits only an `Sconst` (a plain, `E'...'`, `U&'...'`, or dollar-quoted
2443/// constant); a bit-string (`b'...'`/`x'...'`) or national (`N'...'`) constant is not an
2444/// `Sconst`, so — like the code-block body — it is the syntax error PostgreSQL reports. The
2445/// string spelling is a PostgreSQL surface: MySQL's routine `LANGUAGE` admits only the bare
2446/// word `SQL` (`LANGUAGE 'SQL'` is `ER_PARSE_ERROR` on mysql:8), so the parser gates the string
2447/// arm on [`IndexAlterSyntax::routine_language_string`](crate::dialect::IndexAlterSyntax::routine_language_string).
2448#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2449#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2450#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2451pub enum LanguageName {
2452    /// A bare non-reserved word (`LANGUAGE plpgsql`).
2453    Word {
2454        /// Identifier-form language name.
2455        word: Ident,
2456        /// Source location and node identity.
2457        meta: Meta,
2458    },
2459    /// A string constant (`LANGUAGE 'plpgsql'`).
2460    String {
2461        /// Value supplied by this syntax.
2462        value: Literal,
2463        /// Source location and node identity.
2464        meta: Meta,
2465    },
2466}
2467
2468/// A MySQL `DO <expr> [, <expr> ...]` evaluate-and-discard statement (gated by
2469/// [`UtilitySyntax::do_expression_list`](crate::dialect::UtilitySyntax)).
2470///
2471/// A *different behaviour on the same `DO` keyword* from PostgreSQL's anonymous code block
2472/// ([`DoStatement`]): MySQL's `DO` evaluates a list of expressions purely for their side
2473/// effects and throws the results away (`DO SLEEP(1)`, `DO @x := 1`), whereas PostgreSQL's
2474/// `DO` runs an opaque procedural-language body string. The two never coexist in one dialect
2475/// (each preset arms exactly one gate), the split mirroring the transaction-`BEGIN`
2476/// vs compound-block-`BEGIN` dialect-gated arms.
2477///
2478/// The grammar is literally `DO select_item_list` (mysql `sql_yacc.yy` `do_stmt`), so the
2479/// items are [`SelectItem`]s, not bare [`Expr`]s: MySQL grammar-accepts a select alias
2480/// (`DO 1 AS x` PREPAREs) and a wildcard (`DO *`, `DO t.*`) here exactly as in a projection.
2481/// Reusing the projection-item node keeps raw-parse acceptance aligned with the engine; the
2482/// wildcard forms bind-reject (`DO *` is `ER_NO_TABLES_USED`), but that is a resolver verdict
2483/// the parse layer does not model, not a syntax reject. The list is non-empty (a bare `DO` is
2484/// `ER_PARSE_ERROR`), which the parser enforces.
2485#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2486#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2487#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2488pub struct DoExpressionsStatement<X: Extension = NoExt> {
2489    /// The evaluated expression list in source order; always non-empty.
2490    pub items: ThinVec<SelectItem<X>>,
2491    /// Source location and node identity.
2492    pub meta: Meta,
2493}
2494
2495/// A MySQL `LOCK {TABLES | TABLE} <tbl> [[AS] <alias>] <lock-kind> [, ...]` explicit
2496/// table-locking statement (gated by
2497/// [`UtilitySyntax::lock_tables`](crate::dialect::UtilitySyntax)).
2498///
2499/// This is *one of two distinct behaviours a dialect can attach to the leading `LOCK`
2500/// keyword*, the split modelled the [`DoExpressionsStatement`] / [`DoStatement`] way (a
2501/// behaviour-named gate per reading, never both armed in one preset). MySQL's `LOCK TABLES`
2502/// names a per-table lock **kind** (`READ`, `READ LOCAL`, `WRITE`) on each table in a list —
2503/// the shape captured here. PostgreSQL's `LOCK TABLE`, by contrast, takes a single
2504/// statement-level lock **mode** clause (`IN ACCESS SHARE MODE`, `NOWAIT`, …) over a relation
2505/// list; that reading is not implemented, but when it is it takes its own node behind its own
2506/// `LOCK`-keyword gate, so the two never collide. The `MySql`/`Lenient` presets arm
2507/// [`lock_tables`](crate::dialect::UtilitySyntax::lock_tables); every other preset leaves the
2508/// leading `LOCK` keyword undispatched (an unknown statement).
2509///
2510/// The grammar is `LOCK table_or_tables table_lock_list` (mysql `sql_yacc.yy` `lock` /
2511/// `table_lock_list`), so the `TABLES` (plural) and `TABLE` (singular) spellings are
2512/// interchangeable and preserved on [`plural`](Self::plural) to round-trip. Each
2513/// [`TableLock`] carries a mandatory lock kind — a bare `LOCK TABLES t1` with no kind is
2514/// `ER_PARSE_ERROR` (engine-measured on mysql:8.4.10), which the parser enforces. The list is
2515/// non-empty. Non-generic, like [`UseStatement`]: a table lock carries no expressions or
2516/// extension nodes.
2517#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2518#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2519#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2520pub struct LockTablesStatement {
2521    /// The keyword spelling: `true` for `LOCK TABLES` (plural), `false` for `LOCK TABLE`
2522    /// (singular). Both are grammar-equal; preserved so the exact spelling round-trips.
2523    pub plural: bool,
2524    /// The locked tables in source order; always non-empty.
2525    pub tables: ThinVec<TableLock>,
2526    /// Source location and node identity.
2527    pub meta: Meta,
2528}
2529
2530/// One entry of a MySQL [`LockTablesStatement`] table list: a table name, an optional alias,
2531/// and its mandatory [`TableLockKind`] (`table_ident opt_table_alias lock_option` in mysql
2532/// `sql_yacc.yy` `table_lock`). The alias is a single identifier (`opt_as ident`); whether it
2533/// was written with the `AS` keyword is not modelled because MySQL discards it, so the
2534/// renderer emits the canonical `AS`-less spelling.
2535#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2536#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2537#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2538pub struct TableLock {
2539    /// The table being locked.
2540    pub name: ObjectName,
2541    /// The optional table alias (`[AS] <alias>`).
2542    pub alias: Option<Ident>,
2543    /// The lock kind acquired on the table (mandatory).
2544    pub kind: TableLockKind,
2545    /// Source location and node identity.
2546    pub meta: Meta,
2547}
2548
2549/// The lock kind a MySQL [`TableLock`] acquires (`lock_option` in mysql `sql_yacc.yy`). Only
2550/// these three spellings are grammar-valid on mysql:8.4.10 — the historical (pre-8.0)
2551/// `LOW_PRIORITY WRITE` modifier is `ER_PARSE_ERROR` there (engine-measured), so it is
2552/// deliberately not a variant.
2553#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2554#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2555#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2556pub enum TableLockKind {
2557    /// `READ` — a shared read lock.
2558    Read,
2559    /// `READ LOCAL` — a shared read lock permitting concurrent non-conflicting inserts.
2560    ReadLocal,
2561    /// `WRITE` — an exclusive write lock.
2562    Write,
2563}
2564
2565/// A MySQL `UNLOCK {TABLES | TABLE}` statement releasing all table locks held by the session
2566/// (gated by [`UtilitySyntax::lock_tables`](crate::dialect::UtilitySyntax), the release
2567/// counterpart of [`LockTablesStatement`]).
2568///
2569/// Carries no table list — MySQL's `UNLOCK` releases everything the session holds — only the
2570/// `TABLES`/`TABLE` spelling, preserved on [`plural`](Self::plural) to round-trip
2571/// (`unlock: UNLOCK_SYM table_or_tables` in mysql `sql_yacc.yy`).
2572#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2573#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2574#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2575pub struct UnlockTablesStatement {
2576    /// The keyword spelling: `true` for `UNLOCK TABLES` (plural), `false` for `UNLOCK TABLE`
2577    /// (singular). Both are grammar-equal; preserved so the exact spelling round-trips.
2578    pub plural: bool,
2579    /// Source location and node identity.
2580    pub meta: Meta,
2581}
2582
2583/// A MySQL instance-wide backup lock statement — `LOCK INSTANCE FOR BACKUP` (acquire) or
2584/// `UNLOCK INSTANCE` (release) — gated by
2585/// [`UtilitySyntax::lock_instance`](crate::dialect::UtilitySyntax).
2586///
2587/// One node for the pair, distinguished by [`acquire`](Self::acquire), because neither side
2588/// carries any other payload (mysql `sql_yacc.yy` `lock`/`unlock`: both alternatives build a
2589/// bare `Sql_cmd_{lock,unlock}_instance`): a two-struct split would add an empty node for the
2590/// release half. Distinct from [`LockTablesStatement`]/[`UnlockTablesStatement`], which take a
2591/// per-table lock-kind list — the instance lock is a single server-wide DDL-blocking lock
2592/// with fixed spelling on both sides, so there is nothing else to model. Both spellings are
2593/// grammar-positive on mysql:8.4.10 (`ER_UNSUPPORTED_PS` under the PREPARE oracle — parsed,
2594/// then declined by the PREPARE protocol, never `ER_PARSE_ERROR`).
2595#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2596#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2597#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2598pub struct InstanceLockStatement {
2599    /// `true` for `LOCK INSTANCE FOR BACKUP` (acquire), `false` for `UNLOCK INSTANCE`
2600    /// (release).
2601    pub acquire: bool,
2602    /// Source location and node identity.
2603    pub meta: Meta,
2604}
2605
2606/// A MySQL `LOAD {DATA | XML} … INFILE … INTO TABLE …` bulk-import statement (gated by
2607/// [`UtilitySyntax::load_data`](crate::dialect::UtilitySyntax)).
2608///
2609/// One node covers both the `DATA` (delimited text) and `XML` readings, distinguished by
2610/// [`format`](Self::format): the mysql `sql_yacc.yy` `load_stmt` rule is a *single*
2611/// `data_or_xml` production whose whole clause train is grammatically shared, so at the parse
2612/// layer the two forms differ only in the `DATA`/`XML` keyword. The clauses MySQL restricts to
2613/// one reading (`FIELDS`/`LINES` are meaningless under `XML`, `ROWS IDENTIFIED BY` under
2614/// `DATA`) are *semantic* restrictions the server enforces only after it has parsed the whole
2615/// statement and resolved the table — every clause parses under either format (engine-measured
2616/// on mysql:8.4.10: a `LOAD XML … FIELDS TERMINATED BY ','` reaches `ER_NO_SUCH_TABLE` 1146,
2617/// not `ER_PARSE_ERROR` 1064), so this node does not gate them by format and the parse layer
2618/// leaves that binding verdict untouched (the [`PrepareStatement`] "grammar accepts, bind
2619/// restricts" contract).
2620///
2621/// The clause train is strictly *order-sensitive* (engine-measured: any out-of-order clause is
2622/// `ER_PARSE_ERROR` 1064), so the node's field order is the grammar's canonical order and the
2623/// renderer emits that order. The optional clauses are `None`/empty when absent. Generic over
2624/// the extension `X` because the [`set`](Self::set) assignments carry full expressions.
2625///
2626/// Scope boundary: the MySQL 8.4 secondary-engine bulk-load extension clauses on the same
2627/// `load_stmt` rule — the `FROM` keyword, `URL`/`S3` source types, `COUNT n`,
2628/// `IN PRIMARY KEY ORDER`, `COMPRESSION`, `PARALLEL`, `MEMORY`, and `ALGORITHM = BULK` — are a
2629/// distinct cloud-bulk-load feature family and are intentionally not modelled here (they take
2630/// their own follow-up; see the ticket close note). This node is the classic documented
2631/// `LOAD DATA` / `LOAD XML` surface.
2632#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2633#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2634#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2635pub struct LoadDataStatement<X: Extension = NoExt> {
2636    /// Whether the source is delimited text (`LOAD DATA`) or XML (`LOAD XML`); see
2637    /// [`LoadDataFormat`].
2638    pub format: LoadDataFormat,
2639    /// The optional table-lock concurrency modifier (`LOW_PRIORITY` / `CONCURRENT`); see
2640    /// [`LoadDataConcurrency`]. `None` for the default lock (`load_data_lock` %empty).
2641    pub concurrency: Option<LoadDataConcurrency>,
2642    /// `true` when the `LOCAL` keyword was written (the file is read from the client rather
2643    /// than the server host).
2644    pub local: bool,
2645    /// The `INFILE '<path>'` source-file string literal (`TEXT_STRING_filesystem`); only the
2646    /// `INFILE` source type is modelled (see the type-level scope note).
2647    pub file: Literal,
2648    /// The optional duplicate-key handling (`REPLACE` / `IGNORE`); see [`LoadDataDuplicate`].
2649    /// `None` for the default (error on duplicate).
2650    pub on_duplicate: Option<LoadDataDuplicate>,
2651    /// The `INTO TABLE <name>` destination table.
2652    pub table: ObjectName,
2653    /// The `PARTITION (<name> [, ...])` partition list; empty when the clause is absent. The
2654    /// list is never a written empty `()` — MySQL requires at least one partition name.
2655    pub partitions: ThinVec<Ident>,
2656    /// The `CHARACTER SET <name>` charset override (`opt_load_data_charset`); `None` when
2657    /// absent. Held as a single [`Ident`] (a charset name, not a dotted [`ObjectName`]).
2658    pub charset: Option<Ident>,
2659    /// The `ROWS IDENTIFIED BY '<tag>'` row element tag (`opt_xml_rows_identified_by`); `None`
2660    /// when absent. Grammar-shared by both formats (see the type-level note) though only
2661    /// meaningful under `XML`.
2662    pub rows_identified_by: Option<Literal>,
2663    /// The `{FIELDS | COLUMNS} …` field-format clause; `None` when absent. See
2664    /// [`LoadDataFields`].
2665    pub fields: Option<LoadDataFields>,
2666    /// The `LINES …` line-format clause; `None` when absent. See [`LoadDataLines`].
2667    pub lines: Option<LoadDataLines>,
2668    /// The `IGNORE <n> {LINES | ROWS}` header-skip clause; `None` when absent. See
2669    /// [`LoadDataIgnoreRows`].
2670    pub ignore_rows: Option<LoadDataIgnoreRows>,
2671    /// The parenthesized `(col_or_var [, ...])` target list; empty when absent (or a written
2672    /// empty `()`, which MySQL folds to absent — `'(' ')'` yields the same nullptr as an
2673    /// omitted list). See [`LoadDataFieldOrVar`].
2674    pub columns: ThinVec<LoadDataFieldOrVar>,
2675    /// The `SET col = {expr | DEFAULT} [, ...]` post-load assignments; empty when absent.
2676    /// Reuses [`UpdateAssignment`] exactly as `INSERT … SET` does (mysql `load_data_set_elem`
2677    /// is `simple_ident_nospvar equal expr_or_default`, the single-column-assignment shape); a
2678    /// tuple assignment is not grammar-valid here, and the fitted `MySql` preset never emits
2679    /// one (its `multi_column_assignment` gate is off).
2680    pub set: ThinVec<UpdateAssignment<X>>,
2681    /// Source location and node identity.
2682    pub meta: Meta,
2683}
2684
2685/// Whether a [`LoadDataStatement`] reads delimited text (`LOAD DATA`) or XML (`LOAD XML`) —
2686/// the mysql `sql_yacc.yy` `data_or_xml` production.
2687#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2688#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2689#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2690pub enum LoadDataFormat {
2691    /// `LOAD DATA` — a delimited-text file (`FILETYPE_CSV`).
2692    Data,
2693    /// `LOAD XML` — an XML file (`FILETYPE_XML`).
2694    Xml,
2695}
2696
2697/// The optional table-lock concurrency modifier of a [`LoadDataStatement`] (mysql
2698/// `sql_yacc.yy` `load_data_lock`). The default (no keyword) is a plain write lock and is
2699/// modelled as `None` on the statement, so this enum carries only the two written spellings.
2700#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2701#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2702#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2703pub enum LoadDataConcurrency {
2704    /// `LOW_PRIORITY` — defer the load until no clients are reading the table.
2705    LowPriority,
2706    /// `CONCURRENT` — allow other clients to read the table during the load.
2707    Concurrent,
2708}
2709
2710/// The optional duplicate-key handling of a [`LoadDataStatement`] (mysql `sql_yacc.yy`
2711/// `opt_duplicate` / `duplicate`). The default (no keyword) errors on a duplicate and is
2712/// modelled as `None`, so this enum carries only the two written spellings. `REPLACE` and
2713/// `IGNORE` are mutually exclusive — writing both is `ER_PARSE_ERROR` (engine-measured).
2714#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2715#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2716#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2717pub enum LoadDataDuplicate {
2718    /// `REPLACE` — replace existing rows that collide on a unique key.
2719    Replace,
2720    /// `IGNORE` — skip input rows that collide on a unique key.
2721    Ignore,
2722}
2723
2724/// The `{FIELDS | COLUMNS} …` field-format clause of a [`LoadDataStatement`] (mysql
2725/// `sql_yacc.yy` `opt_field_term` / `field_term_list`).
2726///
2727/// The `FIELDS` and `COLUMNS` keywords are interchangeable synonyms; the written spelling
2728/// rides [`spelling`](Self::spelling) so it round-trips. At least one of the three sub-clauses
2729/// is present — a bare `FIELDS` with no sub-clause is `ER_PARSE_ERROR` (engine-measured), which
2730/// the parser enforces. Each sub-clause may appear in any order and the parser folds it onto
2731/// the matching field (a repeat is last-wins, mirroring the grammar's `merge_field_separators`);
2732/// the renderer emits the canonical `TERMINATED` / `[OPTIONALLY] ENCLOSED` / `ESCAPED` order.
2733#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2734#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2735#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2736pub struct LoadDataFields {
2737    /// The interchangeable keyword spelling (`FIELDS` vs `COLUMNS`); see
2738    /// [`LoadFieldsSpelling`].
2739    pub spelling: LoadFieldsSpelling,
2740    /// `TERMINATED BY '<string>'` — the field separator; `None` when absent.
2741    pub terminated_by: Option<Literal>,
2742    /// `[OPTIONALLY] ENCLOSED BY '<char>'` — the field quoting; `None` when absent. See
2743    /// [`LoadDataEnclosed`] (the `OPTIONALLY` modifier rides it, so an `OPTIONALLY` without an
2744    /// `ENCLOSED BY` is unrepresentable).
2745    pub enclosed_by: Option<LoadDataEnclosed>,
2746    /// `ESCAPED BY '<char>'` — the escape character; `None` when absent.
2747    pub escaped_by: Option<Literal>,
2748    /// Source location and node identity.
2749    pub meta: Meta,
2750}
2751
2752/// The interchangeable keyword spelling of a [`LoadDataFields`] clause (mysql `sql_yacc.yy`:
2753/// `opt_field_term` spells the same clause `COLUMNS`, while the documented form is `FIELDS`).
2754#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2755#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2756#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2757pub enum LoadFieldsSpelling {
2758    /// The `FIELDS` spelling (the documented keyword).
2759    Fields,
2760    /// The `COLUMNS` spelling (the grammar synonym).
2761    Columns,
2762}
2763
2764/// The `[OPTIONALLY] ENCLOSED BY '<char>'` sub-clause of a [`LoadDataFields`] clause (mysql
2765/// `sql_yacc.yy` `field_term`: the `ENCLOSED BY` and `OPTIONALLY ENCLOSED BY` alternatives).
2766/// Bundling the `OPTIONALLY` modifier with its value makes an `OPTIONALLY` with no `ENCLOSED
2767/// BY` unrepresentable.
2768#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2769#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2770#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2771pub struct LoadDataEnclosed {
2772    /// `true` when the `OPTIONALLY` modifier was written (`OPTIONALLY ENCLOSED BY`).
2773    pub optionally: bool,
2774    /// The enclosing-character string literal.
2775    pub value: Literal,
2776    /// Source location and node identity.
2777    pub meta: Meta,
2778}
2779
2780/// The `LINES …` line-format clause of a [`LoadDataStatement`] (mysql `sql_yacc.yy`
2781/// `opt_line_term` / `line_term_list`).
2782///
2783/// At least one of the two sub-clauses is present — a bare `LINES` with no sub-clause is
2784/// `ER_PARSE_ERROR` (engine-measured), which the parser enforces. Each sub-clause may appear in
2785/// any order (a repeat is last-wins, mirroring `merge_line_separators`); the renderer emits the
2786/// canonical `STARTING` / `TERMINATED` order.
2787#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2788#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2789#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2790pub struct LoadDataLines {
2791    /// `STARTING BY '<string>'` — the common line prefix to strip; `None` when absent.
2792    pub starting_by: Option<Literal>,
2793    /// `TERMINATED BY '<string>'` — the line separator; `None` when absent.
2794    pub terminated_by: Option<Literal>,
2795    /// Source location and node identity.
2796    pub meta: Meta,
2797}
2798
2799/// The `IGNORE <n> {LINES | ROWS}` header-skip clause of a [`LoadDataStatement`] (mysql
2800/// `sql_yacc.yy` `opt_ignore_lines` / `lines_or_rows`). The `LINES` and `ROWS` keywords are
2801/// interchangeable; the written spelling rides [`unit`](Self::unit) so it round-trips.
2802#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2803#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2804#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2805pub struct LoadDataIgnoreRows {
2806    /// The number of leading rows to skip (a `NUM` token), kept as an unsigned-integer
2807    /// [`Literal`] so the exact spelling round-trips.
2808    pub count: Literal,
2809    /// The interchangeable unit keyword (`LINES` vs `ROWS`); see [`LoadDataIgnoreUnit`].
2810    pub unit: LoadDataIgnoreUnit,
2811    /// Source location and node identity.
2812    pub meta: Meta,
2813}
2814
2815/// The interchangeable unit keyword of a [`LoadDataIgnoreRows`] clause (mysql `sql_yacc.yy`
2816/// `lines_or_rows`).
2817#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2818#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2819#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2820pub enum LoadDataIgnoreUnit {
2821    /// The `LINES` spelling.
2822    Lines,
2823    /// The `ROWS` spelling.
2824    Rows,
2825}
2826
2827/// One entry of a [`LoadDataStatement`] target list (mysql `sql_yacc.yy` `field_or_var`): a
2828/// destination column name, or a user variable (`@name`) that captures the raw field value for
2829/// use in the `SET` clause. A spanned enum so each spelling round-trips its own form and a
2830/// column is never confused with a variable.
2831#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2832#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2833#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2834pub enum LoadDataFieldOrVar {
2835    /// A destination column (`simple_ident_nospvar`).
2836    Column {
2837        /// The column name.
2838        name: Ident,
2839        /// Source location and node identity.
2840        meta: Meta,
2841    },
2842    /// A user variable (`@name`) capturing the field's raw value; the name is held without the
2843    /// `@` sigil, its quote style preserved for round-trip.
2844    Variable {
2845        /// The user-variable name (without the `@` sigil).
2846        name: Ident,
2847        /// Source location and node identity.
2848        meta: Meta,
2849    },
2850}
2851
2852/// A typed `SHOW TABLES` utility statement (MySQL/DuckDB; gated by
2853/// [`ShowSyntax::show_tables`](crate::dialect::UtilitySyntax)).
2854///
2855/// Distinct from the generic session `SHOW <var>`
2856/// ([`SessionStatement::Show`](crate::ast::SessionStatement)), which reads one
2857/// configuration parameter, and from the parenthesized `(SHOW <name>)` table source
2858/// ([`TableFactor::ShowRef`](crate::ast::TableFactor)), which only appears inside a
2859/// `FROM (…)` and produces a relation. `SHOW TABLES` is a top-level catalogue-listing
2860/// *statement*, so it is its own node — the MECE split spelled out on
2861/// [`show_tables`](crate::dialect::ShowSyntax::show_tables).
2862///
2863/// This is the opener of the typed-`SHOW` family (`SHOW COLUMNS`, `SHOW CREATE TABLE`,
2864/// `SHOW FUNCTIONS` join it): the listed thing rides the [`ShowTarget`] axis. That axis
2865/// is an enum with *per-variant* fields — the [`SessionStatement`](crate::ast::SessionStatement)
2866/// / [`ShowRefTarget`](crate::ast::ShowRefTarget) idiom, **not** a flat `kind` tag like
2867/// [`ApplyKind`](crate::ast::ApplyKind): the `SHOW` subforms carry genuinely different
2868/// payloads (`TABLES` takes a `FROM`/filter, `CREATE TABLE` a table name, `FUNCTIONS` a
2869/// scope keyword, schema qualifier, and name-or-regex filter), so a shared tag would
2870/// force an option-soup lowest common denominator. A sibling is then a new [`ShowTarget`]
2871/// variant plus its own gate, leaving the existing variants untouched.
2872#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2873#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2874#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2875pub struct ShowStatement<X: Extension = NoExt> {
2876    /// Object targeted by this syntax.
2877    pub target: ShowTarget<X>,
2878    /// Source location and node identity.
2879    pub meta: Meta,
2880}
2881
2882/// What a [`ShowStatement`] lists — the extensible axis of the typed-`SHOW` family.
2883///
2884/// The cross-dialect subforms [`Tables`](Self::Tables), [`Columns`](Self::Columns),
2885/// [`Functions`](Self::Functions), and [`RoutineStatus`](Self::RoutineStatus) each carry a
2886/// genuinely distinct payload (see [`ShowStatement`] for why this is a per-variant enum
2887/// rather than a flat `kind` tag). The MySQL server-administration / catalogue family folds
2888/// its ~40 near-identical sub-commands onto data axes instead: [`Listing`](Self::Listing)
2889/// and [`Bare`](Self::Bare) carry a sub-command discriminator, [`Create`](Self::Create) a
2890/// [`ShowCreateKind`], and [`Index`](Self::Index), [`Engine`](Self::Engine),
2891/// [`ReplicaStatus`](Self::ReplicaStatus), [`Diagnostics`](Self::Diagnostics), and
2892/// [`RoutineCode`](Self::RoutineCode) the handful with their own operands. The account /
2893/// diagnostics remainder rides its own operand-bearing variants: [`Grants`](Self::Grants) and
2894/// [`CreateUser`](Self::CreateUser) (the shared [`AccountName`] `user` grammar),
2895/// [`Profile`](Self::Profile) (a resource-type list plus `FOR QUERY` / `LIMIT`), and
2896/// [`LogEvents`](Self::LogEvents) (the `BINLOG` / `RELAYLOG` event dump).
2897#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2898#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2899#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2900pub enum ShowTarget<X: Extension = NoExt> {
2901    /// `SHOW [EXTENDED] [FULL] [ALL] TABLES [{FROM | IN} <db>] [LIKE '<pat>' | WHERE <expr>]`.
2902    ///
2903    /// The leading modifiers are dialect-split unions: `EXTENDED`/`FULL` are MySQL's
2904    /// (`SHOW FULL TABLES` adds a table-type column), `ALL` is DuckDB's (`SHOW ALL TABLES`
2905    /// lists every attached database's tables). Each is an independent optional keyword,
2906    /// so they ride separate bools rather than one axis; no shipped dialect mixes them.
2907    /// The `{FROM | IN} <db>` qualifier and the `LIKE`/`WHERE` filter are MySQL's (DuckDB
2908    /// accepts only `FROM <schema>`); the single [`show_tables`](crate::dialect::ShowSyntax::show_tables)
2909    /// gate accepts the union permissively, the DESCRIBE/PRAGMA single-flag-utility precedent.
2910    Tables {
2911        /// MySQL `EXTENDED` — also list hidden tables left by a failed `ALTER`.
2912        extended: bool,
2913        /// MySQL `FULL` — add the `Table_type` column.
2914        full: bool,
2915        /// DuckDB `ALL` — list tables across every attached database.
2916        all: bool,
2917        /// The optional `{FROM | IN} <db>` schema qualifier.
2918        from: Option<ShowFrom>,
2919        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
2920        filter: Option<ShowFilter<X>>,
2921        /// Source location and node identity.
2922        meta: Meta,
2923    },
2924    /// `SHOW [EXTENDED] [FULL] {COLUMNS | FIELDS} {FROM | IN} <tbl> [{FROM | IN} <db>]
2925    /// [LIKE '<pat>' | WHERE <expr>]` (MySQL; gated by
2926    /// [`show_columns`](crate::dialect::ShowSyntax::show_columns)).
2927    ///
2928    /// Unlike [`Tables`](Self::Tables), the `{FROM | IN}` qualifier is *mandatory* — it
2929    /// names the table whose columns are listed — and the grammar has a *second*, optional
2930    /// `{FROM | IN} <db>` naming the database (equivalent to writing `db.tbl` in the
2931    /// [`table`](Self::Columns::table) slot). There is no `ALL` modifier here; DuckDB has
2932    /// no `SHOW COLUMNS` grammar at all (engine-probed reject on 1.5.4), so this is a
2933    /// MySQL-only subform. `FIELDS` is an exact synonym of `COLUMNS`; the written spelling
2934    /// rides the [`ShowColumnsSpelling`] surface tag so the statement round-trips.
2935    Columns {
2936        /// MySQL `EXTENDED` — also list hidden columns MySQL maintains internally.
2937        extended: bool,
2938        /// MySQL `FULL` — add the collation, privileges, and comment columns.
2939        full: bool,
2940        /// Which keyword named the listing: `COLUMNS` or its `FIELDS` synonym.
2941        spelling: ShowColumnsSpelling,
2942        /// The mandatory `{FROM | IN} <tbl>` qualifier naming the target table.
2943        table: ShowFrom,
2944        /// The optional second `{FROM | IN} <db>` qualifier naming the database.
2945        database: Option<ShowFrom>,
2946        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
2947        filter: Option<ShowFilter<X>>,
2948        /// Source location and node identity.
2949        meta: Meta,
2950    },
2951    /// `SHOW CREATE {TABLE | VIEW | DATABASE [IF NOT EXISTS] | EVENT | PROCEDURE | FUNCTION
2952    /// | TRIGGER} <name>` — the DDL that would recreate the named object (MySQL). The
2953    /// `TABLE` spelling is gated by
2954    /// [`show_create_table`](crate::dialect::ShowSyntax::show_create_table); every other
2955    /// object kind is gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin).
2956    ///
2957    /// The object kind rides the [`kind`](Self::Create::kind) axis as DATA rather than
2958    /// forcing one bespoke variant per keyword — the `SHOW CREATE …` subforms are
2959    /// structurally identical (two fixed keywords plus one schema-qualifiable name), so a
2960    /// per-keyword variant would be pure duplication. `IF NOT EXISTS` is a `DATABASE`-only
2961    /// guard ([`if_not_exists`](Self::Create::if_not_exists), always `false` for the other
2962    /// kinds). `SHOW CREATE USER` is deliberately excluded: its operand is a MySQL user
2963    /// specification (`'user'@'host'`), not an [`ObjectName`], so it rides its own
2964    /// [`CreateUser`](Self::CreateUser) variant over the shared [`AccountName`] grammar.
2965    Create {
2966        /// Which object kind followed `CREATE`; see [`ShowCreateKind`].
2967        kind: ShowCreateKind,
2968        /// The target object name (schema-qualifiable).
2969        name: ObjectName,
2970        /// The `DATABASE`-only `IF NOT EXISTS` guard; `false` for every other kind.
2971        if_not_exists: bool,
2972        /// Source location and node identity.
2973        meta: Meta,
2974    },
2975    /// `SHOW [{USER | SYSTEM | ALL}] FUNCTIONS [{FROM | IN} <schema>]
2976    /// [[LIKE] {<function_name> | '<regex>'}]` (Spark / Databricks; gated by
2977    /// [`show_functions`](crate::dialect::ShowSyntax::show_functions)).
2978    ///
2979    /// The only shipped engine with a bare `SHOW FUNCTIONS` listing is Spark/Databricks,
2980    /// and it carries the full grammar: an optional [`kind`](Self::Functions::kind) scope
2981    /// keyword (`USER`/`SYSTEM`/`ALL`) *before* `FUNCTIONS`, an optional `{FROM | IN}`
2982    /// schema qualifier, and an optional trailing name-or-regex narrowing whose `LIKE`
2983    /// keyword is itself optional. MySQL's `SHOW FUNCTION STATUS` is a *different*
2984    /// statement (a routine catalogue over `mysql.proc`, not a bare `SHOW FUNCTIONS`) —
2985    /// modelled by its own [`RoutineStatus`](Self::RoutineStatus) variant; DuckDB has no
2986    /// `SHOW FUNCTIONS` grammar (`SHOW <name>` there is a `DESCRIBE` alias — `SHOW
2987    /// functions` describes a table named `functions`, engine-probed on 1.5.4).
2988    Functions {
2989        /// The optional `USER` / `SYSTEM` / `ALL` scope keyword written before `FUNCTIONS`.
2990        kind: Option<ShowFunctionsScope>,
2991        /// The optional `{FROM | IN} <schema>` schema qualifier.
2992        from: Option<ShowFrom>,
2993        /// The optional trailing `[LIKE] {<function_name> | '<regex>'}` narrowing.
2994        filter: Option<ShowFunctionsFilter>,
2995        /// Source location and node identity.
2996        meta: Meta,
2997    },
2998    /// `SHOW {FUNCTION | PROCEDURE} STATUS [LIKE '<pat>' | WHERE <expr>]` — the stored-routine
2999    /// catalogue listing (MySQL; gated by
3000    /// [`show_routine_status`](crate::dialect::ShowSyntax::show_routine_status)).
3001    ///
3002    /// A *different* statement from the Spark/Databricks [`Functions`](Self::Functions)
3003    /// listing: different keywords (the singular `FUNCTION`/`PROCEDURE` plus a mandatory
3004    /// `STATUS`, not a bare plural `FUNCTIONS`) and a different payload (a row per stored
3005    /// routine from `information_schema.routines`, not the bare function names). It carries
3006    /// no scope keyword and no `{FROM | IN}` qualifier — `SHOW FUNCTION STATUS FROM db` is
3007    /// `ER_PARSE_ERROR` on mysql:8 (engine-probed) — only the optional `LIKE`/`WHERE`
3008    /// narrowing, which reuses the shared [`ShowFilter`] (MySQL's mutually-exclusive `LIKE
3009    /// '<pat>' | WHERE <expr>`, exactly as `SHOW TABLES`/`SHOW COLUMNS`). The `FUNCTION`
3010    /// vs `PROCEDURE` object keyword rides the [`ShowRoutineKind`] surface tag so the
3011    /// statement round-trips.
3012    RoutineStatus {
3013        /// Which stored-routine kind was named: `FUNCTION` or `PROCEDURE`.
3014        kind: ShowRoutineKind,
3015        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
3016        filter: Option<ShowFilter<X>>,
3017        /// Source location and node identity.
3018        meta: Meta,
3019    },
3020    /// A MySQL catalogue/server listing that admits the shared `[LIKE '<pat>' | WHERE
3021    /// <expr>]` tail — `SHOW DATABASES`, `SHOW EVENTS`, `SHOW [GLOBAL | SESSION] STATUS`,
3022    /// `SHOW TRIGGERS`, and their siblings (gated by
3023    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3024    ///
3025    /// The sub-command rides the [`kind`](Self::Listing::kind) axis as DATA: every member
3026    /// shares the trailing [`ShowFilter`], the members that accept an optional `{FROM | IN}
3027    /// <db>` qualifier share [`from`](Self::Listing::from), and each sub-command's own
3028    /// small scalar payload (a `GLOBAL`/`SESSION` scope, a `FULL` flag, a spelling bit)
3029    /// rides the [`ShowListing`] discriminator, so the many near-identical listings collapse
3030    /// into one node instead of one bespoke variant each. The single [`from`](Self::Listing::from)
3031    /// admits the qualifier permissively (`SHOW DATABASES`/`SHOW STATUS` take none — the
3032    /// parser leaves those `None`), the DESCRIBE/PRAGMA single-field precedent.
3033    Listing {
3034        /// Which listing was named, plus its sub-command-specific scalar payload; see
3035        /// [`ShowListing`].
3036        kind: ShowListing,
3037        /// The optional `{FROM | IN} <db>` schema qualifier (only `EVENTS`, `TABLE STATUS`,
3038        /// `OPEN TABLES`, and `TRIGGERS` accept one; `None` for every other member).
3039        from: Option<ShowFrom>,
3040        /// The optional trailing `LIKE '<pat>'` / `WHERE <expr>` narrowing.
3041        filter: Option<ShowFilter<X>>,
3042        /// Source location and node identity.
3043        meta: Meta,
3044    },
3045    /// A MySQL `SHOW` sub-command that takes no filter and no name operand — `SHOW PLUGINS`,
3046    /// `SHOW [STORAGE] ENGINES`, `SHOW PRIVILEGES`, `SHOW [FULL] PROCESSLIST`, `SHOW BINARY
3047    /// LOGS`, and their siblings (gated by
3048    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3049    ///
3050    /// The sub-command rides the [`kind`](Self::Bare::kind) axis as DATA; the only payloads
3051    /// any member carries are single leading-keyword flags (`STORAGE` before `ENGINES`,
3052    /// `FULL` before `PROCESSLIST`), folded into the [`ShowBare`] variant.
3053    Bare {
3054        /// Which bare sub-command was named; see [`ShowBare`].
3055        kind: ShowBare,
3056        /// Source location and node identity.
3057        meta: Meta,
3058    },
3059    /// `SHOW GRANTS [FOR <user> [USING <role> [, …]]]` — the privilege listing for an account
3060    /// (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3061    ///
3062    /// Bare `SHOW GRANTS` (the session's own privileges) leaves [`user`](Self::Grants::user)
3063    /// `None` and [`using_roles`](Self::Grants::using_roles) empty. `FOR <user>` names the
3064    /// account whose grants are shown; the optional `USING <role list>` (only valid after
3065    /// `FOR` — `SHOW GRANTS USING …` is `ER_PARSE_ERROR` on mysql:8.4.10) restricts the
3066    /// listing to the named active roles. Both the user and each role are the shared
3067    /// [`AccountName`] `user` grammar (a named `'u'@'host'` account or `CURRENT_USER [()]`),
3068    /// so this reuses the account-reference axis the DCL landings build. `using_roles` is
3069    /// non-empty only when `USING` was written, which the grammar allows only when
3070    /// [`user`](Self::Grants::user) is `Some`.
3071    Grants {
3072        /// The `FOR <user>` account, or `None` for bare `SHOW GRANTS`.
3073        user: Option<AccountName>,
3074        /// The `USING <role> [, …]` active-role restriction; empty when no `USING` was
3075        /// written (which the grammar permits only when [`user`](Self::Grants::user) is set).
3076        using_roles: ThinVec<AccountName>,
3077        /// Source location and node identity.
3078        meta: Meta,
3079    },
3080    /// `SHOW CREATE USER <user>` — the `CREATE USER` statement that would recreate an account
3081    /// (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3082    ///
3083    /// A sibling of [`Create`](Self::Create) held apart because its operand is the shared
3084    /// [`AccountName`] `user` specification (`'u'@'host'` or `CURRENT_USER [()]`), not an
3085    /// [`ObjectName`] — the exact reason the SHOW-family landing deferred it to the user-spec
3086    /// grammar (see [`ShowCreateKind`], where `USER` is absent).
3087    CreateUser {
3088        /// The account to recreate (the shared `user` grammar).
3089        user: AccountName,
3090        /// Source location and node identity.
3091        meta: Meta,
3092    },
3093    /// `SHOW PROFILE [<type> [, …]] [FOR QUERY <n>] [LIMIT …]` — the per-statement resource
3094    /// profile (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3095    ///
3096    /// The optional [`types`](Self::Profile::types) list selects which resource columns to
3097    /// report (MySQL's `profile_defs`; empty when none written — the server defaults to a
3098    /// standard set). `FOR QUERY <n>` ([`query`](Self::Profile::query)) profiles a specific
3099    /// entry from `SHOW PROFILES` rather than the last statement, and the shared
3100    /// [`ShowLimit`] tail narrows the row set. The three clauses are order-fixed:
3101    /// `SHOW PROFILE ALL FOR QUERY 1` parses but `SHOW PROFILE FOR QUERY 1 ALL` and
3102    /// `SHOW PROFILE LIMIT 5 FOR QUERY 1` are both `ER_PARSE_ERROR` on mysql:8.4.10.
3103    /// Distinct from the bare [`ShowBare::Profiles`] catalogue listing (`SHOW PROFILES`).
3104    Profile {
3105        /// The `profile_defs` resource-type list, in source order; empty when none written.
3106        types: ThinVec<ShowProfileType>,
3107        /// The `FOR QUERY <n>` query-id selector (an integer [`Literal`]); `None` when absent.
3108        query: Option<Literal>,
3109        /// The optional trailing `LIMIT …` narrowing.
3110        limit: Option<ShowLimit>,
3111        /// Source location and node identity.
3112        meta: Meta,
3113    },
3114    /// `SHOW {BINLOG | RELAYLOG} EVENTS [IN '<log>'] [FROM <pos>] [LIMIT …] [FOR CHANNEL
3115    /// '<channel>']` — the binary- / relay-log event dump (MySQL; gated by
3116    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3117    ///
3118    /// The two spellings share one payload — an optional `IN '<log>'` log-file name, an
3119    /// optional `FROM <pos>` start position, and the shared [`ShowLimit`] tail — so they ride
3120    /// this one variant with the spelling as DATA on [`relay`](Self::LogEvents::relay), the
3121    /// SHOW family's fold-near-identical-sub-commands precedent. The sole grammar difference
3122    /// is the trailing `FOR CHANNEL '<channel>'`: it is `RELAYLOG`-only, so
3123    /// [`channel`](Self::LogEvents::channel) is `Some` only when [`relay`](Self::LogEvents::relay)
3124    /// is `true` (`SHOW BINLOG EVENTS FOR CHANNEL …` is `ER_PARSE_ERROR` on mysql:8.4.10). The
3125    /// clause order is fixed: `SHOW BINLOG EVENTS FROM 4 IN '<log>'` is `ER_PARSE_ERROR` (the
3126    /// `IN` must precede the `FROM`).
3127    LogEvents {
3128        /// The log spelling: `false` for `BINLOG`, `true` for `RELAYLOG`.
3129        relay: bool,
3130        /// The `IN '<log>'` log-file name (a string [`Literal`]); `None` when absent.
3131        log_name: Option<Literal>,
3132        /// The `FROM <pos>` start position (an integer [`Literal`]); `None` when absent.
3133        position: Option<Literal>,
3134        /// The optional trailing `LIMIT …` narrowing.
3135        limit: Option<ShowLimit>,
3136        /// The `FOR CHANNEL '<channel>'` replication-channel qualifier (a string [`Literal`]);
3137        /// `Some` only for `RELAYLOG` (see [`relay`](Self::LogEvents::relay)).
3138        channel: Option<Literal>,
3139        /// Source location and node identity.
3140        meta: Meta,
3141    },
3142    /// `SHOW [EXTENDED] {INDEX | INDEXES | KEYS} {FROM | IN} <tbl> [{FROM | IN} <db>]
3143    /// [WHERE <expr>]` — the index-listing statement (MySQL; gated by
3144    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3145    ///
3146    /// Structurally the `{FROM | IN}`-qualified sibling of [`Columns`](Self::Columns): a
3147    /// mandatory table qualifier, an optional database qualifier, and a filter — but the
3148    /// filter here is `WHERE`-only (the MySQL grammar admits no `LIKE` on `SHOW INDEX`), so
3149    /// the parser only ever builds a [`ShowFilter::Where`]. `KEYS`/`INDEX`/`INDEXES` are
3150    /// exact synonyms whose written spelling rides the [`ShowIndexSpelling`] tag.
3151    Index {
3152        /// Which keyword named the listing: `INDEX`, `INDEXES`, or `KEYS`.
3153        spelling: ShowIndexSpelling,
3154        /// MySQL `EXTENDED` — also list hidden indexes MySQL maintains internally.
3155        extended: bool,
3156        /// The mandatory `{FROM | IN} <tbl>` qualifier naming the target table.
3157        table: ShowFrom,
3158        /// The optional second `{FROM | IN} <db>` qualifier naming the database.
3159        database: Option<ShowFrom>,
3160        /// The optional trailing `WHERE <expr>` narrowing (never `LIKE`).
3161        filter: Option<ShowFilter<X>>,
3162        /// Source location and node identity.
3163        meta: Meta,
3164    },
3165    /// `SHOW ENGINE {<name> | ALL} {STATUS | MUTEX | LOGS}` — a storage-engine diagnostic
3166    /// dump (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3167    ///
3168    /// The engine operand ([`engine`](Self::Engine::engine)) is a named engine, or `None`
3169    /// for the `ALL` wildcard; the requested artefact ([`artifact`](Self::Engine::artifact))
3170    /// is one of the three fixed report keywords. Distinct from the bare
3171    /// [`ShowBare::Engines`] catalogue listing, which takes no operand.
3172    Engine {
3173        /// The named storage engine, or `None` for the `ALL` wildcard.
3174        engine: Option<Ident>,
3175        /// Which per-engine report was requested; see [`ShowEngineArtifact`].
3176        artifact: ShowEngineArtifact,
3177        /// Source location and node identity.
3178        meta: Meta,
3179    },
3180    /// `SHOW REPLICA STATUS [FOR CHANNEL '<channel>']` — the replication-applier status
3181    /// (MySQL; gated by [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3182    ///
3183    /// The optional [`channel`](Self::ReplicaStatus::channel) names a replication channel
3184    /// (`FOR CHANNEL '<name>'`). MySQL 8.4 removed the deprecated `SHOW SLAVE STATUS`
3185    /// terminology, so there is no spelling axis here.
3186    ReplicaStatus {
3187        /// The optional `FOR CHANNEL '<channel>'` qualifier.
3188        channel: Option<Literal>,
3189        /// Source location and node identity.
3190        meta: Meta,
3191    },
3192    /// `SHOW {WARNINGS | ERRORS} [LIMIT [<offset>,] <row_count>]` and `SHOW COUNT(*)
3193    /// {WARNINGS | ERRORS}` — the diagnostics-area readouts (MySQL; gated by
3194    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3195    ///
3196    /// The `WARNINGS` vs `ERRORS` choice rides [`kind`](Self::Diagnostics::kind).
3197    /// [`count`](Self::Diagnostics::count) records the `COUNT(*)` cardinality form, which is
3198    /// mutually exclusive with a [`limit`](Self::Diagnostics::limit) in the grammar — the
3199    /// parser never sets both.
3200    Diagnostics {
3201        /// Which diagnostics list was named: `WARNINGS` or `ERRORS`.
3202        kind: ShowDiagnosticKind,
3203        /// Whether this is the `SHOW COUNT(*) …` cardinality form (no `LIMIT`).
3204        count: bool,
3205        /// The optional `LIMIT [<offset>,] <row_count>` narrowing (never set when
3206        /// [`count`](Self::Diagnostics::count) is `true`).
3207        limit: Option<ShowLimit>,
3208        /// Source location and node identity.
3209        meta: Meta,
3210    },
3211    /// `SHOW {PROCEDURE | FUNCTION} CODE <name>` — the compiled stored-routine instruction
3212    /// dump (MySQL debug builds; gated by
3213    /// [`show_admin`](crate::dialect::ShowSyntax::show_admin)).
3214    ///
3215    /// Shares the [`ShowRoutineKind`] object-keyword axis with
3216    /// [`RoutineStatus`](Self::RoutineStatus); the operand is the (schema-qualifiable)
3217    /// routine name.
3218    RoutineCode {
3219        /// Which stored-routine kind was named: `FUNCTION` or `PROCEDURE`.
3220        kind: ShowRoutineKind,
3221        /// The (optionally schema-qualified) routine name.
3222        name: ObjectName,
3223        /// Source location and node identity.
3224        meta: Meta,
3225    },
3226}
3227
3228/// Which stored-routine kind a [`ShowTarget::RoutineStatus`] listing named: `FUNCTION` or
3229/// `PROCEDURE` (MySQL `SHOW {FUNCTION | PROCEDURE} STATUS`).
3230///
3231/// A surface tag (no `meta`, like [`ShowColumnsSpelling`]): the keyword's span is subsumed
3232/// by the enclosing [`ShowStatement`]. Recorded only so the written object keyword
3233/// round-trips.
3234#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3235#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3236#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3237pub enum ShowRoutineKind {
3238    /// `FUNCTION` — list stored functions.
3239    Function,
3240    /// `PROCEDURE` — list stored procedures.
3241    Procedure,
3242}
3243
3244/// The optional scope keyword before `FUNCTIONS` in a [`ShowTarget::Functions`] listing:
3245/// `USER`, `SYSTEM`, or `ALL` (Spark / Databricks).
3246///
3247/// A surface tag (no `meta`, like [`ShowColumnsSpelling`]): the keyword's span is subsumed
3248/// by the enclosing [`ShowStatement`]. Recorded only so the written scope round-trips.
3249#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3250#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3251#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3252pub enum ShowFunctionsScope {
3253    /// `USER` — user-defined functions only.
3254    User,
3255    /// `SYSTEM` — system (built-in) functions only.
3256    System,
3257    /// `ALL` — both user and system functions.
3258    All,
3259}
3260
3261/// The optional trailing narrowing of a [`ShowTarget::Functions`] listing:
3262/// `[LIKE] {<function_name> | '<regex_pattern>'}` (Spark / Databricks).
3263///
3264/// A distinct type from [`ShowFilter`] because that models MySQL's mutually-exclusive
3265/// `LIKE '<pat>' | WHERE <expr>` (a mandatory keyword, no bare-name form, and a `WHERE`
3266/// predicate `SHOW FUNCTIONS` does not accept). Here the `LIKE` keyword is *optional* and
3267/// the operand is either a bare (optionally qualified) function name or a quoted regex
3268/// string; [`like`](Self::Name::like) records whether the keyword was written so the
3269/// statement round-trips.
3270#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3271#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3272#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3273pub enum ShowFunctionsFilter {
3274    /// `[LIKE] <function_name>` — a bare, optionally-qualified function name.
3275    Name {
3276        /// Whether the optional `LIKE` keyword preceded the name.
3277        like: bool,
3278        /// The (optionally qualified) function name to match.
3279        name: ObjectName,
3280        /// Source location and node identity.
3281        meta: Meta,
3282    },
3283    /// `[LIKE] '<regex_pattern>'` — a quoted regex-pattern string.
3284    Regex {
3285        /// Whether the optional `LIKE` keyword preceded the pattern.
3286        like: bool,
3287        /// The quoted regex-pattern string literal.
3288        pattern: Literal,
3289        /// Source location and node identity.
3290        meta: Meta,
3291    },
3292}
3293
3294/// Which keyword named a [`ShowTarget::Columns`] listing: `COLUMNS` or its exact `FIELDS`
3295/// synonym (MySQL).
3296///
3297/// A surface tag (no `meta`, like [`ShowFromKeyword`]): the keyword's span is subsumed by
3298/// the enclosing [`ShowStatement`]. Recorded only so the written spelling round-trips.
3299#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3300#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3301#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3302pub enum ShowColumnsSpelling {
3303    /// Source used the `COLUMNS` spelling.
3304    Columns,
3305    /// Source used the `FIELDS` spelling.
3306    Fields,
3307}
3308
3309/// A `{FROM | IN} <name>` object qualifier in the typed-`SHOW` family.
3310///
3311/// Used for the [`ShowTarget::Tables`] database qualifier and for both the mandatory
3312/// table and optional database qualifiers of [`ShowTarget::Columns`], so [`name`](Self::name)
3313/// is a generic [`ObjectName`] rather than a database- or table-specific field. MySQL
3314/// accepts either keyword interchangeably; DuckDB accepts only `FROM`. The
3315/// [`keyword`](Self::keyword) surface tag records which was written so the statement
3316/// round-trips.
3317#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3318#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3319#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3320pub struct ShowFrom {
3321    /// Whether `FROM` or `IN` introduced the database; see [`ShowFromKeyword`].
3322    pub keyword: ShowFromKeyword,
3323    /// Name referenced by this syntax.
3324    pub name: ObjectName,
3325    /// Source location and node identity.
3326    pub meta: Meta,
3327}
3328
3329/// Which keyword introduced a [`ShowFrom`]: `FROM` or its `IN` synonym.
3330///
3331/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3332/// [`ShowFrom`], as [`ExplainKeyword`] rides its parent's span.
3333#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3334#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3335#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3336pub enum ShowFromKeyword {
3337    /// `SHOW … FROM <db>`.
3338    From,
3339    /// `SHOW … IN <db>` — a synonym for `FROM`.
3340    In,
3341}
3342
3343/// The optional trailing narrowing of a [`ShowTarget::Tables`] or [`ShowTarget::Columns`]:
3344/// a `LIKE` name pattern or a `WHERE` predicate (MySQL).
3345///
3346/// The two are mutually exclusive in the grammar. `LIKE` takes a string pattern
3347/// ([`Literal`]); `WHERE` takes a general predicate ([`Expr`]) over the result columns,
3348/// which is why this enum — and thus [`ShowTarget`]/[`ShowStatement`] — is generic over
3349/// the extension `X`.
3350#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3351#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3352#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3353pub enum ShowFilter<X: Extension = NoExt> {
3354    /// A `LIKE '<pattern>'` name filter.
3355    Like {
3356        /// Pattern matched by this syntax.
3357        pattern: Literal,
3358        /// Source location and node identity.
3359        meta: Meta,
3360    },
3361    /// A `WHERE <predicate>` filter (MySQL).
3362    Where {
3363        /// Predicate that controls this clause.
3364        predicate: Expr<X>,
3365        /// Source location and node identity.
3366        meta: Meta,
3367    },
3368}
3369
3370/// Which object kind a [`ShowTarget::Create`] recreates: `TABLE`, `VIEW`, `DATABASE`,
3371/// `EVENT`, `PROCEDURE`, `FUNCTION`, or `TRIGGER` (MySQL `SHOW CREATE <kind> <name>`).
3372///
3373/// A surface tag (no `meta`, like [`ShowRoutineKind`]): the keyword's span is subsumed by
3374/// the enclosing [`ShowStatement`]. `USER` is absent — its operand is a user specification,
3375/// not an [`ObjectName`], so `SHOW CREATE USER` rides its own
3376/// [`ShowTarget::CreateUser`] variant over the shared [`AccountName`] grammar.
3377#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3378#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3379#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3380pub enum ShowCreateKind {
3381    /// `SHOW CREATE TABLE <tbl>`.
3382    Table,
3383    /// `SHOW CREATE VIEW <view>`.
3384    View,
3385    /// `SHOW CREATE {DATABASE | SCHEMA} [IF NOT EXISTS] <db>`.
3386    Database {
3387        /// Whether the `SCHEMA` synonym spelling was written in place of `DATABASE`.
3388        schema: bool,
3389    },
3390    /// `SHOW CREATE EVENT <event>`.
3391    Event,
3392    /// `SHOW CREATE PROCEDURE <proc>`.
3393    Procedure,
3394    /// `SHOW CREATE FUNCTION <func>`.
3395    Function,
3396    /// `SHOW CREATE TRIGGER <trigger>`.
3397    Trigger,
3398}
3399
3400/// Which catalogue/server listing a [`ShowTarget::Listing`] named, plus that sub-command's
3401/// own small *scalar* payload (MySQL). Every member admits the shared `[LIKE | WHERE]` tail
3402/// and the optional `{FROM | IN} <db>` qualifier carried by the enclosing
3403/// [`ShowTarget::Listing`].
3404///
3405/// A surface discriminator (no `meta`, `Copy` like [`ShowScope`]): the `{FROM | IN}`
3406/// qualifier — the only spanned payload — lives on the enclosing variant, leaving this enum
3407/// a pure keyword tag plus scalar flags whose spans are subsumed by the [`ShowStatement`].
3408#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3409#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3410#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3411pub enum ShowListing {
3412    /// `SHOW {DATABASES | SCHEMAS}` (takes no `{FROM | IN}` qualifier).
3413    Databases {
3414        /// Whether the `SCHEMAS` synonym spelling was written in place of `DATABASES`.
3415        schemas: bool,
3416    },
3417    /// `SHOW {CHARACTER SET | CHARSET}` (takes no `{FROM | IN}` qualifier).
3418    CharacterSet {
3419        /// Whether the one-word `CHARSET` spelling was written in place of `CHARACTER SET`.
3420        charset: bool,
3421    },
3422    /// `SHOW COLLATION` (takes no `{FROM | IN}` qualifier).
3423    Collation,
3424    /// `SHOW [GLOBAL | SESSION | LOCAL] STATUS` (takes no `{FROM | IN}` qualifier).
3425    Status {
3426        /// The optional `GLOBAL`/`SESSION`/`LOCAL` scope; see [`ShowScope`].
3427        scope: Option<ShowScope>,
3428    },
3429    /// `SHOW [GLOBAL | SESSION | LOCAL] VARIABLES` (takes no `{FROM | IN}` qualifier).
3430    Variables {
3431        /// The optional `GLOBAL`/`SESSION`/`LOCAL` scope; see [`ShowScope`].
3432        scope: Option<ShowScope>,
3433    },
3434    /// `SHOW EVENTS [{FROM | IN} <db>]`.
3435    Events,
3436    /// `SHOW TABLE STATUS [{FROM | IN} <db>]`.
3437    TableStatus,
3438    /// `SHOW OPEN TABLES [{FROM | IN} <db>]`.
3439    OpenTables,
3440    /// `SHOW [FULL] TRIGGERS [{FROM | IN} <db>]`.
3441    Triggers {
3442        /// MySQL `FULL` — add the `sql_mode`, definer, and character-set columns.
3443        full: bool,
3444    },
3445}
3446
3447/// The optional `GLOBAL`/`SESSION`/`LOCAL` scope keyword on `SHOW … STATUS` / `SHOW …
3448/// VARIABLES` (MySQL `opt_var_type`).
3449///
3450/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3451/// [`ShowStatement`]. `LOCAL` is an exact synonym of `SESSION` in MySQL, kept distinct here
3452/// only so the written spelling round-trips.
3453#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3454#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3455#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3456pub enum ShowScope {
3457    /// `GLOBAL` — the server-wide value.
3458    Global,
3459    /// `SESSION` — the current session's value.
3460    Session,
3461    /// `LOCAL` — a synonym for `SESSION`.
3462    Local,
3463}
3464
3465/// A MySQL `SHOW` sub-command taking no filter and no name operand; the discriminant of
3466/// [`ShowTarget::Bare`]. The only payloads are single leading-keyword flags.
3467///
3468/// A surface tag (no `meta`): each keyword's span is subsumed by the enclosing
3469/// [`ShowStatement`].
3470#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3471#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3472#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3473pub enum ShowBare {
3474    /// `SHOW PLUGINS`.
3475    Plugins,
3476    /// `SHOW [STORAGE] ENGINES`.
3477    Engines {
3478        /// Whether the optional `STORAGE` keyword preceded `ENGINES`.
3479        storage: bool,
3480    },
3481    /// `SHOW PRIVILEGES`.
3482    Privileges,
3483    /// `SHOW PROFILES`.
3484    Profiles,
3485    /// `SHOW [FULL] PROCESSLIST`.
3486    Processlist {
3487        /// MySQL `FULL` — show the full `Info` column instead of truncating it.
3488        full: bool,
3489    },
3490    /// `SHOW BINARY LOGS`.
3491    BinaryLogs,
3492    /// `SHOW REPLICAS`.
3493    Replicas,
3494    /// `SHOW BINARY LOG STATUS`.
3495    BinaryLogStatus,
3496}
3497
3498/// Which keyword named a [`ShowTarget::Index`] listing: `INDEX`, `INDEXES`, or `KEYS`
3499/// (MySQL `keys_or_index`).
3500///
3501/// A surface tag (no `meta`, like [`ShowColumnsSpelling`]): the three are exact synonyms,
3502/// recorded only so the written spelling round-trips.
3503#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3504#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3505#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3506pub enum ShowIndexSpelling {
3507    /// Source used the singular `INDEX` spelling.
3508    Index,
3509    /// Source used the plural `INDEXES` spelling.
3510    Indexes,
3511    /// Source used the `KEYS` spelling.
3512    Keys,
3513}
3514
3515/// Which per-engine report a [`ShowTarget::Engine`] dump requested: `STATUS`, `MUTEX`, or
3516/// `LOGS` (MySQL).
3517///
3518/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3519/// [`ShowStatement`].
3520#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3521#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3522#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3523pub enum ShowEngineArtifact {
3524    /// `SHOW ENGINE <e> STATUS`.
3525    Status,
3526    /// `SHOW ENGINE <e> MUTEX`.
3527    Mutex,
3528    /// `SHOW ENGINE <e> LOGS`.
3529    Logs,
3530}
3531
3532/// Which diagnostics list a [`ShowTarget::Diagnostics`] readout named: `WARNINGS` or
3533/// `ERRORS` (MySQL).
3534///
3535/// A surface tag (no `meta`): the keyword's span is subsumed by the enclosing
3536/// [`ShowStatement`].
3537#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3538#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3539#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3540pub enum ShowDiagnosticKind {
3541    /// `SHOW WARNINGS` / `SHOW COUNT(*) WARNINGS`.
3542    Warnings,
3543    /// `SHOW ERRORS` / `SHOW COUNT(*) ERRORS`.
3544    Errors,
3545}
3546
3547/// One resource-type selector in a [`ShowTarget::Profile`] `profile_defs` list (MySQL).
3548///
3549/// A surface tag (no `meta`, `Copy` like [`ShowScope`]): every member is a pure keyword or
3550/// keyword pair with no operand, so the list's span is subsumed by the enclosing
3551/// [`ShowStatement`]. MySQL folds duplicates into a bitmask at bind time; the parser keeps
3552/// the written list order and multiplicity so the statement round-trips.
3553#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3554#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3555#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3556pub enum ShowProfileType {
3557    /// `ALL` — every available profile column.
3558    All,
3559    /// `BLOCK IO` — block input/output counts.
3560    BlockIo,
3561    /// `CONTEXT SWITCHES` — voluntary and involuntary context switches.
3562    ContextSwitches,
3563    /// `CPU` — user and system CPU time.
3564    Cpu,
3565    /// `IPC` — messages sent and received.
3566    Ipc,
3567    /// `MEMORY` — memory usage (currently unimplemented server-side, still grammar-valid).
3568    Memory,
3569    /// `PAGE FAULTS` — major and minor page faults.
3570    PageFaults,
3571    /// `SOURCE` — the source-file function names, files, and line numbers.
3572    Source,
3573    /// `SWAPS` — swap counts.
3574    Swaps,
3575}
3576
3577/// The shared `LIMIT` narrowing on the MySQL `SHOW` family — MySQL's `opt_limit_clause`
3578/// (`SHOW {WARNINGS | ERRORS}`, `SHOW PROFILE`, `SHOW {BINLOG | RELAYLOG} EVENTS`).
3579///
3580/// Models all three surface forms of `limit_options`:
3581///
3582/// * `LIMIT <row_count>` — no offset ([`offset`](Self::offset) `None`).
3583/// * `LIMIT <offset>, <row_count>` — the comma form, offset written first
3584///   ([`offset_keyword`](Self::offset_keyword) `false`).
3585/// * `LIMIT <row_count> OFFSET <offset>` — the `OFFSET`-keyword form
3586///   ([`offset_keyword`](Self::offset_keyword) `true`).
3587///
3588/// The two offset spellings are semantically identical (`LIMIT 2, 5` == `LIMIT 5 OFFSET 2`);
3589/// [`offset_keyword`](Self::offset_keyword) records which was written so the statement
3590/// round-trips, and is always `false` when [`offset`](Self::offset) is `None`. Both operands
3591/// are integer [`Literal`]s (MySQL's `limit_option` also admits a `?` param-marker or a
3592/// user-variable, deferred — the whole `SHOW` family parses only integer limits today).
3593#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3594#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3595#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3596pub struct ShowLimit {
3597    /// The optional `<offset>` — present for both the comma and `OFFSET`-keyword forms.
3598    pub offset: Option<Literal>,
3599    /// When [`offset`](Self::offset) is present, whether it was written with the `OFFSET`
3600    /// keyword (`LIMIT <row_count> OFFSET <offset>`) rather than the comma form
3601    /// (`LIMIT <offset>, <row_count>`); always `false` when there is no offset.
3602    pub offset_keyword: bool,
3603    /// The `<row_count>` — the sole operand, the second operand of the comma form, or the
3604    /// first operand of the `OFFSET`-keyword form.
3605    pub row_count: Literal,
3606    /// Source location and node identity.
3607    pub meta: Meta,
3608}