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}