Skip to main content

squonk_ast/ast/
ddl.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! DDL statement AST nodes: table, column, and constraint definitions.
5
6use super::{
7    DataType, ExecuteStatement, Expr, Extension, Ident, IntervalFields, LanguageName, Literal,
8    NamedOperatorSpelling, NoExt, ObjectName, Query, RoutineObjectKind, RoutineSignature,
9    Statement,
10};
11use crate::vocab::{Meta, Symbol};
12use thin_vec::ThinVec;
13
14#[derive(Clone, Debug, PartialEq, Eq, Hash)]
15#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
16#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
17/// An SQL create table.
18pub struct CreateTable<X: Extension = NoExt> {
19    /// `CREATE OR REPLACE TABLE` (DuckDB): atomically replaces an existing table.
20    /// Gated by [`StatementDdlGates::create_or_replace_table`](crate::dialect::StatementDdlGates::create_or_replace_table) —
21    /// off elsewhere, where `OR REPLACE` before `TABLE` surfaces as a clean parse error
22    /// (only `VIEW`/`FUNCTION` take `OR REPLACE` in the other dialects).
23    pub or_replace: bool,
24    /// Optional temporary for this syntax.
25    pub temporary: Option<TemporaryTableKind>,
26    /// `CREATE UNLOGGED TABLE` (PostgreSQL; also a DuckDB no-op): the table's data is not
27    /// written to the write-ahead log. PostgreSQL's `OptTemp` grammar makes `UNLOGGED` a
28    /// *peer* of `TEMP`/`TEMPORARY` — the two are mutually exclusive, so a set `unlogged`
29    /// never co-occurs with a `Some(temporary)` (`CREATE TEMP UNLOGGED TABLE` is a raw-parse
30    /// error, reproduced here). Gated for acceptance by
31    /// [`CreateTableClauseSyntax::unlogged_tables`](crate::dialect::CreateTableClauseSyntax::unlogged_tables) — off
32    /// elsewhere (ANSI/MySQL/SQLite), where `UNLOGGED` after `CREATE` surfaces as a clean
33    /// parse error.
34    pub unlogged: bool,
35    /// Whether the if not exists form was present in the source.
36    pub if_not_exists: bool,
37    /// Name referenced by this syntax.
38    pub name: ObjectName,
39    /// Statement or query body governed by this node.
40    pub body: CreateTableBody<X>,
41    /// The `INHERITS (<parent>, ...)` legacy table-inheritance clause (PostgreSQL), naming the
42    /// parent tables this table inherits columns and constraints from. It follows the
43    /// [`Definition`](CreateTableBody::Definition) `(elements)` body and precedes both
44    /// [`partition_by`](Self::partition_by) and the trailing [`options`](Self::options)
45    /// (PostgreSQL grammar order: `(…) INHERITS (…) PARTITION BY … WITH … ON COMMIT … TABLESPACE
46    /// …`). Empty when no clause is written — an `INHERITS ()` with no parent is a raw-parse
47    /// error, so a non-empty list always corresponds to a written clause and no separate
48    /// `Option` is needed. Only the `(elements)` definition body carries it (the `PARTITION OF`,
49    /// `OF <type>`, and `AS <query>` bodies do not), so it is empty for those. Gated for
50    /// acceptance by
51    /// [`CreateTableClauseSyntax::table_inheritance`](crate::dialect::CreateTableClauseSyntax::table_inheritance) — off
52    /// elsewhere, where the `INHERITS` keyword is left unconsumed and surfaces as a clean parse
53    /// error.
54    pub inherits: ThinVec<ObjectName>,
55    /// The trailing `PARTITION BY {LIST | RANGE | HASH} (<key>, ...)` declarative-partitioning
56    /// clause (PostgreSQL), marking this table as a partitioned *parent*. It follows the
57    /// [`Definition`](CreateTableBody::Definition) body (`CREATE TABLE t (...) PARTITION BY
58    /// LIST (a)`) and the [`PartitionOf`](CreateTableBody::PartitionOf) child body (a
59    /// sub-partitioned child, `... FOR VALUES IN (1) PARTITION BY RANGE (b)`); it never rides
60    /// an `AS <query>` body. Boxed as a cold clause so a non-partitioned table pays only a
61    /// null pointer. `None` when the table is not a partition parent. Gated for acceptance by
62    /// [`CreateTableClauseSyntax::declarative_partitioning`](crate::dialect::CreateTableClauseSyntax::declarative_partitioning) —
63    /// off elsewhere, where the `PARTITION` keyword is left unconsumed and surfaces as a clean
64    /// parse error.
65    pub partition_by: Option<Box<PartitionSpec<X>>>,
66    /// The `USING <access_method>` table access-method clause (PostgreSQL): the storage
67    /// engine backing the table (`heap`, an extension-provided method). In PostgreSQL's
68    /// `CreateStmt` grammar it sits *after* the body, [`inherits`](Self::inherits), and
69    /// [`partition_by`](Self::partition_by) but *before* the trailing
70    /// [`options`](Self::options) (`(…) INHERITS (…) PARTITION BY … USING heap WITH (…) ON
71    /// COMMIT … TABLESPACE …`) — a `WITH (…)` before `USING` is a raw-parse error, so parsing
72    /// it here reproduces that order. On an `AS <query>` body the slot instead precedes the
73    /// `AS` (`CreateTableAsStmt`: `CREATE TABLE t [(cols)] USING m [WITH (…)] AS query`; a
74    /// `USING` *after* the query is a raw-parse error). The method is a single (optionally
75    /// quoted) identifier — a qualified `schema.method` is a raw-parse error. `None` when the
76    /// clause is unwritten. Boxed as a cold clause (rarely written) so a plain `CREATE TABLE`
77    /// pays only a null pointer, keeping this node within its size budget (the box/inline
78    /// budget). Gated for acceptance by
79    /// [`CreateTableClauseSyntax::table_access_method`](crate::dialect::CreateTableClauseSyntax::table_access_method) — off
80    /// elsewhere, where the `USING` keyword is left unconsumed and surfaces as a clean parse
81    /// error.
82    pub access_method: Option<Box<Ident>>,
83    /// Options supplied in source order.
84    pub options: ThinVec<CreateTableOption<X>>,
85    /// Source location and node identity.
86    pub meta: Meta,
87}
88
89/// Surface spelling for temporary table creation.
90#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
91#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
92#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
93pub enum TemporaryTableKind {
94    /// `TEMP` — a session-local temporary table (the short spelling).
95    Temp,
96    /// `TEMPORARY` — a session-local temporary table.
97    Temporary,
98}
99
100/// The `CREATE TABLE` body: a `(elements)` definition, an `AS <query>`, a `PARTITION OF`
101/// declarative-partitioning child, or an `OF <type>` typed table.
102#[derive(Clone, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
104#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
105pub enum CreateTableBody<X: Extension = NoExt> {
106    /// A parenthesized `(col …, constraint …)` column/constraint definition list.
107    Definition {
108        /// elements in source order.
109        elements: ThinVec<TableElement<X>>,
110        /// Source location and node identity.
111        meta: Meta,
112    },
113    /// A `CREATE TABLE … AS <query>` — populate the table from a query.
114    AsQuery {
115        /// Columns in source order.
116        columns: ThinVec<Ident>,
117        /// Query governed by this node.
118        query: Box<Query<X>>,
119        /// Whether the with data form was present in the source.
120        with_data: Option<bool>,
121        /// Source location and node identity.
122        meta: Meta,
123    },
124    /// `AS EXECUTE <prepared> [(<arg>, ...)] [WITH [NO] DATA]` — a CTAS whose source rows come
125    /// from running a prepared statement rather than an inline query (PostgreSQL's
126    /// `CreateTableAsStmt` over an `ExecuteStmt`, `CREATE TABLE t AS EXECUTE plan(1, 2)`). The
127    /// optional [`columns`](Self::AsExecute::columns) rename the result columns (empty when the
128    /// `(...)` list is absent) exactly as on [`AsQuery`](Self::AsQuery), and the same trailing
129    /// `WITH [NO] DATA` populate flag rides here. The prepared-statement source is a reused
130    /// [`ExecuteStatement`] (name + positional args), boxed to keep this variant within the enum's
131    /// size budget. Gated for acceptance by
132    /// [`CreateTableClauseSyntax::create_table_as_execute`](crate::dialect::CreateTableClauseSyntax::create_table_as_execute); off
133    /// elsewhere, where `EXECUTE` after `AS` is left unconsumed and surfaces as a clean parse
134    /// error (the inline-query CTAS path takes over and rejects the `EXECUTE` keyword).
135    AsExecute {
136        /// Columns in source order.
137        columns: ThinVec<Ident>,
138        /// The prepared statement executed to populate the table; see [`ExecuteStatement`].
139        execute: Box<ExecuteStatement<X>>,
140        /// Whether the with data form was present in the source.
141        with_data: Option<bool>,
142        /// Source location and node identity.
143        meta: Meta,
144    },
145    /// `PARTITION OF <parent> [(<augmentation>, ...)] <bound>` — a declarative-partitioning
146    /// *child*, whose column shape is inherited from `parent` (PostgreSQL). The optional
147    /// parenthesized augmentation list carries per-column overrides (a bare `ColId` with a
148    /// `[WITH OPTIONS]` constraint list — *no* type, so each [`ColumnDef`] leaves
149    /// [`data_type`](ColumnDef::data_type) `None`) and table constraints; it reuses
150    /// [`TableElement`] and stays empty when the `(...)` is absent (PostgreSQL rejects an empty
151    /// `()`). The `bound` is the mandatory `FOR VALUES …` / `DEFAULT` partition-bound spec.
152    /// Gated for acceptance by
153    /// [`CreateTableClauseSyntax::declarative_partitioning`](crate::dialect::CreateTableClauseSyntax::declarative_partitioning); off
154    /// elsewhere, `PARTITION` after the table name surfaces as a clean parse error.
155    PartitionOf {
156        /// The parent partitioned table (`PARTITION OF <parent>`).
157        parent: ObjectName,
158        /// elements in source order.
159        elements: ThinVec<TableElement<X>>,
160        // Boxed as the fat child (a `PartitionBound` is ~48 B, the widest field here): inlining
161        // it would set `CreateTableBody`'s width and tax the warm `Definition`/`AsQuery` paths,
162        // so a partition child pays one cold allocation instead — the same reason `AsQuery` boxes
163        // its `Query` (ADR-0007, box/inline budget).
164        /// The partition bound (`FOR VALUES …` / `DEFAULT`); see [`PartitionBound`].
165        bound: Box<PartitionBound<X>>,
166        /// Source location and node identity.
167        meta: Meta,
168    },
169    /// `OF <type_name> [(<augmentation>, ...)]` — a *typed table* whose column shape is drawn
170    /// from a composite type (PostgreSQL). Like [`PartitionOf`](Self::PartitionOf) the optional
171    /// parenthesized list *augments* rather than declares: each element is a bare `ColId` with an
172    /// optional `WITH OPTIONS` noise phrase and a constraint list (*no* type — the column's type
173    /// comes from `type_name`, so each [`ColumnDef`] leaves [`data_type`](ColumnDef::data_type)
174    /// `None`) or a table constraint. It reuses [`TableElement`] and stays empty when the `(...)`
175    /// is absent (PostgreSQL rejects an empty `()`). Unlike the `(elements)`
176    /// [`Definition`](Self::Definition) body, the `OF` form takes no `INHERITS` clause (`CREATE
177    /// TABLE t OF ty INHERITS (p)` is a raw-parse error), so [`CreateTable::inherits`] is always
178    /// empty for it. Gated for acceptance by
179    /// [`CreateTableClauseSyntax::typed_tables`](crate::dialect::CreateTableClauseSyntax::typed_tables); off elsewhere,
180    /// `OF` after the table name surfaces as a clean parse error.
181    OfType {
182        /// The composite type the table is defined `OF` (`CREATE TABLE t OF <type>`).
183        type_name: ObjectName,
184        /// elements in source order.
185        elements: ThinVec<TableElement<X>>,
186        /// Source location and node identity.
187        meta: Meta,
188    },
189    /// `LIKE <source>` / `(LIKE <source>)` — MySQL's statement-level table-clone body, which
190    /// copies the whole definition of an existing table (`CREATE TABLE t LIKE src`). Unlike
191    /// PostgreSQL's [`TableElement::Like`] copy *element* (which sits inside a `(elements)`
192    /// [`Definition`](Self::Definition) list, carries `{INCLUDING | EXCLUDING} <feature>`
193    /// options, and can co-occur with other elements — gated by
194    /// [`CreateTableClauseSyntax::like_source_table`](crate::dialect::CreateTableClauseSyntax::like_source_table)), this is a
195    /// whole-statement production that replaces the entire body: exactly one bare source name,
196    /// no feature-copy options, no co-element, no trailing table options — MySQL rejects `LIKE
197    /// src ENGINE=…`, `(LIKE src, x INT)`, and `(LIKE src INCLUDING ALL)` all as
198    /// `ER_PARSE_ERROR`. MySQL spells it two ways, differing only by optional parentheses
199    /// ([`parenthesized`](Self::LikeSource::parenthesized) records which so the source form
200    /// round-trips); both admit a qualified source name and compose freely with `IF NOT EXISTS`
201    /// and `TEMPORARY`. Gated for acceptance by
202    /// [`CreateTableClauseSyntax::statement_level_table_like`](crate::dialect::CreateTableClauseSyntax::statement_level_table_like);
203    /// off elsewhere, `LIKE` after the table name (or as the first token inside `(`) is left
204    /// unconsumed and surfaces as a clean parse error.
205    LikeSource {
206        /// Input source for this syntax.
207        source: ObjectName,
208        /// Whether the source was written parenthesized (`(LIKE src)`) rather than bare
209        /// (`LIKE src`). Both are the same MySQL production; this preserves the spelling.
210        parenthesized: bool,
211        /// Source location and node identity.
212        meta: Meta,
213    },
214}
215
216/// The `PARTITION BY {LIST | RANGE | HASH} (<key>, ...)` declarative-partitioning spec on a
217/// partitioned parent (or a sub-partitioned child). PostgreSQL-only; see
218/// [`CreateTable::partition_by`].
219///
220/// PostgreSQL 17 validates the strategy word in the grammar action (`parsePartitionStrategy`),
221/// so an unrecognized strategy (`PARTITION BY foo (a)`) is a raw-parse error — hence
222/// [`strategy`](Self::strategy) is a closed [`PartitionStrategy`] enum, not a free identifier.
223#[derive(Clone, Debug, PartialEq, Eq, Hash)]
224#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
225#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
226pub struct PartitionSpec<X: Extension = NoExt> {
227    /// The partitioning strategy (`LIST`/`RANGE`/`HASH`); see [`PartitionStrategy`].
228    pub strategy: PartitionStrategy,
229    /// The partition key: one or more [`PartitionElem`]s (the grammar requires ≥ 1 — an empty
230    /// `()` is a raw-parse error).
231    pub columns: ThinVec<PartitionElem<X>>,
232    /// Source location and node identity.
233    pub meta: Meta,
234}
235
236/// The partitioning strategy keyword. A `Copy` leaf enum whose span rides the owning
237/// [`PartitionSpec`], like [`DropBehavior`].
238#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
239#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
240#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
241pub enum PartitionStrategy {
242    /// `PARTITION BY LIST` — assign rows by a discrete key value.
243    List,
244    /// `PARTITION BY RANGE` — assign rows by a key range.
245    Range,
246    /// `PARTITION BY HASH` — assign rows by a hash of the key.
247    Hash,
248}
249
250/// One partition-key element: a key expression plus its optional `COLLATE` and operator-class
251/// tails (PostgreSQL `part_elem`).
252///
253/// The grammar admits three key forms — a bare column (`a`), a bare `func_expr_windowless`
254/// (`lower(a)`), or a parenthesized expression (`(a + b)`) — the first two rendered bare and
255/// the last parenthesized. [`parenthesized`](Self::parenthesized) records which so the source
256/// form round-trips; the `COLLATE` and operator-class clauses are *separate* `part_elem`
257/// tails (not expression postfixes), so a bare-column key keeps them out of `expr`.
258#[derive(Clone, Debug, PartialEq, Eq, Hash)]
259#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
260#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
261pub struct PartitionElem<X: Extension = NoExt> {
262    /// Expression evaluated by this syntax.
263    pub expr: Expr<X>,
264    /// Whether the key was written parenthesized (`(a_expr)`). A bare column / function call is
265    /// unparenthesized; every other expression form must be parenthesized (the grammar's
266    /// `'(' a_expr ')'` production), and rendering re-adds the parentheses from this flag.
267    pub parenthesized: bool,
268    /// Optional collation for this syntax.
269    pub collation: Option<ObjectName>,
270    /// The trailing operator-class name (`opt_qualified_name`), when written —
271    /// `a part_test_int4_ops`, `a myschema.text_ops`.
272    pub opclass: Option<ObjectName>,
273    /// Source location and node identity.
274    pub meta: Meta,
275}
276
277/// The partition-bound spec of a [`PartitionOf`](CreateTableBody::PartitionOf) child (or an
278/// [`AttachPartition`](AlterTableAction::AttachPartition) action): `FOR VALUES {IN | FROM…TO |
279/// WITH} (…)` or `DEFAULT` (PostgreSQL).
280///
281/// The bound datums are full `a_expr`s (PostgreSQL parses the `minvalue`/`maxvalue` range
282/// sentinels as ordinary column references at raw-parse time, so they need no dedicated node).
283/// The hash bound's `MODULUS m, REMAINDER r` are unsigned integer literals validated in the
284/// grammar action — exactly one of each, either order — so they are modelled as a resolved
285/// pair rather than a raw option list (a missing/duplicate/non-integer value is a raw-parse
286/// error PostgreSQL raises, matched at the same layer).
287#[derive(Clone, Debug, PartialEq, Eq, Hash)]
288#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
289#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
290pub enum PartitionBound<X: Extension = NoExt> {
291    /// `FOR VALUES IN (<expr>, ...)` — a list partition (≥ 1 value).
292    List {
293        /// Values in source order.
294        values: ThinVec<Expr<X>>,
295        /// Source location and node identity.
296        meta: Meta,
297    },
298    /// `FOR VALUES FROM (<expr>, ...) TO (<expr>, ...)` — a range partition (each side ≥ 1).
299    Range {
300        /// from in source order.
301        from: ThinVec<Expr<X>>,
302        /// to in source order.
303        to: ThinVec<Expr<X>>,
304        /// Source location and node identity.
305        meta: Meta,
306    },
307    /// `FOR VALUES WITH (MODULUS <n>, REMAINDER <n>)` — a hash partition.
308    Hash {
309        /// The `MODULUS` of a `FOR VALUES WITH (MODULUS m, REMAINDER r)` hash bound.
310        modulus: Literal,
311        /// The `REMAINDER` of a hash partition bound.
312        remainder: Literal,
313        /// Source location and node identity.
314        meta: Meta,
315    },
316    /// `DEFAULT` — the catch-all partition.
317    Default {
318        /// Source location and node identity.
319        meta: Meta,
320    },
321}
322
323#[derive(Clone, Debug, PartialEq, Eq, Hash)]
324#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
325#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
326/// The SQL table element forms represented by the AST.
327pub enum TableElement<X: Extension = NoExt> {
328    /// A column definition.
329    Column {
330        /// Column referenced by this syntax.
331        column: ColumnDef<X>,
332        /// Source location and node identity.
333        meta: Meta,
334    },
335    /// A table-level constraint.
336    Constraint {
337        /// The table constraint; see [`TableConstraintDef`].
338        constraint: TableConstraintDef<X>,
339        /// Source location and node identity.
340        meta: Meta,
341    },
342    /// `LIKE <source_table> [{INCLUDING | EXCLUDING} <feature> ...]` — the source-table copy
343    /// element (PostgreSQL). Appears *inside* the parenthesized definition list (`CREATE TABLE t
344    /// (a int, LIKE src INCLUDING ALL, b int)`), copying the named table's structure. The
345    /// [`options`](Self::Like::options) are the repeatable, order-free
346    /// `{INCLUDING | EXCLUDING} <feature>` selectors, preserved as written (PostgreSQL folds them
347    /// into a bitmask, but the source sequence round-trips faithfully here); the list is empty
348    /// for a bare `LIKE src`. Gated for acceptance by
349    /// [`CreateTableClauseSyntax::like_source_table`](crate::dialect::CreateTableClauseSyntax::like_source_table) — off
350    /// elsewhere, where `LIKE` at an element position surfaces as a clean parse error. Distinct
351    /// from MySQL's statement-level `CREATE TABLE t LIKE src` (no parentheses), a separate
352    /// production.
353    Like {
354        /// Input source for this syntax.
355        source: ObjectName,
356        /// Options supplied in source order.
357        options: ThinVec<TableLikeOption>,
358        /// Source location and node identity.
359        meta: Meta,
360    },
361}
362
363#[derive(Clone, Debug, PartialEq, Eq, Hash)]
364#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
365#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
366/// An SQL table like option.
367pub struct TableLikeOption {
368    /// Whether the feature is `INCLUDING` or `EXCLUDING`; see [`TableLikeAction`].
369    pub action: TableLikeAction,
370    /// Which feature is copied/omitted; see [`TableLikeFeature`].
371    pub feature: TableLikeFeature,
372    /// Source location and node identity.
373    pub meta: Meta,
374}
375
376/// Whether a [`TableLikeOption`] includes or excludes its feature. A `Copy` leaf enum whose span
377/// rides the owning [`TableLikeOption`], like [`PartitionStrategy`].
378#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
379#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
380#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
381pub enum TableLikeAction {
382    /// `INCLUDING <feature>` — copy the feature from the source table.
383    Including,
384    /// `EXCLUDING <feature>` — do not copy the feature.
385    Excluding,
386}
387
388/// The feature a [`TableLikeOption`] copies (or omits) from the source table (PostgreSQL's
389/// `TableLikeOption` set). A `Copy` leaf enum whose span rides the owning [`TableLikeOption`].
390#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
391#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
392#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
393pub enum TableLikeFeature {
394    /// `COMMENTS` — column and constraint comments.
395    Comments,
396    /// `COMPRESSION` — per-column compression methods.
397    Compression,
398    /// `CONSTRAINTS` — `CHECK` constraints.
399    Constraints,
400    /// `DEFAULTS` — column default expressions.
401    Defaults,
402    /// `GENERATED` — generated-column expressions.
403    Generated,
404    /// `IDENTITY` — identity/serial column specifications.
405    Identity,
406    /// `INDEXES` — indexes plus primary-key and unique constraints.
407    Indexes,
408    /// `STATISTICS` — extended planner statistics.
409    Statistics,
410    /// `STORAGE` — per-column storage settings.
411    Storage,
412    /// `ALL` — every feature (PostgreSQL's `CREATE_TABLE_LIKE_ALL`).
413    All,
414}
415
416#[derive(Clone, Debug, PartialEq, Eq, Hash)]
417#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
418#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
419/// An SQL column def.
420pub struct ColumnDef<X: Extension = NoExt> {
421    /// Name referenced by this syntax.
422    pub name: Ident,
423    /// The column's declared type, or `None` for a SQLite *typeless* column
424    /// (`CREATE TABLE t (a, b)`): SQLite's flexible typing lets a column omit its
425    /// type entirely (the column then takes BLOB affinity). Every other dialect
426    /// requires a type, so the omission is gated for acceptance by
427    /// [`ColumnDefinitionSyntax::typeless_column_definitions`](crate::dialect::ColumnDefinitionSyntax::typeless_column_definitions)
428    /// — off elsewhere, where a missing type surfaces as a clean parse error. The
429    /// omitted and written forms round-trip distinctly (an `Option`, not a synthesized
430    /// default), so a downstream converter sees exactly what the source declared.
431    pub data_type: Option<DataType<X>>,
432    /// The per-column `STORAGE {<strategy> | DEFAULT}` clause (PostgreSQL): the column's
433    /// on-disk storage strategy (`PLAIN` / `EXTERNAL` / `EXTENDED` / `MAIN`, or the `DEFAULT`
434    /// keyword). It is a *fixed-position* clause in PostgreSQL's `columnDef` grammar — after the
435    /// type and before both [`compression`](Self::compression) and the
436    /// [`constraints`](Self::constraints) (`ColId Typename [STORAGE …] [COMPRESSION …]
437    /// ColQualList`) — so a `STORAGE` *after* a constraint, or after `COMPRESSION`, is a
438    /// raw-parse error. The value is an *open* name, not a closed enum: PostgreSQL's grammar is
439    /// `STORAGE {ColId | DEFAULT}` and validates the strategy word at analysis, out of this
440    /// parser's layer (`STORAGE bogus` is engine-measured accepted at raw parse), so any single
441    /// (optionally quoted) identifier — or the `DEFAULT` keyword, interned as written — rides
442    /// here as an [`Ident`], boxed as the cold clause it is (see
443    /// [`compression`](Self::compression)). `None` when unwritten. Gated for acceptance by
444    /// [`ColumnDefinitionSyntax::column_storage`](crate::dialect::ColumnDefinitionSyntax::column_storage) — off
445    /// elsewhere, where the `STORAGE` keyword surfaces as a clean parse error.
446    pub storage: Option<Box<Ident>>,
447    /// The per-column `COMPRESSION <method>` clause (PostgreSQL): the compression method for the
448    /// column's TOAST-able values (`pglz`, `lz4`, or the `DEFAULT` keyword). Like
449    /// [`storage`](Self::storage) a fixed-position clause with the same open
450    /// `{ColId | DEFAULT}` value grammar — after the type and [`storage`](Self::storage), before
451    /// the [`constraints`](Self::constraints) — so a `COMPRESSION` after a constraint or before
452    /// `STORAGE` is a raw-parse error, while a qualified `schema.method` is one too (the value
453    /// is a single identifier). PostgreSQL validates the specific method name at a later stage,
454    /// out of this parser's layer. `None` when unwritten. The name is boxed to keep this cold
455    /// clause from widening the warm, vec-embedded [`ColumnDef`] (the box/inline budget — a wide
456    /// table holds many `ColumnDef`s and `COMPRESSION` is rarely written). Gated for acceptance
457    /// by the same [`ColumnDefinitionSyntax::column_storage`](crate::dialect::ColumnDefinitionSyntax::column_storage)
458    /// flag as `STORAGE` (the two physical-storage attributes travel together).
459    pub compression: Option<Box<Ident>>,
460    /// constraints in source order.
461    pub constraints: ThinVec<ColumnConstraint<X>>,
462    /// Source location and node identity.
463    pub meta: Meta,
464}
465
466#[derive(Clone, Debug, PartialEq, Eq, Hash)]
467#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
468#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
469/// An SQL column constraint.
470pub struct ColumnConstraint<X: Extension = NoExt> {
471    /// Name referenced by this syntax.
472    pub name: Option<Ident>,
473    /// Optional option for this syntax.
474    pub option: ColumnOption<X>,
475    /// The SQLite `ON CONFLICT <resolution>` clause attached to this constraint —
476    /// `a INTEGER UNIQUE ON CONFLICT REPLACE`. Modelled on the constraint wrapper (like
477    /// [`characteristics`](Self::characteristics)) rather than on the individual
478    /// [`ColumnOption`] variants because the conflict clause qualifies a *constraint*
479    /// (SQLite admits it after `NOT NULL` / `UNIQUE` / `PRIMARY KEY` / `CHECK`), and the
480    /// parser only reads it after one of those. `None` when unwritten. Gated for
481    /// acceptance by
482    /// [`ColumnDefinitionSyntax::column_conflict_resolution_clause`](crate::dialect::ColumnDefinitionSyntax::column_conflict_resolution_clause);
483    /// off elsewhere, where `ON CONFLICT` on a column constraint is a clean parse error.
484    pub conflict: Option<ConflictResolution>,
485    /// The trailing `DEFERRABLE`/`INITIALLY` characteristics, when written. Modelled
486    /// on the constraint wrapper rather than on [`ColumnOption::References`] because
487    /// the SQL characteristic qualifies a *constraint* generally (unique / primary
488    /// key / foreign key), not the reference clause specifically. Boxed because it is
489    /// a cold, rarely written clause: the common column constraint then pays only a
490    /// null pointer, keeping this warm node lean (the box/inline budget).
491    pub characteristics: Option<Box<ConstraintCharacteristics>>,
492    /// Source location and node identity.
493    pub meta: Meta,
494}
495
496/// The `<constraint characteristics>` of a constraint: its deferral mode.
497///
498/// `deferrable` records `DEFERRABLE` (`Some(true)`) / `NOT DEFERRABLE`
499/// (`Some(false)`); `initially_deferred` records `INITIALLY DEFERRED` (`Some(true)`)
500/// / `INITIALLY IMMEDIATE` (`Some(false)`). Each stays `None` when its clause is
501/// unwritten so the omitted form and the explicit default round-trip distinctly —
502/// PostgreSQL materializes both to `NOT DEFERRABLE INITIALLY IMMEDIATE`, so the
503/// omitted and default-explicit spellings are representation-equivalent.
504#[derive(Clone, Debug, PartialEq, Eq, Hash)]
505#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
506#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
507pub struct ConstraintCharacteristics {
508    /// Whether the deferrable form was present in the source.
509    pub deferrable: Option<bool>,
510    /// Whether the initially deferred form was present in the source.
511    pub initially_deferred: Option<bool>,
512    /// Source location and node identity.
513    pub meta: Meta,
514}
515
516#[derive(Clone, Debug, PartialEq, Eq, Hash)]
517#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
518#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
519/// The SQL column option forms represented by the AST.
520pub enum ColumnOption<X: Extension = NoExt> {
521    /// An explicit `NULL` — the column is allowed to contain nulls.
522    Null {
523        /// Source location and node identity.
524        meta: Meta,
525    },
526    /// A `NOT NULL` constraint.
527    NotNull {
528        /// Source location and node identity.
529        meta: Meta,
530    },
531    /// A `DEFAULT <expr>` value clause.
532    Default {
533        /// Expression evaluated by this syntax.
534        expr: Box<Expr<X>>,
535        /// Source location and node identity.
536        meta: Meta,
537    },
538    /// A `GENERATED ALWAYS AS (…)` computed column.
539    Generated {
540        /// The generated-column specification; see [`GeneratedColumn`].
541        generated: Box<GeneratedColumn<X>>,
542        /// Source location and node identity.
543        meta: Meta,
544    },
545    /// A `GENERATED … AS IDENTITY` identity column.
546    Identity {
547        /// The identity-column specification; see [`IdentityColumn`].
548        identity: Box<IdentityColumn<X>>,
549        /// Source location and node identity.
550        meta: Meta,
551    },
552    /// An inline `PRIMARY KEY` constraint.
553    PrimaryKey {
554        /// The SQLite `ASC` / `DESC` sort-order qualifier on an inline primary key
555        /// (`id INT PRIMARY KEY DESC`): `Some(true)` for `ASC`, `Some(false)` for
556        /// `DESC`, `None` when unwritten. Gated for acceptance by
557        /// [`ColumnDefinitionSyntax::inline_primary_key_ordering`](crate::dialect::ColumnDefinitionSyntax::inline_primary_key_ordering)
558        /// — off elsewhere, where the trailing `ASC`/`DESC` is a clean parse error.
559        ascending: Option<bool>,
560        /// The `USING INDEX TABLESPACE <name>` index-parameter clause (PostgreSQL): the
561        /// tablespace of the implicit index backing this inline primary key (`a int PRIMARY
562        /// KEY USING INDEX TABLESPACE ts`). `None` when unwritten. Boxed as the cold clause it
563        /// is (the box/inline budget — a wide table holds many `ColumnDef`s and this is rarely
564        /// written), matching [`Collate`](Self::Collate). Gated for acceptance by
565        /// [`ConstraintSyntax::index_constraint_parameters`](crate::dialect::ConstraintSyntax::index_constraint_parameters);
566        /// off elsewhere, where the `USING` keyword after `PRIMARY KEY` is a clean parse error.
567        index_tablespace: Option<Box<Ident>>,
568        /// Source location and node identity.
569        meta: Meta,
570    },
571    /// An inline `UNIQUE` constraint.
572    Unique {
573        /// The `NULLS [NOT] DISTINCT` null-treatment (PostgreSQL 15+): `Some(false)` for
574        /// `NULLS NOT DISTINCT` (nulls collide, so at most one null row), `Some(true)` for the
575        /// explicit `NULLS DISTINCT` default, `None` when unwritten. Gated for acceptance by
576        /// [`ConstraintSyntax::index_constraint_parameters`](crate::dialect::ConstraintSyntax::index_constraint_parameters);
577        /// off elsewhere, where the `NULLS` keyword after `UNIQUE` is a clean parse error.
578        nulls_not_distinct: Option<bool>,
579        /// The `USING INDEX TABLESPACE <name>` index-parameter clause (PostgreSQL), as on
580        /// [`PrimaryKey`](Self::PrimaryKey::index_tablespace). `None` when unwritten. Boxed and
581        /// gated identically.
582        index_tablespace: Option<Box<Ident>>,
583        /// Source location and node identity.
584        meta: Meta,
585    },
586    /// The MySQL `AUTO_INCREMENT` / SQLite `AUTOINCREMENT` column attribute: the
587    /// column's value defaults to the next sequence number. The one canonical shape
588    /// carries a [`spelling`](AutoIncrementSpelling) tag so the two surface forms
589    /// round-trip. Each spelling gates on its own flag: the underscored `AUTO_INCREMENT`
590    /// under `create_table_clause_syntax.table_options` (MySQL) and the joined `AUTOINCREMENT`
591    /// under `column_definition_syntax.joined_autoincrement_attribute` (SQLite). Neither preset
592    /// admits the other's spelling.
593    AutoIncrement {
594        /// Exact source spelling retained for faithful rendering.
595        spelling: AutoIncrementSpelling,
596        /// Source location and node identity.
597        meta: Meta,
598    },
599    /// The column-definition `COLLATE <collation>` clause: the column's default collating
600    /// sequence. One shape spans three dialects that each spell it in a column definition —
601    /// PostgreSQL (`a text COLLATE "C"` / `COLLATE pg_catalog."default"`), SQLite (`a TEXT
602    /// COLLATE NOCASE`), and DuckDB (`a VARCHAR COLLATE nocase`) — interleaving freely with the
603    /// other [`constraints`](ColumnDef::constraints) in PostgreSQL's `ColQualList` order. The
604    /// name is a (possibly qualified, possibly quoted) [`ObjectName`], matching the
605    /// expression-level `COLLATE` ([`ExpressionSyntax::collate`](crate::dialect::ExpressionSyntax));
606    /// SQLite/DuckDB write a single bare identifier (a one-part name), PostgreSQL a full
607    /// `any_name`. Distinct from that expression-level `COLLATE`, which qualifies an expression,
608    /// not a column. Gated for acceptance by
609    /// [`ColumnDefinitionSyntax::column_collation`](crate::dialect::ColumnDefinitionSyntax::column_collation); off
610    /// elsewhere (ANSI/MySQL), where a column-level `COLLATE` is a clean parse error. The
611    /// collation name is boxed to keep this cold clause from widening the warm `ColumnOption`
612    /// enum (the box/inline budget): every other fat variant here boxes its payload, so
613    /// `Collate` matches them and the common unit constraints pay only a null pointer.
614    Collate {
615        /// The collation name.
616        collation: Box<ObjectName>,
617        /// Source location and node identity.
618        meta: Meta,
619    },
620    /// An inline `CHECK (<predicate>)` constraint.
621    Check {
622        /// Expression evaluated by this syntax.
623        expr: Box<Expr<X>>,
624        /// The `NO INHERIT` marker (PostgreSQL/DuckDB): the check is not inherited by child
625        /// tables (`a int CHECK (a > 0) NO INHERIT`). At column level PostgreSQL bakes
626        /// `opt_no_inherit` directly into the `CHECK` production (unlike the table-level
627        /// [`TableConstraintDef`] marker, which rides the shared constraint-attribute slot), and
628        /// no column-level `NOT VALID` exists. Gated for acceptance by
629        /// [`ConstraintSyntax::constraint_no_inherit_not_valid`](crate::dialect::ConstraintSyntax::constraint_no_inherit_not_valid);
630        /// off elsewhere, where `NO INHERIT` after a column `CHECK` is a clean parse error.
631        no_inherit: bool,
632        /// Source location and node identity.
633        meta: Meta,
634    },
635    /// An inline `REFERENCES <table> (<cols>)` foreign-key constraint.
636    References {
637        // Boxed to keep `ColumnOption` lean: a `ForeignKeyRef` (48 B) is the only
638        // fat payload among these variants (every other is boxed or a unit), so
639        // inlining it set the whole enum's width. `REFERENCES` is a cold DDL path, so
640        // one allocation is a cheap price for the per-node saving — the fat-variant
641        // skew ADR-0007 calls out, data-backed by the box/inline budget benchmark.
642        /// The foreign-key target table, columns, and options; see [`ForeignKeyRef`].
643        reference: Box<ForeignKeyRef>,
644        /// Source location and node identity.
645        meta: Meta,
646    },
647    /// A trailing bodyless `CONSTRAINT <name>` (SQLite): the [`ColumnConstraint::name`] is
648    /// written but no constraint element follows it before the column/table-element
649    /// terminator. Gated for acceptance by
650    /// [`ConstraintSyntax::bare_constraint_name`](crate::dialect::ConstraintSyntax::bare_constraint_name); off
651    /// elsewhere, where a `CONSTRAINT <name>` with nothing following is a clean parse error.
652    Bare {
653        /// Source location and node identity.
654        meta: Meta,
655    },
656    /// Dialect extension node supplied by the extension type.
657    Other {
658        /// The dialect extension node value.
659        ext: X,
660        /// Source location and node identity.
661        meta: Meta,
662    },
663}
664
665/// Surface spelling for the auto-increment column attribute ([`ColumnOption::AutoIncrement`]).
666///
667/// The same canonical attribute is written `AUTO_INCREMENT` (MySQL, an underscore) or
668/// `AUTOINCREMENT` (SQLite, one solid word); this tag records which the source used so
669/// rendering round-trips exactly, mirroring the [`GeneratedColumnSpelling`] tag. A leaf
670/// `Copy` enum whose span lives on the owning [`ColumnOption`], like [`DropBehavior`].
671#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
672#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
673#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
674pub enum AutoIncrementSpelling {
675    /// The MySQL `AUTO_INCREMENT` spelling (with the underscore).
676    Underscored,
677    /// The SQLite `AUTOINCREMENT` spelling (one solid word).
678    Joined,
679}
680
681use super::ConflictResolution;
682
683/// A foreign-key reference: `REFERENCES <table> [(<column>, ...)]` plus the
684/// optional `<referential triggered action>` clauses.
685///
686/// `match_type`, `on_delete`, and `on_update` stay `None` when their clause is
687/// unwritten — the canonical shape keeps one node for both the column-level
688/// `REFERENCES` form and the table-level `FOREIGN KEY` form. The clauses
689/// are order-independent in the standard, so the parser accepts them in any order
690/// and renders them in a fixed canonical order (`ON DELETE` before `ON UPDATE`); the
691/// [`update_before_delete`](Self::update_before_delete) tag records the one bit of
692/// order the two separate fields otherwise lose, so a source-fidelity render replays
693/// the written order. PostgreSQL always materializes these (defaulting `MATCH SIMPLE`
694/// / `NO ACTION`), so an unwritten clause and the explicit default are
695/// representation-equivalent.
696#[derive(Clone, Debug, PartialEq, Eq, Hash)]
697#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
698#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
699pub struct ForeignKeyRef {
700    /// Table referenced by this syntax.
701    pub table: ObjectName,
702    /// Columns in source order.
703    pub columns: ThinVec<Ident>,
704    /// Optional match type for this syntax.
705    pub match_type: Option<ForeignKeyMatch>,
706    /// The referential actions are boxed because they are usually absent and a
707    /// [`ReferentialAction`] is comparatively large: a bare `REFERENCES` then pays
708    /// only a null pointer, keeping the inline node small where this type is
709    /// embedded in [`ColumnOption`]/[`TableConstraint`] (the same reason those
710    /// enums box their generated-column and identity payloads).
711    pub on_delete: Option<Box<ReferentialAction>>,
712    /// Optional on update for this syntax.
713    pub on_update: Option<Box<ReferentialAction>>,
714    /// Whether the source wrote `ON UPDATE` before `ON DELETE`. Meaningful only when
715    /// both actions are present; the canonical render emits `ON DELETE` first, and a
716    /// source-fidelity render swaps to the written order. Exact-order fidelity — the
717    /// clauses are order-independent, so a target re-spell and the redacted fingerprint
718    /// keep the canonical order.
719    pub update_before_delete: bool,
720    /// Source location and node identity.
721    pub meta: Meta,
722}
723
724/// The `MATCH` mode of a foreign key, governing how a composite key containing
725/// NULLs matches. A bare `REFERENCES` omits it; PostgreSQL reports the omitted
726/// form as `SIMPLE`. A leaf vocabulary enum (no `meta`): like [`DropBehavior`] it
727/// holds no spanned children, so its span lives on the owning [`ForeignKeyRef`].
728#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
729#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
730#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
731pub enum ForeignKeyMatch {
732    /// `MATCH FULL` — a partially-null referencing key is rejected.
733    Full,
734    /// `MATCH PARTIAL` — reserved by the standard; unimplemented by most engines.
735    Partial,
736    /// `MATCH SIMPLE` — the default; a null in any referencing column skips the check.
737    Simple,
738}
739
740/// A foreign-key `<referential action>`: what a delete (`ON DELETE`) or update
741/// (`ON UPDATE`) of the referenced row does to the referencing rows.
742///
743/// `SetNull`/`SetDefault` carry the optional PostgreSQL column list (`SET NULL
744/// (col, ...)`), which restricts the action to a subset of the referencing
745/// columns; the grammar accepts the list only on `ON DELETE`, so it is always
746/// empty for an `ON UPDATE` action and for the bare keyword forms. Each variant
747/// carries `meta` because the action is a span-bearing node — the `SET NULL (col)`
748/// form references spanned [`Ident`]s — mirroring [`IdentityOption`].
749#[derive(Clone, Debug, PartialEq, Eq, Hash)]
750#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
751#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
752pub enum ReferentialAction {
753    /// `NO ACTION` — reject the change if references remain (deferrable).
754    NoAction {
755        /// Source location and node identity.
756        meta: Meta,
757    },
758    /// `RESTRICT` — reject the change immediately if references remain.
759    Restrict {
760        /// Source location and node identity.
761        meta: Meta,
762    },
763    /// `CASCADE` — propagate the delete/update to the referencing rows.
764    Cascade {
765        /// Source location and node identity.
766        meta: Meta,
767    },
768    /// `SET NULL` — null out the referencing columns (optionally a subset).
769    SetNull {
770        /// Columns in source order.
771        columns: ThinVec<Ident>,
772        /// Source location and node identity.
773        meta: Meta,
774    },
775    /// `SET DEFAULT` — reset the referencing columns to their defaults.
776    SetDefault {
777        /// Columns in source order.
778        columns: ThinVec<Ident>,
779        /// Source location and node identity.
780        meta: Meta,
781    },
782}
783
784#[derive(Clone, Debug, PartialEq, Eq, Hash)]
785#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
786#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
787/// An SQL generated column.
788pub struct GeneratedColumn<X: Extension = NoExt> {
789    /// Expression evaluated by this syntax.
790    pub expr: Expr<X>,
791    /// Optional storage for this syntax.
792    pub storage: Option<GeneratedColumnStorage>,
793    /// Which surface spelled the generation clause: the standard `GENERATED ALWAYS AS`
794    /// or the MySQL/SQLite keywordless `AS` shorthand (see [`GeneratedColumnSpelling`]).
795    pub spelling: GeneratedColumnSpelling,
796    /// Source location and node identity.
797    pub meta: Meta,
798}
799
800#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
801#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
802#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
803/// The SQL generated column storage forms represented by the AST.
804pub enum GeneratedColumnStorage {
805    /// `STORED` — the generated value is materialized on write.
806    Stored,
807    /// `VIRTUAL` — the generated value is computed on read.
808    Virtual,
809}
810
811/// Surface spelling for a [`GeneratedColumn`]'s generation clause.
812///
813/// A stored/virtual generated column is written two interchangeable-shape ways: the
814/// standard `GENERATED ALWAYS AS (<expr>)` and the keywordless `AS (<expr>)` shorthand
815/// (MySQL, SQLite). The canonical AST keeps one [`GeneratedColumn`] node and
816/// this tag records which the source used so rendering round-trips exactly, mirroring
817/// the [`LimitSyntax`](super::LimitSyntax) surface tag. The shorthand is gated for
818/// acceptance by
819/// [`ColumnDefinitionSyntax::generated_column_shorthand`](crate::dialect::ColumnDefinitionSyntax::generated_column_shorthand).
820#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
821#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
822#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
823pub enum GeneratedColumnSpelling {
824    /// The standard `GENERATED ALWAYS AS (<expr>)` spelling.
825    GeneratedAlways,
826    /// The keywordless `AS (<expr>)` shorthand (MySQL, SQLite).
827    Shorthand,
828}
829
830#[derive(Clone, Debug, PartialEq, Eq, Hash)]
831#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
832#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
833/// An SQL identity column.
834pub struct IdentityColumn<X: Extension = NoExt> {
835    /// Whether the identity is `GENERATED ALWAYS` or `BY DEFAULT`; see [`IdentityGeneration`].
836    pub generation: IdentityGeneration,
837    /// Options supplied in source order.
838    pub options: ThinVec<IdentityOption<X>>,
839    /// Source location and node identity.
840    pub meta: Meta,
841}
842
843#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
844#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
845#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
846/// The SQL identity generation forms represented by the AST.
847pub enum IdentityGeneration {
848    /// `GENERATED ALWAYS` — the identity value cannot be overridden on insert.
849    Always,
850    /// `GENERATED BY DEFAULT` — an explicit insert value overrides the sequence.
851    ByDefault,
852}
853
854#[derive(Clone, Debug, PartialEq, Eq, Hash)]
855#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
856#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
857/// The SQL identity option forms represented by the AST.
858pub enum IdentityOption<X: Extension = NoExt> {
859    /// `START WITH <n>` — the sequence's initial value.
860    StartWith {
861        /// Expression evaluated by this syntax.
862        expr: Expr<X>,
863        /// Source location and node identity.
864        meta: Meta,
865    },
866    /// `INCREMENT BY <n>` — the step between successive values.
867    IncrementBy {
868        /// Expression evaluated by this syntax.
869        expr: Expr<X>,
870        /// Source location and node identity.
871        meta: Meta,
872    },
873    /// `MINVALUE <n>` / `NO MINVALUE` — the sequence's lower bound.
874    MinValue {
875        /// Value supplied by this syntax.
876        value: Option<Expr<X>>,
877        /// Source location and node identity.
878        meta: Meta,
879    },
880    /// `MAXVALUE <n>` / `NO MAXVALUE` — the sequence's upper bound.
881    MaxValue {
882        /// Value supplied by this syntax.
883        value: Option<Expr<X>>,
884        /// Source location and node identity.
885        meta: Meta,
886    },
887    /// `CACHE <n>` — how many values to preallocate per session.
888    Cache {
889        /// Expression evaluated by this syntax.
890        expr: Expr<X>,
891        /// Source location and node identity.
892        meta: Meta,
893    },
894    /// `CYCLE` / `NO CYCLE` — whether the sequence wraps around at its bound.
895    Cycle {
896        /// Whether cycling is enabled.
897        cycle: bool,
898        /// Source location and node identity.
899        meta: Meta,
900    },
901}
902
903#[derive(Clone, Debug, PartialEq, Eq, Hash)]
904#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
905#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
906/// An SQL table constraint def.
907pub struct TableConstraintDef<X: Extension = NoExt> {
908    /// Name referenced by this syntax.
909    pub name: Option<Ident>,
910    /// The constraint kind and its data; see [`TableConstraint`].
911    pub constraint: TableConstraint<X>,
912    /// The `NO INHERIT` marker (PostgreSQL/DuckDB): the constraint is not inherited by child
913    /// tables. It rides PostgreSQL's shared `ConstraintAttributeSpec` slot alongside
914    /// [`characteristics`](Self::characteristics), order-free with `NOT VALID` and
915    /// `DEFERRABLE`. PostgreSQL admits it only on `CHECK` constraints (`FOREIGN KEY` /
916    /// `PRIMARY KEY` / `UNIQUE` / `EXCLUDE` reject it in the grammar action — reproduced at
917    /// parse), so a set flag always pairs with a [`TableConstraint::Check`]. Gated for
918    /// acceptance by
919    /// [`ConstraintSyntax::constraint_no_inherit_not_valid`](crate::dialect::ConstraintSyntax::constraint_no_inherit_not_valid).
920    pub no_inherit: bool,
921    /// The `NOT VALID` marker (PostgreSQL/DuckDB): the constraint is recorded but existing rows
922    /// are not checked. Shares the `ConstraintAttributeSpec` slot with
923    /// [`no_inherit`](Self::no_inherit) and [`characteristics`](Self::characteristics).
924    /// PostgreSQL admits it only on `CHECK` and `FOREIGN KEY` constraints (`PRIMARY KEY` /
925    /// `UNIQUE` / `EXCLUDE` reject it — reproduced at parse), and there is no column-level
926    /// form. Gated for acceptance by the same
927    /// [`ConstraintSyntax::constraint_no_inherit_not_valid`](crate::dialect::ConstraintSyntax::constraint_no_inherit_not_valid)
928    /// flag as [`no_inherit`](Self::no_inherit).
929    pub not_valid: bool,
930    /// The trailing `DEFERRABLE`/`INITIALLY` characteristics, when written; boxed as
931    /// the cold clause it is (see [`ColumnConstraint::characteristics`]).
932    pub characteristics: Option<Box<ConstraintCharacteristics>>,
933    /// Source location and node identity.
934    pub meta: Meta,
935}
936
937#[derive(Clone, Debug, PartialEq, Eq, Hash)]
938#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
939#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
940/// The SQL table constraint forms represented by the AST.
941pub enum TableConstraint<X: Extension = NoExt> {
942    /// A `PRIMARY KEY (col, …)` table constraint.
943    PrimaryKey {
944        /// The key columns. Each is an [`IndexColumn`] — the same "indexed-column" shape
945        /// [`CreateIndex`] uses — so a column may carry a `COLLATE <collation>` postfix
946        /// (folded into [`IndexColumn::expr`] as an [`Expr::Collate`](super::Expr)) and an
947        /// `ASC`/`DESC` sort order. The bare-column case is an
948        /// [`Expr::Column`](super::Expr) with `asc`/`nulls_first` unset — the shape every
949        /// dialect produces. The COLLATE/ordering decoration is gated for *acceptance* by
950        /// [`ConstraintSyntax::constraint_column_collate_order`](crate::dialect::ConstraintSyntax::constraint_column_collate_order)
951        /// (SQLite): off elsewhere, where a bare name is the only accepted form and a
952        /// trailing `COLLATE`/`ASC`/`DESC` is a clean parse error. SQLite prohibits general
953        /// expressions and `NULLS FIRST`/`LAST` in this position, so the parser never fills
954        /// `nulls_first` nor produces a non-column/non-collate `expr` here.
955        columns: ThinVec<IndexColumn<X>>,
956        /// The `INCLUDE (<col>, ...)` covering-index columns (PostgreSQL): non-key payload
957        /// columns stored in the primary key's implicit index (`PRIMARY KEY (a) INCLUDE (b)`).
958        /// Empty when no `INCLUDE` clause is written. Gated for acceptance by
959        /// [`ConstraintSyntax::index_constraint_parameters`](crate::dialect::ConstraintSyntax::index_constraint_parameters);
960        /// off elsewhere, where `INCLUDE` after the key list is a clean parse error.
961        include: ThinVec<Ident>,
962        /// Source location and node identity.
963        meta: Meta,
964    },
965    /// A `UNIQUE (col, …)` table constraint.
966    Unique {
967        /// The key columns, as on [`PrimaryKey`](Self::PrimaryKey::columns): [`IndexColumn`]s
968        /// admitting a per-column `COLLATE <collation>` and `ASC`/`DESC` under the same
969        /// [`ConstraintSyntax::constraint_column_collate_order`](crate::dialect::ConstraintSyntax::constraint_column_collate_order)
970        /// gate.
971        columns: ThinVec<IndexColumn<X>>,
972        /// The `NULLS [NOT] DISTINCT` null-treatment (PostgreSQL 15+), as on the column-level
973        /// [`ColumnOption::Unique`]. `Some(false)` = `NULLS NOT DISTINCT`, `Some(true)` = the
974        /// explicit `NULLS DISTINCT` default, `None` = unwritten. Gated for acceptance by
975        /// [`ConstraintSyntax::index_constraint_parameters`](crate::dialect::ConstraintSyntax::index_constraint_parameters).
976        nulls_not_distinct: Option<bool>,
977        /// The `INCLUDE (<col>, ...)` covering-index columns (PostgreSQL), as on
978        /// [`PrimaryKey`](Self::PrimaryKey::include). Empty when unwritten. Same gate.
979        include: ThinVec<Ident>,
980        /// Source location and node identity.
981        meta: Meta,
982    },
983    /// A `CHECK (<predicate>)` table constraint.
984    Check {
985        /// Expression evaluated by this syntax.
986        expr: Box<Expr<X>>,
987        /// Source location and node identity.
988        meta: Meta,
989    },
990    /// A PostgreSQL `EXCLUDE [USING <method>] (<element> WITH <operator> [, ...]) [INCLUDE
991    /// (...)] [WITH (...)] [USING INDEX TABLESPACE ...] [WHERE (...)]` exclusion constraint —
992    /// no two rows may hold values that all pairwise satisfy the named operators. The whole
993    /// clause is boxed into [`ExcludeConstraint`] so this cold, rarely-written variant does not
994    /// widen the warm `TableConstraint` enum (the box/inline budget — the same reason
995    /// [`ForeignKey`](Self::ForeignKey) boxes its `ForeignKeyRef`). Gated for acceptance by
996    /// [`ConstraintSyntax::exclusion_constraints`](crate::dialect::ConstraintSyntax::exclusion_constraints); off
997    /// elsewhere (DuckDB included, which rejects it), where `EXCLUDE` at a constraint position is
998    /// a clean parse error.
999    Exclude {
1000        /// The exclusion-constraint specification; see [`ExcludeConstraint`].
1001        exclude: Box<ExcludeConstraint<X>>,
1002        /// Source location and node identity.
1003        meta: Meta,
1004    },
1005    /// A `FOREIGN KEY (col, …) REFERENCES …` table constraint.
1006    ForeignKey {
1007        /// Columns in source order.
1008        columns: ThinVec<Ident>,
1009        // Boxed for the same fat-variant reason as `ColumnOption::References`: the
1010        // inline `ForeignKeyRef` (48 B) alone set `TableConstraint`'s width — and
1011        // transitively that of the `TableConstraintDef` containing it. Cold DDL path,
1012        // so the lone allocation is cheap (ADR-0007, box/inline budget benchmark).
1013        /// The foreign-key target table, columns, and options; see [`ForeignKeyRef`].
1014        references: Box<ForeignKeyRef>,
1015        /// Source location and node identity.
1016        meta: Meta,
1017    },
1018    /// A trailing bodyless `CONSTRAINT <name>` (SQLite), standalone in the table-element list
1019    /// (`CREATE TABLE t (a INT, CONSTRAINT cn)`) — the [`TableConstraintDef::name`] is written
1020    /// but no constraint element follows it. SQLite also lets the comma separating this from a
1021    /// preceding table constraint be omitted (`UNIQUE(a) CONSTRAINT cn`, engine-measured
1022    /// accepted); the parser's table-element loop handles that comma elision, not this variant.
1023    /// Gated for acceptance by
1024    /// [`ConstraintSyntax::bare_constraint_name`](crate::dialect::ConstraintSyntax::bare_constraint_name); off
1025    /// elsewhere, where a `CONSTRAINT <name>` with nothing following is a clean parse error.
1026    Bare {
1027        /// Source location and node identity.
1028        meta: Meta,
1029    },
1030    /// Dialect extension node supplied by the extension type.
1031    Other {
1032        /// The dialect extension node value.
1033        ext: X,
1034        /// Source location and node identity.
1035        meta: Meta,
1036    },
1037}
1038
1039/// The body of a PostgreSQL [`EXCLUDE`](TableConstraint::Exclude) exclusion constraint.
1040///
1041/// `EXCLUDE [USING <method>] (<element> [, ...]) [INCLUDE (<col>, ...)] [WITH (<param>, ...)]
1042/// [USING INDEX TABLESPACE <name>] [WHERE (<predicate>)]` — the tail clauses appear in that
1043/// fixed PostgreSQL grammar order. Boxed inside [`TableConstraint::Exclude`] so the cold clause
1044/// does not widen the warm constraint enum.
1045#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1046#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1047#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1048pub struct ExcludeConstraint<X: Extension = NoExt> {
1049    /// The `USING <method>` index access method (`gist`, `btree`, ...); `None` for the bare
1050    /// `EXCLUDE (...)` form, which PostgreSQL defaults to `btree`. A single identifier — a
1051    /// qualified method is a raw-parse error.
1052    pub method: Option<Ident>,
1053    /// The exclusion elements, each an index element paired with an operator (`c WITH &&`);
1054    /// non-empty (an empty `()` is a raw-parse error).
1055    pub elements: ThinVec<ExcludeElement<X>>,
1056    /// include in source order.
1057    pub include: ThinVec<Ident>,
1058    /// The `WITH (<param> [= <value>], ...)` index storage parameters (reloptions); empty when
1059    /// unwritten. Reuses [`TableStorageParameter`], the same element the `CREATE TABLE … WITH
1060    /// (…)` option list holds.
1061    pub with_params: ThinVec<TableStorageParameter<X>>,
1062    /// Optional index tablespace for this syntax.
1063    pub index_tablespace: Option<Ident>,
1064    /// The `WHERE (<predicate>)` partial-constraint predicate; `None` when unwritten. Boxed as a
1065    /// cold clause so an exclusion constraint without a predicate pays only a null pointer.
1066    pub predicate: Option<Box<Expr<X>>>,
1067    /// Source location and node identity.
1068    pub meta: Meta,
1069}
1070
1071/// One `<index_element> WITH <operator>` element of an [`ExcludeConstraint`] (PostgreSQL
1072/// `ExclusionConstraintElem`).
1073///
1074/// The index element is PostgreSQL's `index_elem`: a key (a bare column, a bare function call,
1075/// or a parenthesized `(a_expr)` — the [`parenthesized`](Self::parenthesized) flag records which
1076/// so the source form round-trips), then optional `COLLATE`, an operator-class name with
1077/// optional reloptions, and `ASC`/`DESC` + `NULLS FIRST`/`LAST` sort modifiers — exactly the
1078/// [`CreateIndex`] key grammar. The `WITH <operator>` names the exclusion operator.
1079#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1080#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1081#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1082pub struct ExcludeElement<X: Extension = NoExt> {
1083    /// Expression evaluated by this syntax.
1084    pub expr: Expr<X>,
1085    /// Whether the key was written parenthesized (`(a + b)`); a bare column / function call is
1086    /// unparenthesized. Mirrors [`PartitionElem::parenthesized`].
1087    pub parenthesized: bool,
1088    /// Optional collation for this syntax.
1089    pub collation: Option<ObjectName>,
1090    /// The operator-class name (`index_elem` `opt_qualified_name`), when written — `a int4_ops`,
1091    /// `a myschema.text_ops`.
1092    pub opclass: Option<ObjectName>,
1093    /// The operator-class reloptions `(<param> [= <value>], ...)` (`index_elem` `reloptions`),
1094    /// present only with an [`opclass`](Self::opclass); empty otherwise.
1095    pub opclass_params: ThinVec<TableStorageParameter<X>>,
1096    /// `ASC` (`Some(true)`) / `DESC` (`Some(false)`) sort order; `None` when unwritten.
1097    pub asc: Option<bool>,
1098    /// `NULLS FIRST` (`Some(true)`) / `NULLS LAST` (`Some(false)`); `None` when unwritten.
1099    pub nulls_first: Option<bool>,
1100    /// Operator applied by this expression.
1101    pub operator: ExcludeOperator,
1102    /// Source location and node identity.
1103    pub meta: Meta,
1104}
1105
1106/// The `WITH <operator>` operator of an [`ExcludeElement`] — PostgreSQL `any_operator`, a bare
1107/// symbolic operator (`&&`, `=`, `-|-`) or the explicit `OPERATOR(<schema>.<op>)` keyword form.
1108///
1109/// Models the operator the same way [`NamedOperatorExpr`](super::NamedOperatorExpr) does — an
1110/// optional schema qualification, the interned symbolic [`op`](Self::op), and a
1111/// [`spelling`](Self::spelling) tag recording the bare-vs-`OPERATOR(...)` surface — so the source
1112/// form round-trips. A leaf node without `meta`: its span is subsumed by the owning
1113/// [`ExcludeElement`], like [`DropBehavior`].
1114#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1115#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1116#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1117pub struct ExcludeOperator {
1118    /// The optional schema qualification (`pg_catalog` in `OPERATOR(pg_catalog.=)`); an empty
1119    /// name for a bare or unqualified operator.
1120    pub schema: ObjectName,
1121    /// The symbolic operator spelling (`&&`, `=`, `-|-`), interned exact-case.
1122    pub op: Symbol,
1123    /// Which surface spelled the operator — a bare `&&` or the explicit `OPERATOR(...)` keyword.
1124    pub spelling: NamedOperatorSpelling,
1125}
1126
1127#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1128#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1129#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1130/// An SQL create table option.
1131pub struct CreateTableOption<X: Extension = NoExt> {
1132    /// Which option kind (`WITH`/`ON COMMIT`/`TABLESPACE`/…); see [`CreateTableOptionKind`].
1133    pub kind: CreateTableOptionKind<X>,
1134    /// Source location and node identity.
1135    pub meta: Meta,
1136}
1137
1138#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1139#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1140#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1141/// The SQL create table option kind forms represented by the AST.
1142pub enum CreateTableOptionKind<X: Extension = NoExt> {
1143    /// `COLOCATE WITH <anchor> ON (<columns>)`.
1144    ColocateWith {
1145        /// Anchor table.
1146        table: ObjectName,
1147        /// This table's colocation-key columns.
1148        columns: ThinVec<Ident>,
1149        /// Source location and node identity.
1150        meta: Meta,
1151    },
1152    /// `IN COLOCATION GROUP <group> [ON (<columns>)]`.
1153    InColocationGroup {
1154        /// Group name.
1155        group: Ident,
1156        /// Optional member-key columns; omission is valid with explicit table storage options.
1157        columns: ThinVec<Ident>,
1158        /// Source location and node identity.
1159        meta: Meta,
1160    },
1161    /// A `WITH (<param> = <value>, …)` storage-parameter clause.
1162    With {
1163        /// params in source order.
1164        params: ThinVec<TableStorageParameter<X>>,
1165        /// Source location and node identity.
1166        meta: Meta,
1167    },
1168    /// An `ON COMMIT {PRESERVE ROWS | DELETE ROWS | DROP}` temporary-table clause.
1169    OnCommit {
1170        /// The on-commit action; see [`OnCommitAction`].
1171        action: OnCommitAction,
1172        /// Source location and node identity.
1173        meta: Meta,
1174    },
1175    /// A `TABLESPACE <name>` clause.
1176    Tablespace {
1177        /// The tablespace name.
1178        tablespace: Ident,
1179        /// Source location and node identity.
1180        meta: Meta,
1181    },
1182    /// A MySQL trailing table option written as an open `<name> [=] <value>` pair —
1183    /// `ENGINE = InnoDB`, `AUTO_INCREMENT = 100`, `DEFAULT CHARSET = utf8mb4`,
1184    /// `COMMENT = '...'`, `ROW_FORMAT = DYNAMIC`, `COLLATE = utf8mb4_general_ci`.
1185    ///
1186    /// Modelled as one canonical name/value pair rather than a variant (or a
1187    /// `CreateTable` field) per option keyword: the MySQL option
1188    /// vocabulary is large and server-version-dependent, so a single open shape
1189    /// round-trips an arbitrary option list — the same reasoning the [`CopyOption`]
1190    /// list follows. The optional `DEFAULT` noise word MySQL accepts before
1191    /// `CHARSET`/`COLLATE` is normalized away by the parser, and the `=` is optional.
1192    /// Gated by `create_table_clause_syntax.table_options`.
1193    ///
1194    /// [`CopyOption`]: super::CopyOption
1195    KeyValue {
1196        // The whole `<name> = <value>` payload is boxed (ADR-0007): a `TableOption`
1197        // (name + a 40 B value + meta) is the only fat payload among these variants, so
1198        // inlining it would set the enum's width and tax the lean `With`/`OnCommit`/
1199        // `Tablespace` variants — and, transitively, every `CreateTableOption` an
1200        // ANSI/PostgreSQL parse stores in its options `ThinVec`. Table options are a
1201        // cold DDL path, so the lone allocation per MySQL option is cheap, the same
1202        // box call `ColumnOption::Identity` makes for its `IdentityColumn`.
1203        /// The `<name> = <value>` option pair; see [`TableOption`].
1204        option: Box<TableOption>,
1205        /// Source location and node identity.
1206        meta: Meta,
1207    },
1208    /// The SQLite `WITHOUT ROWID` trailing table option: the table is stored as a
1209    /// clustered index on its primary key rather than the default implicit `rowid`.
1210    /// A bare keyword-style option (no `= value`), so a variant of its own rather than
1211    /// the MySQL [`KeyValue`](Self::KeyValue) name/value catch-all. SQLite
1212    /// comma-separates these trailing options (`... STRICT, WITHOUT ROWID`). Gated for
1213    /// acceptance by
1214    /// [`CreateTableClauseSyntax::without_rowid_table_option`](crate::dialect::CreateTableClauseSyntax::without_rowid_table_option).
1215    WithoutRowid {
1216        /// Source location and node identity.
1217        meta: Meta,
1218    },
1219    /// The SQLite `STRICT` trailing table option: the table enforces its declared
1220    /// column types instead of SQLite's default flexible typing. Like
1221    /// [`WithoutRowid`](Self::WithoutRowid) a bare keyword-style option, gated for
1222    /// acceptance by
1223    /// [`CreateTableClauseSyntax::strict_table_option`](crate::dialect::CreateTableClauseSyntax::strict_table_option).
1224    Strict {
1225        /// Source location and node identity.
1226        meta: Meta,
1227    },
1228    /// The legacy PostgreSQL `WITHOUT OIDS` trailing option: historically suppressed the
1229    /// system `oid` column. Modern PostgreSQL keeps it in the grammar as an accepted no-op,
1230    /// while the affirmative `WITH OIDS` has no production and *rejects* (both
1231    /// engine-measured). A bare keyword-style option, so a
1232    /// variant of its own rather than the MySQL [`KeyValue`](Self::KeyValue) catch-all. It
1233    /// occupies PostgreSQL's `OptWith` slot — mutually exclusive with a `WITH (…)` storage-
1234    /// parameter list, and it precedes `ON COMMIT` / `TABLESPACE`. Gated for acceptance by
1235    /// [`CreateTableClauseSyntax::without_oids`](crate::dialect::CreateTableClauseSyntax::without_oids).
1236    WithoutOids {
1237        /// Source location and node identity.
1238        meta: Meta,
1239    },
1240}
1241
1242/// One MySQL trailing table option: a `<name> = <value>` pair.
1243///
1244/// The canonical "options as data" shape: `name` is the option keyword as
1245/// written (`ENGINE`, `AUTO_INCREMENT`, `CHARSET`, ...) and `value` is its argument.
1246/// Boxed inside [`CreateTableOptionKind::KeyValue`] to keep that enum lean; mirrors
1247/// the [`CopyOption`](super::CopyOption) list element.
1248#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1249#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1250#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1251pub struct TableOption {
1252    /// Name referenced by this syntax.
1253    pub name: Ident,
1254    /// Value supplied by this syntax.
1255    pub value: TableOptionValue,
1256    /// Source location and node identity.
1257    pub meta: Meta,
1258}
1259
1260/// The value of a [`TableOption`].
1261///
1262/// Three surface forms a downstream converter cannot otherwise recover:
1263/// a bareword/keyword (`InnoDB`, `DYNAMIC`), a string (`COMMENT = '...'`), or a
1264/// number (`AUTO_INCREMENT = 100`). String and numeric values ride their
1265/// [`Literal`]'s `meta.span` and materialise lazily; a bareword keeps its
1266/// source spelling on the [`Ident`]. Mirrors [`CopyOptionValue`](super::CopyOptionValue),
1267/// which omits the numeric form COPY options never take.
1268#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1269#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1270#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1271pub enum TableOptionValue {
1272    /// A bareword/keyword value: `ENGINE = InnoDB`, `ROW_FORMAT = DYNAMIC`.
1273    Word {
1274        /// Identifier-form value.
1275        word: Ident,
1276        /// Source location and node identity.
1277        meta: Meta,
1278    },
1279    /// A string value: `COMMENT = 'text'`.
1280    String {
1281        /// Value supplied by this syntax.
1282        value: Literal,
1283        /// Source location and node identity.
1284        meta: Meta,
1285    },
1286    /// A numeric value: `AUTO_INCREMENT = 100`, `KEY_BLOCK_SIZE = 8`.
1287    Number {
1288        /// Value supplied by this syntax.
1289        value: Literal,
1290        /// Source location and node identity.
1291        meta: Meta,
1292    },
1293}
1294
1295#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1296#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1297#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1298/// The SQL on commit action forms represented by the AST.
1299pub enum OnCommitAction {
1300    /// `ON COMMIT PRESERVE ROWS` — keep the temp table's rows after commit.
1301    PreserveRows,
1302    /// `ON COMMIT DELETE ROWS` — empty the temp table on each commit.
1303    DeleteRows,
1304    /// `ON COMMIT DROP` — drop the temp table at the end of the transaction.
1305    Drop,
1306}
1307
1308#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1309#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1310#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1311/// An SQL table storage parameter.
1312pub struct TableStorageParameter<X: Extension = NoExt> {
1313    /// Name referenced by this syntax.
1314    pub name: ObjectName,
1315    /// Value supplied by this syntax.
1316    pub value: Option<Expr<X>>,
1317    /// Source location and node identity.
1318    pub meta: Meta,
1319}
1320
1321/// `ALTER TABLE [IF EXISTS] <name> <action> [, <action>]...`.
1322///
1323/// The schema-evolution counterpart of [`CreateTable`]. A single statement carries
1324/// one or more comma-separated [`AlterTableAction`]s, matching PostgreSQL's
1325/// multi-action form (`ALTER TABLE t ADD COLUMN a INT, DROP COLUMN b`). The `IF
1326/// EXISTS` prefix is gated by `existence_guards.if_exists` dialect data:
1327/// off under ANSI, on under PostgreSQL. The PostgreSQL-only `ONLY`
1328/// inheritance qualifier is a deliberate follow-up, tracked separately.
1329#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1330#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1331#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1332pub struct AlterTable<X: Extension = NoExt> {
1333    /// Whether the if exists form was present in the source.
1334    pub if_exists: bool,
1335    /// Name referenced by this syntax.
1336    pub name: ObjectName,
1337    /// actions in source order.
1338    pub actions: ThinVec<AlterTableAction<X>>,
1339    /// Source location and node identity.
1340    pub meta: Meta,
1341}
1342
1343/// A column target inside an `ALTER TABLE` action.
1344///
1345/// Most dialects name a top-level column (`c`), while DuckDB also admits dotted paths into
1346/// nested STRUCT fields for selected actions (`s.s2.k`). Kept separate from [`ObjectName`]
1347/// so ALTER-specific nested paths do not imply that table columns generally use relation-name
1348/// semantics.
1349#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1350#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1351#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1352pub struct AlterColumnTarget {
1353    /// parts in source order.
1354    pub parts: ThinVec<Ident>,
1355    /// Source location and node identity.
1356    pub meta: Meta,
1357}
1358
1359/// One action in an `ALTER TABLE` statement.
1360///
1361/// `AddColumn`/`AddConstraint` reuse the `CREATE TABLE` element nodes ([`ColumnDef`]
1362/// and [`TableConstraintDef`]) rather than inventing alter-specific copies, so the
1363/// `Other(X)` extension seams already on [`ColumnOption`]/[`TableConstraint`] cover
1364/// custom DDL attached through an alter too. The optional `COLUMN` noise
1365/// word is not represented: rendering normalizes to the canonical `ADD COLUMN`
1366/// spelling.
1367#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1368#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1369#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1370pub enum AlterTableAction<X: Extension = NoExt> {
1371    /// `SET COLOCATION GROUP <group>`.
1372    SetColocationGroup {
1373        /// Destination group.
1374        group: Ident,
1375        /// Source location and node identity.
1376        meta: Meta,
1377    },
1378    /// `DROP COLOCATION GROUP`.
1379    DropColocationGroup {
1380        /// Source location and node identity.
1381        meta: Meta,
1382    },
1383    /// `ADD [COLUMN] <col> <type> …` — add a column to the table.
1384    AddColumn {
1385        /// Whether the if not exists form was present in the source.
1386        if_not_exists: bool,
1387        /// Whether the optional `COLUMN` noise word was written (`ADD COLUMN c` vs the
1388        /// bare `ADD c`). Exact-synonym fidelity; the canonical render emits `COLUMN`.
1389        column_keyword: bool,
1390        /// Object targeted by this syntax.
1391        target: Option<AlterColumnTarget>,
1392        /// Column referenced by this syntax.
1393        column: ColumnDef<X>,
1394        /// Source location and node identity.
1395        meta: Meta,
1396    },
1397    /// `DROP [COLUMN] <name>` — remove a column from the table.
1398    DropColumn {
1399        /// Whether the if exists form was present in the source.
1400        if_exists: bool,
1401        /// Whether the optional `COLUMN` noise word was written (`DROP COLUMN c` vs the
1402        /// bare `DROP c`). Exact-synonym fidelity; the canonical render emits `COLUMN`.
1403        column_keyword: bool,
1404        /// Name referenced by this syntax.
1405        name: AlterColumnTarget,
1406        /// Optional behavior for this syntax.
1407        behavior: Option<DropBehavior>,
1408        /// Source location and node identity.
1409        meta: Meta,
1410    },
1411    /// `ALTER [COLUMN] <name> …` — change an existing column's definition.
1412    AlterColumn {
1413        /// Whether the optional `COLUMN` noise word was written (`ALTER COLUMN c` vs the
1414        /// bare `ALTER c`). Exact-synonym fidelity; the canonical render emits `COLUMN`.
1415        column_keyword: bool,
1416        /// Name referenced by this syntax.
1417        name: Ident,
1418        /// The per-column change; see [`AlterColumnAction`].
1419        change: AlterColumnAction<X>,
1420        /// Source location and node identity.
1421        meta: Meta,
1422    },
1423    /// `ADD CONSTRAINT …` — add a table constraint.
1424    AddConstraint {
1425        /// The constraint being added; see [`TableConstraintDef`].
1426        constraint: TableConstraintDef<X>,
1427        /// Source location and node identity.
1428        meta: Meta,
1429    },
1430    /// `DROP CONSTRAINT <name>` — remove a table constraint.
1431    DropConstraint {
1432        /// Whether the if exists form was present in the source.
1433        if_exists: bool,
1434        /// Name referenced by this syntax.
1435        name: Ident,
1436        /// Optional behavior for this syntax.
1437        behavior: Option<DropBehavior>,
1438        /// Source location and node identity.
1439        meta: Meta,
1440    },
1441    /// `DROP PRIMARY KEY` — remove the table's unnamed primary key.
1442    DropPrimaryKey {
1443        /// Optional drop behavior.
1444        behavior: Option<DropBehavior>,
1445        /// Source location and node identity.
1446        meta: Meta,
1447    },
1448    /// `SET (<name> = <value>, …)` — replace table storage/property options.
1449    SetOptions {
1450        /// Options in source order.
1451        params: ThinVec<TableStorageParameter<X>>,
1452        /// Source location and node identity.
1453        meta: Meta,
1454    },
1455    /// `RENAME [COLUMN] <name> TO <new_name>`.
1456    ///
1457    /// PostgreSQL parses the rename forms as a distinct `RenameStmt`, not the
1458    /// `AlterTableStmt` the other actions take; the canonical AST keeps one
1459    /// [`AlterTable`] node for every `ALTER TABLE` spelling, so the rename
1460    /// is a further action variant here. The optional `COLUMN` noise word is
1461    /// recorded, mirroring [`AddColumn`](Self::AddColumn).
1462    RenameColumn {
1463        /// Whether the optional `COLUMN` noise word was written (`RENAME COLUMN c` vs
1464        /// the bare `RENAME c`). Exact-synonym fidelity; the canonical render emits
1465        /// `COLUMN`.
1466        column_keyword: bool,
1467        /// Name referenced by this syntax.
1468        name: AlterColumnTarget,
1469        /// The column's new name.
1470        new_name: Ident,
1471        /// Source location and node identity.
1472        meta: Meta,
1473    },
1474    /// `RENAME CONSTRAINT <name> TO <new_name>`.
1475    RenameConstraint {
1476        /// Existing constraint name.
1477        name: Ident,
1478        /// Replacement constraint name.
1479        new_name: Ident,
1480        /// Source location and node identity.
1481        meta: Meta,
1482    },
1483    /// `RENAME TO <new_name>` — rename the table.
1484    RenameTable {
1485        /// The table's new name.
1486        new_name: Ident,
1487        /// Source location and node identity.
1488        meta: Meta,
1489    },
1490    /// `ATTACH PARTITION <name> <bound>` — attach an existing table as a partition
1491    /// (PostgreSQL declarative partitioning). A *standalone* action: PostgreSQL parses
1492    /// `ATTACH`/`DETACH PARTITION` as their own `AlterTableStmt` productions, so they never
1493    /// combine with other actions in a comma list (`ALTER TABLE p ADD COLUMN x, ATTACH …` is a
1494    /// syntax error). Gated by
1495    /// [`CreateTableClauseSyntax::declarative_partitioning`](crate::dialect::CreateTableClauseSyntax::declarative_partitioning).
1496    AttachPartition {
1497        /// The partition (child) table name.
1498        partition: ObjectName,
1499        // Boxed as the fat child (matching the `PartitionOf` body): a `PartitionBound` is the
1500        // widest payload here, so a partition attach pays one cold allocation rather than setting
1501        // `AlterTableAction`'s width (ADR-0007, box/inline budget).
1502        /// The partition bound (`FOR VALUES …` / `DEFAULT`); see [`PartitionBound`].
1503        bound: Box<PartitionBound<X>>,
1504        /// Source location and node identity.
1505        meta: Meta,
1506    },
1507    /// `DETACH PARTITION <name> [CONCURRENTLY | FINALIZE]` — detach a partition (PostgreSQL).
1508    /// Like [`AttachPartition`](Self::AttachPartition) a standalone action. The optional
1509    /// [`mode`](Self::DetachPartition::mode) records the `CONCURRENTLY` / `FINALIZE`
1510    /// qualifier; `None` for the bare form.
1511    DetachPartition {
1512        /// The partition (child) table name.
1513        partition: ObjectName,
1514        /// Mode selected by this syntax.
1515        mode: Option<DetachPartitionMode>,
1516        /// Source location and node identity.
1517        meta: Meta,
1518    },
1519}
1520
1521/// The qualifier on a `DETACH PARTITION` action. A `Copy` leaf enum whose span rides the owning
1522/// [`AlterTableAction::DetachPartition`], like [`DropBehavior`].
1523#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1524#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1525#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1526pub enum DetachPartitionMode {
1527    /// `CONCURRENTLY` — detach without a long-held lock (PostgreSQL).
1528    Concurrently,
1529    /// `FINALIZE` — complete an interrupted concurrent detach.
1530    Finalize,
1531}
1532
1533/// A single-column alteration inside `ALTER TABLE ... ALTER COLUMN <name> ...`.
1534///
1535/// The ANSI `SET DATA TYPE` and PostgreSQL `TYPE` spellings map to one canonical
1536/// [`SetDataType`](Self::SetDataType) variant; the optional PostgreSQL
1537/// `USING <expr>` conversion expression rides along when present.
1538#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1539#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1540#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1541pub enum AlterColumnAction<X: Extension = NoExt> {
1542    /// `SET DEFAULT <expr>` — set the column's default value.
1543    SetDefault {
1544        /// Expression evaluated by this syntax.
1545        expr: Box<Expr<X>>,
1546        /// Source location and node identity.
1547        meta: Meta,
1548    },
1549    /// `DROP DEFAULT` — remove the column's default value.
1550    DropDefault {
1551        /// Source location and node identity.
1552        meta: Meta,
1553    },
1554    /// `SET NOT NULL` — add a not-null constraint to the column.
1555    SetNotNull {
1556        /// Source location and node identity.
1557        meta: Meta,
1558    },
1559    /// `DROP NOT NULL` — remove the column's not-null constraint.
1560    DropNotNull {
1561        /// Source location and node identity.
1562        meta: Meta,
1563    },
1564    /// `ADD GENERATED {ALWAYS | BY DEFAULT} AS IDENTITY [(…)]` — add identity generation
1565    /// to an existing column.
1566    AddIdentity {
1567        /// Identity generation mode and sequence options.
1568        identity: Box<IdentityColumn<X>>,
1569        /// Source location and node identity.
1570        meta: Meta,
1571    },
1572    /// `SET DATA TYPE <t>` / `TYPE <t> [USING …]` — change the column's data type.
1573    SetDataType {
1574        /// Whether the ANSI `SET DATA` prefix was written (`SET DATA TYPE <t>`) versus
1575        /// the bare PostgreSQL `TYPE <t>`. Exact-synonym fidelity; the canonical render
1576        /// emits `SET DATA TYPE`.
1577        set_data: bool,
1578        /// Data type named by this syntax.
1579        data_type: DataType<X>,
1580        /// Optional using for this syntax.
1581        using: Option<Box<Expr<X>>>,
1582        /// Source location and node identity.
1583        meta: Meta,
1584    },
1585}
1586
1587/// `DROP {TABLE | VIEW | INDEX | SCHEMA} [IF EXISTS] <name> [, ...] [CASCADE | RESTRICT]`.
1588///
1589/// Non-generic: a drop names objects and a behaviour only — it carries no
1590/// expressions or extension nodes — so it parallels the other leaf statement
1591/// payloads ([`super::TransactionStatement`], [`super::SessionStatement`]). The `IF
1592/// EXISTS` prefix and the `CASCADE`/`RESTRICT` drop behaviour are each gated by
1593/// `schema_change_syntax` dialect data. Materialized-view and concurrent-index drop
1594/// surfaces are not modelled here.
1595#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1596#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1597#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1598pub struct DropStatement {
1599    /// Which object kind is dropped (`TABLE`/`VIEW`/…); see [`DropObjectKind`].
1600    pub object_kind: DropObjectKind,
1601    /// Whether the if exists form was present in the source.
1602    pub if_exists: bool,
1603    /// Names in source order.
1604    pub names: ThinVec<ObjectName>,
1605    /// Optional behavior for this syntax.
1606    pub behavior: Option<DropBehavior>,
1607    /// Source location and node identity.
1608    pub meta: Meta,
1609}
1610
1611/// The kind of object a `DROP` statement removes.
1612///
1613/// The routine kinds (`FUNCTION`/`PROCEDURE`) take an argument-type signature, so
1614/// they are a separate [`Statement::DropRoutine`]
1615/// statement rather than a variant here;
1616/// this covers the object kinds a plain name list can drop.
1617#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1618#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1619#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1620pub enum DropObjectKind {
1621    /// `DROP TABLE`.
1622    Table,
1623    /// `DROP VIEW`.
1624    View,
1625    /// `DROP MATERIALIZED VIEW`.
1626    MaterializedView,
1627    /// `DROP INDEX`.
1628    Index,
1629    /// `DROP SCHEMA`.
1630    Schema,
1631    /// `DROP TYPE <name> [, …]` — a user-defined type (DuckDB/PostgreSQL), gated by
1632    /// [`StatementDdlGates::create_type`](crate::dialect::StatementDdlGates::create_type) (the same
1633    /// flag that admits [`CreateType`]). The comma list and `CASCADE`/`RESTRICT` behaviour
1634    /// ride the shared [`DropStatement`] grammar; DuckDB parse-accepts a multi-name list and
1635    /// only rejects it at plan time ("can only drop one object at a time"), so the shape is
1636    /// the general one.
1637    Type,
1638    /// `DROP SEQUENCE <name> [, …]` — a sequence generator (DuckDB/PostgreSQL), gated by
1639    /// [`StatementDdlGates::create_sequence`](crate::dialect::StatementDdlGates::create_sequence) (the same
1640    /// flag that admits [`CreateSequence`]). The comma list and `CASCADE`/`RESTRICT` ride the
1641    /// shared [`DropStatement`] grammar. DuckDB's parser accepts `DROP SEQUENCE` (the
1642    /// `IF EXISTS` form binds; the bare/list/`CASCADE` forms parse but bind-reject a missing
1643    /// object — engine-measured), so the modelled shape is the general one.
1644    Sequence,
1645    /// `DROP MACRO <name> [, …]` — a scalar macro (DuckDB), gated by
1646    /// [`StatementDdlGates::create_macro`](crate::dialect::StatementDdlGates::create_macro) (the same
1647    /// flag that admits [`CreateMacro`]). Unlike PostgreSQL's `DROP FUNCTION`, the macro drop
1648    /// takes *no* argument-type signature (`DROP MACRO m(int)` is a DuckDB syntax error), so it
1649    /// rides the shared [`DropStatement`] name-list grammar rather than the signature
1650    /// [`Statement::DropRoutine`] path — the `FUNCTION` spelling
1651    /// (which DuckDB accepts as a synonym) still routes to that routine drop, so the two DROP
1652    /// spellings land on distinct nodes and no [`MacroSpelling`] tag is needed here. The comma
1653    /// list and `CASCADE`/`RESTRICT` ride the shared grammar; DuckDB parse-accepts a multi-name
1654    /// list and only bind-rejects it ("can only drop one object at a time" — engine-measured).
1655    Macro,
1656    /// `DROP MACRO TABLE <name> [, …]` — a *table* macro (DuckDB), the drop counterpart of a
1657    /// `CREATE MACRO … AS TABLE`. Separate from [`Macro`](DropObjectKind::Macro) because DuckDB
1658    /// keeps scalar and table macros in distinct namespaces (`DROP MACRO m` drops a "Macro
1659    /// Function", `DROP MACRO TABLE m` a "Table Macro Function" — engine-measured) and the
1660    /// `TABLE` keyword must round-trip verbatim. Gated by the same
1661    /// [`StatementDdlGates::create_macro`](crate::dialect::StatementDdlGates::create_macro) flag;
1662    /// only `MACRO TABLE` (not `TABLE MACRO` or `FUNCTION TABLE`) is accepted — engine-measured.
1663    MacroTable,
1664    /// `DROP TRIGGER [IF EXISTS] [<schema> .] <name>` — a trigger (MySQL / SQLite), gated by
1665    /// either trigger-modelling flag (SQLite's
1666    /// [`StatementDdlGates::create_trigger`](crate::dialect::StatementDdlGates::create_trigger) or
1667    /// MySQL's
1668    /// [`StatementDdlGates::compound_statements`](crate::dialect::StatementDdlGates::compound_statements)).
1669    /// Both dialects spell the same name-only drop (no `ON <table>`, which is PostgreSQL's
1670    /// separate trigger-drop shape), so it rides the shared [`DropStatement`] name-list grammar.
1671    Trigger,
1672}
1673
1674/// The dependency behaviour of a `DROP` or `ALTER TABLE ... DROP`: whether dependent
1675/// objects are dropped too (`CASCADE`) or block the drop (`RESTRICT`).
1676#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1677#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1678#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1679pub enum DropBehavior {
1680    /// `CASCADE` — also drop objects that depend on the target.
1681    Cascade,
1682    /// `RESTRICT` — refuse the drop if any object depends on the target.
1683    Restrict,
1684}
1685
1686/// A PostgreSQL `DROP TRANSFORM [IF EXISTS] FOR <type> LANGUAGE <lang>
1687/// [CASCADE | RESTRICT]` statement (`DropTransformStmt`, gram.y).
1688///
1689/// Kept apart from [`DropStatement`] because a transform is not named by a plain
1690/// name list: it is identified by the `(type, language)` pair a `CREATE TRANSFORM`
1691/// registers, which is exactly the [`ObjectReference::Transform`] shape the shared
1692/// object-reference axis already models (also reached by `ALTER EXTENSION … ADD|DROP
1693/// TRANSFORM`). Reusing that variant keeps every transform reference on one node, so
1694/// an AST walk finds transforms uniformly regardless of the parent statement, rather
1695/// than re-deriving the `FOR type LANGUAGE lang` shape here (the reason [`DropRoutine`]
1696/// inlines its signature does not apply — a transform has a single fixed shape, not a
1697/// per-kind one).
1698///
1699/// PostgreSQL admits exactly one transform per statement (no comma list — engine-measured)
1700/// and places `IF EXISTS` between the `TRANSFORM` keyword and `FOR` (`DROP TRANSFORM IF
1701/// EXISTS FOR …`; `FOR type IF EXISTS LANGUAGE` is a syntax error), which is why the guard
1702/// rides on this node rather than on the shared reference render.
1703///
1704/// [`DropRoutine`]: super::Statement::DropRoutine
1705#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1706#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1707#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1708pub struct DropTransform<X: Extension = NoExt> {
1709    /// The dropped transform, always an [`ObjectReference::Transform`] (`FOR <type>
1710    /// LANGUAGE <lang>`).
1711    pub object: ObjectReference<X>,
1712    /// Whether the `IF EXISTS` guard was present in the source.
1713    pub if_exists: bool,
1714    /// Optional `CASCADE`/`RESTRICT` behaviour for this syntax.
1715    pub behavior: Option<DropBehavior>,
1716    /// Source location and node identity.
1717    pub meta: Meta,
1718}
1719
1720/// The object kind a [`Statement::CommentOn`] targets.
1721///
1722/// The object's name rides [`Statement::CommentOn::name`](super::Statement::CommentOn).
1723/// Targets with additional grammar carry that data in their variant.
1724#[non_exhaustive]
1725#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1726#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1727#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1728pub enum CommentTarget<X: Extension = NoExt> {
1729    /// `COMMENT ON TABLE <name>`.
1730    Table,
1731    /// `COMMENT ON COLUMN <name>`.
1732    Column,
1733    /// `COMMENT ON DATABASE <name>`.
1734    Database,
1735    /// `COMMENT ON VIEW <name>`.
1736    View,
1737    /// `COMMENT ON MATERIALIZED VIEW <name>`.
1738    MaterializedView,
1739    /// `COMMENT ON INDEX <name>`.
1740    Index,
1741    /// `COMMENT ON CONSTRAINT <name> ON <table>`.
1742    Constraint,
1743    /// `COMMENT ON PROCEDURE <name>[(<arg types>)]`. The argument-type list
1744    /// disambiguates overloaded procedures; `None` is an unspecified signature
1745    /// (bare `PROCEDURE foo`), `Some` a written list — possibly empty (`foo()`) —
1746    /// mirroring [`RoutineSignature::arg_types`](super::RoutineSignature).
1747    Procedure {
1748        /// Optional arg types for this syntax.
1749        arg_types: Option<ThinVec<DataType<X>>>,
1750    },
1751}
1752
1753/// The payload of a [`Statement::CommentOn`], boxed off the
1754/// statement enum to keep it within its size budget (like [`DropStatement`]).
1755///
1756/// `name` is the object's (possibly qualified) name; `target` records its kind (and, for
1757/// a procedure, its argument-type signature). `comment` is `None` for `IS NULL` — which
1758/// clears the object's comment — and `Some` for a string literal.
1759#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1760#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1761#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1762pub struct CommentOnStatement<X: Extension = NoExt> {
1763    /// Whether the front-position `IF EXISTS` guard was present.
1764    pub if_exists: bool,
1765    /// Object targeted by this syntax.
1766    pub target: CommentTarget<X>,
1767    /// Name referenced by this syntax.
1768    pub name: ObjectName,
1769    /// Relation owning a `CONSTRAINT` target; absent for every other target.
1770    pub constraint_table: Option<ObjectName>,
1771    /// Optional comment for this syntax.
1772    pub comment: Option<Literal>,
1773    /// Source location and node identity.
1774    pub meta: Meta,
1775}
1776
1777/// `CREATE SCHEMA [IF NOT EXISTS] [<name>] [AUTHORIZATION <role>] [<schema element> ...]`.
1778///
1779/// At least one of `name` / `authorization` is present — PostgreSQL's `CREATE SCHEMA
1780/// AUTHORIZATION joe` omits the schema name and derives it from the owning role.
1781///
1782/// The SQL-standard form embeds a list of component objects created *inside* the new
1783/// schema (`CREATE SCHEMA s CREATE TABLE t (...)`); [`elements`](Self::elements) holds
1784/// them as children so the whole construct stays ONE statement, mirroring PostgreSQL's
1785/// `CreateSchemaStmt.schemaElts` (which likewise carries the components as raw
1786/// statement parsenodes). Rendering them embedded preserves the statement COUNT that
1787/// downstream consumers observe — the earlier model split them into separate
1788/// `;`-joined statements, a statement-level rewrite no source-fidelity render can undo.
1789///
1790/// The element list is a *closed* admissible set enforced by the parser (measured
1791/// against PostgreSQL: `CREATE TABLE`/`VIEW`/`INDEX`/`SEQUENCE`/`TRIGGER` and `GRANT`
1792/// — and PostgreSQL rejects `CREATE MATERIALIZED VIEW`/`FUNCTION`, a nested `CREATE
1793/// SCHEMA`, `DROP`/`ALTER`/`INSERT`/… as elements). Modelled as a general
1794/// [`Statement`] list rather than a bespoke element enum — the same idiom as
1795/// [`CreateTrigger`]'s body — because the members ARE statements and reusing the
1796/// statement shape keeps the differential/structural oracles element-agnostic; the
1797/// closed set is the recorded acceptance bound, enforced at parse time. Non-empty only
1798/// under PostgreSQL (`schema_elements` gate); every other dialect parses a bare schema
1799/// head. Generic over `X` because the elements carry the extension node.
1800#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1801#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1802#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1803pub struct CreateSchema<X: Extension = NoExt> {
1804    /// Whether the if not exists form was present in the source.
1805    pub if_not_exists: bool,
1806    /// Name referenced by this syntax.
1807    pub name: Option<ObjectName>,
1808    /// Optional authorization for this syntax.
1809    pub authorization: Option<Ident>,
1810    /// The embedded schema-element statements, in source order. Empty for a bare
1811    /// `CREATE SCHEMA` head (the common case) and for every non-PostgreSQL dialect.
1812    pub elements: ThinVec<Statement<X>>,
1813    /// Source location and node identity.
1814    pub meta: Meta,
1815}
1816
1817/// `CREATE [OR REPLACE] [TEMP|TEMPORARY] VIEW` and `CREATE MATERIALIZED VIEW`,
1818/// with the `AS <query>` body that every view shares (a view body reuses [`Query`]
1819/// rather than re-deriving the SELECT grammar).
1820///
1821/// One node covers both spellings, distinguished by `materialized`. The divergent
1822/// tails are mutually exclusive by construction: the parser fills `check_option`
1823/// only for a regular view and `with_data` only for a materialized one. `OR
1824/// REPLACE` and the `MATERIALIZED` keyword (with its `WITH [NO] DATA` populate
1825/// clause) are PostgreSQL extensions gated by `schema_change_syntax` dialect data;
1826/// the ANSI `WITH [CASCADED|LOCAL] CHECK OPTION` is always accepted.
1827///
1828/// `recursive` records the `CREATE [OR REPLACE] [TEMP|TEMPORARY] RECURSIVE VIEW`
1829/// spelling (DuckDB, gated by
1830/// [`ViewSequenceClauseSyntax::recursive_views`](crate::dialect::ViewSequenceClauseSyntax::recursive_views)):
1831/// the keyword
1832/// sits between the `TEMP`/`TEMPORARY` prefix and `VIEW`, never composes with
1833/// `MATERIALIZED` (engine-rejected), and — mirroring the engine, which desugars a
1834/// recursive view to `WITH RECURSIVE` — requires the explicit `columns` list, so
1835/// the parser rejects the bare `RECURSIVE VIEW v AS …` form.
1836#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1837#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1838#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1839pub struct CreateView<X: Extension = NoExt> {
1840    /// Whether the or replace form was present in the source.
1841    pub or_replace: bool,
1842    /// The MySQL `[ALGORITHM = …] [DEFINER = …] [SQL SECURITY …]` definition-option prefix
1843    /// (see [`ViewOptions`]); all-`None` for every non-MySQL view and a bare MySQL view. The
1844    /// options sit between `OR REPLACE` and the `VIEW` keyword.
1845    pub options: ViewOptions,
1846    /// Whether the materialized form was present in the source.
1847    pub materialized: bool,
1848    /// Whether the recursive form was present in the source.
1849    pub recursive: bool,
1850    /// Optional temporary for this syntax.
1851    pub temporary: Option<TemporaryTableKind>,
1852    /// Whether the if not exists form was present in the source.
1853    pub if_not_exists: bool,
1854    /// Name referenced by this syntax.
1855    pub name: ObjectName,
1856    /// Columns in source order.
1857    pub columns: ThinVec<Ident>,
1858    /// Optional `TO <target>` storage relation for dialects that expose one.
1859    pub to: Option<ObjectName>,
1860    /// Query governed by this node.
1861    pub query: Box<Query<X>>,
1862    /// Optional check option for this syntax.
1863    pub check_option: Option<ViewCheckOption>,
1864    /// Whether the with data form was present in the source.
1865    pub with_data: Option<bool>,
1866    /// Source location and node identity.
1867    pub meta: Meta,
1868}
1869
1870/// `REFRESH MATERIALIZED VIEW [CONCURRENTLY] <name> [WITH [NO] DATA]`.
1871///
1872/// The modifier fields are retained even when a downstream engine chooses to reject them
1873/// semantically; parsing them prevents an accepted spelling from being silently discarded.
1874#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1875#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1876#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1877pub struct RefreshMaterializedView {
1878    /// Whether `CONCURRENTLY` was written.
1879    pub concurrently: bool,
1880    /// Materialized view being refreshed.
1881    pub name: ObjectName,
1882    /// `Some(true)` for `WITH DATA`, `Some(false)` for `WITH NO DATA`, and `None` when omitted.
1883    pub with_data: Option<bool>,
1884    /// Source location and node identity.
1885    pub meta: Meta,
1886}
1887
1888/// Partition function declared by a colocation group.
1889#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1890#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1891#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1892pub enum ColocationPartitionKind {
1893    /// Hash partitioning.
1894    Hash,
1895    /// Range partitioning.
1896    Range,
1897}
1898
1899/// `CREATE COLOCATION GROUP [IF NOT EXISTS] ...`.
1900#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1901#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1902#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1903pub struct CreateColocationGroup {
1904    /// Whether `IF NOT EXISTS` was written.
1905    pub if_not_exists: bool,
1906    /// Group name.
1907    pub name: Ident,
1908    /// Partition function.
1909    pub partition: ColocationPartitionKind,
1910    /// Partition-key columns.
1911    pub columns: ThinVec<Ident>,
1912    /// Shard count, preserved as the parsed integer literal for downstream range validation.
1913    pub shards: Literal,
1914    /// Source location and node identity.
1915    pub meta: Meta,
1916}
1917
1918/// `DROP COLOCATION GROUP [IF EXISTS] <name>`.
1919#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1920#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1921#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1922pub struct DropColocationGroup {
1923    /// Whether `IF EXISTS` was written.
1924    pub if_exists: bool,
1925    /// Group name.
1926    pub name: Ident,
1927    /// Source location and node identity.
1928    pub meta: Meta,
1929}
1930
1931/// `WITH [ CASCADED | LOCAL ] CHECK OPTION` on a regular (non-materialized) view.
1932#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1933#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1934#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1935pub enum ViewCheckOption {
1936    /// Bare `WITH CHECK OPTION`; PostgreSQL treats the unqualified form as `CASCADED`.
1937    Unspecified,
1938    /// `WITH CASCADED CHECK OPTION` — enforce the check on this and all underlying views.
1939    Cascaded,
1940    /// `WITH LOCAL CHECK OPTION` — enforce the check on this view only.
1941    Local,
1942}
1943
1944/// The MySQL `ALGORITHM = { UNDEFINED | MERGE | TEMPTABLE }` view-processing algorithm, the
1945/// first of the [`ViewOptions`] definition-option prefix. A `Copy` tag whose span rides the
1946/// owning [`ViewOptions`], like [`SqlSecurityContext`].
1947#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1948#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1949#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1950pub enum ViewAlgorithm {
1951    /// `ALGORITHM = UNDEFINED` — the server chooses between `MERGE` and `TEMPTABLE` (the default).
1952    Undefined,
1953    /// `ALGORITHM = MERGE` — the view's text is merged into the referencing statement.
1954    Merge,
1955    /// `ALGORITHM = TEMPTABLE` — the view result is materialized into a temporary table.
1956    TempTable,
1957}
1958
1959/// The MySQL view definition-option prefix `[ALGORITHM = …] [DEFINER = …] [SQL SECURITY …]`,
1960/// shared verbatim by [`CreateView`] and [`AlterView`] — the two statements name the same
1961/// options in the same fixed source order, immediately before the `VIEW` keyword.
1962///
1963/// Each option is independently optional; the source order (algorithm, then definer, then SQL
1964/// security) is engine-required — a permutation (`DEFINER` before `ALGORITHM`, `SQL SECURITY`
1965/// before either) is `ER_PARSE_ERROR`. All-`None` is the ordinary view with no prefix (every
1966/// non-MySQL dialect, and a bare MySQL `CREATE/ALTER VIEW`). The [`Definer`] reuses the shared
1967/// routine/event account reference; the [`SqlSecurityContext`] reuses the routine security tag.
1968/// This is a non-spanned bundle — [`Definer`] carries its own span, and the two `Copy` tags
1969/// ride the owning statement's span, so the prefix needs no `meta` of its own.
1970#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
1971#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1972#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1973pub struct ViewOptions {
1974    /// The optional `ALGORITHM = …` processing algorithm; `None` when omitted.
1975    pub algorithm: Option<ViewAlgorithm>,
1976    /// The optional `DEFINER = <user>` account; `None` when omitted. Boxed like the routine
1977    /// definer (a rare fat field paying only a null pointer when absent).
1978    pub definer: Option<Box<Definer>>,
1979    /// The optional `SQL SECURITY { DEFINER | INVOKER }` privilege context; `None` when omitted.
1980    pub sql_security: Option<SqlSecurityContext>,
1981}
1982
1983/// `ALTER [ALGORITHM = …] [DEFINER = …] [SQL SECURITY …] VIEW <name> [(<columns>)] AS <query>
1984/// [WITH [CASCADED | LOCAL] CHECK OPTION]` — the MySQL view redefinition (gated by
1985/// [`ViewSequenceClauseSyntax::view_definition_options`](crate::dialect::ViewSequenceClauseSyntax::view_definition_options)).
1986///
1987/// MySQL's `ALTER VIEW` re-specifies the whole view body; it is the [`CreateView`] grammar
1988/// minus `OR REPLACE`/`IF NOT EXISTS` (server-measured: both `ER_PARSE_ERROR` after `ALTER`)
1989/// and the `TEMP`/`MATERIALIZED`/`RECURSIVE` prefixes MySQL has no views for. The shared
1990/// [`ViewOptions`] prefix and the [`ViewCheckOption`] tail are reused verbatim, and the body
1991/// reuses [`Query`] like every view.
1992#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1993#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1994#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1995pub struct AlterView<X: Extension = NoExt> {
1996    /// The `[ALGORITHM = …] [DEFINER = …] [SQL SECURITY …]` definition-option prefix; all-`None`
1997    /// for a bare `ALTER VIEW`.
1998    pub options: ViewOptions,
1999    /// The view name (`db.view` or bare `view`).
2000    pub name: ObjectName,
2001    /// The optional explicit output-column list; empty when omitted.
2002    pub columns: ThinVec<Ident>,
2003    /// The redefining `AS <query>` body.
2004    pub query: Box<Query<X>>,
2005    /// The optional `WITH [CASCADED | LOCAL] CHECK OPTION` constraint.
2006    pub check_option: Option<ViewCheckOption>,
2007    /// Source location and node identity.
2008    pub meta: Meta,
2009}
2010
2011/// A DuckDB `CREATE [PERSISTENT] SECRET <name> ( <option> <value> [, ...] )`
2012/// secrets-management statement (DuckDB-specific; gated by
2013/// [`StatementDdlGates::create_secret`](crate::dialect::StatementDdlGates::create_secret)).
2014///
2015/// DuckDB stores credentials (S3/HTTP/… access) as named secrets. `persistent`
2016/// records the `PERSISTENT` keyword (a persistent secret survives the session; the
2017/// bare form is session-temporary). The parenthesized [`options`](Self::options) are
2018/// `<name> <value>` pairs — the required `TYPE <provider>` plus provider-specific
2019/// settings (`KEY_ID`, `REGION`, …). Only the bare/`PERSISTENT` name-then-options form
2020/// the corpus exercises is modelled; the explicit `TEMPORARY` keyword, `OR REPLACE`,
2021/// `IF NOT EXISTS`, the anonymous (unnamed) secret, and the `IN <storage>` clause are
2022/// not part of this shape.
2023#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2024#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2025#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2026pub struct CreateSecret<X: Extension = NoExt> {
2027    /// Whether the persistent form was present in the source.
2028    pub persistent: bool,
2029    /// Name referenced by this syntax.
2030    pub name: ObjectName,
2031    /// Options supplied in source order.
2032    pub options: ThinVec<SecretOption<X>>,
2033    /// Source location and node identity.
2034    pub meta: Meta,
2035}
2036
2037/// One `<name> <value>` option inside a [`CreateSecret`] option list (`TYPE S3`,
2038/// `KEY_ID '...'`). The value is modelled as a general [`Expr`] — DuckDB's own option
2039/// value is a bare word, string, or list — so the corpus `TYPE <provider>` form
2040/// round-trips; the wider expression grammar is the recorded acceptance bound.
2041#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2042#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2043#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2044pub struct SecretOption<X: Extension = NoExt> {
2045    /// Name referenced by this syntax.
2046    pub name: Ident,
2047    /// Value supplied by this syntax.
2048    pub value: Expr<X>,
2049    /// Source location and node identity.
2050    pub meta: Meta,
2051}
2052
2053/// A DuckDB `DROP [PERSISTENT | TEMPORARY] SECRET [IF EXISTS] <name> [FROM <storage>]`
2054/// secrets-management statement (DuckDB-specific; gated by
2055/// [`StatementDdlGates::create_secret`](crate::dialect::StatementDdlGates::create_secret) —
2056/// the same whole-statement gate that admits [`CreateSecret`], because `DROP SECRET` is the
2057/// same secrets behaviour surface and rides the one flag rather than a second gate).
2058///
2059/// The drop counterpart of [`CreateSecret`]. In DuckDB's grammar `drop_secret.y` is the
2060/// *only* `DROP` with its own top-level `stmt` production (not a [`DropStatement`] object
2061/// kind), because it carries the `opt_persist` persistence modifier and a `FROM <storage>`
2062/// backend selector — neither of which the shared name-list DROP grammar has.
2063/// [`persistence`](Self::persistence) records which of the three `opt_persist` spellings
2064/// preceded `SECRET` (absent → [`SecretPersistence::Default`]);
2065/// [`storage`](Self::storage) is the optional `FROM <backend>` secret-storage name. The
2066/// dropped secret is a single identifier (grammar `ColId`), never a qualified object name.
2067///
2068/// Non-generic: a secret drop names a secret, an optional storage backend, and two flags —
2069/// it carries no expressions or extension nodes — so it parallels the leaf
2070/// [`DropStatement`]/[`DropTransform`] payloads.
2071#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2072#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2073#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2074pub struct DropSecretStmt {
2075    /// Which `opt_persist` spelling preceded `SECRET`; see [`SecretPersistence`].
2076    pub persistence: SecretPersistence,
2077    /// Whether the `IF EXISTS` existence guard was present in the source.
2078    pub if_exists: bool,
2079    /// Name referenced by this syntax.
2080    pub name: Ident,
2081    /// Optional `FROM <storage>` secret-storage backend selector.
2082    pub storage: Option<Ident>,
2083    /// Source location and node identity.
2084    pub meta: Meta,
2085}
2086
2087/// The `opt_persist` persistence modifier on a DuckDB `DROP SECRET`: which of the three
2088/// storage scopes the statement names. (The wider `CREATE SECRET` grammar shares the same
2089/// `opt_persist` production; [`CreateSecret`] models only its `PERSISTENT`/absent forms as
2090/// a `bool`, so this three-valued modifier lives with the drop that needs all three.)
2091#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2092#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2093#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2094pub enum SecretPersistence {
2095    /// No modifier written — DuckDB's `default` secret scope (`DROP SECRET s`).
2096    Default,
2097    /// `TEMPORARY` — the session-scoped, in-memory secret store.
2098    Temporary,
2099    /// `PERSISTENT` — the on-disk persistent secret store.
2100    Persistent,
2101}
2102
2103/// A SQLite `CREATE VIRTUAL TABLE [IF NOT EXISTS] [<schema> .] <name> USING <module>
2104/// [( <arg> [, <arg>] * )]` statement (SQLite-specific; gated by
2105/// [`StatementDdlGates::create_virtual_table`](crate::dialect::StatementDdlGates::create_virtual_table)).
2106///
2107/// A virtual table delegates its storage and query implementation to a *module*
2108/// (`fts5`, `rtree`, `csv`, …). The module — not SQLite's core grammar — owns the
2109/// argument syntax: `fts5` reads column names and `tokenize = …` options, `rtree`
2110/// reads dimension columns, and a bespoke module reads whatever it likes. SQLite's
2111/// own parser therefore imposes almost no structure on the argument list — it splits
2112/// the parenthesized text on the *top-level* commas (parentheses nest and quoted
2113/// strings are transparent) and hands each raw slice to the module verbatim, even
2114/// tolerating empty members (`USING m(a,,b)`). Module resolution and any argument
2115/// validation happen at execution time, so the parse layer accepts an unknown module
2116/// and any balanced-parenthesis token soup.
2117///
2118/// Each argument is consequently modelled as an OPAQUE verbatim [`ModuleArg`] (the
2119/// interned source text of one top-level slice), never a parsed sub-grammar — imposing
2120/// column/option structure would invent constraints SQLite does not enforce.
2121///
2122/// [`args`](Self::args) is `None` for the bare `USING m` form and `Some` (possibly
2123/// empty) for the parenthesized `USING m (…)` form, so the two round-trip distinctly.
2124/// The name admits at most two parts (`schema.table`); SQLite has no `TEMP` virtual
2125/// table, so no temporary modifier is modelled. Non-generic: the arguments hold no
2126/// expressions or extension nodes.
2127#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2128#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2129#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2130pub struct CreateVirtualTable {
2131    /// Whether the if not exists form was present in the source.
2132    pub if_not_exists: bool,
2133    /// Name referenced by this syntax.
2134    pub name: ObjectName,
2135    /// The virtual-table module name (`USING <module>`).
2136    pub module: Ident,
2137    /// Arguments in source order.
2138    pub args: Option<ThinVec<ModuleArg>>,
2139    /// Source location and node identity.
2140    pub meta: Meta,
2141}
2142
2143/// One opaque, verbatim argument of a [`CreateVirtualTable`] module argument list.
2144///
2145/// [`text`](Self::text) is the interned source text of a single top-level
2146/// comma-delimited slice, preserved exactly (internal spacing and all) because the
2147/// module owns its grammar — the parser never interprets it. An empty slice (from
2148/// `USING m(a,,b)` or a trailing comma) is a legal empty argument.
2149#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2150#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2151#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2152pub struct ModuleArg {
2153    /// The comment text.
2154    pub text: Symbol,
2155    /// Source location and node identity.
2156    pub meta: Meta,
2157}
2158
2159/// `CREATE [UNIQUE] INDEX [CONCURRENTLY] [IF NOT EXISTS] [<name>] ON <table>
2160/// [USING <method>] (<column> [, ...]) [WHERE <predicate>]`.
2161///
2162/// `UNIQUE` is portable; `CONCURRENTLY`, the `USING <method>` access-method clause,
2163/// and the trailing `WHERE` partial-index predicate are PostgreSQL extensions gated
2164/// by `schema_change_syntax.index_extensions` dialect data. The index
2165/// name is optional — PostgreSQL derives one from the table and columns when it is
2166/// omitted (`CREATE INDEX ON t (a)`). COLLATE / operator-class / `INCLUDE` column
2167/// decorations are a deliberate follow-up.
2168#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2169#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2170#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2171pub struct CreateIndex<X: Extension = NoExt> {
2172    /// Whether the unique form was present in the source.
2173    pub unique: bool,
2174    /// Whether the concurrently form was present in the source.
2175    pub concurrently: bool,
2176    /// Whether the if not exists form was present in the source.
2177    pub if_not_exists: bool,
2178    /// Name referenced by this syntax.
2179    pub name: Option<Ident>,
2180    /// Table referenced by this syntax.
2181    pub table: ObjectName,
2182    /// Optional using for this syntax.
2183    pub using: Option<Ident>,
2184    /// Columns in source order.
2185    pub columns: ThinVec<IndexColumn<X>>,
2186    /// `WITH (<name> = <value>, …)` index storage parameters.
2187    pub with_params: ThinVec<TableStorageParameter<X>>,
2188    /// Predicate that controls this clause.
2189    pub predicate: Option<Box<Expr<X>>>,
2190    /// Source location and node identity.
2191    pub meta: Meta,
2192}
2193
2194/// One key column of a [`CreateIndex`]: an expression with optional sort modifiers.
2195///
2196/// A bare column is the common case (`expr` is an [`Expr::Column`](super::Expr)); a
2197/// parenthesized expression indexes a computed value. `asc` / `nulls_first` mirror
2198/// [`OrderByExpr`](super::OrderByExpr) and stay `None` when the modifier is
2199/// unwritten, leaving the dialect default to the consumer.
2200#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2201#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2202#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2203pub struct IndexColumn<X: Extension = NoExt> {
2204    /// Expression evaluated by this syntax.
2205    pub expr: Expr<X>,
2206    /// Whether the asc form was present in the source.
2207    pub asc: Option<bool>,
2208    /// Whether the nulls first form was present in the source.
2209    pub nulls_first: Option<bool>,
2210    /// Source location and node identity.
2211    pub meta: Meta,
2212}
2213
2214/// A SQLite `CREATE [TEMP|TEMPORARY] TRIGGER [IF NOT EXISTS] [<schema> .] <name>
2215/// [BEFORE | AFTER | INSTEAD OF] <event> ON <table> [FOR EACH ROW] [WHEN <expr>]
2216/// BEGIN <stmt>; ... END` statement (SQLite-specific; gated by
2217/// [`StatementDdlGates::create_trigger`](crate::dialect::StatementDdlGates::create_trigger)).
2218///
2219/// Unlike the other `CREATE` families — which are always accepted because their body
2220/// grammar is standard — the trigger gate is real: only SQLite's `BEGIN … END`
2221/// SQL-statement-body form is modelled, and PostgreSQL/MySQL spell an incompatible
2222/// body (`EXECUTE FUNCTION f()` / an external routine), which they genuinely reject
2223/// for this form. Gating to SQLite (and Lenient) is therefore behaviour-accurate, not
2224/// only a modelling limitation.
2225///
2226/// The [`body`](Self::body) is a non-empty sequence of `INSERT`/`UPDATE`/`DELETE`/
2227/// `SELECT` statements — a statement list inside a statement (the structurally
2228/// heaviest SQLite shape). It reuses the existing [`Statement`] nodes rather than
2229/// minting trigger-body variants (the node stays generic over `X`); the
2230/// parser gates the body kinds and routes each through the recursion-guarded
2231/// statement dispatcher, and the enclosing [`Statement::CreateTrigger`]
2232/// boxes this payload to keep the enum within its size budget.
2233#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2234#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2235#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2236pub struct CreateTrigger<X: Extension = NoExt> {
2237    /// Optional temporary for this syntax.
2238    pub temporary: Option<TemporaryTableKind>,
2239    /// Whether the if not exists form was present in the source.
2240    pub if_not_exists: bool,
2241    /// Name referenced by this syntax.
2242    pub name: ObjectName,
2243    /// The fire time; `None` when unwritten (SQLite defaults it to `BEFORE`, but the
2244    /// absent form is preserved so it round-trips).
2245    pub timing: Option<TriggerTiming>,
2246    /// Which DML event fires the trigger; see [`TriggerEvent`].
2247    pub event: TriggerEvent,
2248    /// Table referenced by this syntax.
2249    pub table: ObjectName,
2250    /// Whether `FOR EACH ROW` was written. SQLite parses (and ignores) it but rejects
2251    /// `FOR EACH STATEMENT`, so only the `ROW` form is modelled as a surface tag.
2252    pub for_each_row: bool,
2253    /// Optional when for this syntax.
2254    pub when: Option<Expr<X>>,
2255    /// The trigger-body statement list, in source order (always non-empty — SQLite
2256    /// rejects an empty `BEGIN END`).
2257    pub body: ThinVec<Statement<X>>,
2258    /// Source location and node identity.
2259    pub meta: Meta,
2260}
2261
2262/// When a [`CreateTrigger`] fires relative to the event.
2263///
2264/// A tag (no `meta`): the timing keyword's span is subsumed by the enclosing
2265/// [`CreateTrigger`], exactly as [`TemporaryTableKind`] rides its parent's span.
2266#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2267#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2268#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2269pub enum TriggerTiming {
2270    /// `BEFORE` — fire before the event is applied.
2271    Before,
2272    /// `AFTER` — fire after the event is applied.
2273    After,
2274    /// `INSTEAD OF` (valid on a view).
2275    InsteadOf,
2276}
2277
2278/// The DML event that fires a [`CreateTrigger`].
2279///
2280/// `UPDATE` alone fires on any column; `UPDATE OF a, b` restricts it to the listed
2281/// columns ([`columns`](Self::Update::columns) empty for the bare `UPDATE`). `INSERT`
2282/// and `DELETE` take no column list.
2283#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2284#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2285#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2286pub enum TriggerEvent {
2287    /// `DELETE` — fire on row deletion.
2288    Delete {
2289        /// Source location and node identity.
2290        meta: Meta,
2291    },
2292    /// `INSERT` — fire on row insertion.
2293    Insert {
2294        /// Source location and node identity.
2295        meta: Meta,
2296    },
2297    /// `UPDATE [OF col, …]` — fire on row update, optionally restricted to columns.
2298    Update {
2299        /// Columns in source order.
2300        columns: ThinVec<Ident>,
2301        /// Source location and node identity.
2302        meta: Meta,
2303    },
2304}
2305
2306/// `CREATE [DEFINER = <user>] TRIGGER [IF NOT EXISTS] <name> {BEFORE | AFTER}
2307/// {INSERT | UPDATE | DELETE} ON <table> FOR EACH ROW [{FOLLOWS | PRECEDES} <other>]
2308/// <sp_proc_stmt>` — the MySQL SQL/PSM trigger (gated by
2309/// [`StatementDdlGates::compound_statements`](crate::dialect::StatementDdlGates::compound_statements),
2310/// the same stored-program gate the routine wrappers ride).
2311///
2312/// A DISTINCT node from the SQLite [`CreateTrigger`], not an extension of it, because the two
2313/// shapes do not genuinely unify — the decisive split is the body: SQLite's is a
2314/// `BEGIN <stmt>; … END` *list* of plain SQL statements ([`CreateTrigger::body`], a
2315/// [`ThinVec`]), MySQL's is a *single* `sp_proc_stmt` — usually a
2316/// [`Statement::Compound`] block, but any one body statement (a `SET`,
2317/// a flow-control construct) — parsed through the shared `parse_body_statement` seam and boxed
2318/// because [`Statement`] is large. Their decorating axes are disjoint too: SQLite carries
2319/// `TEMP` / `WHEN` / `INSTEAD OF` / `UPDATE OF <cols>`, MySQL carries [`DEFINER`](Self::definer),
2320/// mandatory `BEFORE`/`AFTER` timing, mandatory `FOR EACH ROW`, and the
2321/// [`FOLLOWS`/`PRECEDES` ordering](Self::ordering) anchor. Folding both into one node would
2322/// leave half its fields dialect-dead — the [`CreateProcedure`] precedent (a MySQL
2323/// stored-program object gets its own node rather than overloading a cross-dialect one) applies.
2324///
2325/// The [`timing`](Self::timing) and [`event`](Self::event) axes reuse the shared
2326/// [`TriggerTiming`] / [`TriggerEvent`] vocabulary — MySQL simply never emits the SQLite-only
2327/// [`TriggerTiming::InsteadOf`] or a non-empty [`TriggerEvent::Update`] column list (the parser
2328/// enforces the bare forms), so the reuse is safe and keeps one trigger-axis vocabulary.
2329#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2330#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2331#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2332pub struct CreateStoredTrigger<X: Extension = NoExt> {
2333    /// The optional `DEFINER = <user>` account prefix; `None` when omitted. Boxed (the rare
2334    /// fat field pays only a null pointer when absent), like [`CreateProcedure::definer`].
2335    pub definer: Option<Box<Definer>>,
2336    /// Whether the `IF NOT EXISTS` guard was written (MySQL 8.0.29+).
2337    pub if_not_exists: bool,
2338    /// Name referenced by this syntax.
2339    pub name: ObjectName,
2340    /// The fire time (`BEFORE` / `AFTER`) — mandatory in MySQL (no defaulted/absent form, and
2341    /// never [`TriggerTiming::InsteadOf`]).
2342    pub timing: TriggerTiming,
2343    /// The DML event that fires the trigger (`INSERT` / `UPDATE` / `DELETE`) — always the bare
2344    /// form; MySQL has no `UPDATE OF <cols>`, so a [`TriggerEvent::Update`] carries no columns.
2345    pub event: TriggerEvent,
2346    /// Table referenced by this syntax.
2347    pub table: ObjectName,
2348    /// The optional `{FOLLOWS | PRECEDES} <other>` ordering anchor relative to another trigger on
2349    /// the same table and event; `None` for the unordered form.
2350    pub ordering: Option<TriggerOrder>,
2351    /// The trigger body: one `sp_proc_stmt`, boxed. Usually a
2352    /// [`Statement::Compound`] `BEGIN … END` block, but any single
2353    /// body statement is admitted, parsed through the `parse_body_statement` seam.
2354    pub body: Box<Statement<X>>,
2355    /// Source location and node identity.
2356    pub meta: Meta,
2357}
2358
2359/// The MySQL `{FOLLOWS | PRECEDES} <other_trigger>` ordering clause of a
2360/// [`CreateStoredTrigger`] (`trigger_follows_precedes_clause`): where the new trigger fires
2361/// relative to an existing one on the same table and event.
2362///
2363/// The anchor is an [`Ident`] (MySQL's `ident_or_text` — a bare or quoted trigger name) so both
2364/// source spellings round-trip from its span. The keyword direction is the enum variant.
2365#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2366#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2367#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2368pub enum TriggerOrder {
2369    /// `FOLLOWS <other>` — fire immediately after the named trigger.
2370    Follows {
2371        /// The anchor trigger this one follows.
2372        anchor: Ident,
2373        /// Source location and node identity.
2374        meta: Meta,
2375    },
2376    /// `PRECEDES <other>` — fire immediately before the named trigger.
2377    Precedes {
2378        /// The anchor trigger this one precedes.
2379        anchor: Ident,
2380        /// Source location and node identity.
2381        meta: Meta,
2382    },
2383}
2384
2385/// `CREATE DATABASE [IF NOT EXISTS] <name>`.
2386///
2387/// Non-generic, like [`CreateSchema`]: a database is named by identifiers only. The
2388/// trailing `[WITH] <option> ...` list (`OWNER`, `TEMPLATE`, `ENCODING`, …) is a
2389/// deliberate follow-up — real migrations rarely spell it and it carries no
2390/// expressions the shape must compare.
2391#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2392#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2393#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2394pub struct CreateDatabase {
2395    /// The MySQL `IF NOT EXISTS` existence guard (the [`CreateTable`] precedent). Gated
2396    /// for acceptance by
2397    /// [`ExistenceGuards::create_database_if_not_exists`](crate::dialect::ExistenceGuards):
2398    /// PostgreSQL has no `CREATE DATABASE IF NOT EXISTS`, so the flag is off there and
2399    /// the guard surfaces as a clean parse error.
2400    pub if_not_exists: bool,
2401    /// Name referenced by this syntax.
2402    pub name: ObjectName,
2403    /// Source location and node identity.
2404    pub meta: Meta,
2405}
2406
2407/// `CREATE [OR REPLACE] FUNCTION <name> ( [<param> [, ...]] ) [RETURNS <type>]
2408/// [<option> ...]`.
2409///
2410/// The routine body has two disjoint grammatical homes on the [`FunctionBody`] axis. An
2411/// opaque source *string* is introduced by the order-independent `AS` option, spelled either
2412/// single-quoted (`AS 'SELECT 1'`) or dollar-quoted (`AS $$ … $$` / `AS $tag$ … $tag$`) where
2413/// the dialect enables [`dollar_quoted_strings`](crate::dialect::StringLiteralSyntax); the
2414/// delimiter and verbatim body text round-trip from the body [`Literal`]'s span. A *live* SQL
2415/// body — a SQL-standard `RETURN <expr>` (PostgreSQL 14+ / standard `opt_routine_body`) — is
2416/// the trailing [`body`](Self::body) slot, which strictly follows the whole option list (proven
2417/// against the PG oracle: `LANGUAGE sql RETURN 1` accepts, `RETURN 1 LANGUAGE sql` rejects), so
2418/// it is a distinct parse position from `AS`, not another order-independent option. The two
2419/// homes share the [`FunctionBody`] *type* (the axis vocabulary the dollar-body sibling staged)
2420/// but not a grammatical slot.
2421///
2422/// The node is generic over `X` because a parameter or `RETURNS` type can be a host-owned
2423/// [`DataType::Other`] under a custom dialect, and the live
2424/// [`body`](Self::body) carries an [`Expr`]`<X>` (stock builtins pin `X = NoExt`). The M1
2425/// surface is the parameter list, an optional `RETURNS <type>`, the [`FunctionOption`] cluster
2426/// the corpus exercises (`LANGUAGE` / `AS` / null-call behaviour), and the trailing `RETURN`
2427/// body; the fuller volatility, security, parallelism, and `SET` option matrix — and the
2428/// statement-list `BEGIN ATOMIC … END` body that shares the trailing slot — are deliberate
2429/// follow-ups the open option list and the [`FunctionBody`] axis extend without a shape change.
2430#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2431#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2432#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2433pub struct CreateFunction<X: Extension = NoExt> {
2434    /// Whether the or replace form was present in the source.
2435    pub or_replace: bool,
2436    /// The optional MySQL `DEFINER = <user>` clause; `None` when omitted (always `None`
2437    /// for the PostgreSQL string-body routine, which has no definer prefix). Boxed because a
2438    /// [`Definer`] is comparatively large and the clause is rare, so a definer-less routine
2439    /// pays only a null pointer (the [`FunctionParam::default`] precedent).
2440    pub definer: Option<Box<Definer>>,
2441    /// Whether the MySQL `IF NOT EXISTS` guard was written (MySQL 8.0.29+); always `false`
2442    /// for PostgreSQL, which spells the intent `CREATE OR REPLACE`.
2443    pub if_not_exists: bool,
2444    /// Name referenced by this syntax.
2445    pub name: ObjectName,
2446    /// params in source order.
2447    pub params: ThinVec<FunctionParam<X>>,
2448    /// Optional returns for this syntax.
2449    pub returns: Option<DataType<X>>,
2450    /// Options supplied in source order.
2451    pub options: ThinVec<FunctionOption<X>>,
2452    /// The trailing SQL-standard routine body (`opt_routine_body`) — a `RETURN <expr>` live
2453    /// SQL expression that follows the entire option list. Boxed and optional because it is
2454    /// usually absent (an `AS`-string routine leaves it unset, paying one null pointer — the
2455    /// [`FunctionParam::default`] precedent); a [`FunctionBody::Definition`] string body never
2456    /// lands here (it rides the `AS` option instead).
2457    pub body: Option<Box<FunctionBody<X>>>,
2458    /// Source location and node identity.
2459    pub meta: Meta,
2460}
2461
2462/// One routine parameter: an optional argument [`mode`](Self::mode)
2463/// (`IN`/`OUT`/`INOUT`/`VARIADIC`), an optional name, its type (`b INT`), and an
2464/// optional default value (`b INT DEFAULT 0` / `b INT = 0`).
2465///
2466/// The three lead-in facets are independent: a bare type (`f(int)`) leaves both
2467/// `mode` and `name` unset, a mode may precede an unnamed type (`f(OUT int)`), and a
2468/// named parameter may carry a default. The name is PostgreSQL's `param_name` =
2469/// `type_function_name` production (parsed with the [type-name reservation
2470/// set](crate::dialect::FeatureSet::reserved_type_name)): a `type_func_name` keyword
2471/// like `left` is a legal parameter name, a `col_name` keyword like `int` is not — so
2472/// a lone `int` is unambiguously the type, mirroring how PostgreSQL resolves the
2473/// name-vs-type ambiguity. The default is the PostgreSQL `func_arg_with_default`
2474/// tail — boxed because it is usually absent and a [`FunctionParamDefault`] (an
2475/// [`Expr`]) is comparatively large, so a default-less parameter pays only a null
2476/// pointer (the [`ForeignKeyRef`] referential-action precedent). It is *parameter
2477/// metadata*: an ordinary function-**call** argument is a
2478/// [`FunctionArg`](crate::ast::FunctionArg) on the unrelated call path and is
2479/// unaffected.
2480#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2481#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2482#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2483pub struct FunctionParam<X: Extension = NoExt> {
2484    /// Mode selected by this syntax.
2485    pub mode: Option<FunctionParamMode>,
2486    /// Name referenced by this syntax.
2487    pub name: Option<Ident>,
2488    /// Data type named by this syntax.
2489    pub data_type: DataType<X>,
2490    /// Optional default for this syntax.
2491    pub default: Option<Box<FunctionParamDefault<X>>>,
2492    /// Source location and node identity.
2493    pub meta: Meta,
2494}
2495
2496/// A routine parameter's argument mode — PostgreSQL's `arg_class` prefix
2497/// (`func_arg: arg_class param_name func_type | …`). `IN` is the default and may be
2498/// omitted; `OUT`/`INOUT` mark output parameters; `VARIADIC` marks a trailing
2499/// array-spread parameter. A fieldless [`Copy`] tag whose span rides the owning
2500/// [`FunctionParam`] (the [`FunctionParamDefaultSpelling`] precedent): it records only
2501/// the written mode so it round-trips.
2502///
2503/// PostgreSQL admits the mode either before *or* after the name (`IN a int` and
2504/// `a IN int` both parse); this models the documented, canonical mode-first spelling
2505/// (which sqlparser-rs's `ArgMode` also targets) and adds [`Variadic`](Self::Variadic),
2506/// which sqlparser-rs omits but real PostgreSQL requires. The rarer name-first spelling
2507/// is a deliberate parse-surface boundary — no corpus exercises it and PostgreSQL itself
2508/// normalizes the two orders to one AST.
2509#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2510#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2511#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2512pub enum FunctionParamMode {
2513    /// `IN` — an input parameter (the default when omitted).
2514    In,
2515    /// `OUT` — an output parameter.
2516    Out,
2517    /// `INOUT` — an input/output parameter.
2518    InOut,
2519    /// `VARIADIC` — a trailing array-spread parameter.
2520    Variadic,
2521}
2522
2523/// A routine parameter's default-value clause: the [`spelling`](Self::spelling)
2524/// (`DEFAULT` keyword vs `=`) and the value [`Expr`]. PostgreSQL's grammar spells
2525/// one production two ways — `func_arg_with_default: func_arg DEFAULT a_expr |
2526/// func_arg '=' a_expr` — so the tag records which the source used and the value is
2527/// a live SQL expression reusing [`Expr`] rather than a re-derived literal grammar.
2528///
2529/// sqlparser-rs models the same clause as a bare `default_expr: Option<Expr>` and
2530/// always re-renders it as `= <expr>`, normalizing the `DEFAULT` spelling away; this
2531/// AST instead carries the fieldless [`FunctionParamDefaultSpelling`] tag so both
2532/// forms round-trip verbatim (the spelling-fidelity doctrine, as for
2533/// [`EqualsSpelling`](crate::ast::EqualsSpelling)).
2534#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2535#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2536#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2537pub struct FunctionParamDefault<X: Extension = NoExt> {
2538    /// Exact source spelling retained for faithful rendering.
2539    pub spelling: FunctionParamDefaultSpelling,
2540    /// Value supplied by this syntax.
2541    pub value: Expr<X>,
2542    /// Source location and node identity.
2543    pub meta: Meta,
2544}
2545
2546/// Surface spelling for a [`FunctionParamDefault`]: PostgreSQL admits both the
2547/// `DEFAULT` keyword and a bare `=` to introduce a routine parameter's default, two
2548/// spellings of the one `func_arg_with_default` production. A `Copy` tag whose span
2549/// rides the owning [`FunctionParamDefault`], like [`MacroSpelling`]; it records the
2550/// source form only so rendering round-trips exactly (a fidelity tag, not a validity
2551/// one — the parser accepts both spellings wherever defaults are gated on).
2552#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2553#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2554#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2555pub enum FunctionParamDefaultSpelling {
2556    /// The `DEFAULT <expr>` keyword form.
2557    Default,
2558    /// The `= <expr>` operator form.
2559    Equals,
2560}
2561
2562#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2563#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2564#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2565/// The SQL function option forms represented by the AST.
2566pub enum FunctionOption<X: Extension = NoExt> {
2567    /// A `LANGUAGE <name>` clause naming the routine's implementation language. The name is a
2568    /// `NonReservedWord_or_Sconst` ([`LanguageName`]) — a bare word or, on the PostgreSQL
2569    /// surface, an `Sconst` string (`LANGUAGE 'sql'`/`E'sql'`/`$$sql$$`) — shared with the
2570    /// [`DoArg::Language`](crate::ast::DoArg) argument.
2571    Language {
2572        /// Name referenced by this syntax.
2573        name: LanguageName,
2574        /// Source location and node identity.
2575        meta: Meta,
2576    },
2577    /// `AS <body>` — the *string* routine body, carried on the [`FunctionBody`] axis. The `AS`
2578    /// option only ever homes an opaque source string (plain `'…'` or dollar-quoted
2579    /// `$tag$…$tag$`), i.e. [`FunctionBody::Definition`]; the live `RETURN <expr>` body rides
2580    /// the trailing [`CreateFunction::body`] slot, a disjoint grammatical position.
2581    As {
2582        /// The routine's string body; see [`FunctionBody`].
2583        body: FunctionBody<X>,
2584        /// Source location and node identity.
2585        meta: Meta,
2586    },
2587    /// The null-call behaviour (`CALLED ON NULL INPUT` / `RETURNS NULL ON NULL INPUT`
2588    /// / `STRICT`).
2589    NullBehavior {
2590        /// The null-input behaviour; see [`FunctionNullBehavior`].
2591        behavior: FunctionNullBehavior,
2592        /// Source location and node identity.
2593        meta: Meta,
2594    },
2595    /// The MySQL `[NOT] DETERMINISTIC` routine characteristic — whether the routine
2596    /// always produces the same result for the same inputs. The `not` field records the negated
2597    /// spelling so `NOT DETERMINISTIC` round-trips distinctly from a
2598    /// routine that simply omits the characteristic. A stored-routine option (MySQL SQL/PSM),
2599    /// not a PostgreSQL `CREATE FUNCTION` clause.
2600    Deterministic {
2601        /// Whether the `NOT` prefix was written (`NOT DETERMINISTIC`).
2602        not: bool,
2603        /// Source location and node identity.
2604        meta: Meta,
2605    },
2606    /// The MySQL SQL-data-access routine characteristic (`CONTAINS SQL` / `NO SQL` /
2607    /// `READS SQL DATA` / `MODIFIES SQL DATA`) — the advisory declaration of how the
2608    /// routine body touches data.
2609    DataAccess {
2610        /// Which data-access class was written; see [`SqlDataAccess`].
2611        access: SqlDataAccess,
2612        /// Source location and node identity.
2613        meta: Meta,
2614    },
2615    /// The MySQL `SQL SECURITY {DEFINER | INVOKER}` routine characteristic — the privilege
2616    /// context the routine executes under.
2617    SqlSecurity {
2618        /// The security context; see [`SqlSecurityContext`].
2619        context: SqlSecurityContext,
2620        /// Source location and node identity.
2621        meta: Meta,
2622    },
2623    /// The MySQL `COMMENT '<string>'` routine characteristic — a stored descriptive comment.
2624    /// The PostgreSQL comment surface is the separate `COMMENT ON FUNCTION` statement, so
2625    /// this inline option is MySQL-only.
2626    Comment {
2627        /// The comment string literal.
2628        comment: Literal,
2629        /// Source location and node identity.
2630        meta: Meta,
2631    },
2632}
2633
2634/// The MySQL routine SQL-data-access characteristic — how the routine body is declared to
2635/// touch data. Advisory (the server does not enforce it), recorded so it round-trips. A
2636/// `Copy` tag whose span rides the owning [`FunctionOption::DataAccess`], like
2637/// [`FunctionNullBehavior`].
2638#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2639#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2640#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2641pub enum SqlDataAccess {
2642    /// `CONTAINS SQL` — the body contains SQL but neither reads nor writes data (the default).
2643    ContainsSql,
2644    /// `NO SQL` — the body contains no SQL.
2645    NoSql,
2646    /// `READS SQL DATA` — the body reads but does not modify data.
2647    ReadsSqlData,
2648    /// `MODIFIES SQL DATA` — the body may modify data.
2649    ModifiesSqlData,
2650}
2651
2652/// The MySQL `SQL SECURITY` context — whose privileges a routine runs under. A `Copy` tag
2653/// whose span rides the owning [`FunctionOption::SqlSecurity`], like [`SqlDataAccess`].
2654#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2655#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2656#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2657pub enum SqlSecurityContext {
2658    /// `SQL SECURITY DEFINER` — run under the routine definer's privileges (the default).
2659    Definer,
2660    /// `SQL SECURITY INVOKER` — run under the calling user's privileges.
2661    Invoker,
2662}
2663
2664/// The executable body of a [`CreateFunction`] routine — the body-representation **axis**.
2665///
2666/// Two body *kinds* are modelled today. [`Definition`](Self::Definition) is an opaque source
2667/// string introduced by the order-independent `AS` option (`AS 'SELECT 1'`, `AS $$ … $$`,
2668/// `AS $body$ … $body$`). [`Return`](Self::Return) is the SQL-standard *live* body
2669/// `RETURN <expr>` (PostgreSQL 14+ / standard `opt_routine_body`), a real SQL [`Expr`] that
2670/// rides the trailing [`CreateFunction::body`] slot — a *distinct* parse position from `AS`
2671/// (the `RETURN` body strictly follows the whole option list, oracle-proven). This is a
2672/// distinct enum — not a bare [`Literal`] field on [`FunctionOption::As`] — precisely so the
2673/// live body reuses one body vocabulary across both slots rather than reshaping the option.
2674///
2675/// The axis is body *kind* (opaque string vs. live SQL), **not** quote style: a plain `'…'`
2676/// and a dollar-quoted `$tag$…$tag$` body are both
2677/// [`LiteralKind::String`](crate::ast::LiteralKind) and differ only in source spelling. That
2678/// spelling — the delimiter tag and the verbatim body text — round-trips from the
2679/// [`Literal`]'s span; it is recovered from source, never normalized, so a dollar body
2680/// re-renders byte-for-byte.
2681///
2682/// The enum is generic over `X` because [`Return`](Self::Return) carries an [`Expr`]`<X>`; the
2683/// [`Definition`](Self::Definition) string variant is generic-free. A statement-list
2684/// `BEGIN ATOMIC … END` body (PostgreSQL, oracle-accepted) shares the trailing slot and slots
2685/// in here as a further variant carrying a routine-body statement list — deliberately deferred:
2686/// modelling it honestly needs a routine-body statement grammar (its inner `RETURN` is not a
2687/// top-level [`Statement`]), which is a separate surface from this single-`Expr` body.
2688#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2689#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2690#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2691pub enum FunctionBody<X: Extension = NoExt> {
2692    /// `AS <string>` — an opaque routine body in the target language, kept as the source
2693    /// [`Literal`] (plain-quoted or dollar-quoted) rather than re-parsed, because the body's
2694    /// grammar is the target language, not SQL.
2695    Definition {
2696        /// Definition text supplied by this syntax.
2697        definition: Literal,
2698        /// Source location and node identity.
2699        meta: Meta,
2700    },
2701    /// `RETURN <expr>` — the SQL-standard live expression body (`opt_routine_body`'s
2702    /// `ReturnStmt`). The [`Expr`] is boxed (the [`MacroBody`] precedent) so the common `AS`
2703    /// string body pays no [`Expr`] footprint on the shared [`FunctionOption::As`] path.
2704    Return {
2705        /// Expression evaluated by this syntax.
2706        expr: Box<Expr<X>>,
2707        /// Source location and node identity.
2708        meta: Meta,
2709    },
2710    /// A MySQL SQL/PSM routine body — one `routine_body` statement, usually the
2711    /// `BEGIN … END` compound block ([`Statement::Compound`])
2712    /// but any single body statement (a bare `SET`, a flow-control construct, a `RETURN`).
2713    /// This is the live statement-list body the [`FunctionBody`] axis doc anticipated; it
2714    /// rides the trailing [`CreateFunction::body`] slot exactly as [`Return`](Self::Return)
2715    /// does — a distinct grammatical position after the whole characteristic list. Boxed
2716    /// ([`Statement`] is large) so the common `AS`-string path pays no footprint.
2717    Block {
2718        /// The routine body statement (typically a compound block); parsed through the
2719        /// `parse_body_statement` seam so its inner grammar is the MySQL body sub-language.
2720        body: Box<Statement<X>>,
2721        /// Source location and node identity.
2722        meta: Meta,
2723    },
2724}
2725
2726/// How a routine handles a NULL argument. `Strict` is the exact shorthand for
2727/// `ReturnsNullOnNull`, kept distinct so the written spelling round-trips. A `Copy`
2728/// leaf enum whose span lives on the owning [`FunctionOption`], like [`DropBehavior`].
2729#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2730#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2731#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2732pub enum FunctionNullBehavior {
2733    /// `CALLED ON NULL INPUT` — run the routine even when an argument is null.
2734    CalledOnNull,
2735    /// `RETURNS NULL ON NULL INPUT` — return null without running when any argument is null.
2736    ReturnsNullOnNull,
2737    /// `STRICT` — shorthand for `RETURNS NULL ON NULL INPUT`.
2738    Strict,
2739}
2740
2741/// The MySQL `DEFINER = <user>` clause prefixing a routine (and, on other tickets, a
2742/// view/trigger/event) definition — the account the object's `SQL SECURITY DEFINER` context
2743/// resolves to.
2744///
2745/// This is the *measured minimal* account surface the routine family needs: the bare
2746/// `<user>[@<host>]` account name and the `CURRENT_USER [()]` self-reference, the two forms
2747/// the corpus and the oracle exercise. The full MySQL account-name axis — the quoting-nuance
2748/// matrix, role grants, and the `IDENTIFIED BY` authentication tail — is owned by
2749/// `parse-mysql-user-role-ddl`; this node deliberately stops at the account *reference* a
2750/// routine header carries, and that ticket widens it (or lifts it to a shared account node)
2751/// without reshaping the routine statements that hold it.
2752#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2753#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2754#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2755pub enum Definer {
2756    /// `DEFINER = <user>[@<host>]` — a named account. The `user` and optional `host` are
2757    /// each an [`Ident`] so both the bare (`root`) and quoted (`'root'@'localhost'`) source
2758    /// spellings round-trip from their spans.
2759    Account {
2760        /// The account user name.
2761        user: Ident,
2762        /// The optional `@<host>` part; `None` for a bare user with no host.
2763        host: Option<Ident>,
2764        /// Source location and node identity.
2765        meta: Meta,
2766    },
2767    /// `DEFINER = CURRENT_USER [()]` — the current user at definition time.
2768    CurrentUser {
2769        /// Whether the empty `()` call-style parentheses were written.
2770        parens: bool,
2771        /// Source location and node identity.
2772        meta: Meta,
2773    },
2774}
2775
2776/// `CREATE [DEFINER = <user>] PROCEDURE [IF NOT EXISTS] <name> ( [<param> [, …]] )
2777/// [<characteristic> …] <routine_body>` — the MySQL stored-procedure definition (SQL/PSM).
2778///
2779/// A distinct node from [`CreateFunction`]: a procedure has no `RETURNS` type, its body may
2780/// not contain `RETURN` (the routine family enforces `ER_SP_BADRETURN`), and it is invoked by
2781/// `CALL` rather than in an expression. It shares the routine *vocabulary* — the parameter
2782/// list reuses [`FunctionParam`] (with its `IN`/`OUT`/`INOUT` [`FunctionParamMode`]) and the
2783/// characteristic list reuses [`FunctionOption`] (`LANGUAGE`/`COMMENT`/`DETERMINISTIC`/data
2784/// access/`SQL SECURITY`) — but not a statement shape. The [`body`](Self::body) is a single
2785/// MySQL body statement (typically a [`Statement::Compound`]
2786/// block), parsed through the `parse_body_statement` seam and boxed because [`Statement`] is
2787/// large.
2788#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2789#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2790#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2791pub struct CreateProcedure<X: Extension = NoExt> {
2792    /// The optional `DEFINER = <user>` clause; `None` when omitted. Boxed (the rare fat
2793    /// field pays only a null pointer when absent), like [`CreateFunction::definer`].
2794    pub definer: Option<Box<Definer>>,
2795    /// Whether the `IF NOT EXISTS` guard was written (MySQL 8.0.29+).
2796    pub if_not_exists: bool,
2797    /// The procedure name.
2798    pub name: ObjectName,
2799    /// The parameter list, in source order (always parenthesized, possibly empty).
2800    pub params: ThinVec<FunctionParam<X>>,
2801    /// The routine characteristics, in source order (`LANGUAGE`/`COMMENT`/`DETERMINISTIC`/
2802    /// data-access/`SQL SECURITY`), carried on the shared [`FunctionOption`] axis.
2803    pub characteristics: ThinVec<FunctionOption<X>>,
2804    /// The routine body — one MySQL body statement, usually a compound block.
2805    pub body: Box<Statement<X>>,
2806    /// Source location and node identity.
2807    pub meta: Meta,
2808}
2809
2810/// Which routine kind an [`AlterRoutine`] targets — the `PROCEDURE`/`FUNCTION` object keyword.
2811/// A `Copy` surface tag whose span rides the owning [`AlterRoutine`], like
2812/// [`ShowRoutineKind`](crate::ast::ShowRoutineKind).
2813#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2814#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2815#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2816pub enum RoutineKind {
2817    /// `PROCEDURE`.
2818    Procedure,
2819    /// `FUNCTION`.
2820    Function,
2821}
2822
2823/// `ALTER {PROCEDURE | FUNCTION} <name> [<characteristic> …]` — the MySQL routine-characteristics
2824/// alteration (SQL/PSM). Characteristics only: unlike [`CreateProcedure`]/[`CreateFunction`]
2825/// there is no body and no parameter list — `ALTER` re-declares only the mutable characteristic
2826/// subset (`COMMENT` / `LANGUAGE` / data access / `SQL SECURITY`; the server rejects
2827/// `DETERMINISTIC` here, which the parser enforces). One node spans both routine kinds via
2828/// [`kind`](Self::kind); boxed by [`Statement::AlterRoutine`].
2829#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2830#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2831#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2832pub struct AlterRoutine<X: Extension = NoExt> {
2833    /// Whether this alters a `PROCEDURE` or a `FUNCTION`.
2834    pub kind: RoutineKind,
2835    /// The routine name.
2836    pub name: ObjectName,
2837    /// The re-declared characteristics, in source order (the `ALTER`-legal subset).
2838    pub characteristics: ThinVec<FunctionOption<X>>,
2839    /// Source location and node identity.
2840    pub meta: Meta,
2841}
2842
2843/// The `ON SCHEDULE <when>` clause of a MySQL `CREATE`/`ALTER EVENT` — the one-shot
2844/// `AT <timestamp>` form or the recurring `EVERY <interval> [STARTS …] [ENDS …]` form.
2845///
2846/// Both timestamp positions ([`At::at`](Self::At::at), [`Every::starts`](Self::Every::starts),
2847/// [`Every::ends`](Self::Every::ends)) are ordinary [`Expr`]s, so a `NOW() + INTERVAL 1 DAY`
2848/// offset rides the expression grammar with no special schedule handling. The recurring
2849/// interval is `<value> <unit>` where the unit reuses the shared [`IntervalFields`] vocabulary
2850/// (MySQL's `interval` production, rendered in underscore spelling — `DAY_HOUR`, `MINUTE_SECOND`).
2851#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2852#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2853#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2854pub enum EventSchedule<X: Extension = NoExt> {
2855    /// `AT <timestamp>` — a one-shot event fired once at the given time. The [`Expr`] is
2856    /// boxed to keep the schedule (and the enclosing event node) lean.
2857    At {
2858        /// The one-shot execution timestamp (any expression, e.g. `NOW() + INTERVAL 1 DAY`).
2859        at: Box<Expr<X>>,
2860        /// Source location and node identity.
2861        meta: Meta,
2862    },
2863    /// `EVERY <value> <unit> [STARTS <ts>] [ENDS <ts>]` — a recurring event. The `<value>
2864    /// <unit>` interval reuses the shared [`IntervalFields`] vocabulary; `STARTS`/`ENDS` are
2865    /// optional window bounds (each an ordinary expression).
2866    Every {
2867        /// The interval count (an expression, typically an integer literal).
2868        value: Box<Expr<X>>,
2869        /// The interval unit, in the shared [`IntervalFields`] vocabulary (MySQL `interval`).
2870        unit: IntervalFields,
2871        /// The optional `STARTS <ts>` window start.
2872        starts: Option<Box<Expr<X>>>,
2873        /// The optional `ENDS <ts>` window end.
2874        ends: Option<Box<Expr<X>>>,
2875        /// Source location and node identity.
2876        meta: Meta,
2877    },
2878}
2879
2880/// `ON COMPLETION [NOT] PRESERVE` — whether a MySQL event is retained after its last
2881/// execution. A `Copy` surface tag whose span rides the owning event node, like
2882/// [`RoutineKind`].
2883#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2884#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2885#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2886pub enum EventOnCompletion {
2887    /// `ON COMPLETION PRESERVE` — keep the event definition after its final run.
2888    Preserve,
2889    /// `ON COMPLETION NOT PRESERVE` — drop the event after its final run (the default).
2890    NotPreserve,
2891}
2892
2893/// The `ENABLE | DISABLE [ON SLAVE|REPLICA]` activation state of a MySQL event. A `Copy`
2894/// surface tag whose span rides the owning event node.
2895///
2896/// [`DisableOnReplica`](Self::DisableOnReplica) carries a [`ReplicaSpelling`] because MySQL
2897/// 8.4 still admits BOTH the deprecated `DISABLE ON SLAVE` and the current `DISABLE ON
2898/// REPLICA` (server-measured: both accept, `SLAVE` warns) and the source spelling must
2899/// round-trip.
2900#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2901#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2902#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2903pub enum EventStatus {
2904    /// `ENABLE` — the event is active.
2905    Enable,
2906    /// `DISABLE` — the event is inactive on every node.
2907    Disable,
2908    /// `DISABLE ON SLAVE` / `DISABLE ON REPLICA` — the event is active only on the source,
2909    /// disabled on replicas. The [`ReplicaSpelling`] records which keyword was written.
2910    DisableOnReplica(ReplicaSpelling),
2911}
2912
2913/// Which of MySQL's two interchangeable replica keywords a source used — the deprecated
2914/// `SLAVE` or the current `REPLICA`. Both parse on MySQL 8.4 (`SLAVE` with a deprecation
2915/// warning), so the spelling is a round-trip tag, not a semantic difference. A `Copy` leaf.
2916#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2917#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2918#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2919pub enum ReplicaSpelling {
2920    /// The deprecated `SLAVE` keyword (still admitted on MySQL 8.4 with a warning).
2921    Slave,
2922    /// The current `REPLICA` keyword.
2923    Replica,
2924}
2925
2926/// `CREATE [DEFINER = <user>] EVENT [IF NOT EXISTS] <name> ON SCHEDULE <schedule>
2927/// [ON COMPLETION [NOT] PRESERVE] [ENABLE | DISABLE [ON SLAVE|REPLICA]] [COMMENT '…']
2928/// DO <body>` — the MySQL scheduled-event definition (SQL/PSM, gated by
2929/// [`StatementDdlGates::compound_statements`](crate::dialect::StatementDdlGates::compound_statements),
2930/// the stored-program body surface).
2931///
2932/// The clause order after the name is fixed by the grammar (schedule, completion, status,
2933/// comment, `DO`) — server-measured: a comment before the status is a syntax error. The
2934/// [`body`](Self::body) is a single MySQL body statement (usually a [`Statement::Compound`]
2935/// block), parsed through the `parse_body_statement` seam and boxed because [`Statement`] is
2936/// large; an event body carries no return value, so `RETURN` inside it is rejected (as in a
2937/// procedure body — server `ER_SP_BADRETURN`). The [`Definer`] prefix reuses the shared
2938/// routine account reference.
2939#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2940#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2941#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2942pub struct CreateEvent<X: Extension = NoExt> {
2943    /// The optional `DEFINER = <user>` clause; `None` when omitted. Boxed like the routine
2944    /// definer (a rare fat field paying only a null pointer when absent).
2945    pub definer: Option<Box<Definer>>,
2946    /// Whether the `IF NOT EXISTS` guard was written.
2947    pub if_not_exists: bool,
2948    /// The event name (`db.event` or bare `event`).
2949    pub name: ObjectName,
2950    /// The `ON SCHEDULE` clause — the `AT` one-shot or `EVERY` recurring form.
2951    pub schedule: EventSchedule<X>,
2952    /// The optional `ON COMPLETION [NOT] PRESERVE` clause.
2953    pub on_completion: Option<EventOnCompletion>,
2954    /// The optional `ENABLE | DISABLE [ON SLAVE|REPLICA]` activation state.
2955    pub status: Option<EventStatus>,
2956    /// The optional `COMMENT '…'` description.
2957    pub comment: Option<Literal>,
2958    /// The event body — one MySQL body statement (the `DO <stmt>` clause).
2959    pub body: Box<Statement<X>>,
2960    /// Source location and node identity.
2961    pub meta: Meta,
2962}
2963
2964/// `ALTER [DEFINER = <user>] EVENT <name> [ON SCHEDULE <schedule>] [ON COMPLETION [NOT]
2965/// PRESERVE] [RENAME TO <name>] [ENABLE | DISABLE [ON SLAVE|REPLICA]] [COMMENT '…']
2966/// [DO <body>]` — the MySQL event alteration.
2967///
2968/// Every clause is optional, but the grammar requires **at least one** (server-measured: a
2969/// bare `ALTER EVENT e` is a syntax error), which the parser enforces. The clause order is
2970/// the same fixed order as [`CreateEvent`], with `RENAME TO` between the schedule/completion
2971/// and the status. Unlike [`CreateEvent`] the schedule and body are optional, and
2972/// [`on_completion`](Self::on_completion) may appear with or without a schedule.
2973#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2974#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2975#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2976pub struct AlterEvent<X: Extension = NoExt> {
2977    /// The optional `DEFINER = <user>` clause; `None` when omitted.
2978    pub definer: Option<Box<Definer>>,
2979    /// The event name.
2980    pub name: ObjectName,
2981    /// The optional `ON SCHEDULE` clause.
2982    pub schedule: Option<EventSchedule<X>>,
2983    /// The optional `ON COMPLETION [NOT] PRESERVE` clause.
2984    pub on_completion: Option<EventOnCompletion>,
2985    /// The optional `RENAME TO <name>` clause.
2986    pub rename_to: Option<ObjectName>,
2987    /// The optional `ENABLE | DISABLE [ON SLAVE|REPLICA]` activation state.
2988    pub status: Option<EventStatus>,
2989    /// The optional `COMMENT '…'` description.
2990    pub comment: Option<Literal>,
2991    /// The optional `DO <body>` re-definition (one MySQL body statement).
2992    pub body: Option<Box<Statement<X>>>,
2993    /// Source location and node identity.
2994    pub meta: Meta,
2995}
2996
2997/// `DROP EVENT [IF EXISTS] <name>` — the MySQL scheduled-event drop. Kept apart from the
2998/// generic [`Statement::Drop`] because MySQL's event drop names exactly ONE event
2999/// (server-measured: `DROP EVENT a, b` is a syntax error) and takes no `CASCADE`/`RESTRICT`
3000/// behaviour, unlike the shared name-list [`DropStatement`] grammar.
3001#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3002#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3003#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3004pub struct DropEvent {
3005    /// Whether the `IF EXISTS` guard was written.
3006    pub if_exists: bool,
3007    /// The single event name (`db.event` or bare `event`).
3008    pub name: ObjectName,
3009    /// Source location and node identity.
3010    pub meta: Meta,
3011}
3012
3013/// `DROP {DATABASE | SCHEMA} [IF EXISTS] <name>` — MySQL's single-database drop, where
3014/// `DATABASE` and `SCHEMA` are exact synonyms (the lexer folds `SCHEMA` onto `DATABASE`).
3015/// Kept apart from the generic name-list [`Statement::Drop`] because the form names exactly
3016/// ONE unqualified database — server-measured on mysql:8, `DROP DATABASE a, b` is
3017/// `ER_PARSE_ERROR`, `DROP DATABASE db.x` is `ER_PARSE_ERROR`, and it takes no
3018/// `CASCADE`/`RESTRICT` (both syntax errors) — unlike PostgreSQL/DuckDB's `DROP SCHEMA
3019/// <name> [, …] [CASCADE | RESTRICT]`, which keeps riding the shared [`DropStatement`]
3020/// grammar there. Gated by
3021/// [`StatementDdlGates::drop_database`](crate::dialect::StatementDdlGates::drop_database).
3022#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3023#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3024#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3025pub struct DropDatabase {
3026    /// Which of the two synonymous keywords was written; recorded so the source spelling
3027    /// round-trips verbatim (the tag pattern of [`TemporaryTableKind`], not a semantic
3028    /// difference — MySQL treats the two identically).
3029    pub spelling: DatabaseKeyword,
3030    /// Whether the `IF EXISTS` guard was written.
3031    pub if_exists: bool,
3032    /// The single database name — a bare unqualified identifier (`ident`; a dotted
3033    /// `db.x` is a server syntax error).
3034    pub name: Ident,
3035    /// Source location and node identity.
3036    pub meta: Meta,
3037}
3038
3039/// The keyword spelling of a database object — `DATABASE` or its exact synonym `SCHEMA`
3040/// (MySQL folds the two onto one grammar). Recorded on [`DropDatabase`] so the written
3041/// keyword round-trips.
3042#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3043#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3044#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3045pub enum DatabaseKeyword {
3046    /// The `DATABASE` spelling.
3047    Database,
3048    /// The `SCHEMA` spelling (a MySQL synonym for `DATABASE`).
3049    Schema,
3050}
3051
3052/// `DROP INDEX <name> ON <table> [ALGORITHM [=] {DEFAULT | INPLACE | INSTANT | COPY}]
3053/// [LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}]` — MySQL's index drop (`drop_index_stmt`,
3054/// `sql_yacc.yy`). Kept apart from the generic name-list [`Statement::Drop`] because it names
3055/// the owning table with a mandatory `ON <table>` (server-measured: `DROP INDEX i` with no
3056/// `ON` is `ER_PARSE_ERROR` on mysql:8) and carries the online-DDL `ALGORITHM`/`LOCK`
3057/// execution hints, neither of which the shared grammar has. The index name is a bare
3058/// identifier (`ident`; a dotted `i.j` is a syntax error) while the table is a possibly-dotted
3059/// [`ObjectName`] (`db.t` binds). Gated by
3060/// [`IndexAlterSyntax::index_drop_on_table`](crate::dialect::IndexAlterSyntax::index_drop_on_table).
3061#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3062#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3063#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3064pub struct DropIndexOnTable {
3065    /// The dropped index — a bare unqualified identifier.
3066    pub name: Ident,
3067    /// The table the index belongs to (`ON <table>`); may be schema-qualified (`db.t`).
3068    pub table: ObjectName,
3069    /// The trailing `ALGORITHM`/`LOCK` execution hints in source order (`opt_index_lock_and_algorithm`).
3070    /// The grammar admits at most one of each and both orderings (`ALGORITHM … LOCK …` or
3071    /// `LOCK … ALGORITHM …`, server-measured: a repeated `ALGORITHM`/`LOCK` is `ER_PARSE_ERROR`),
3072    /// so this holds zero, one, or two entries and preserves the written order for round-trip.
3073    pub options: ThinVec<IndexLockAlgorithmOption>,
3074    /// Source location and node identity.
3075    pub meta: Meta,
3076}
3077
3078/// One entry of a `DROP INDEX … ON …` (or online `ALTER TABLE`) `opt_index_lock_and_algorithm`
3079/// tail — an `ALGORITHM` or a `LOCK` execution hint. Each records whether the optional `=`
3080/// (`opt_equal`) was written so the surface form round-trips verbatim.
3081#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3082#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3083#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3084pub enum IndexLockAlgorithmOption {
3085    /// `ALGORITHM [=] {DEFAULT | INPLACE | INSTANT | COPY}`.
3086    Algorithm {
3087        /// Whether the optional `=` was written between the keyword and the value.
3088        equals: bool,
3089        /// The chosen algorithm.
3090        value: IndexAlgorithm,
3091    },
3092    /// `LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}`.
3093    Lock {
3094        /// Whether the optional `=` was written between the keyword and the value.
3095        equals: bool,
3096        /// The chosen lock level.
3097        value: IndexLock,
3098    },
3099}
3100
3101/// The online-DDL `ALGORITHM` value (`alter_algorithm_option_value`). `DEFAULT` is a keyword;
3102/// the rest are matched case-insensitively as identifiers, and an unknown value is a *binding*
3103/// reject (`ER_UNKNOWN_ALTER_ALGORITHM` 1800), not a syntax error — so only these four bind.
3104#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3105#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3106#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3107pub enum IndexAlgorithm {
3108    /// `DEFAULT` — let the server choose.
3109    Default,
3110    /// `INPLACE` — rebuild in place where supported.
3111    Inplace,
3112    /// `INSTANT` — metadata-only change where supported.
3113    Instant,
3114    /// `COPY` — rebuild via a table copy.
3115    Copy,
3116}
3117
3118/// The online-DDL `LOCK` value (`alter_lock_option_value`). `DEFAULT` is a keyword; the rest
3119/// are matched case-insensitively as identifiers, and an unknown value is a *binding* reject
3120/// (`ER_UNKNOWN_ALTER_LOCK` 1801), not a syntax error — so only these four bind.
3121#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3122#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3123#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3124pub enum IndexLock {
3125    /// `DEFAULT` — the server's default concurrency.
3126    Default,
3127    /// `NONE` — permit concurrent reads and writes.
3128    None,
3129    /// `SHARED` — permit concurrent reads only.
3130    Shared,
3131    /// `EXCLUSIVE` — permit no concurrent access.
3132    Exclusive,
3133}
3134
3135/// `CREATE [OR REPLACE] [TEMP|TEMPORARY] {MACRO | FUNCTION} [IF NOT EXISTS] <name>
3136/// (<param> [, ...]) AS <body>` — DuckDB's macro DDL (gated by
3137/// [`StatementDdlGates::create_macro`](crate::dialect::StatementDdlGates::create_macro)).
3138///
3139/// This is a distinct node from [`CreateFunction`], not a variant of it: a DuckDB
3140/// macro body is a *live* SQL expression or query (`AS x + 1` / `AS TABLE SELECT …`),
3141/// whereas a PostgreSQL/MySQL `CREATE FUNCTION` body is an opaque source *string* in a
3142/// target language (`AS 'RETURN 1'`). The two share only the `CREATE … (params) AS`
3143/// prefix; their bodies, parameter grammars (a macro parameter is a bare untyped name,
3144/// a routine parameter is `[name] <type>`), and option tails are disjoint.
3145///
3146/// DuckDB spells the same feature with either the `MACRO` keyword or `FUNCTION` as an
3147/// exact synonym; [`spelling`](Self::spelling) records which so the source keyword
3148/// round-trips (the tag pattern of [`TemporaryTableKind`], not a semantic difference).
3149/// Whole-statement gated to DuckDB (and Lenient): every other dialect either has no
3150/// macro grammar (`MACRO` is left unconsumed and surfaces as an unknown statement) or
3151/// spells `CREATE FUNCTION` as the incompatible string-body routine, so the gate is
3152/// behaviour-accurate, not merely a modelling limitation. Boxed by the enclosing
3153/// [`Statement::CreateMacro`] to keep the enum within its size budget.
3154///
3155/// The single-body surface — one `(params) AS body` per statement — is modelled; DuckDB
3156/// additionally accepts a comma-separated *overload* list (`… AS a, (x, y) AS b`), a
3157/// deliberate follow-up (no vendored corpus statement spells it) the shape would extend
3158/// by lifting `params`/`body` into an overload sequence.
3159#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3160#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3161#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3162pub struct CreateMacro<X: Extension = NoExt> {
3163    /// Whether the or replace form was present in the source.
3164    pub or_replace: bool,
3165    /// Optional temporary for this syntax.
3166    pub temporary: Option<TemporaryTableKind>,
3167    /// Exact source spelling retained for faithful rendering.
3168    pub spelling: MacroSpelling,
3169    /// Whether the if not exists form was present in the source.
3170    pub if_not_exists: bool,
3171    /// Name referenced by this syntax.
3172    pub name: ObjectName,
3173    /// params in source order.
3174    pub params: ThinVec<MacroParam<X>>,
3175    /// Statement or query body governed by this node.
3176    pub body: MacroBody<X>,
3177    /// Source location and node identity.
3178    pub meta: Meta,
3179}
3180
3181/// Which keyword introduced a [`CreateMacro`] — DuckDB accepts `MACRO` and `FUNCTION`
3182/// as exact synonyms. A `Copy` tag whose span rides the owning [`CreateMacro`], like
3183/// [`TemporaryTableKind`].
3184#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3185#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3186#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3187pub enum MacroSpelling {
3188    /// Source used the `MACRO` spelling.
3189    Macro,
3190    /// Source used the `FUNCTION` spelling.
3191    Function,
3192}
3193
3194/// One [`CreateMacro`] parameter: a bare untyped name with an optional `:= <expr>`
3195/// default (`CREATE MACRO m(a, b := 10) AS …`). Unlike a [`FunctionParam`], a macro
3196/// parameter carries no type — DuckDB macros are template-substituted, not typed — so
3197/// the name is mandatory and no `DataType` rides the node.
3198#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3199#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3200#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3201pub struct MacroParam<X: Extension = NoExt> {
3202    /// Name referenced by this syntax.
3203    pub name: Ident,
3204    /// Optional default for this syntax.
3205    pub default: Option<Expr<X>>,
3206    /// Source location and node identity.
3207    pub meta: Meta,
3208}
3209
3210/// A [`CreateMacro`] body: a scalar expression (`AS <expr>`) or a table-producing
3211/// query (`AS TABLE <query>`). The `TABLE` keyword is the DuckDB discriminant between a
3212/// scalar macro (returns one value) and a table macro (returns a relation); the body
3213/// grammar reuses [`Expr`] / [`Query`] rather than re-deriving either.
3214#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3215#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3216#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3217pub enum MacroBody<X: Extension = NoExt> {
3218    /// A scalar macro body — an expression returning a single value.
3219    Scalar {
3220        /// Expression evaluated by this syntax.
3221        expr: Box<Expr<X>>,
3222        /// Source location and node identity.
3223        meta: Meta,
3224    },
3225    /// A table macro body — a query returning a relation.
3226    Table {
3227        /// Query governed by this node.
3228        query: Box<Query<X>>,
3229        /// Source location and node identity.
3230        meta: Meta,
3231    },
3232}
3233
3234/// `CREATE [OR REPLACE] [TEMP|TEMPORARY] TYPE [IF NOT EXISTS] <name> AS <definition>` —
3235/// DuckDB's user-defined-type DDL (gated by
3236/// [`StatementDdlGates::create_type`](crate::dialect::StatementDdlGates::create_type)).
3237///
3238/// The name is optionally schema-qualified (`CREATE TYPE s1.mood AS …`). Its
3239/// [`definition`](Self::definition) is either the dedicated `ENUM` production (a label
3240/// list or a label-supplying query) or an alias to any other (possibly composite/nested)
3241/// data type — see [`CreateTypeDefinition`].
3242///
3243/// `OR REPLACE` and `IF NOT EXISTS` are mutually exclusive in DuckDB's grammar (under `OR
3244/// REPLACE` the parser reads `IF` as the type name), so the parser fills `if_not_exists`
3245/// only on the plain form; the two flags are never both set. Whole-statement gated to
3246/// DuckDB (and Lenient): every other dialect leaves `TYPE` unconsumed after `CREATE`,
3247/// where it surfaces as the `TABLE` expectation (an unknown statement). Boxed by the
3248/// enclosing [`Statement::CreateType`] to keep the enum
3249/// within its size budget.
3250#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3251#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3252#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3253pub struct CreateType<X: Extension = NoExt> {
3254    /// Whether the or replace form was present in the source.
3255    pub or_replace: bool,
3256    /// Optional temporary for this syntax.
3257    pub temporary: Option<TemporaryTableKind>,
3258    /// Whether the if not exists form was present in the source.
3259    pub if_not_exists: bool,
3260    /// Name referenced by this syntax.
3261    pub name: ObjectName,
3262    /// The type definition — an `ENUM` label list or a type alias; see [`CreateTypeDefinition`].
3263    pub definition: CreateTypeDefinition<X>,
3264    /// Source location and node identity.
3265    pub meta: Meta,
3266}
3267
3268/// The `AS <definition>` body of a [`CreateType`]. DuckDB's `CREATE TYPE` grammar has a
3269/// dedicated `ENUM` rule distinct from the general data-type grammar — its labels are
3270/// parser-restricted to string constants (`ENUM(1, 2)` is a *parse* error, unlike the
3271/// data-type-position `x::ENUM(...)` cast, which parses any modifier and only bind-rejects
3272/// a non-string) — so an enum definition is its own variant rather than a
3273/// [`DataType::Enum`](crate::ast::DataType). Every non-`ENUM` spelling (`STRUCT`/`MAP`/
3274/// `UNION`, an alias to a named type, arrays, `DECIMAL(p, s)`, …) reuses the shared data
3275/// type via [`Alias`](Self::Alias).
3276#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3277#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3278#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3279pub enum CreateTypeDefinition<X: Extension = NoExt> {
3280    /// `AS ENUM ('a', 'b', …)` — a labelled enumeration. The label list may be empty
3281    /// (`ENUM ()`, which DuckDB accepts) and each label is a string literal.
3282    Enum {
3283        /// labels in source order.
3284        labels: ThinVec<Literal>,
3285        /// Source location and node identity.
3286        meta: Meta,
3287    },
3288    /// `AS ENUM (<query>)` — the enum labels are drawn from a query's single column
3289    /// (`ENUM (SELECT DISTINCT month FROM …)`); the parenthesized query reuses [`Query`].
3290    EnumFromQuery {
3291        /// Query governed by this node.
3292        query: Box<Query<X>>,
3293        /// Source location and node identity.
3294        meta: Meta,
3295    },
3296    /// `AS <type>` — an alias to another type: a scalar (`AS VARCHAR`), a composite/nested
3297    /// constructor (`AS STRUCT(…)` / `AS MAP(…)` / `AS UNION(…)`), an array (`AS my_int[]`),
3298    /// or a named user type. Reuses the shared [`DataType`] grammar.
3299    Alias {
3300        /// Data type named by this syntax.
3301        data_type: DataType<X>,
3302        /// Source location and node identity.
3303        meta: Meta,
3304    },
3305}
3306
3307/// A `CREATE [TEMPORARY] SEQUENCE [IF NOT EXISTS] <name> [<option> ...]` sequence-generator
3308/// statement (SQL:2003 T176; PostgreSQL and DuckDB), gated by
3309/// [`StatementDdlGates::create_sequence`](crate::dialect::StatementDdlGates::create_sequence).
3310///
3311/// The trailing options are the SQL-standard sequence-generator core — `START [WITH]`,
3312/// `INCREMENT [BY]`, `MINVALUE`/`NO MINVALUE`, `MAXVALUE`/`NO MAXVALUE`, `CYCLE`/`NO CYCLE` —
3313/// which both engines' parsers accept in any order, so this single node is gated
3314/// per-dialect by the one flag rather than split into parallel PostgreSQL/DuckDB nodes
3315/// (ADR-0011). They reuse [`IdentityOption`], the identical option vocabulary the
3316/// `GENERATED … AS IDENTITY` column form already carries; the [`Cache`](IdentityOption::Cache)
3317/// variant is never produced here — DuckDB's `CREATE SEQUENCE` grammar rejects `CACHE`
3318/// (engine-measured), so admitting it would over-accept, and it is a PostgreSQL-only
3319/// extension left to a follow-up.
3320///
3321/// PostgreSQL's extended tails (`AS <type>`, `CACHE`, `OWNED BY`, `RESTART`) and DuckDB's
3322/// `OR REPLACE SEQUENCE` are deliberately unmodelled here: each is one engine's extension,
3323/// not the shared standard core, and none is exercised by the DuckDB corpus.
3324#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3325#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3326#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3327pub struct CreateSequence<X: Extension = NoExt> {
3328    /// Optional temporary for this syntax.
3329    pub temporary: Option<TemporaryTableKind>,
3330    /// Whether the if not exists form was present in the source.
3331    pub if_not_exists: bool,
3332    /// Name referenced by this syntax.
3333    pub name: ObjectName,
3334    /// Options supplied in source order.
3335    pub options: ThinVec<IdentityOption<X>>,
3336    /// Source location and node identity.
3337    pub meta: Meta,
3338}
3339
3340/// A `CREATE EXTENSION [IF NOT EXISTS] <name> [WITH] [SCHEMA s] [VERSION v] [CASCADE]`
3341/// statement (PostgreSQL; gated by
3342/// [`StatementDdlGates::extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl)).
3343///
3344/// Non-generic: an extension is named by a bare identifier and its options carry only
3345/// names and version strings — no expressions or extension nodes. The `WITH` keyword is
3346/// optional sugar (PostgreSQL's `opt_with`); [`with`](Self::with) records whether it was
3347/// written so it round-trips. The options are order-independent and repeatable in the
3348/// grammar (`create_extension_opt_list`), kept here in source order.
3349#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3350#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3351#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3352pub struct CreateExtension {
3353    /// Whether the if not exists form was present in the source.
3354    pub if_not_exists: bool,
3355    /// The extension name.
3356    pub name: Ident,
3357    /// Whether the optional `WITH` keyword preceded the option list.
3358    pub with: bool,
3359    /// Options supplied in source order.
3360    pub options: ThinVec<CreateExtensionOption>,
3361    /// Source location and node identity.
3362    pub meta: Meta,
3363}
3364
3365/// One `CREATE EXTENSION` option (`create_extension_opt_item`). PostgreSQL's grammar
3366/// carries a `FROM <old_version>` item whose action is a parse-time
3367/// `FEATURE_NOT_SUPPORTED` reject, so that form is not modelled here.
3368#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3369#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3370#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3371pub enum CreateExtensionOption {
3372    /// `SCHEMA <name>` — the schema the extension's objects are installed into.
3373    Schema {
3374        /// The target schema name.
3375        name: Ident,
3376        /// Source location and node identity.
3377        meta: Meta,
3378    },
3379    /// `VERSION <version>` — the version to install.
3380    Version {
3381        /// Version value supplied by this syntax.
3382        version: ExtensionVersion,
3383        /// Source location and node identity.
3384        meta: Meta,
3385    },
3386    /// `CASCADE` — install missing required extensions too.
3387    Cascade {
3388        /// Source location and node identity.
3389        meta: Meta,
3390    },
3391}
3392
3393/// An extension version value (PostgreSQL's `NonReservedWord_or_Sconst`): a bare word or
3394/// a string constant. PostgreSQL folds both to the same string internally, but the
3395/// surface spelling is kept distinct so it round-trips.
3396#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3397#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3398#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3399pub enum ExtensionVersion {
3400    /// A bare non-reserved word (`VERSION v1`).
3401    Word {
3402        /// Identifier-form version value.
3403        word: Ident,
3404        /// Source location and node identity.
3405        meta: Meta,
3406    },
3407    /// A string constant (`VERSION '1.0'`).
3408    String {
3409        /// Value supplied by this syntax.
3410        value: Literal,
3411        /// Source location and node identity.
3412        meta: Meta,
3413    },
3414}
3415
3416/// An `ALTER EXTENSION <name> ...` statement, gated by
3417/// [`StatementDdlGates::extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl).
3418///
3419/// Unifies PostgreSQL's two `ALTER EXTENSION` productions under one node, since both
3420/// share the `ALTER EXTENSION <name>` head: the `UPDATE [TO <version>]` version bump
3421/// (`AlterExtensionStmt`) and the `ADD`/`DROP <member>` membership change
3422/// (`AlterExtensionContentsStmt`). PostgreSQL's `ALTER EXTENSION <name> SET SCHEMA
3423/// <schema>` is a separate `AlterObjectSchemaStmt` production (shared with every other
3424/// relocatable object), not an `AlterExtensionStmt`, so it is out of this node's scope.
3425#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3426#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3427#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3428pub struct AlterExtension<X: Extension = NoExt> {
3429    /// The extension name.
3430    pub name: Ident,
3431    /// The change applied to the extension.
3432    pub action: AlterExtensionAction<X>,
3433    /// Source location and node identity.
3434    pub meta: Meta,
3435}
3436
3437/// The change an [`AlterExtension`] applies.
3438#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3439#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3440#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3441pub enum AlterExtensionAction<X: Extension = NoExt> {
3442    /// `UPDATE [TO <version>]` — bump the installed version (`AlterExtensionStmt`).
3443    Update {
3444        /// The target version, when a `TO` clause was written.
3445        version: Option<ExtensionVersion>,
3446        /// Source location and node identity.
3447        meta: Meta,
3448    },
3449    /// `ADD <member>` (`add` is `true`) or `DROP <member>` — change extension membership
3450    /// (`AlterExtensionContentsStmt`).
3451    Change {
3452        /// `true` for `ADD`, `false` for `DROP`.
3453        add: bool,
3454        /// The member object added to or dropped from the extension.
3455        member: ObjectReference<X>,
3456        /// Source location and node identity.
3457        meta: Meta,
3458    },
3459}
3460
3461/// An `ALTER DATABASE [IF EXISTS] <name> SET ALIAS TO <alias>` statement (DuckDB), gated by
3462/// [`StatementDdlGates::alter_database`](crate::dialect::StatementDdlGates::alter_database).
3463///
3464/// DuckDB's sole `AlterDatabaseStmt` production re-aliases an attached database
3465/// (`third_party/libpg_query/grammar/statements/alter_database.y` at the pinned v1.5.4
3466/// commit `08e34c447b`). The grammar reduces `SET <ident> TO <name>`, but its action rejects
3467/// any keyword but `ALIAS`, so only `SET ALIAS TO` is a parse-accept (engine-measured: `SET
3468/// FOO TO` is a syntax error, and `ALTER DATABASE d RENAME TO e` is likewise a syntax error —
3469/// DuckDB has no `RENAME` form for a database). The change is held in an
3470/// [`AlterDatabaseAction`] enum so a sibling dialect (MySQL's charset/collation `ALTER
3471/// DATABASE`) can add its own action variants without reshaping this node.
3472#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3473#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3474#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3475pub struct AlterDatabase {
3476    /// Whether the `IF EXISTS` existence guard was present in the source.
3477    pub if_exists: bool,
3478    /// The database name.
3479    pub name: Ident,
3480    /// The change applied to the database.
3481    pub action: AlterDatabaseAction,
3482    /// Source location and node identity.
3483    pub meta: Meta,
3484}
3485
3486/// The change an [`AlterDatabase`] applies.
3487#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3488#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3489#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3490pub enum AlterDatabaseAction {
3491    /// `SET ALIAS TO <alias>` — re-alias the attached database.
3492    SetAlias {
3493        /// The new alias.
3494        new_name: Ident,
3495        /// Source location and node identity.
3496        meta: Meta,
3497    },
3498}
3499
3500/// An `ALTER SEQUENCE [IF EXISTS] <name> <option>...` statement (DuckDB), gated by
3501/// [`StatementDdlGates::alter_sequence`](crate::dialect::StatementDdlGates::alter_sequence).
3502///
3503/// DuckDB's `AlterSeqStmt` production changes a sequence generator's options
3504/// (`third_party/libpg_query/grammar/statements/alter_sequence.y` at the pinned v1.5.4
3505/// commit). Its `SeqOptList` is the same option grammar `CREATE SEQUENCE` reduces, so the
3506/// shared core is carried through the reused [`IdentityOption`] axis
3507/// ([`AlterSequenceOption::Common`]); the ALTER-only leads DuckDB additionally parses
3508/// (`RESTART`, `AS <type>`, `OWNED BY`, `SEQUENCE NAME`) are their own variants. Options
3509/// are kept in source order. `SET SCHEMA` is *not* an option here — it is DuckDB's separate
3510/// [`AlterObjectSchema`] production, dispatched before this one.
3511#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3512#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3513#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3514pub struct AlterSequence<X: Extension = NoExt> {
3515    /// Whether the `IF EXISTS` existence guard was present in the source.
3516    pub if_exists: bool,
3517    /// The sequence name.
3518    pub name: ObjectName,
3519    /// Options supplied in source order.
3520    pub options: ThinVec<AlterSequenceOption<X>>,
3521    /// Source location and node identity.
3522    pub meta: Meta,
3523}
3524
3525/// One `ALTER SEQUENCE` option (DuckDB's `SeqOptElem`).
3526#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3527#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3528#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3529pub enum AlterSequenceOption<X: Extension = NoExt> {
3530    /// A shared sequence-generator option `CREATE SEQUENCE` also accepts (`START [WITH]`,
3531    /// `INCREMENT [BY]`, `MIN`/`MAXVALUE`, `NO MIN`/`MAXVALUE`, `CYCLE`/`NO CYCLE`, `CACHE`),
3532    /// reusing the [`IdentityOption`] axis.
3533    Common {
3534        /// The reused shared option.
3535        option: IdentityOption<X>,
3536        /// Source location and node identity.
3537        meta: Meta,
3538    },
3539    /// `RESTART [[WITH] <n>]` — reset the sequence's current value (bare = restart to its
3540    /// start value).
3541    Restart {
3542        /// The restart value, when a `[WITH] <n>` tail was written.
3543        value: Option<Expr<X>>,
3544        /// Source location and node identity.
3545        meta: Meta,
3546    },
3547    /// `AS <type>` — change the sequence's integer type.
3548    As {
3549        /// The sequence's new integer type.
3550        data_type: DataType<X>,
3551        /// Source location and node identity.
3552        meta: Meta,
3553    },
3554    /// `OWNED BY {<column> | NONE}` — tie the sequence's lifetime to a column, or detach it
3555    /// (`None` renders `NONE`).
3556    OwnedBy {
3557        /// The owning column, or `None` for `OWNED BY NONE`.
3558        owner: Option<ObjectName>,
3559        /// Source location and node identity.
3560        meta: Meta,
3561    },
3562    /// `SEQUENCE NAME <name>` — the internal rename form libpg_query documents as pg_dump-only.
3563    SequenceName {
3564        /// The new internal name.
3565        name: ObjectName,
3566        /// Source location and node identity.
3567        meta: Meta,
3568    },
3569}
3570
3571/// An `ALTER {TABLE | VIEW | SEQUENCE} [IF EXISTS] <name> SET SCHEMA <schema>` statement
3572/// (DuckDB's `AlterObjectSchemaStmt`), gated by
3573/// [`StatementDdlGates::alter_object_set_schema`](crate::dialect::StatementDdlGates::alter_object_set_schema).
3574///
3575/// Relocates a relocatable object to another schema
3576/// (`third_party/libpg_query/grammar/statements/alter_schema.y` at the pinned v1.5.4 commit).
3577/// DuckDB 1.5.4's binder rejects this with `Not implemented: T_AlterObjectSchemaStmt`, but the
3578/// production is parse-reachable (engine-measured via `json_serialize_sql`), and PARSE-level
3579/// parity is the modelled bar — analogous to PostgreSQL's grammar-present, engine-unimplemented
3580/// `CreateAssertionStmt`. The object kind is one of [`SchemaRelocationObject`]'s three.
3581#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3582#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3583#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3584pub struct AlterObjectSchema {
3585    /// Which relocatable object kind was named.
3586    pub object_type: SchemaRelocationObject,
3587    /// Whether the `IF EXISTS` existence guard was present in the source.
3588    pub if_exists: bool,
3589    /// The object's current (schema-qualified) name.
3590    pub name: ObjectName,
3591    /// The destination schema.
3592    pub new_schema: Ident,
3593    /// Source location and node identity.
3594    pub meta: Meta,
3595}
3596
3597/// The relocatable object kind an [`AlterObjectSchema`] moves — DuckDB's `AlterObjectSchemaStmt`
3598/// admits exactly these three object heads.
3599#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3600#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3601#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3602pub enum SchemaRelocationObject {
3603    /// `ALTER TABLE … SET SCHEMA`.
3604    Table,
3605    /// `ALTER VIEW … SET SCHEMA`.
3606    View,
3607    /// `ALTER SEQUENCE … SET SCHEMA`.
3608    Sequence,
3609}
3610
3611/// A reference to a database object by kind and kind-appropriate signature — the shared
3612/// object-reference axis for PostgreSQL object-membership DDL.
3613///
3614/// PostgreSQL's `ALTER EXTENSION … ADD|DROP <member>` grammar
3615/// (`AlterExtensionContentsStmt`) names a member object by a kind keyword plus a
3616/// signature whose shape depends on the kind: a bare or qualified name, a routine
3617/// argument-type signature, an aggregate/operator signature, a cast type pair, a type
3618/// name, or a transform. The sibling object-DDL heads name objects the same way — the
3619/// `parse-pg-alter-object-depends` head (`ALTER … DEPENDS ON EXTENSION`) reuses
3620/// [`Routine`](Self::Routine) and [`Named`](Self::Named) for its `FUNCTION`/`PROCEDURE`/
3621/// `ROUTINE`, `MATERIALIZED VIEW`, and `INDEX` objects, and `parse-pg-drop-transform`
3622/// reuses [`Transform`](Self::Transform) — so the axis lives here once rather than being
3623/// re-derived per head.
3624///
3625/// [`Trigger`](Self::Trigger) is the one shape no `ALTER EXTENSION` member takes: its
3626/// `TRIGGER <name> ON <table>` form is exclusive to the `DEPENDS ON EXTENSION` head
3627/// (`ALTER EXTENSION … ADD TRIGGER` is a reject), so it was added to the axis additively
3628/// when that head's measured grammar demanded it, keeping every object-DDL head on one
3629/// shared reference type.
3630#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3631#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3632#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3633pub enum ObjectReference<X: Extension = NoExt> {
3634    /// A kind keyword and a name. [`kind`](ObjectRefKind) records whether the name may be
3635    /// schema-qualified: PostgreSQL's `object_type_any_name` kinds (`TABLE`, `VIEW`, …)
3636    /// accept a dotted `any_name`, its `object_type_name` kinds (`SCHEMA`, `ROLE`, …)
3637    /// only a single `name`.
3638    Named {
3639        /// The object kind; see [`ObjectRefKind`].
3640        kind: ObjectRefKind,
3641        /// The referenced name.
3642        name: ObjectName,
3643        /// Source location and node identity.
3644        meta: Meta,
3645    },
3646    /// `FUNCTION`/`PROCEDURE`/`ROUTINE <name>[(<argtypes>)]`
3647    /// (PostgreSQL's `function_with_argtypes`; the argument list is optional).
3648    Routine {
3649        /// Which routine kind; see [`RoutineObjectKind`].
3650        kind: RoutineObjectKind,
3651        /// The routine name and optional argument-type signature.
3652        signature: RoutineSignature<X>,
3653        /// Source location and node identity.
3654        meta: Meta,
3655    },
3656    /// `AGGREGATE <name>(<aggr_args>)`.
3657    Aggregate {
3658        /// The aggregate name.
3659        name: ObjectName,
3660        /// The aggregate argument signature.
3661        args: AggregateArgs<X>,
3662        /// Source location and node identity.
3663        meta: Meta,
3664    },
3665    /// `OPERATOR [<schema>.]<sym>(<left>, <right>)` (PostgreSQL's `operator_with_argtypes`).
3666    Operator {
3667        /// The optional schema qualification (empty for an unqualified operator).
3668        schema: ObjectName,
3669        /// The symbolic operator spelling, interned exact-case.
3670        op: Symbol,
3671        /// The operand types.
3672        args: OperatorArgs<X>,
3673        /// Source location and node identity.
3674        meta: Meta,
3675    },
3676    /// `OPERATOR CLASS`/`OPERATOR FAMILY <name> USING <access_method>`.
3677    OperatorClass {
3678        /// `true` for `OPERATOR FAMILY`, `false` for `OPERATOR CLASS`.
3679        family: bool,
3680        /// The operator class/family name.
3681        name: ObjectName,
3682        /// The `USING <access_method>` index access method.
3683        access_method: Ident,
3684        /// Source location and node identity.
3685        meta: Meta,
3686    },
3687    /// `CAST (<from> AS <to>)`.
3688    Cast {
3689        /// The cast's source type.
3690        from: DataType<X>,
3691        /// The cast's target type.
3692        to: DataType<X>,
3693        /// Source location and node identity.
3694        meta: Meta,
3695    },
3696    /// `DOMAIN`/`TYPE <typename>` — a type-named object.
3697    Type {
3698        /// `true` for the `DOMAIN` keyword, `false` for `TYPE` (both name a type via a
3699        /// `Typename`).
3700        domain: bool,
3701        /// The type name (a `Typename`, possibly schema-qualified).
3702        name: DataType<X>,
3703        /// Source location and node identity.
3704        meta: Meta,
3705    },
3706    /// `TRANSFORM FOR <typename> LANGUAGE <language>`.
3707    Transform {
3708        /// The type the transform is `FOR`.
3709        type_name: DataType<X>,
3710        /// The transform's procedural language name.
3711        language: Ident,
3712        /// Source location and node identity.
3713        meta: Meta,
3714    },
3715    /// `TRIGGER <name> ON <table>` — a trigger named by its bare name plus the table it
3716    /// is defined on (PostgreSQL's `ALTER TRIGGER name ON qualified_name`). Unlike the
3717    /// other object kinds, a trigger is not schema-qualifiable on its own name (it is a
3718    /// `name`, a single `ColId`); the `table` carries the qualification.
3719    ///
3720    /// This variant is reached only by the `DEPENDS ON EXTENSION` head — no `ALTER
3721    /// EXTENSION` member is a trigger — so `parse_object_reference` never constructs it.
3722    Trigger {
3723        /// The trigger's bare name.
3724        name: Ident,
3725        /// The table the trigger is defined on (a `qualified_name`).
3726        table: ObjectName,
3727        /// Source location and node identity.
3728        meta: Meta,
3729    },
3730}
3731
3732/// A member-object kind naming an object by a bare or qualified name — the
3733/// name-only shapes of [`ObjectReference::Named`], covering PostgreSQL's
3734/// `object_type_any_name` and `object_type_name` productions.
3735#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3736#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3737#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3738pub enum ObjectRefKind {
3739    /// `TABLE`.
3740    Table,
3741    /// `SEQUENCE`.
3742    Sequence,
3743    /// `VIEW`.
3744    View,
3745    /// `MATERIALIZED VIEW`.
3746    MaterializedView,
3747    /// `INDEX`.
3748    Index,
3749    /// `FOREIGN TABLE`.
3750    ForeignTable,
3751    /// `COLLATION`.
3752    Collation,
3753    /// `CONVERSION`.
3754    Conversion,
3755    /// `STATISTICS`.
3756    Statistics,
3757    /// `TEXT SEARCH PARSER`.
3758    TextSearchParser,
3759    /// `TEXT SEARCH DICTIONARY`.
3760    TextSearchDictionary,
3761    /// `TEXT SEARCH TEMPLATE`.
3762    TextSearchTemplate,
3763    /// `TEXT SEARCH CONFIGURATION`.
3764    TextSearchConfiguration,
3765    /// `ACCESS METHOD`.
3766    AccessMethod,
3767    /// `EVENT TRIGGER`.
3768    EventTrigger,
3769    /// `EXTENSION`.
3770    Extension,
3771    /// `FOREIGN DATA WRAPPER`.
3772    ForeignDataWrapper,
3773    /// `[PROCEDURAL] LANGUAGE`. The optional `PROCEDURAL` keyword is exact-synonym sugar
3774    /// (PostgreSQL's `opt_procedural`); the canonical render emits `LANGUAGE`.
3775    Language,
3776    /// `PUBLICATION`.
3777    Publication,
3778    /// `SCHEMA`.
3779    Schema,
3780    /// `SERVER` (a foreign server).
3781    Server,
3782    /// `DATABASE`.
3783    Database,
3784    /// `ROLE`.
3785    Role,
3786    /// `TABLESPACE`.
3787    Tablespace,
3788}
3789
3790impl ObjectRefKind {
3791    /// Whether this kind names a schema-qualifiable object (PostgreSQL's
3792    /// `object_type_any_name` group), so its name may be a dotted `any_name`. The
3793    /// remaining kinds are `object_type_name`, which take only a single `name`.
3794    pub fn schema_qualifiable(self) -> bool {
3795        matches!(
3796            self,
3797            Self::Table
3798                | Self::Sequence
3799                | Self::View
3800                | Self::MaterializedView
3801                | Self::Index
3802                | Self::ForeignTable
3803                | Self::Collation
3804                | Self::Conversion
3805                | Self::Statistics
3806                | Self::TextSearchParser
3807                | Self::TextSearchDictionary
3808                | Self::TextSearchTemplate
3809                | Self::TextSearchConfiguration
3810        )
3811    }
3812}
3813
3814/// The argument signature of an `AGGREGATE` member (PostgreSQL's `aggr_args`).
3815///
3816/// Argument names and modes are dropped, keeping only the type list — the same
3817/// simplification [`RoutineSignature`] applies to `function_with_argtypes`.
3818#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3819#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3820#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3821pub enum AggregateArgs<X: Extension = NoExt> {
3822    /// `(*)` — the zero-argument wildcard.
3823    Star {
3824        /// Source location and node identity.
3825        meta: Meta,
3826    },
3827    /// A direct argument-type list, optionally followed by an ordered-set `ORDER BY`
3828    /// list: `(<types>)`, `(ORDER BY <types>)` (empty `direct`), or
3829    /// `(<types> ORDER BY <types>)`.
3830    Types {
3831        /// Direct argument types (empty for the leading `ORDER BY` form).
3832        direct: ThinVec<DataType<X>>,
3833        /// Ordered-set `ORDER BY` argument types, when the clause is present.
3834        order_by: Option<ThinVec<DataType<X>>>,
3835        /// Source location and node identity.
3836        meta: Meta,
3837    },
3838}
3839
3840/// The operand types of an `OPERATOR` member (PostgreSQL's `oper_argtypes`): a binary or
3841/// unary operator's left and right operand types, where a `NONE` operand (a unary
3842/// operator's missing side) is `None`. At least one side is always present.
3843#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3844#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3845#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3846pub struct OperatorArgs<X: Extension = NoExt> {
3847    /// The left operand type, or `None` for a left-unary operator's `NONE`.
3848    pub left: Option<DataType<X>>,
3849    /// The right operand type, or `None` for a right-unary operator's `NONE`.
3850    pub right: Option<DataType<X>>,
3851    /// Source location and node identity.
3852    pub meta: Meta,
3853}
3854
3855/// An `ALTER <object> [NO] DEPENDS ON EXTENSION <extension>` statement (PostgreSQL's
3856/// `AlterObjectDependsStmt`), gated by
3857/// [`StatementDdlGates::extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl).
3858///
3859/// Records or clears a dependency of a database object on an extension, so that dropping
3860/// the extension cascades to the object (and `pg_dump` skips it). PostgreSQL admits only
3861/// four object heads here — `FUNCTION`/`PROCEDURE`/`ROUTINE` (a `function_with_argtypes`),
3862/// `TRIGGER <name> ON <table>`, `MATERIALIZED VIEW`, and `INDEX` — captured by the shared
3863/// [`ObjectReference`] axis, so the wider `object_type` keyword set (`TABLE`, `VIEW`,
3864/// `SEQUENCE`, …) that `object` could otherwise spell is out of the parsed grammar.
3865///
3866/// The construct references an extension but operates on a non-extension object; it shares
3867/// the [`extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl) gate rather than
3868/// carrying its own, because it is meaningless without the extension system and its dialect
3869/// boundary (PostgreSQL on, everything else off) is identical to extension DDL's — a
3870/// dialect that has extensions has this, one that does not (DuckDB's `INSTALL`/`LOAD`) has
3871/// neither.
3872#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3873#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3874#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3875pub struct AlterObjectDepends<X: Extension = NoExt> {
3876    /// The object whose extension dependency is being set — one of the four
3877    /// [`ObjectReference`] shapes the `DEPENDS` grammar admits.
3878    pub object: ObjectReference<X>,
3879    /// Whether the `NO DEPENDS` negation form was written (PostgreSQL's `opt_no`), which
3880    /// removes the recorded dependency instead of adding it.
3881    pub no: bool,
3882    /// The extension the object depends on (a bare `name`, not schema-qualifiable).
3883    pub extension: Ident,
3884    /// Source location and node identity.
3885    pub meta: Meta,
3886}
3887
3888/// A MySQL `CREATE SERVER <name> FOREIGN DATA WRAPPER <wrapper> OPTIONS ( <option> [, ...] )`
3889/// federated-server definition (`sql_yacc.yy` `CREATE SERVER_SYM …`), gated by
3890/// [`StatementDdlGates::server_definition`](crate::dialect::StatementDdlGates::server_definition).
3891///
3892/// Registers a remote server for the `FEDERATED` storage engine. The server name and the
3893/// `FOREIGN DATA WRAPPER` name are each an `ident_or_text` (a bare/backtick identifier or a
3894/// quoted string, folded to an [`Ident`] whose quote style round-trips). The `OPTIONS` list is
3895/// the fixed [`ServerOption`] keyword set and is non-empty — an empty `OPTIONS ()` is
3896/// `ER_PARSE_ERROR` on mysql:8.4.10. Shares its gate and the [`ServerOption`] axis with
3897/// [`AlterServer`] (the two differ only in whether a wrapper is named); [`DropServer`] disposes
3898/// of the same object.
3899#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3900#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3901#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3902pub struct CreateServer {
3903    /// The server name (`ident_or_text`).
3904    pub name: Ident,
3905    /// The `FOREIGN DATA WRAPPER` name (`ident_or_text`) — the storage-engine wrapper the
3906    /// server uses.
3907    pub wrapper: Ident,
3908    /// The `OPTIONS` list, in source order; non-empty (`server_options_list`).
3909    pub options: ThinVec<ServerOption>,
3910    /// Source location and node identity.
3911    pub meta: Meta,
3912}
3913
3914/// A MySQL `ALTER SERVER <name> OPTIONS ( <option> [, ...] )` federated-server change
3915/// (`sql_yacc.yy` `ALTER SERVER_SYM …`), gated by
3916/// [`StatementDdlGates::server_definition`](crate::dialect::StatementDdlGates::server_definition).
3917///
3918/// Re-sets connection options on an existing server. Unlike [`CreateServer`] it names no
3919/// `FOREIGN DATA WRAPPER` (the wrapper is fixed at creation), but the `OPTIONS` list is the
3920/// same non-empty [`ServerOption`] grammar.
3921#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3922#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3923#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3924pub struct AlterServer {
3925    /// The server name (`ident_or_text`).
3926    pub name: Ident,
3927    /// The `OPTIONS` list, in source order; non-empty (`server_options_list`).
3928    pub options: ThinVec<ServerOption>,
3929    /// Source location and node identity.
3930    pub meta: Meta,
3931}
3932
3933/// A MySQL `DROP SERVER [IF EXISTS] <name>` federated-server drop (`sql_yacc.yy`
3934/// `drop_server_stmt`), gated by
3935/// [`StatementDdlGates::server_definition`](crate::dialect::StatementDdlGates::server_definition).
3936///
3937/// Removes a single server; no comma list (`DROP SERVER a, b` is `ER_PARSE_ERROR` on
3938/// mysql:8.4.10). The name is an `ident_or_text`.
3939#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3940#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3941#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3942pub struct DropServer {
3943    /// Whether the `IF EXISTS` guard was written (`drop_server_stmt`'s `if_exists`).
3944    pub if_exists: bool,
3945    /// The server name (`ident_or_text`).
3946    pub name: Ident,
3947    /// Source location and node identity.
3948    pub meta: Meta,
3949}
3950
3951/// One `CREATE`/`ALTER SERVER` option — a fixed keyword ([`ServerOptionKind`]) and its value
3952/// (`sql_yacc.yy` `server_option`). Every option carries exactly one value: the string-valued
3953/// keywords (`HOST`/`DATABASE`/`USER`/`PASSWORD`/`SOCKET`/`OWNER`) take a `TEXT_STRING_sys`
3954/// string literal, and `PORT` takes a `ulong_num` unsigned-integer literal — the parser holds
3955/// each to its measured value type (`HOST 123` and `PORT '3306'` are both `ER_PARSE_ERROR` on
3956/// mysql:8.4.10). The value is a [`Literal`] whose [`kind`](Literal::kind) records which.
3957#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3958#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3959#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3960pub struct ServerOption {
3961    /// Which fixed option keyword this is.
3962    pub kind: ServerOptionKind,
3963    /// The option value — a string literal for every kind but [`ServerOptionKind::Port`],
3964    /// which carries an unsigned-integer literal.
3965    pub value: Literal,
3966    /// Source location and node identity.
3967    pub meta: Meta,
3968}
3969
3970/// The fixed keyword of a [`ServerOption`] (`sql_yacc.yy` `server_option`). A surface tag (no
3971/// `meta` — its span rides the owning [`ServerOption`]). Only these seven keywords are
3972/// options; any other (`FOO 'bar'`) is `ER_PARSE_ERROR` on mysql:8.4.10.
3973#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3974#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3975#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3976pub enum ServerOptionKind {
3977    /// `HOST '<host>'` — the remote host name.
3978    Host,
3979    /// `DATABASE '<db>'` — the remote default database.
3980    Database,
3981    /// `USER '<user>'` — the connection user.
3982    User,
3983    /// `PASSWORD '<password>'` — the connection password.
3984    Password,
3985    /// `SOCKET '<socket>'` — the remote unix socket path.
3986    Socket,
3987    /// `OWNER '<owner>'` — the server owner.
3988    Owner,
3989    /// `PORT <n>` — the remote TCP port (an unsigned-integer value, not a string).
3990    Port,
3991}
3992
3993/// A MySQL `ALTER INSTANCE <action>` server-instance administration statement (`sql_yacc.yy`
3994/// `alter_instance_stmt`), gated by
3995/// [`StatementDdlGates::alter_instance`](crate::dialect::StatementDdlGates::alter_instance).
3996///
3997/// A single instance-wide maintenance action (rotate an encryption master key, reload TLS
3998/// material or the keyring, toggle the InnoDB redo log) — see [`AlterInstanceAction`]. Unlike
3999/// the server and database DDL this names no object.
4000#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4001#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4002#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4003pub struct AlterInstance {
4004    /// The instance action performed.
4005    pub action: AlterInstanceAction,
4006    /// Source location and node identity.
4007    pub meta: Meta,
4008}
4009
4010/// The action of an [`AlterInstance`] (`sql_yacc.yy` `alter_instance_action`). The `INNODB`,
4011/// `BINLOG`, and `REDO_LOG` words are matched as identifiers by the server (`is_identifier`),
4012/// so a wrong word is `ER_PARSE_ERROR` (`ROTATE FOO MASTER KEY`, `ENABLE INNODB FOO`); this
4013/// models only the measured accepted set.
4014#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4015#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4016#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4017pub enum AlterInstanceAction {
4018    /// `ROTATE INNODB MASTER KEY` — rotate the InnoDB tablespace-encryption master key.
4019    RotateInnodbMasterKey {
4020        /// Source location and node identity.
4021        meta: Meta,
4022    },
4023    /// `ROTATE BINLOG MASTER KEY` — rotate the binary-log encryption master key.
4024    RotateBinlogMasterKey {
4025        /// Source location and node identity.
4026        meta: Meta,
4027    },
4028    /// `RELOAD TLS [FOR CHANNEL <channel>] [NO ROLLBACK ON ERROR]` — reconfigure the TLS
4029    /// context. The optional `FOR CHANNEL` names a bare `ident` (a quoted `'ch'` is
4030    /// `ER_PARSE_ERROR` on mysql:8.4.10, unlike `FLUSH … FOR CHANNEL`'s string); the default
4031    /// is to roll back the active context on error, and `NO ROLLBACK ON ERROR` suppresses it.
4032    ReloadTls {
4033        /// The `FOR CHANNEL <channel>` name (a bare `ident`), or `None`.
4034        channel: Option<Ident>,
4035        /// Whether `NO ROLLBACK ON ERROR` was written.
4036        no_rollback_on_error: bool,
4037        /// Source location and node identity.
4038        meta: Meta,
4039    },
4040    /// `RELOAD KEYRING` — reload keyring component options from the manifest.
4041    ReloadKeyring {
4042        /// Source location and node identity.
4043        meta: Meta,
4044    },
4045    /// `ENABLE INNODB REDO_LOG` — re-enable redo logging.
4046    EnableInnodbRedoLog {
4047        /// Source location and node identity.
4048        meta: Meta,
4049    },
4050    /// `DISABLE INNODB REDO_LOG` — disable redo logging (for bulk load).
4051    DisableInnodbRedoLog {
4052        /// Source location and node identity.
4053        meta: Meta,
4054    },
4055}
4056
4057/// A MySQL `CREATE [OR REPLACE] SPATIAL REFERENCE SYSTEM [IF NOT EXISTS] <srid> <attributes>`
4058/// spatial-reference-system definition (`sql_yacc.yy` `create_srs_stmt`), gated by
4059/// [`StatementDdlGates::spatial_reference_system`](crate::dialect::StatementDdlGates::spatial_reference_system).
4060///
4061/// `OR REPLACE` and `IF NOT EXISTS` are mutually exclusive: the grammar has one branch for each
4062/// (`CREATE OR REPLACE …` and `CREATE … IF NOT EXISTS …`), so `CREATE OR REPLACE … IF NOT EXISTS`
4063/// is `ER_PARSE_ERROR` on mysql:8.4.10. The `<srid>` is a `real_ulonglong_num` unsigned integer
4064/// (decimal or `0x` hex; a negative or fractional value is `ER_PARSE_ERROR`, an out-of-`u64`-range
4065/// value is the post-parse `ER_DATA_OUT_OF_RANGE`). Every attribute is optional and the list is
4066/// order-free at the grammar level — a missing NAME/DEFINITION is the post-parse semantic reject
4067/// `ER_SRS_MISSING_MANDATORY_ATTRIBUTE`, and a repeated attribute is
4068/// `ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS`, neither a syntax error — so [`attributes`](Self::attributes)
4069/// keeps the source order and admits repeats, matching the recognized grammar rather than the
4070/// narrower semantic rule.
4071#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4072#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4073#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4074pub struct CreateSpatialReferenceSystem {
4075    /// Whether the `OR REPLACE` branch was written (mutually exclusive with
4076    /// [`if_not_exists`](Self::if_not_exists)).
4077    pub or_replace: bool,
4078    /// Whether the `IF NOT EXISTS` guard was written (mutually exclusive with
4079    /// [`or_replace`](Self::or_replace)).
4080    pub if_not_exists: bool,
4081    /// The SRID — a `real_ulonglong_num` unsigned-integer literal (decimal or `0x` hex).
4082    pub srid: Literal,
4083    /// The attribute list, in source order (order-free grammar; repeats admitted).
4084    pub attributes: ThinVec<SrsAttribute>,
4085    /// Source location and node identity.
4086    pub meta: Meta,
4087}
4088
4089/// One `CREATE SPATIAL REFERENCE SYSTEM` attribute (`sql_yacc.yy` `srs_attributes`). The
4090/// attributes are order-free and each independently optional; a repeat of the same attribute is
4091/// a post-parse semantic reject (`ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS`), not a syntax error,
4092/// so the parser accepts the recognized grammar and preserves whatever the source wrote.
4093#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4094#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4095#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4096pub enum SrsAttribute {
4097    /// `NAME '<name>'` — the SRS name (a `TEXT_STRING_sys_nonewline` string literal).
4098    Name {
4099        /// The name string.
4100        value: Literal,
4101        /// Source location and node identity.
4102        meta: Meta,
4103    },
4104    /// `DEFINITION '<wkt>'` — the WKT coordinate-system definition string.
4105    Definition {
4106        /// The definition string.
4107        value: Literal,
4108        /// Source location and node identity.
4109        meta: Meta,
4110    },
4111    /// `ORGANIZATION '<org>' IDENTIFIED BY <id>` — the authoring organization and its numeric
4112    /// coordinate-system id. `IDENTIFIED BY` is mandatory (`ORGANIZATION 'o'` alone is
4113    /// `ER_PARSE_ERROR`) and the id is a `real_ulonglong_num` integer (`IDENTIFIED BY 'x'` is
4114    /// `ER_PARSE_ERROR`).
4115    Organization {
4116        /// The organization name string.
4117        organization: Literal,
4118        /// The `IDENTIFIED BY` numeric coordinate-system id (unsigned-integer literal).
4119        identifier: Literal,
4120        /// Source location and node identity.
4121        meta: Meta,
4122    },
4123    /// `DESCRIPTION '<text>'` — the free-text description string.
4124    Description {
4125        /// The description string.
4126        value: Literal,
4127        /// Source location and node identity.
4128        meta: Meta,
4129    },
4130}
4131
4132/// A MySQL `DROP SPATIAL REFERENCE SYSTEM [IF EXISTS] <srid>` spatial-reference-system drop
4133/// (`sql_yacc.yy` `drop_srs_stmt`), gated by
4134/// [`StatementDdlGates::spatial_reference_system`](crate::dialect::StatementDdlGates::spatial_reference_system).
4135///
4136/// Removes a single SRS by id; no comma list (`DROP SPATIAL REFERENCE SYSTEM 1, 2` is
4137/// `ER_PARSE_ERROR` on mysql:8.4.10). The `<srid>` is a `real_ulonglong_num` unsigned integer.
4138#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4139#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4140#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4141pub struct DropSpatialReferenceSystem {
4142    /// Whether the `IF EXISTS` guard was written (`drop_srs_stmt`'s `if_exists`).
4143    pub if_exists: bool,
4144    /// The SRID — a `real_ulonglong_num` unsigned-integer literal.
4145    pub srid: Literal,
4146    /// Source location and node identity.
4147    pub meta: Meta,
4148}
4149
4150/// A MySQL `CREATE RESOURCE GROUP <name> TYPE [=] {SYSTEM | USER} [VCPU …] [THREAD_PRIORITY …]
4151/// [ENABLE | DISABLE]` resource-group definition (`sql_yacc.yy` `create_resource_group_stmt`),
4152/// gated by [`StatementDdlGates::resource_group`](crate::dialect::StatementDdlGates::resource_group).
4153///
4154/// `TYPE` is mandatory (a bare `CREATE RESOURCE GROUP g` is `ER_PARSE_ERROR` on mysql:8.4.10) and
4155/// the option train is fixed-order — `VCPU`, then `THREAD_PRIORITY`, then `ENABLE`/`DISABLE`; any
4156/// permutation (`… ENABLE VCPU …`) is `ER_PARSE_ERROR`. `CREATE` admits no `FORCE` (`ENABLE FORCE`
4157/// is `ER_PARSE_ERROR`), unlike [`AlterResourceGroup`] and [`DropResourceGroup`], which share the
4158/// same [`resource_group`](crate::dialect::StatementDdlGates::resource_group) gate.
4159#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4160#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4161#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4162pub struct CreateResourceGroup {
4163    /// The resource-group name (`ident`).
4164    pub name: Ident,
4165    /// Whether the `TYPE =` spelling (vs bare `TYPE`) was written (`opt_equal`).
4166    pub type_equals: bool,
4167    /// The group type (`SYSTEM`/`USER`), mandatory.
4168    pub group_type: ResourceGroupType,
4169    /// The optional `VCPU [=] <ranges>` CPU-affinity clause.
4170    pub vcpu: Option<ResourceGroupVcpu>,
4171    /// The optional `THREAD_PRIORITY [=] <n>` clause.
4172    pub thread_priority: Option<ResourceGroupThreadPriority>,
4173    /// The optional `ENABLE`/`DISABLE` state clause.
4174    pub state: Option<ResourceGroupState>,
4175    /// Source location and node identity.
4176    pub meta: Meta,
4177}
4178
4179/// A MySQL `ALTER RESOURCE GROUP <name> [VCPU …] [THREAD_PRIORITY …] [ENABLE | DISABLE] [FORCE]`
4180/// resource-group change (`sql_yacc.yy` `alter_resource_group_stmt`), gated by
4181/// [`StatementDdlGates::resource_group`](crate::dialect::StatementDdlGates::resource_group).
4182///
4183/// Every clause is optional — a bare `ALTER RESOURCE GROUP g` grammar-accepts on mysql:8.4.10.
4184/// The trailing `FORCE` is an *independent* optional (`opt_force`), not a suffix of
4185/// `ENABLE`/`DISABLE`: `ALTER RESOURCE GROUP g FORCE`, with neither state keyword, grammar-accepts
4186/// — so [`force`](Self::force) is a peer of [`state`](Self::state), not nested inside it. Shares
4187/// the [`ResourceGroupVcpu`]/[`ResourceGroupThreadPriority`]/[`ResourceGroupState`] axes with
4188/// [`CreateResourceGroup`]; `CREATE` differs only in requiring `TYPE` and forbidding `FORCE`.
4189#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4190#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4191#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4192pub struct AlterResourceGroup {
4193    /// The resource-group name (`ident`).
4194    pub name: Ident,
4195    /// The optional `VCPU [=] <ranges>` CPU-affinity clause.
4196    pub vcpu: Option<ResourceGroupVcpu>,
4197    /// The optional `THREAD_PRIORITY [=] <n>` clause.
4198    pub thread_priority: Option<ResourceGroupThreadPriority>,
4199    /// The optional `ENABLE`/`DISABLE` state clause.
4200    pub state: Option<ResourceGroupState>,
4201    /// Whether the trailing `FORCE` was written (`opt_force`, independent of
4202    /// [`state`](Self::state)).
4203    pub force: bool,
4204    /// Source location and node identity.
4205    pub meta: Meta,
4206}
4207
4208/// A MySQL `DROP RESOURCE GROUP <name> [FORCE]` resource-group drop (`sql_yacc.yy`
4209/// `drop_resource_group_stmt`), gated by
4210/// [`StatementDdlGates::resource_group`](crate::dialect::StatementDdlGates::resource_group).
4211///
4212/// Removes a single group by name; the optional trailing `FORCE` (`opt_force`) evicts running
4213/// threads.
4214#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4215#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4216#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4217pub struct DropResourceGroup {
4218    /// The resource-group name (`ident`).
4219    pub name: Ident,
4220    /// Whether the trailing `FORCE` was written (`opt_force`).
4221    pub force: bool,
4222    /// Source location and node identity.
4223    pub meta: Meta,
4224}
4225
4226/// The `TYPE` of a [`CreateResourceGroup`] (`sql_yacc.yy` `resource_group_types`): the fixed
4227/// `SYSTEM`/`USER` keyword set. A surface tag (no `meta` — its span rides the owning node); any
4228/// other word is `ER_PARSE_ERROR`.
4229#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4230#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4231#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4232pub enum ResourceGroupType {
4233    /// `SYSTEM` — a system resource group.
4234    System,
4235    /// `USER` — a user resource group.
4236    User,
4237}
4238
4239/// The `ENABLE`/`DISABLE` state clause of a resource-group `CREATE`/`ALTER`
4240/// (`opt_resource_group_enable_disable`). A surface tag (no `meta` — its span rides the owning
4241/// node).
4242#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4243#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4244#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4245pub enum ResourceGroupState {
4246    /// `ENABLE` — enable the group.
4247    Enable,
4248    /// `DISABLE` — disable the group.
4249    Disable,
4250}
4251
4252/// The `VCPU [=] <range> [, <range> …]` CPU-affinity clause of a resource-group `CREATE`/`ALTER`
4253/// (`sql_yacc.yy` `VCPU_SYM opt_equal vcpu_range_spec_list`). The `[=]` presence
4254/// ([`equals`](Self::equals)) and the non-empty [`ranges`](Self::ranges) list round-trip. The
4255/// separator between ranges is `opt_comma` (a comma *or* whitespace both parse), so the parse is
4256/// separator-insensitive and the list is rendered canonically comma-separated.
4257#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4258#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4259#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4260pub struct ResourceGroupVcpu {
4261    /// Whether the `VCPU =` spelling (vs bare `VCPU`) was written (`opt_equal`).
4262    pub equals: bool,
4263    /// The CPU-id range list, in source order; non-empty (`vcpu_range_spec_list`).
4264    pub ranges: ThinVec<VcpuRange>,
4265    /// Source location and node identity.
4266    pub meta: Meta,
4267}
4268
4269/// One `vcpu_num_or_range`: a single CPU id ([`end`](Self::end) `None`) or an inclusive
4270/// `start-end` range. Each bound is a `NUM` unsigned-integer literal; a value beyond the host CPU
4271/// count is a post-parse semantic reject (`ER_RESOURCE_GROUP_…`), not a syntax error, so the
4272/// parser accepts any integer bounds.
4273#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4274#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4275#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4276pub struct VcpuRange {
4277    /// The single id, or the inclusive-range start.
4278    pub start: Literal,
4279    /// The inclusive-range end (`start-end`), or `None` for a single id.
4280    pub end: Option<Literal>,
4281    /// Source location and node identity.
4282    pub meta: Meta,
4283}
4284
4285/// The `THREAD_PRIORITY [=] <n>` clause of a resource-group `CREATE`/`ALTER`
4286/// (`opt_resource_group_priority`). The value is a `signed_num` — an optionally negative integer
4287/// (`THREAD_PRIORITY = -5` grammar-accepts on mysql:8.4.10) — so the sign is carried apart from
4288/// the magnitude literal (`-` is a separate token). The `[=]` presence and the sign round-trip.
4289#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4290#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4291#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4292pub struct ResourceGroupThreadPriority {
4293    /// Whether the `THREAD_PRIORITY =` spelling (vs bare `THREAD_PRIORITY`) was written
4294    /// (`opt_equal`).
4295    pub equals: bool,
4296    /// Whether a leading `-` was written (`signed_num`'s negative branch).
4297    pub negative: bool,
4298    /// The magnitude — a `NUM` unsigned-integer literal.
4299    pub value: Literal,
4300    /// Source location and node identity.
4301    pub meta: Meta,
4302}
4303
4304/// A MySQL `ALTER {DATABASE | SCHEMA} [<name>] <option> …` schema-option change (`sql_yacc.yy`
4305/// `alter_database_stmt`), gated by
4306/// [`StatementDdlGates::alter_database_options`](crate::dialect::StatementDdlGates::alter_database_options).
4307///
4308/// A distinct node — and gate — from DuckDB's [`AlterDatabase`] `SET ALIAS` relocation: the two
4309/// share only the `ALTER DATABASE` head, and their grammars are disjoint (DuckDB requires a name
4310/// and a single `SET ALIAS TO`; MySQL's name is optional — `ALTER DATABASE CHARACTER SET utf8`
4311/// binds the default schema — and it takes a non-empty, repeatable list of charset/collation/
4312/// encryption/read-only [`AlterDatabaseOption`]s). Modelling them apart keeps each behaviour's
4313/// invariants in the type rather than as XOR-populated fields on one node. The `DATABASE`/
4314/// `SCHEMA` [`spelling`](Self::spelling) is an exact synonym recorded for round-trip.
4315#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4316#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4317#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4318pub struct AlterDatabaseOptions {
4319    /// Which of the synonymous `DATABASE`/`SCHEMA` keywords was written.
4320    pub spelling: DatabaseKeyword,
4321    /// The database name, or `None` for the unqualified `ALTER DATABASE <option> …` form that
4322    /// targets the session's default schema (`ident_or_empty`; a dotted `d.x` is
4323    /// `ER_PARSE_ERROR`, so a bare [`Ident`]).
4324    pub name: Option<Ident>,
4325    /// The option list, in source order; non-empty (`alter_database_options`).
4326    pub options: ThinVec<AlterDatabaseOption>,
4327    /// Source location and node identity.
4328    pub meta: Meta,
4329}
4330
4331/// One `ALTER {DATABASE | SCHEMA}` option (`sql_yacc.yy` `alter_database_option`). The `[=]`
4332/// ([`opt_equal`]) and, where admitted, the leading `[DEFAULT]` are recorded so the surface
4333/// form round-trips verbatim. The value grammars are fixed; a value outside the set is a parse
4334/// or bind reject on mysql:8.4.10 (`READ ONLY 2` is `ER_PARSE_ERROR`; `ENCRYPTION 'X'` binds
4335/// then rejects `ER_WRONG_VALUE`, so any string parses).
4336///
4337/// [`opt_equal`]: https://dev.mysql.com/doc/refman/8.4/en/alter-database.html
4338#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4339#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4340#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4341pub enum AlterDatabaseOption {
4342    /// `[DEFAULT] {CHARACTER SET | CHARSET} [=] <charset>` — the default character set. The
4343    /// charset name is `charset_name` (`ident_or_text`, or the reserved `BINARY`), folded to
4344    /// an [`Ident`].
4345    CharacterSet {
4346        /// Whether the leading `DEFAULT` was written.
4347        default: bool,
4348        /// Which of the `CHARACTER SET`/`CHARSET` synonyms was written.
4349        keyword: CharsetKeyword,
4350        /// Whether the optional `=` was written.
4351        equals: bool,
4352        /// The character-set name.
4353        charset: Ident,
4354        /// Source location and node identity.
4355        meta: Meta,
4356    },
4357    /// `[DEFAULT] COLLATE [=] <collation>` — the default collation. The name is
4358    /// `collation_name` (`ident_or_text`, or the reserved `BINARY`), folded to an [`Ident`].
4359    Collate {
4360        /// Whether the leading `DEFAULT` was written.
4361        default: bool,
4362        /// Whether the optional `=` was written.
4363        equals: bool,
4364        /// The collation name.
4365        collation: Ident,
4366        /// Source location and node identity.
4367        meta: Meta,
4368    },
4369    /// `[DEFAULT] ENCRYPTION [=] '<Y|N>'` — the default tablespace encryption. Any string
4370    /// literal parses; the `Y`/`N` restriction is a bind-time check (`ER_WRONG_VALUE`), so the
4371    /// value is held verbatim as a [`Literal`].
4372    Encryption {
4373        /// Whether the leading `DEFAULT` was written.
4374        default: bool,
4375        /// Whether the optional `=` was written.
4376        equals: bool,
4377        /// The encryption value (a string literal).
4378        value: Literal,
4379        /// Source location and node identity.
4380        meta: Meta,
4381    },
4382    /// `READ ONLY [=] {DEFAULT | 0 | 1}` — the schema read-only flag. Takes no leading
4383    /// `DEFAULT` prefix (unlike the charset/collation/encryption options — `DEFAULT READ ONLY`
4384    /// is `ER_PARSE_ERROR`); the value is a [`ReadOnlyValue`] `ternary_option`, and any other
4385    /// number (`READ ONLY 2`) is `ER_PARSE_ERROR`.
4386    ReadOnly {
4387        /// Whether the optional `=` was written.
4388        equals: bool,
4389        /// The read-only value.
4390        value: ReadOnlyValue,
4391        /// Source location and node identity.
4392        meta: Meta,
4393    },
4394}
4395
4396/// Which of the synonymous `CHARACTER SET`/`CHARSET` keywords introduced an
4397/// [`AlterDatabaseOption::CharacterSet`]. A surface tag (no `meta`) recording the spelling for
4398/// round-trip; the two are semantically identical.
4399#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4400#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4401#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4402pub enum CharsetKeyword {
4403    /// The two-word `CHARACTER SET` spelling.
4404    CharacterSet,
4405    /// The one-word `CHARSET` synonym.
4406    Charset,
4407}
4408
4409/// The value of an [`AlterDatabaseOption::ReadOnly`] — MySQL's `ternary_option`. A surface tag
4410/// (no `meta`); `0`/`1` are the boolean settings and `DEFAULT` inherits the global default.
4411/// Any other number is `ER_PARSE_ERROR` on mysql:8.4.10.
4412#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4413#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4414#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4415pub enum ReadOnlyValue {
4416    /// `DEFAULT` — inherit the global read-only default.
4417    Default,
4418    /// `0` — read-write.
4419    Off,
4420    /// `1` — read-only.
4421    On,
4422}
4423
4424/// The binary unit suffix on a MySQL storage-DDL size literal (`size_number`'s `IDENT_sys`
4425/// form): `K`, `M`, or `G`. MySQL folds `<digits><suffix>` to a byte count by shifting the
4426/// number left 10/20/30 bits, so the tag names the multiplier a downstream converter would
4427/// otherwise re-decode from the spelling. Case is *not* carried here — the exact source
4428/// letter (`16m` vs `16M`) round-trips from the enclosing [`SizeLiteral`]'s span.
4429#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4430#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4431#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4432pub enum SizeUnit {
4433    /// `K` — the value is shifted left 10 bits (× 1024).
4434    Kilo,
4435    /// `M` — the value is shifted left 20 bits (× 1024²).
4436    Mega,
4437    /// `G` — the value is shifted left 30 bits (× 1024³).
4438    Giga,
4439}
4440
4441/// A MySQL storage-DDL size literal (`size_number`): a byte count written either as a plain
4442/// integer (`134217728`) or as an integer with a binary unit suffix (`128M`, `2G`, `16k`).
4443///
4444/// MySQL lexes the suffixed form as a *single* `IDENT_sys` token (an unquoted identifier may
4445/// begin with digits), so the digits and the suffix letter must abut with no space — `16 M`
4446/// is a syntax error. This crate's tokenizer instead splits `16M` into a number token and an
4447/// adjacent word; the parser rejoins them by span adjacency, so [`meta`](Self::meta) spans the
4448/// whole literal and renders verbatim. [`unit`](Self::unit) records the multiplier as
4449/// structured metadata (`None` for a bare byte count); like a [`Literal`], the numeric value
4450/// and the exact spelling round-trip from the span, so the value carries only the surface tag
4451/// a consumer cannot otherwise recover.
4452#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4453#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4454#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4455pub struct SizeLiteral {
4456    /// The binary unit suffix, or `None` for a bare integer byte count; see [`SizeUnit`].
4457    pub unit: Option<SizeUnit>,
4458    /// Source location and node identity. Spans the whole literal (digits + any suffix),
4459    /// which is what round-trips the exact spelling.
4460    pub meta: Meta,
4461}
4462
4463/// Which storage-DDL option keyword carries a [`SizeLiteral`] value — the `size_number`-valued
4464/// members of MySQL's shared `ts_option_*` family, grouped here because they share the exact
4465/// `<KEYWORD> [=] <size>` shape and differ only in the keyword.
4466#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4467#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4468#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4469pub enum TablespaceSizeOption {
4470    /// `INITIAL_SIZE` — the file's initial size.
4471    InitialSize,
4472    /// `AUTOEXTEND_SIZE` — the auto-extension increment.
4473    AutoextendSize,
4474    /// `MAX_SIZE` — the file's maximum size.
4475    MaxSize,
4476    /// `EXTENT_SIZE` — the extent size.
4477    ExtentSize,
4478    /// `UNDO_BUFFER_SIZE` — the UNDO buffer size (logfile group).
4479    UndoBufferSize,
4480    /// `REDO_BUFFER_SIZE` — the REDO buffer size (logfile group).
4481    RedoBufferSize,
4482    /// `FILE_BLOCK_SIZE` — the file block size (tablespace).
4483    FileBlockSize,
4484}
4485
4486/// One option in a MySQL tablespace / logfile-group statement — a member of the server's shared
4487/// `ts_option_*` family (every one of these DDL statements builds one
4488/// `PT_alter_tablespace_option` list). The full universe is modelled here as one axis; each
4489/// statement context accepts only its own subset (e.g. `UNDO TABLESPACE` takes `ENGINE` alone,
4490/// `ALTER TABLESPACE` excludes `FILE_BLOCK_SIZE`), enforced by the parser rather than the type,
4491/// so an out-of-context option ends the list and surfaces as a clean parse error — matching the
4492/// live server's per-context grammar.
4493///
4494/// Non-generic: every value is a size literal, an integer/string [`Literal`], an [`Ident`], or a
4495/// flag — no expressions or extension nodes. Each option records whether its optional `=`
4496/// (`opt_equal`) was written so the surface form round-trips.
4497#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4498#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4499#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4500pub enum TablespaceOption {
4501    /// A `size_number`-valued option (`INITIAL_SIZE [=] 128M`, …); see [`TablespaceSizeOption`].
4502    Size {
4503        /// Which size option keyword this is.
4504        kind: TablespaceSizeOption,
4505        /// Whether the optional `=` was written between the keyword and the value.
4506        equals: bool,
4507        /// The size value.
4508        size: SizeLiteral,
4509        /// Source location and node identity.
4510        meta: Meta,
4511    },
4512    /// `NODEGROUP [=] <n>` — a plain integer node-group id (`real_ulong_num`, never suffixed).
4513    Nodegroup {
4514        /// Whether the optional `=` was written between the keyword and the value.
4515        equals: bool,
4516        /// The node-group id.
4517        value: Literal,
4518        /// Source location and node identity.
4519        meta: Meta,
4520    },
4521    /// `[STORAGE] ENGINE [=] <name>` — the storage engine (`ident_or_text`: a bare identifier or
4522    /// a quoted string, folded to an [`Ident`] whose quote style round-trips).
4523    Engine {
4524        /// Whether the optional leading `STORAGE` noise keyword was written (`opt_storage`).
4525        storage: bool,
4526        /// Whether the optional `=` was written between `ENGINE` and the value.
4527        equals: bool,
4528        /// The engine name.
4529        name: Ident,
4530        /// Source location and node identity.
4531        meta: Meta,
4532    },
4533    /// `WAIT` or `NO_WAIT` — the NDB completion-wait flag (`ts_option_wait`).
4534    Wait {
4535        /// `true` for `NO_WAIT`, `false` for `WAIT`.
4536        negated: bool,
4537        /// Source location and node identity.
4538        meta: Meta,
4539    },
4540    /// `COMMENT [=] '<text>'` — a free-text comment string.
4541    Comment {
4542        /// Whether the optional `=` was written between the keyword and the value.
4543        equals: bool,
4544        /// The comment string.
4545        value: Literal,
4546        /// Source location and node identity.
4547        meta: Meta,
4548    },
4549    /// `ENCRYPTION [=] '<y_or_n>'` — the tablespace encryption flag string (`'Y'`/`'N'`).
4550    Encryption {
4551        /// Whether the optional `=` was written between the keyword and the value.
4552        equals: bool,
4553        /// The encryption flag string.
4554        value: Literal,
4555        /// Source location and node identity.
4556        meta: Meta,
4557    },
4558    /// `ENGINE_ATTRIBUTE [=] '<json>'` — the engine-specific JSON attribute string.
4559    EngineAttribute {
4560        /// Whether the optional `=` was written between the keyword and the value.
4561        equals: bool,
4562        /// The JSON attribute string.
4563        value: Literal,
4564        /// Source location and node identity.
4565        meta: Meta,
4566    },
4567}
4568
4569/// A MySQL `CREATE [UNDO] TABLESPACE <name> [ADD DATAFILE '<f>'] [USE LOGFILE GROUP <lg>]
4570/// [<option>...]` statement (gated by
4571/// [`StatementDdlGates::tablespace_ddl`](crate::dialect::StatementDdlGates::tablespace_ddl)).
4572///
4573/// One node for both the InnoDB/NDB tablespace (`Sql_cmd_create_tablespace`) and the
4574/// `UNDO`-tablespace variant (`Sql_cmd_create_undo_tablespace`), distinguished by
4575/// [`undo`](Self::undo). The two differ only in the leading `UNDO` keyword, whether the datafile
4576/// is mandatory (it is for `UNDO`, optional otherwise), and the accepted option set (`UNDO`
4577/// admits `ENGINE` alone) — all enforced by the parser. The `USE LOGFILE GROUP` clause is
4578/// NDB-only and never appears on the `UNDO` form.
4579#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4580#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4581#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4582pub struct CreateTablespace {
4583    /// Whether this is the `CREATE UNDO TABLESPACE` variant.
4584    pub undo: bool,
4585    /// The tablespace name.
4586    pub name: Ident,
4587    /// `ADD DATAFILE '<file>'`. Mandatory (`Some`) for the `UNDO` form; optional otherwise.
4588    pub datafile: Option<Literal>,
4589    /// `USE LOGFILE GROUP <name>` (NDB, regular tablespace only).
4590    pub use_logfile_group: Option<Ident>,
4591    /// Options supplied in source order.
4592    pub options: ThinVec<TablespaceOption>,
4593    /// Source location and node identity.
4594    pub meta: Meta,
4595}
4596
4597/// The undo-tablespace access state set by `ALTER UNDO TABLESPACE <name> SET {ACTIVE | INACTIVE}`
4598/// (`undo_tablespace_state`).
4599#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4600#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4601#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4602pub enum UndoTablespaceState {
4603    /// `SET ACTIVE`.
4604    Active,
4605    /// `SET INACTIVE`.
4606    Inactive,
4607}
4608
4609/// A MySQL `ALTER [UNDO] TABLESPACE <name> <action>` statement (gated by
4610/// [`StatementDdlGates::tablespace_ddl`](crate::dialect::StatementDdlGates::tablespace_ddl)).
4611///
4612/// The four regular productions (`ADD`/`DROP DATAFILE`, `RENAME TO`, a bare option list) and the
4613/// `UNDO` production (`SET {ACTIVE | INACTIVE}`) share the `ALTER [UNDO] TABLESPACE <name>` head
4614/// and are unified under [`AlterTablespaceAction`]. The action itself distinguishes the `UNDO`
4615/// form (only it carries [`SetState`](AlterTablespaceAction::SetState)), so no separate `undo`
4616/// flag is needed.
4617#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4618#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4619#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4620pub struct AlterTablespace {
4621    /// The tablespace name.
4622    pub name: Ident,
4623    /// The change applied to the tablespace.
4624    pub action: AlterTablespaceAction,
4625    /// Source location and node identity.
4626    pub meta: Meta,
4627}
4628
4629/// The change an [`AlterTablespace`] applies.
4630#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4631#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4632#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4633pub enum AlterTablespaceAction {
4634    /// `ADD DATAFILE '<f>' [<option>...]`.
4635    AddDatafile {
4636        /// The datafile path.
4637        datafile: Literal,
4638        /// The trailing alter-tablespace options in source order.
4639        options: ThinVec<TablespaceOption>,
4640        /// Source location and node identity.
4641        meta: Meta,
4642    },
4643    /// `DROP DATAFILE '<f>' [<option>...]`.
4644    DropDatafile {
4645        /// The datafile path.
4646        datafile: Literal,
4647        /// The trailing alter-tablespace options in source order.
4648        options: ThinVec<TablespaceOption>,
4649        /// Source location and node identity.
4650        meta: Meta,
4651    },
4652    /// `RENAME TO <new_name>` — takes no options (a trailing option is a parse error).
4653    Rename {
4654        /// The new tablespace name.
4655        new_name: Ident,
4656        /// Source location and node identity.
4657        meta: Meta,
4658    },
4659    /// A bare (non-empty) option list — `ALTER TABLESPACE <name> <option> [, <option>]...`
4660    /// with no `ADD`/`DROP`/`RENAME` lead.
4661    Options {
4662        /// The options in source order (at least one).
4663        options: ThinVec<TablespaceOption>,
4664        /// Source location and node identity.
4665        meta: Meta,
4666    },
4667    /// `SET {ACTIVE | INACTIVE} [<option>...]` — the `ALTER UNDO TABLESPACE` form (the trailing
4668    /// options are the `UNDO` subset, `ENGINE` alone).
4669    SetState {
4670        /// The new access state.
4671        state: UndoTablespaceState,
4672        /// The trailing undo-tablespace options in source order.
4673        options: ThinVec<TablespaceOption>,
4674        /// Source location and node identity.
4675        meta: Meta,
4676    },
4677}
4678
4679/// A MySQL `DROP [UNDO] TABLESPACE <name> [<option>...]` statement (gated by
4680/// [`StatementDdlGates::tablespace_ddl`](crate::dialect::StatementDdlGates::tablespace_ddl)).
4681///
4682/// One node for `DROP TABLESPACE` (`Sql_cmd_drop_tablespace`) and `DROP UNDO TABLESPACE`
4683/// (`Sql_cmd_drop_undo_tablespace`), distinguished by [`undo`](Self::undo): the regular form
4684/// accepts `ENGINE`/`WAIT` options, the `UNDO` form `ENGINE` alone (parser-enforced).
4685#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4686#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4687#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4688pub struct DropTablespace {
4689    /// Whether this is the `DROP UNDO TABLESPACE` variant.
4690    pub undo: bool,
4691    /// The tablespace name.
4692    pub name: Ident,
4693    /// Options supplied in source order.
4694    pub options: ThinVec<TablespaceOption>,
4695    /// Source location and node identity.
4696    pub meta: Meta,
4697}
4698
4699/// A MySQL `CREATE LOGFILE GROUP <name> ADD UNDOFILE '<f>' [<option>...]` statement (NDB; gated
4700/// by
4701/// [`StatementDdlGates::logfile_group_ddl`](crate::dialect::StatementDdlGates::logfile_group_ddl)).
4702///
4703/// The `ADD UNDOFILE` clause is mandatory (a bare `CREATE LOGFILE GROUP <name>` is a syntax
4704/// error). The option set is the logfile-group subset of the shared `ts_option_*` family
4705/// (`INITIAL_SIZE`, `UNDO_BUFFER_SIZE`, `REDO_BUFFER_SIZE`, `NODEGROUP`, `ENGINE`, `WAIT`,
4706/// `COMMENT`), parser-enforced.
4707#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4708#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4709#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4710pub struct CreateLogfileGroup {
4711    /// The logfile-group name.
4712    pub name: Ident,
4713    /// The mandatory `ADD UNDOFILE '<file>'` path.
4714    pub undofile: Literal,
4715    /// Options supplied in source order.
4716    pub options: ThinVec<TablespaceOption>,
4717    /// Source location and node identity.
4718    pub meta: Meta,
4719}
4720
4721/// A MySQL `ALTER LOGFILE GROUP <name> ADD UNDOFILE '<f>' [<option>...]` statement (NDB; gated by
4722/// [`StatementDdlGates::logfile_group_ddl`](crate::dialect::StatementDdlGates::logfile_group_ddl)).
4723///
4724/// Like [`CreateLogfileGroup`] the `ADD UNDOFILE` clause is mandatory; the accepted option set is
4725/// the narrower alter subset (`INITIAL_SIZE`, `ENGINE`, `WAIT`), parser-enforced.
4726#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4727#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4728#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4729pub struct AlterLogfileGroup {
4730    /// The logfile-group name.
4731    pub name: Ident,
4732    /// The mandatory `ADD UNDOFILE '<file>'` path.
4733    pub undofile: Literal,
4734    /// Options supplied in source order.
4735    pub options: ThinVec<TablespaceOption>,
4736    /// Source location and node identity.
4737    pub meta: Meta,
4738}
4739
4740/// A MySQL `DROP LOGFILE GROUP <name> [<option>...]` statement (NDB; gated by
4741/// [`StatementDdlGates::logfile_group_ddl`](crate::dialect::StatementDdlGates::logfile_group_ddl)).
4742///
4743/// The option set is `ENGINE`/`WAIT` (`opt_drop_ts_options`, shared with `DROP TABLESPACE`).
4744#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4745#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4746#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4747pub struct DropLogfileGroup {
4748    /// The logfile-group name.
4749    pub name: Ident,
4750    /// Options supplied in source order.
4751    pub options: ThinVec<TablespaceOption>,
4752    /// Source location and node identity.
4753    pub meta: Meta,
4754}