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