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 [`CreateTableClauseSyntax::create_or_replace_table`](crate::dialect::CreateTableClauseSyntax::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    /// A `WITH (<param> = <value>, …)` storage-parameter clause.
1144    With {
1145        /// params in source order.
1146        params: ThinVec<TableStorageParameter<X>>,
1147        /// Source location and node identity.
1148        meta: Meta,
1149    },
1150    /// An `ON COMMIT {PRESERVE ROWS | DELETE ROWS | DROP}` temporary-table clause.
1151    OnCommit {
1152        /// The on-commit action; see [`OnCommitAction`].
1153        action: OnCommitAction,
1154        /// Source location and node identity.
1155        meta: Meta,
1156    },
1157    /// A `TABLESPACE <name>` clause.
1158    Tablespace {
1159        /// The tablespace name.
1160        tablespace: Ident,
1161        /// Source location and node identity.
1162        meta: Meta,
1163    },
1164    /// A MySQL trailing table option written as an open `<name> [=] <value>` pair —
1165    /// `ENGINE = InnoDB`, `AUTO_INCREMENT = 100`, `DEFAULT CHARSET = utf8mb4`,
1166    /// `COMMENT = '...'`, `ROW_FORMAT = DYNAMIC`, `COLLATE = utf8mb4_general_ci`.
1167    ///
1168    /// Modelled as one canonical name/value pair rather than a variant (or a
1169    /// `CreateTable` field) per option keyword: the MySQL option
1170    /// vocabulary is large and server-version-dependent, so a single open shape
1171    /// round-trips an arbitrary option list — the same reasoning the [`CopyOption`]
1172    /// list follows. The optional `DEFAULT` noise word MySQL accepts before
1173    /// `CHARSET`/`COLLATE` is normalized away by the parser, and the `=` is optional.
1174    /// Gated by `create_table_clause_syntax.table_options`.
1175    ///
1176    /// [`CopyOption`]: super::CopyOption
1177    KeyValue {
1178        // The whole `<name> = <value>` payload is boxed (ADR-0007): a `TableOption`
1179        // (name + a 40 B value + meta) is the only fat payload among these variants, so
1180        // inlining it would set the enum's width and tax the lean `With`/`OnCommit`/
1181        // `Tablespace` variants — and, transitively, every `CreateTableOption` an
1182        // ANSI/PostgreSQL parse stores in its options `ThinVec`. Table options are a
1183        // cold DDL path, so the lone allocation per MySQL option is cheap, the same
1184        // box call `ColumnOption::Identity` makes for its `IdentityColumn`.
1185        /// The `<name> = <value>` option pair; see [`TableOption`].
1186        option: Box<TableOption>,
1187        /// Source location and node identity.
1188        meta: Meta,
1189    },
1190    /// The SQLite `WITHOUT ROWID` trailing table option: the table is stored as a
1191    /// clustered index on its primary key rather than the default implicit `rowid`.
1192    /// A bare keyword-style option (no `= value`), so a variant of its own rather than
1193    /// the MySQL [`KeyValue`](Self::KeyValue) name/value catch-all. SQLite
1194    /// comma-separates these trailing options (`... STRICT, WITHOUT ROWID`). Gated for
1195    /// acceptance by
1196    /// [`CreateTableClauseSyntax::without_rowid_table_option`](crate::dialect::CreateTableClauseSyntax::without_rowid_table_option).
1197    WithoutRowid {
1198        /// Source location and node identity.
1199        meta: Meta,
1200    },
1201    /// The SQLite `STRICT` trailing table option: the table enforces its declared
1202    /// column types instead of SQLite's default flexible typing. Like
1203    /// [`WithoutRowid`](Self::WithoutRowid) a bare keyword-style option, gated for
1204    /// acceptance by
1205    /// [`CreateTableClauseSyntax::strict_table_option`](crate::dialect::CreateTableClauseSyntax::strict_table_option).
1206    Strict {
1207        /// Source location and node identity.
1208        meta: Meta,
1209    },
1210    /// The legacy PostgreSQL `WITHOUT OIDS` trailing option: historically suppressed the
1211    /// system `oid` column. Modern PostgreSQL keeps it in the grammar as an accepted no-op,
1212    /// while the affirmative `WITH OIDS` has no production and *rejects* (both
1213    /// engine-measured). A bare keyword-style option, so a
1214    /// variant of its own rather than the MySQL [`KeyValue`](Self::KeyValue) catch-all. It
1215    /// occupies PostgreSQL's `OptWith` slot — mutually exclusive with a `WITH (…)` storage-
1216    /// parameter list, and it precedes `ON COMMIT` / `TABLESPACE`. Gated for acceptance by
1217    /// [`CreateTableClauseSyntax::without_oids`](crate::dialect::CreateTableClauseSyntax::without_oids).
1218    WithoutOids {
1219        /// Source location and node identity.
1220        meta: Meta,
1221    },
1222}
1223
1224/// One MySQL trailing table option: a `<name> = <value>` pair.
1225///
1226/// The canonical "options as data" shape: `name` is the option keyword as
1227/// written (`ENGINE`, `AUTO_INCREMENT`, `CHARSET`, ...) and `value` is its argument.
1228/// Boxed inside [`CreateTableOptionKind::KeyValue`] to keep that enum lean; mirrors
1229/// the [`CopyOption`](super::CopyOption) list element.
1230#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1231#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1232#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1233pub struct TableOption {
1234    /// Name referenced by this syntax.
1235    pub name: Ident,
1236    /// Value supplied by this syntax.
1237    pub value: TableOptionValue,
1238    /// Source location and node identity.
1239    pub meta: Meta,
1240}
1241
1242/// The value of a [`TableOption`].
1243///
1244/// Three surface forms a downstream converter cannot otherwise recover:
1245/// a bareword/keyword (`InnoDB`, `DYNAMIC`), a string (`COMMENT = '...'`), or a
1246/// number (`AUTO_INCREMENT = 100`). String and numeric values ride their
1247/// [`Literal`]'s `meta.span` and materialise lazily; a bareword keeps its
1248/// source spelling on the [`Ident`]. Mirrors [`CopyOptionValue`](super::CopyOptionValue),
1249/// which omits the numeric form COPY options never take.
1250#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1251#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1252#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1253pub enum TableOptionValue {
1254    /// A bareword/keyword value: `ENGINE = InnoDB`, `ROW_FORMAT = DYNAMIC`.
1255    Word {
1256        /// Identifier-form value.
1257        word: Ident,
1258        /// Source location and node identity.
1259        meta: Meta,
1260    },
1261    /// A string value: `COMMENT = 'text'`.
1262    String {
1263        /// Value supplied by this syntax.
1264        value: Literal,
1265        /// Source location and node identity.
1266        meta: Meta,
1267    },
1268    /// A numeric value: `AUTO_INCREMENT = 100`, `KEY_BLOCK_SIZE = 8`.
1269    Number {
1270        /// Value supplied by this syntax.
1271        value: Literal,
1272        /// Source location and node identity.
1273        meta: Meta,
1274    },
1275}
1276
1277#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1278#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1279#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1280/// The SQL on commit action forms represented by the AST.
1281pub enum OnCommitAction {
1282    /// `ON COMMIT PRESERVE ROWS` — keep the temp table's rows after commit.
1283    PreserveRows,
1284    /// `ON COMMIT DELETE ROWS` — empty the temp table on each commit.
1285    DeleteRows,
1286    /// `ON COMMIT DROP` — drop the temp table at the end of the transaction.
1287    Drop,
1288}
1289
1290#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1291#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1292#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1293/// An SQL table storage parameter.
1294pub struct TableStorageParameter<X: Extension = NoExt> {
1295    /// Name referenced by this syntax.
1296    pub name: ObjectName,
1297    /// Value supplied by this syntax.
1298    pub value: Option<Expr<X>>,
1299    /// Source location and node identity.
1300    pub meta: Meta,
1301}
1302
1303/// `ALTER TABLE [IF EXISTS] <name> <action> [, <action>]...`.
1304///
1305/// The schema-evolution counterpart of [`CreateTable`]. A single statement carries
1306/// one or more comma-separated [`AlterTableAction`]s, matching PostgreSQL's
1307/// multi-action form (`ALTER TABLE t ADD COLUMN a INT, DROP COLUMN b`). The `IF
1308/// EXISTS` prefix is gated by `existence_guards.if_exists` dialect data:
1309/// off under ANSI, on under PostgreSQL. The PostgreSQL-only `ONLY`
1310/// inheritance qualifier is a deliberate follow-up, tracked separately.
1311#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1312#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1313#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1314pub struct AlterTable<X: Extension = NoExt> {
1315    /// Whether the if exists form was present in the source.
1316    pub if_exists: bool,
1317    /// Name referenced by this syntax.
1318    pub name: ObjectName,
1319    /// actions in source order.
1320    pub actions: ThinVec<AlterTableAction<X>>,
1321    /// Source location and node identity.
1322    pub meta: Meta,
1323}
1324
1325/// A column target inside an `ALTER TABLE` action.
1326///
1327/// Most dialects name a top-level column (`c`), while DuckDB also admits dotted paths into
1328/// nested STRUCT fields for selected actions (`s.s2.k`). Kept separate from [`ObjectName`]
1329/// so ALTER-specific nested paths do not imply that table columns generally use relation-name
1330/// semantics.
1331#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1332#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1333#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1334pub struct AlterColumnTarget {
1335    /// parts in source order.
1336    pub parts: ThinVec<Ident>,
1337    /// Source location and node identity.
1338    pub meta: Meta,
1339}
1340
1341/// One action in an `ALTER TABLE` statement.
1342///
1343/// `AddColumn`/`AddConstraint` reuse the `CREATE TABLE` element nodes ([`ColumnDef`]
1344/// and [`TableConstraintDef`]) rather than inventing alter-specific copies, so the
1345/// `Other(X)` extension seams already on [`ColumnOption`]/[`TableConstraint`] cover
1346/// custom DDL attached through an alter too. The optional `COLUMN` noise
1347/// word is not represented: rendering normalizes to the canonical `ADD COLUMN`
1348/// spelling.
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 enum AlterTableAction<X: Extension = NoExt> {
1353    /// `ADD [COLUMN] <col> <type> …` — add a column to the table.
1354    AddColumn {
1355        /// Whether the if not exists form was present in the source.
1356        if_not_exists: bool,
1357        /// Whether the optional `COLUMN` noise word was written (`ADD COLUMN c` vs the
1358        /// bare `ADD c`). Exact-synonym fidelity; the canonical render emits `COLUMN`.
1359        column_keyword: bool,
1360        /// Object targeted by this syntax.
1361        target: Option<AlterColumnTarget>,
1362        /// Column referenced by this syntax.
1363        column: ColumnDef<X>,
1364        /// Source location and node identity.
1365        meta: Meta,
1366    },
1367    /// `DROP [COLUMN] <name>` — remove a column from the table.
1368    DropColumn {
1369        /// Whether the if exists form was present in the source.
1370        if_exists: bool,
1371        /// Whether the optional `COLUMN` noise word was written (`DROP COLUMN c` vs the
1372        /// bare `DROP c`). Exact-synonym fidelity; the canonical render emits `COLUMN`.
1373        column_keyword: bool,
1374        /// Name referenced by this syntax.
1375        name: AlterColumnTarget,
1376        /// Optional behavior for this syntax.
1377        behavior: Option<DropBehavior>,
1378        /// Source location and node identity.
1379        meta: Meta,
1380    },
1381    /// `ALTER [COLUMN] <name> …` — change an existing column's definition.
1382    AlterColumn {
1383        /// Whether the optional `COLUMN` noise word was written (`ALTER COLUMN c` vs the
1384        /// bare `ALTER c`). Exact-synonym fidelity; the canonical render emits `COLUMN`.
1385        column_keyword: bool,
1386        /// Name referenced by this syntax.
1387        name: Ident,
1388        /// The per-column change; see [`AlterColumnAction`].
1389        change: AlterColumnAction<X>,
1390        /// Source location and node identity.
1391        meta: Meta,
1392    },
1393    /// `ADD CONSTRAINT …` — add a table constraint.
1394    AddConstraint {
1395        /// The constraint being added; see [`TableConstraintDef`].
1396        constraint: TableConstraintDef<X>,
1397        /// Source location and node identity.
1398        meta: Meta,
1399    },
1400    /// `DROP CONSTRAINT <name>` — remove a table constraint.
1401    DropConstraint {
1402        /// Whether the if exists form was present in the source.
1403        if_exists: bool,
1404        /// Name referenced by this syntax.
1405        name: Ident,
1406        /// Optional behavior for this syntax.
1407        behavior: Option<DropBehavior>,
1408        /// Source location and node identity.
1409        meta: Meta,
1410    },
1411    /// `RENAME [COLUMN] <name> TO <new_name>`.
1412    ///
1413    /// PostgreSQL parses the rename forms as a distinct `RenameStmt`, not the
1414    /// `AlterTableStmt` the other actions take; the canonical AST keeps one
1415    /// [`AlterTable`] node for every `ALTER TABLE` spelling, so the rename
1416    /// is a further action variant here. The optional `COLUMN` noise word is
1417    /// recorded, mirroring [`AddColumn`](Self::AddColumn).
1418    RenameColumn {
1419        /// Whether the optional `COLUMN` noise word was written (`RENAME COLUMN c` vs
1420        /// the bare `RENAME c`). Exact-synonym fidelity; the canonical render emits
1421        /// `COLUMN`.
1422        column_keyword: bool,
1423        /// Name referenced by this syntax.
1424        name: AlterColumnTarget,
1425        /// The column's new name.
1426        new_name: Ident,
1427        /// Source location and node identity.
1428        meta: Meta,
1429    },
1430    /// `RENAME TO <new_name>` — rename the table.
1431    RenameTable {
1432        /// The table's new name.
1433        new_name: Ident,
1434        /// Source location and node identity.
1435        meta: Meta,
1436    },
1437    /// `ATTACH PARTITION <name> <bound>` — attach an existing table as a partition
1438    /// (PostgreSQL declarative partitioning). A *standalone* action: PostgreSQL parses
1439    /// `ATTACH`/`DETACH PARTITION` as their own `AlterTableStmt` productions, so they never
1440    /// combine with other actions in a comma list (`ALTER TABLE p ADD COLUMN x, ATTACH …` is a
1441    /// syntax error). Gated by
1442    /// [`CreateTableClauseSyntax::declarative_partitioning`](crate::dialect::CreateTableClauseSyntax::declarative_partitioning).
1443    AttachPartition {
1444        /// The partition (child) table name.
1445        partition: ObjectName,
1446        // Boxed as the fat child (matching the `PartitionOf` body): a `PartitionBound` is the
1447        // widest payload here, so a partition attach pays one cold allocation rather than setting
1448        // `AlterTableAction`'s width (ADR-0007, box/inline budget).
1449        /// The partition bound (`FOR VALUES …` / `DEFAULT`); see [`PartitionBound`].
1450        bound: Box<PartitionBound<X>>,
1451        /// Source location and node identity.
1452        meta: Meta,
1453    },
1454    /// `DETACH PARTITION <name> [CONCURRENTLY | FINALIZE]` — detach a partition (PostgreSQL).
1455    /// Like [`AttachPartition`](Self::AttachPartition) a standalone action. The optional
1456    /// [`mode`](Self::DetachPartition::mode) records the `CONCURRENTLY` / `FINALIZE`
1457    /// qualifier; `None` for the bare form.
1458    DetachPartition {
1459        /// The partition (child) table name.
1460        partition: ObjectName,
1461        /// Mode selected by this syntax.
1462        mode: Option<DetachPartitionMode>,
1463        /// Source location and node identity.
1464        meta: Meta,
1465    },
1466}
1467
1468/// The qualifier on a `DETACH PARTITION` action. A `Copy` leaf enum whose span rides the owning
1469/// [`AlterTableAction::DetachPartition`], like [`DropBehavior`].
1470#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1471#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1472#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1473pub enum DetachPartitionMode {
1474    /// `CONCURRENTLY` — detach without a long-held lock (PostgreSQL).
1475    Concurrently,
1476    /// `FINALIZE` — complete an interrupted concurrent detach.
1477    Finalize,
1478}
1479
1480/// A single-column alteration inside `ALTER TABLE ... ALTER COLUMN <name> ...`.
1481///
1482/// The ANSI `SET DATA TYPE` and PostgreSQL `TYPE` spellings map to one canonical
1483/// [`SetDataType`](Self::SetDataType) variant; the optional PostgreSQL
1484/// `USING <expr>` conversion expression rides along when present.
1485#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1486#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1487#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1488pub enum AlterColumnAction<X: Extension = NoExt> {
1489    /// `SET DEFAULT <expr>` — set the column's default value.
1490    SetDefault {
1491        /// Expression evaluated by this syntax.
1492        expr: Box<Expr<X>>,
1493        /// Source location and node identity.
1494        meta: Meta,
1495    },
1496    /// `DROP DEFAULT` — remove the column's default value.
1497    DropDefault {
1498        /// Source location and node identity.
1499        meta: Meta,
1500    },
1501    /// `SET NOT NULL` — add a not-null constraint to the column.
1502    SetNotNull {
1503        /// Source location and node identity.
1504        meta: Meta,
1505    },
1506    /// `DROP NOT NULL` — remove the column's not-null constraint.
1507    DropNotNull {
1508        /// Source location and node identity.
1509        meta: Meta,
1510    },
1511    /// `SET DATA TYPE <t>` / `TYPE <t> [USING …]` — change the column's data type.
1512    SetDataType {
1513        /// Whether the ANSI `SET DATA` prefix was written (`SET DATA TYPE <t>`) versus
1514        /// the bare PostgreSQL `TYPE <t>`. Exact-synonym fidelity; the canonical render
1515        /// emits `SET DATA TYPE`.
1516        set_data: bool,
1517        /// Data type named by this syntax.
1518        data_type: DataType<X>,
1519        /// Optional using for this syntax.
1520        using: Option<Box<Expr<X>>>,
1521        /// Source location and node identity.
1522        meta: Meta,
1523    },
1524}
1525
1526/// `DROP {TABLE | VIEW | INDEX | SCHEMA} [IF EXISTS] <name> [, ...] [CASCADE | RESTRICT]`.
1527///
1528/// Non-generic: a drop names objects and a behaviour only — it carries no
1529/// expressions or extension nodes — so it parallels the other leaf statement
1530/// payloads ([`super::TransactionStatement`], [`super::SessionStatement`]). The `IF
1531/// EXISTS` prefix and the `CASCADE`/`RESTRICT` drop behaviour are each gated by
1532/// `schema_change_syntax` dialect data. Materialized-view and concurrent-index drop
1533/// surfaces are not modelled here.
1534#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1535#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1536#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1537pub struct DropStatement {
1538    /// Which object kind is dropped (`TABLE`/`VIEW`/…); see [`DropObjectKind`].
1539    pub object_kind: DropObjectKind,
1540    /// Whether the if exists form was present in the source.
1541    pub if_exists: bool,
1542    /// Names in source order.
1543    pub names: ThinVec<ObjectName>,
1544    /// Optional behavior for this syntax.
1545    pub behavior: Option<DropBehavior>,
1546    /// Source location and node identity.
1547    pub meta: Meta,
1548}
1549
1550/// The kind of object a `DROP` statement removes.
1551///
1552/// The routine kinds (`FUNCTION`/`PROCEDURE`) take an argument-type signature, so
1553/// they are a separate [`Statement::DropRoutine`]
1554/// statement rather than a variant here;
1555/// this covers the object kinds a plain name list can drop.
1556#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1557#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1558#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1559pub enum DropObjectKind {
1560    /// `DROP TABLE`.
1561    Table,
1562    /// `DROP VIEW`.
1563    View,
1564    /// `DROP MATERIALIZED VIEW`.
1565    MaterializedView,
1566    /// `DROP INDEX`.
1567    Index,
1568    /// `DROP SCHEMA`.
1569    Schema,
1570    /// `DROP TYPE <name> [, …]` — a user-defined type (DuckDB/PostgreSQL), gated by
1571    /// [`StatementDdlGates::create_type`](crate::dialect::StatementDdlGates::create_type) (the same
1572    /// flag that admits [`CreateType`]). The comma list and `CASCADE`/`RESTRICT` behaviour
1573    /// ride the shared [`DropStatement`] grammar; DuckDB parse-accepts a multi-name list and
1574    /// only rejects it at plan time ("can only drop one object at a time"), so the shape is
1575    /// the general one.
1576    Type,
1577    /// `DROP SEQUENCE <name> [, …]` — a sequence generator (DuckDB/PostgreSQL), gated by
1578    /// [`StatementDdlGates::create_sequence`](crate::dialect::StatementDdlGates::create_sequence) (the same
1579    /// flag that admits [`CreateSequence`]). The comma list and `CASCADE`/`RESTRICT` ride the
1580    /// shared [`DropStatement`] grammar. DuckDB's parser accepts `DROP SEQUENCE` (the
1581    /// `IF EXISTS` form binds; the bare/list/`CASCADE` forms parse but bind-reject a missing
1582    /// object — engine-measured), so the modelled shape is the general one.
1583    Sequence,
1584    /// `DROP MACRO <name> [, …]` — a scalar macro (DuckDB), gated by
1585    /// [`StatementDdlGates::create_macro`](crate::dialect::StatementDdlGates::create_macro) (the same
1586    /// flag that admits [`CreateMacro`]). Unlike PostgreSQL's `DROP FUNCTION`, the macro drop
1587    /// takes *no* argument-type signature (`DROP MACRO m(int)` is a DuckDB syntax error), so it
1588    /// rides the shared [`DropStatement`] name-list grammar rather than the signature
1589    /// [`Statement::DropRoutine`] path — the `FUNCTION` spelling
1590    /// (which DuckDB accepts as a synonym) still routes to that routine drop, so the two DROP
1591    /// spellings land on distinct nodes and no [`MacroSpelling`] tag is needed here. The comma
1592    /// list and `CASCADE`/`RESTRICT` ride the shared grammar; DuckDB parse-accepts a multi-name
1593    /// list and only bind-rejects it ("can only drop one object at a time" — engine-measured).
1594    Macro,
1595    /// `DROP MACRO TABLE <name> [, …]` — a *table* macro (DuckDB), the drop counterpart of a
1596    /// `CREATE MACRO … AS TABLE`. Separate from [`Macro`](DropObjectKind::Macro) because DuckDB
1597    /// keeps scalar and table macros in distinct namespaces (`DROP MACRO m` drops a "Macro
1598    /// Function", `DROP MACRO TABLE m` a "Table Macro Function" — engine-measured) and the
1599    /// `TABLE` keyword must round-trip verbatim. Gated by the same
1600    /// [`StatementDdlGates::create_macro`](crate::dialect::StatementDdlGates::create_macro) flag;
1601    /// only `MACRO TABLE` (not `TABLE MACRO` or `FUNCTION TABLE`) is accepted — engine-measured.
1602    MacroTable,
1603    /// `DROP TRIGGER [IF EXISTS] [<schema> .] <name>` — a trigger (MySQL / SQLite), gated by
1604    /// either trigger-modelling flag (SQLite's
1605    /// [`StatementDdlGates::create_trigger`](crate::dialect::StatementDdlGates::create_trigger) or
1606    /// MySQL's
1607    /// [`StatementDdlGates::compound_statements`](crate::dialect::StatementDdlGates::compound_statements)).
1608    /// Both dialects spell the same name-only drop (no `ON <table>`, which is PostgreSQL's
1609    /// separate trigger-drop shape), so it rides the shared [`DropStatement`] name-list grammar.
1610    Trigger,
1611}
1612
1613/// The dependency behaviour of a `DROP` or `ALTER TABLE ... DROP`: whether dependent
1614/// objects are dropped too (`CASCADE`) or block the drop (`RESTRICT`).
1615#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1616#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1617#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1618pub enum DropBehavior {
1619    /// `CASCADE` — also drop objects that depend on the target.
1620    Cascade,
1621    /// `RESTRICT` — refuse the drop if any object depends on the target.
1622    Restrict,
1623}
1624
1625/// A PostgreSQL `DROP TRANSFORM [IF EXISTS] FOR <type> LANGUAGE <lang>
1626/// [CASCADE | RESTRICT]` statement (`DropTransformStmt`, gram.y).
1627///
1628/// Kept apart from [`DropStatement`] because a transform is not named by a plain
1629/// name list: it is identified by the `(type, language)` pair a `CREATE TRANSFORM`
1630/// registers, which is exactly the [`ObjectReference::Transform`] shape the shared
1631/// object-reference axis already models (also reached by `ALTER EXTENSION … ADD|DROP
1632/// TRANSFORM`). Reusing that variant keeps every transform reference on one node, so
1633/// an AST walk finds transforms uniformly regardless of the parent statement, rather
1634/// than re-deriving the `FOR type LANGUAGE lang` shape here (the reason [`DropRoutine`]
1635/// inlines its signature does not apply — a transform has a single fixed shape, not a
1636/// per-kind one).
1637///
1638/// PostgreSQL admits exactly one transform per statement (no comma list — engine-measured)
1639/// and places `IF EXISTS` between the `TRANSFORM` keyword and `FOR` (`DROP TRANSFORM IF
1640/// EXISTS FOR …`; `FOR type IF EXISTS LANGUAGE` is a syntax error), which is why the guard
1641/// rides on this node rather than on the shared reference render.
1642///
1643/// [`DropRoutine`]: super::Statement::DropRoutine
1644#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1645#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1646#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1647pub struct DropTransform<X: Extension = NoExt> {
1648    /// The dropped transform, always an [`ObjectReference::Transform`] (`FOR <type>
1649    /// LANGUAGE <lang>`).
1650    pub object: ObjectReference<X>,
1651    /// Whether the `IF EXISTS` guard was present in the source.
1652    pub if_exists: bool,
1653    /// Optional `CASCADE`/`RESTRICT` behaviour for this syntax.
1654    pub behavior: Option<DropBehavior>,
1655    /// Source location and node identity.
1656    pub meta: Meta,
1657}
1658
1659/// The object kind a [`Statement::CommentOn`] targets.
1660///
1661/// PostgreSQL's `COMMENT ON` object list is large (tables, columns, functions, types,
1662/// operators, extensions, roles, …). This models only the deliberate subset the
1663/// datafusion-parity corpus needs — `TABLE`, `COLUMN`, `DATABASE`, and `PROCEDURE` —
1664/// and is `#[non_exhaustive]` so the remaining object kinds can be added later without
1665/// a breaking change. The object's name rides
1666/// [`Statement::CommentOn::name`](super::Statement::CommentOn); only a procedure adds
1667/// per-target data (its argument-type signature).
1668#[non_exhaustive]
1669#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1670#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1671#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1672pub enum CommentTarget<X: Extension = NoExt> {
1673    /// `COMMENT ON TABLE <name>`.
1674    Table,
1675    /// `COMMENT ON COLUMN <name>`.
1676    Column,
1677    /// `COMMENT ON DATABASE <name>`.
1678    Database,
1679    /// `COMMENT ON PROCEDURE <name>[(<arg types>)]`. The argument-type list
1680    /// disambiguates overloaded procedures; `None` is an unspecified signature
1681    /// (bare `PROCEDURE foo`), `Some` a written list — possibly empty (`foo()`) —
1682    /// mirroring [`RoutineSignature::arg_types`](super::RoutineSignature).
1683    Procedure {
1684        /// Optional arg types for this syntax.
1685        arg_types: Option<ThinVec<DataType<X>>>,
1686    },
1687}
1688
1689/// The payload of a [`Statement::CommentOn`], boxed off the
1690/// statement enum to keep it within its size budget (like [`DropStatement`]).
1691///
1692/// `name` is the object's (possibly qualified) name; `target` records its kind (and, for
1693/// a procedure, its argument-type signature). `comment` is `None` for `IS NULL` — which
1694/// clears the object's comment — and `Some` for a string literal.
1695#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1696#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1697#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1698pub struct CommentOnStatement<X: Extension = NoExt> {
1699    /// Object targeted by this syntax.
1700    pub target: CommentTarget<X>,
1701    /// Name referenced by this syntax.
1702    pub name: ObjectName,
1703    /// Optional comment for this syntax.
1704    pub comment: Option<Literal>,
1705    /// Source location and node identity.
1706    pub meta: Meta,
1707}
1708
1709/// `CREATE SCHEMA [IF NOT EXISTS] [<name>] [AUTHORIZATION <role>] [<schema element> ...]`.
1710///
1711/// At least one of `name` / `authorization` is present — PostgreSQL's `CREATE SCHEMA
1712/// AUTHORIZATION joe` omits the schema name and derives it from the owning role.
1713///
1714/// The SQL-standard form embeds a list of component objects created *inside* the new
1715/// schema (`CREATE SCHEMA s CREATE TABLE t (...)`); [`elements`](Self::elements) holds
1716/// them as children so the whole construct stays ONE statement, mirroring PostgreSQL's
1717/// `CreateSchemaStmt.schemaElts` (which likewise carries the components as raw
1718/// statement parsenodes). Rendering them embedded preserves the statement COUNT that
1719/// downstream consumers observe — the earlier model split them into separate
1720/// `;`-joined statements, a statement-level rewrite no source-fidelity render can undo.
1721///
1722/// The element list is a *closed* admissible set enforced by the parser (measured
1723/// against PostgreSQL: `CREATE TABLE`/`VIEW`/`INDEX`/`SEQUENCE`/`TRIGGER` and `GRANT`
1724/// — and PostgreSQL rejects `CREATE MATERIALIZED VIEW`/`FUNCTION`, a nested `CREATE
1725/// SCHEMA`, `DROP`/`ALTER`/`INSERT`/… as elements). Modelled as a general
1726/// [`Statement`] list rather than a bespoke element enum — the same idiom as
1727/// [`CreateTrigger`]'s body — because the members ARE statements and reusing the
1728/// statement shape keeps the differential/structural oracles element-agnostic; the
1729/// closed set is the recorded acceptance bound, enforced at parse time. Non-empty only
1730/// under PostgreSQL (`schema_elements` gate); every other dialect parses a bare schema
1731/// head. Generic over `X` because the elements carry the extension node.
1732#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1733#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1734#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1735pub struct CreateSchema<X: Extension = NoExt> {
1736    /// Whether the if not exists form was present in the source.
1737    pub if_not_exists: bool,
1738    /// Name referenced by this syntax.
1739    pub name: Option<ObjectName>,
1740    /// Optional authorization for this syntax.
1741    pub authorization: Option<Ident>,
1742    /// The embedded schema-element statements, in source order. Empty for a bare
1743    /// `CREATE SCHEMA` head (the common case) and for every non-PostgreSQL dialect.
1744    pub elements: ThinVec<Statement<X>>,
1745    /// Source location and node identity.
1746    pub meta: Meta,
1747}
1748
1749/// `CREATE [OR REPLACE] [TEMP|TEMPORARY] VIEW` and `CREATE MATERIALIZED VIEW`,
1750/// with the `AS <query>` body that every view shares (a view body reuses [`Query`]
1751/// rather than re-deriving the SELECT grammar).
1752///
1753/// One node covers both spellings, distinguished by `materialized`. The divergent
1754/// tails are mutually exclusive by construction: the parser fills `check_option`
1755/// only for a regular view and `with_data` only for a materialized one. `OR
1756/// REPLACE` and the `MATERIALIZED` keyword (with its `WITH [NO] DATA` populate
1757/// clause) are PostgreSQL extensions gated by `schema_change_syntax` dialect data;
1758/// the ANSI `WITH [CASCADED|LOCAL] CHECK OPTION` is always accepted.
1759///
1760/// `recursive` records the `CREATE [OR REPLACE] [TEMP|TEMPORARY] RECURSIVE VIEW`
1761/// spelling (DuckDB, gated by
1762/// [`StatementDdlGates::recursive_views`](crate::dialect::StatementDdlGates::recursive_views)):
1763/// the keyword
1764/// sits between the `TEMP`/`TEMPORARY` prefix and `VIEW`, never composes with
1765/// `MATERIALIZED` (engine-rejected), and — mirroring the engine, which desugars a
1766/// recursive view to `WITH RECURSIVE` — requires the explicit `columns` list, so
1767/// the parser rejects the bare `RECURSIVE VIEW v AS …` form.
1768#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1769#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1770#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1771pub struct CreateView<X: Extension = NoExt> {
1772    /// Whether the or replace form was present in the source.
1773    pub or_replace: bool,
1774    /// The MySQL `[ALGORITHM = …] [DEFINER = …] [SQL SECURITY …]` definition-option prefix
1775    /// (see [`ViewOptions`]); all-`None` for every non-MySQL view and a bare MySQL view. The
1776    /// options sit between `OR REPLACE` and the `VIEW` keyword.
1777    pub options: ViewOptions,
1778    /// Whether the materialized form was present in the source.
1779    pub materialized: bool,
1780    /// Whether the recursive form was present in the source.
1781    pub recursive: bool,
1782    /// Optional temporary for this syntax.
1783    pub temporary: Option<TemporaryTableKind>,
1784    /// Whether the if not exists form was present in the source.
1785    pub if_not_exists: bool,
1786    /// Name referenced by this syntax.
1787    pub name: ObjectName,
1788    /// Columns in source order.
1789    pub columns: ThinVec<Ident>,
1790    /// Query governed by this node.
1791    pub query: Box<Query<X>>,
1792    /// Optional check option for this syntax.
1793    pub check_option: Option<ViewCheckOption>,
1794    /// Whether the with data form was present in the source.
1795    pub with_data: Option<bool>,
1796    /// Source location and node identity.
1797    pub meta: Meta,
1798}
1799
1800/// `WITH [ CASCADED | LOCAL ] CHECK OPTION` on a regular (non-materialized) view.
1801#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1802#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1803#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1804pub enum ViewCheckOption {
1805    /// Bare `WITH CHECK OPTION`; PostgreSQL treats the unqualified form as `CASCADED`.
1806    Unspecified,
1807    /// `WITH CASCADED CHECK OPTION` — enforce the check on this and all underlying views.
1808    Cascaded,
1809    /// `WITH LOCAL CHECK OPTION` — enforce the check on this view only.
1810    Local,
1811}
1812
1813/// The MySQL `ALGORITHM = { UNDEFINED | MERGE | TEMPTABLE }` view-processing algorithm, the
1814/// first of the [`ViewOptions`] definition-option prefix. A `Copy` tag whose span rides the
1815/// owning [`ViewOptions`], like [`SqlSecurityContext`].
1816#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1817#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1818#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1819pub enum ViewAlgorithm {
1820    /// `ALGORITHM = UNDEFINED` — the server chooses between `MERGE` and `TEMPTABLE` (the default).
1821    Undefined,
1822    /// `ALGORITHM = MERGE` — the view's text is merged into the referencing statement.
1823    Merge,
1824    /// `ALGORITHM = TEMPTABLE` — the view result is materialized into a temporary table.
1825    TempTable,
1826}
1827
1828/// The MySQL view definition-option prefix `[ALGORITHM = …] [DEFINER = …] [SQL SECURITY …]`,
1829/// shared verbatim by [`CreateView`] and [`AlterView`] — the two statements name the same
1830/// options in the same fixed source order, immediately before the `VIEW` keyword.
1831///
1832/// Each option is independently optional; the source order (algorithm, then definer, then SQL
1833/// security) is engine-required — a permutation (`DEFINER` before `ALGORITHM`, `SQL SECURITY`
1834/// before either) is `ER_PARSE_ERROR`. All-`None` is the ordinary view with no prefix (every
1835/// non-MySQL dialect, and a bare MySQL `CREATE/ALTER VIEW`). The [`Definer`] reuses the shared
1836/// routine/event account reference; the [`SqlSecurityContext`] reuses the routine security tag.
1837/// This is a non-spanned bundle — [`Definer`] carries its own span, and the two `Copy` tags
1838/// ride the owning statement's span, so the prefix needs no `meta` of its own.
1839#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
1840#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1841#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1842pub struct ViewOptions {
1843    /// The optional `ALGORITHM = …` processing algorithm; `None` when omitted.
1844    pub algorithm: Option<ViewAlgorithm>,
1845    /// The optional `DEFINER = <user>` account; `None` when omitted. Boxed like the routine
1846    /// definer (a rare fat field paying only a null pointer when absent).
1847    pub definer: Option<Box<Definer>>,
1848    /// The optional `SQL SECURITY { DEFINER | INVOKER }` privilege context; `None` when omitted.
1849    pub sql_security: Option<SqlSecurityContext>,
1850}
1851
1852/// `ALTER [ALGORITHM = …] [DEFINER = …] [SQL SECURITY …] VIEW <name> [(<columns>)] AS <query>
1853/// [WITH [CASCADED | LOCAL] CHECK OPTION]` — the MySQL view redefinition (gated by
1854/// [`StatementDdlGates::view_definition_options`](crate::dialect::StatementDdlGates::view_definition_options)).
1855///
1856/// MySQL's `ALTER VIEW` re-specifies the whole view body; it is the [`CreateView`] grammar
1857/// minus `OR REPLACE`/`IF NOT EXISTS` (server-measured: both `ER_PARSE_ERROR` after `ALTER`)
1858/// and the `TEMP`/`MATERIALIZED`/`RECURSIVE` prefixes MySQL has no views for. The shared
1859/// [`ViewOptions`] prefix and the [`ViewCheckOption`] tail are reused verbatim, and the body
1860/// reuses [`Query`] like every view.
1861#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1862#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1863#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1864pub struct AlterView<X: Extension = NoExt> {
1865    /// The `[ALGORITHM = …] [DEFINER = …] [SQL SECURITY …]` definition-option prefix; all-`None`
1866    /// for a bare `ALTER VIEW`.
1867    pub options: ViewOptions,
1868    /// The view name (`db.view` or bare `view`).
1869    pub name: ObjectName,
1870    /// The optional explicit output-column list; empty when omitted.
1871    pub columns: ThinVec<Ident>,
1872    /// The redefining `AS <query>` body.
1873    pub query: Box<Query<X>>,
1874    /// The optional `WITH [CASCADED | LOCAL] CHECK OPTION` constraint.
1875    pub check_option: Option<ViewCheckOption>,
1876    /// Source location and node identity.
1877    pub meta: Meta,
1878}
1879
1880/// A DuckDB `CREATE [PERSISTENT] SECRET <name> ( <option> <value> [, ...] )`
1881/// secrets-management statement (DuckDB-specific; gated by
1882/// [`StatementDdlGates::create_secret`](crate::dialect::StatementDdlGates::create_secret)).
1883///
1884/// DuckDB stores credentials (S3/HTTP/… access) as named secrets. `persistent`
1885/// records the `PERSISTENT` keyword (a persistent secret survives the session; the
1886/// bare form is session-temporary). The parenthesized [`options`](Self::options) are
1887/// `<name> <value>` pairs — the required `TYPE <provider>` plus provider-specific
1888/// settings (`KEY_ID`, `REGION`, …). Only the bare/`PERSISTENT` name-then-options form
1889/// the corpus exercises is modelled; the explicit `TEMPORARY` keyword, `OR REPLACE`,
1890/// `IF NOT EXISTS`, the anonymous (unnamed) secret, and the `IN <storage>` clause are
1891/// not part of this shape.
1892#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1893#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1894#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1895pub struct CreateSecret<X: Extension = NoExt> {
1896    /// Whether the persistent form was present in the source.
1897    pub persistent: bool,
1898    /// Name referenced by this syntax.
1899    pub name: ObjectName,
1900    /// Options supplied in source order.
1901    pub options: ThinVec<SecretOption<X>>,
1902    /// Source location and node identity.
1903    pub meta: Meta,
1904}
1905
1906/// One `<name> <value>` option inside a [`CreateSecret`] option list (`TYPE S3`,
1907/// `KEY_ID '...'`). The value is modelled as a general [`Expr`] — DuckDB's own option
1908/// value is a bare word, string, or list — so the corpus `TYPE <provider>` form
1909/// round-trips; the wider expression grammar is the recorded acceptance bound.
1910#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1911#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1912#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1913pub struct SecretOption<X: Extension = NoExt> {
1914    /// Name referenced by this syntax.
1915    pub name: Ident,
1916    /// Value supplied by this syntax.
1917    pub value: Expr<X>,
1918    /// Source location and node identity.
1919    pub meta: Meta,
1920}
1921
1922/// A DuckDB `DROP [PERSISTENT | TEMPORARY] SECRET [IF EXISTS] <name> [FROM <storage>]`
1923/// secrets-management statement (DuckDB-specific; gated by
1924/// [`StatementDdlGates::create_secret`](crate::dialect::StatementDdlGates::create_secret) —
1925/// the same whole-statement gate that admits [`CreateSecret`], because `DROP SECRET` is the
1926/// same secrets behaviour surface and rides the one flag rather than a second gate).
1927///
1928/// The drop counterpart of [`CreateSecret`]. In DuckDB's grammar `drop_secret.y` is the
1929/// *only* `DROP` with its own top-level `stmt` production (not a [`DropStatement`] object
1930/// kind), because it carries the `opt_persist` persistence modifier and a `FROM <storage>`
1931/// backend selector — neither of which the shared name-list DROP grammar has.
1932/// [`persistence`](Self::persistence) records which of the three `opt_persist` spellings
1933/// preceded `SECRET` (absent → [`SecretPersistence::Default`]);
1934/// [`storage`](Self::storage) is the optional `FROM <backend>` secret-storage name. The
1935/// dropped secret is a single identifier (grammar `ColId`), never a qualified object name.
1936///
1937/// Non-generic: a secret drop names a secret, an optional storage backend, and two flags —
1938/// it carries no expressions or extension nodes — so it parallels the leaf
1939/// [`DropStatement`]/[`DropTransform`] payloads.
1940#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1941#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1942#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1943pub struct DropSecretStmt {
1944    /// Which `opt_persist` spelling preceded `SECRET`; see [`SecretPersistence`].
1945    pub persistence: SecretPersistence,
1946    /// Whether the `IF EXISTS` existence guard was present in the source.
1947    pub if_exists: bool,
1948    /// Name referenced by this syntax.
1949    pub name: Ident,
1950    /// Optional `FROM <storage>` secret-storage backend selector.
1951    pub storage: Option<Ident>,
1952    /// Source location and node identity.
1953    pub meta: Meta,
1954}
1955
1956/// The `opt_persist` persistence modifier on a DuckDB `DROP SECRET`: which of the three
1957/// storage scopes the statement names. (The wider `CREATE SECRET` grammar shares the same
1958/// `opt_persist` production; [`CreateSecret`] models only its `PERSISTENT`/absent forms as
1959/// a `bool`, so this three-valued modifier lives with the drop that needs all three.)
1960#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1961#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1962#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1963pub enum SecretPersistence {
1964    /// No modifier written — DuckDB's `default` secret scope (`DROP SECRET s`).
1965    Default,
1966    /// `TEMPORARY` — the session-scoped, in-memory secret store.
1967    Temporary,
1968    /// `PERSISTENT` — the on-disk persistent secret store.
1969    Persistent,
1970}
1971
1972/// A SQLite `CREATE VIRTUAL TABLE [IF NOT EXISTS] [<schema> .] <name> USING <module>
1973/// [( <arg> [, <arg>] * )]` statement (SQLite-specific; gated by
1974/// [`StatementDdlGates::create_virtual_table`](crate::dialect::StatementDdlGates::create_virtual_table)).
1975///
1976/// A virtual table delegates its storage and query implementation to a *module*
1977/// (`fts5`, `rtree`, `csv`, …). The module — not SQLite's core grammar — owns the
1978/// argument syntax: `fts5` reads column names and `tokenize = …` options, `rtree`
1979/// reads dimension columns, and a bespoke module reads whatever it likes. SQLite's
1980/// own parser therefore imposes almost no structure on the argument list — it splits
1981/// the parenthesized text on the *top-level* commas (parentheses nest and quoted
1982/// strings are transparent) and hands each raw slice to the module verbatim, even
1983/// tolerating empty members (`USING m(a,,b)`). Module resolution and any argument
1984/// validation happen at execution time, so the parse layer accepts an unknown module
1985/// and any balanced-parenthesis token soup.
1986///
1987/// Each argument is consequently modelled as an OPAQUE verbatim [`ModuleArg`] (the
1988/// interned source text of one top-level slice), never a parsed sub-grammar — imposing
1989/// column/option structure would invent constraints SQLite does not enforce.
1990///
1991/// [`args`](Self::args) is `None` for the bare `USING m` form and `Some` (possibly
1992/// empty) for the parenthesized `USING m (…)` form, so the two round-trip distinctly.
1993/// The name admits at most two parts (`schema.table`); SQLite has no `TEMP` virtual
1994/// table, so no temporary modifier is modelled. Non-generic: the arguments hold no
1995/// expressions or extension nodes.
1996#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1997#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
1998#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
1999pub struct CreateVirtualTable {
2000    /// Whether the if not exists form was present in the source.
2001    pub if_not_exists: bool,
2002    /// Name referenced by this syntax.
2003    pub name: ObjectName,
2004    /// The virtual-table module name (`USING <module>`).
2005    pub module: Ident,
2006    /// Arguments in source order.
2007    pub args: Option<ThinVec<ModuleArg>>,
2008    /// Source location and node identity.
2009    pub meta: Meta,
2010}
2011
2012/// One opaque, verbatim argument of a [`CreateVirtualTable`] module argument list.
2013///
2014/// [`text`](Self::text) is the interned source text of a single top-level
2015/// comma-delimited slice, preserved exactly (internal spacing and all) because the
2016/// module owns its grammar — the parser never interprets it. An empty slice (from
2017/// `USING m(a,,b)` or a trailing comma) is a legal empty argument.
2018#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2019#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2020#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2021pub struct ModuleArg {
2022    /// The comment text.
2023    pub text: Symbol,
2024    /// Source location and node identity.
2025    pub meta: Meta,
2026}
2027
2028/// `CREATE [UNIQUE] INDEX [CONCURRENTLY] [IF NOT EXISTS] [<name>] ON <table>
2029/// [USING <method>] (<column> [, ...]) [WHERE <predicate>]`.
2030///
2031/// `UNIQUE` is portable; `CONCURRENTLY`, the `USING <method>` access-method clause,
2032/// and the trailing `WHERE` partial-index predicate are PostgreSQL extensions gated
2033/// by `schema_change_syntax.index_extensions` dialect data. The index
2034/// name is optional — PostgreSQL derives one from the table and columns when it is
2035/// omitted (`CREATE INDEX ON t (a)`). COLLATE / operator-class / `INCLUDE` column
2036/// decorations are a deliberate follow-up.
2037#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2038#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2039#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2040pub struct CreateIndex<X: Extension = NoExt> {
2041    /// Whether the unique form was present in the source.
2042    pub unique: bool,
2043    /// Whether the concurrently form was present in the source.
2044    pub concurrently: bool,
2045    /// Whether the if not exists form was present in the source.
2046    pub if_not_exists: bool,
2047    /// Name referenced by this syntax.
2048    pub name: Option<Ident>,
2049    /// Table referenced by this syntax.
2050    pub table: ObjectName,
2051    /// Optional using for this syntax.
2052    pub using: Option<Ident>,
2053    /// Columns in source order.
2054    pub columns: ThinVec<IndexColumn<X>>,
2055    /// Predicate that controls this clause.
2056    pub predicate: Option<Box<Expr<X>>>,
2057    /// Source location and node identity.
2058    pub meta: Meta,
2059}
2060
2061/// One key column of a [`CreateIndex`]: an expression with optional sort modifiers.
2062///
2063/// A bare column is the common case (`expr` is an [`Expr::Column`](super::Expr)); a
2064/// parenthesized expression indexes a computed value. `asc` / `nulls_first` mirror
2065/// [`OrderByExpr`](super::OrderByExpr) and stay `None` when the modifier is
2066/// unwritten, leaving the dialect default to the consumer.
2067#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2068#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2069#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2070pub struct IndexColumn<X: Extension = NoExt> {
2071    /// Expression evaluated by this syntax.
2072    pub expr: Expr<X>,
2073    /// Whether the asc form was present in the source.
2074    pub asc: Option<bool>,
2075    /// Whether the nulls first form was present in the source.
2076    pub nulls_first: Option<bool>,
2077    /// Source location and node identity.
2078    pub meta: Meta,
2079}
2080
2081/// A SQLite `CREATE [TEMP|TEMPORARY] TRIGGER [IF NOT EXISTS] [<schema> .] <name>
2082/// [BEFORE | AFTER | INSTEAD OF] <event> ON <table> [FOR EACH ROW] [WHEN <expr>]
2083/// BEGIN <stmt>; ... END` statement (SQLite-specific; gated by
2084/// [`StatementDdlGates::create_trigger`](crate::dialect::StatementDdlGates::create_trigger)).
2085///
2086/// Unlike the other `CREATE` families — which are always accepted because their body
2087/// grammar is standard — the trigger gate is real: only SQLite's `BEGIN … END`
2088/// SQL-statement-body form is modelled, and PostgreSQL/MySQL spell an incompatible
2089/// body (`EXECUTE FUNCTION f()` / an external routine), which they genuinely reject
2090/// for this form. Gating to SQLite (and Lenient) is therefore behaviour-accurate, not
2091/// only a modelling limitation.
2092///
2093/// The [`body`](Self::body) is a non-empty sequence of `INSERT`/`UPDATE`/`DELETE`/
2094/// `SELECT` statements — a statement list inside a statement (the structurally
2095/// heaviest SQLite shape). It reuses the existing [`Statement`] nodes rather than
2096/// minting trigger-body variants (the node stays generic over `X`); the
2097/// parser gates the body kinds and routes each through the recursion-guarded
2098/// statement dispatcher, and the enclosing [`Statement::CreateTrigger`]
2099/// boxes this payload to keep the enum within its size budget.
2100#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2101#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2102#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2103pub struct CreateTrigger<X: Extension = NoExt> {
2104    /// Optional temporary for this syntax.
2105    pub temporary: Option<TemporaryTableKind>,
2106    /// Whether the if not exists form was present in the source.
2107    pub if_not_exists: bool,
2108    /// Name referenced by this syntax.
2109    pub name: ObjectName,
2110    /// The fire time; `None` when unwritten (SQLite defaults it to `BEFORE`, but the
2111    /// absent form is preserved so it round-trips).
2112    pub timing: Option<TriggerTiming>,
2113    /// Which DML event fires the trigger; see [`TriggerEvent`].
2114    pub event: TriggerEvent,
2115    /// Table referenced by this syntax.
2116    pub table: ObjectName,
2117    /// Whether `FOR EACH ROW` was written. SQLite parses (and ignores) it but rejects
2118    /// `FOR EACH STATEMENT`, so only the `ROW` form is modelled as a surface tag.
2119    pub for_each_row: bool,
2120    /// Optional when for this syntax.
2121    pub when: Option<Expr<X>>,
2122    /// The trigger-body statement list, in source order (always non-empty — SQLite
2123    /// rejects an empty `BEGIN END`).
2124    pub body: ThinVec<Statement<X>>,
2125    /// Source location and node identity.
2126    pub meta: Meta,
2127}
2128
2129/// When a [`CreateTrigger`] fires relative to the event.
2130///
2131/// A tag (no `meta`): the timing keyword's span is subsumed by the enclosing
2132/// [`CreateTrigger`], exactly as [`TemporaryTableKind`] rides its parent's span.
2133#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2134#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2135#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2136pub enum TriggerTiming {
2137    /// `BEFORE` — fire before the event is applied.
2138    Before,
2139    /// `AFTER` — fire after the event is applied.
2140    After,
2141    /// `INSTEAD OF` (valid on a view).
2142    InsteadOf,
2143}
2144
2145/// The DML event that fires a [`CreateTrigger`].
2146///
2147/// `UPDATE` alone fires on any column; `UPDATE OF a, b` restricts it to the listed
2148/// columns ([`columns`](Self::Update::columns) empty for the bare `UPDATE`). `INSERT`
2149/// and `DELETE` take no column list.
2150#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2151#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2152#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2153pub enum TriggerEvent {
2154    /// `DELETE` — fire on row deletion.
2155    Delete {
2156        /// Source location and node identity.
2157        meta: Meta,
2158    },
2159    /// `INSERT` — fire on row insertion.
2160    Insert {
2161        /// Source location and node identity.
2162        meta: Meta,
2163    },
2164    /// `UPDATE [OF col, …]` — fire on row update, optionally restricted to columns.
2165    Update {
2166        /// Columns in source order.
2167        columns: ThinVec<Ident>,
2168        /// Source location and node identity.
2169        meta: Meta,
2170    },
2171}
2172
2173/// `CREATE [DEFINER = <user>] TRIGGER [IF NOT EXISTS] <name> {BEFORE | AFTER}
2174/// {INSERT | UPDATE | DELETE} ON <table> FOR EACH ROW [{FOLLOWS | PRECEDES} <other>]
2175/// <sp_proc_stmt>` — the MySQL SQL/PSM trigger (gated by
2176/// [`StatementDdlGates::compound_statements`](crate::dialect::StatementDdlGates::compound_statements),
2177/// the same stored-program gate the routine wrappers ride).
2178///
2179/// A DISTINCT node from the SQLite [`CreateTrigger`], not an extension of it, because the two
2180/// shapes do not genuinely unify — the decisive split is the body: SQLite's is a
2181/// `BEGIN <stmt>; … END` *list* of plain SQL statements ([`CreateTrigger::body`], a
2182/// [`ThinVec`]), MySQL's is a *single* `sp_proc_stmt` — usually a
2183/// [`Statement::Compound`] block, but any one body statement (a `SET`,
2184/// a flow-control construct) — parsed through the shared `parse_body_statement` seam and boxed
2185/// because [`Statement`] is large. Their decorating axes are disjoint too: SQLite carries
2186/// `TEMP` / `WHEN` / `INSTEAD OF` / `UPDATE OF <cols>`, MySQL carries [`DEFINER`](Self::definer),
2187/// mandatory `BEFORE`/`AFTER` timing, mandatory `FOR EACH ROW`, and the
2188/// [`FOLLOWS`/`PRECEDES` ordering](Self::ordering) anchor. Folding both into one node would
2189/// leave half its fields dialect-dead — the [`CreateProcedure`] precedent (a MySQL
2190/// stored-program object gets its own node rather than overloading a cross-dialect one) applies.
2191///
2192/// The [`timing`](Self::timing) and [`event`](Self::event) axes reuse the shared
2193/// [`TriggerTiming`] / [`TriggerEvent`] vocabulary — MySQL simply never emits the SQLite-only
2194/// [`TriggerTiming::InsteadOf`] or a non-empty [`TriggerEvent::Update`] column list (the parser
2195/// enforces the bare forms), so the reuse is safe and keeps one trigger-axis vocabulary.
2196#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2197#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2198#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2199pub struct CreateStoredTrigger<X: Extension = NoExt> {
2200    /// The optional `DEFINER = <user>` account prefix; `None` when omitted. Boxed (the rare
2201    /// fat field pays only a null pointer when absent), like [`CreateProcedure::definer`].
2202    pub definer: Option<Box<Definer>>,
2203    /// Whether the `IF NOT EXISTS` guard was written (MySQL 8.0.29+).
2204    pub if_not_exists: bool,
2205    /// Name referenced by this syntax.
2206    pub name: ObjectName,
2207    /// The fire time (`BEFORE` / `AFTER`) — mandatory in MySQL (no defaulted/absent form, and
2208    /// never [`TriggerTiming::InsteadOf`]).
2209    pub timing: TriggerTiming,
2210    /// The DML event that fires the trigger (`INSERT` / `UPDATE` / `DELETE`) — always the bare
2211    /// form; MySQL has no `UPDATE OF <cols>`, so a [`TriggerEvent::Update`] carries no columns.
2212    pub event: TriggerEvent,
2213    /// Table referenced by this syntax.
2214    pub table: ObjectName,
2215    /// The optional `{FOLLOWS | PRECEDES} <other>` ordering anchor relative to another trigger on
2216    /// the same table and event; `None` for the unordered form.
2217    pub ordering: Option<TriggerOrder>,
2218    /// The trigger body: one `sp_proc_stmt`, boxed. Usually a
2219    /// [`Statement::Compound`] `BEGIN … END` block, but any single
2220    /// body statement is admitted, parsed through the `parse_body_statement` seam.
2221    pub body: Box<Statement<X>>,
2222    /// Source location and node identity.
2223    pub meta: Meta,
2224}
2225
2226/// The MySQL `{FOLLOWS | PRECEDES} <other_trigger>` ordering clause of a
2227/// [`CreateStoredTrigger`] (`trigger_follows_precedes_clause`): where the new trigger fires
2228/// relative to an existing one on the same table and event.
2229///
2230/// The anchor is an [`Ident`] (MySQL's `ident_or_text` — a bare or quoted trigger name) so both
2231/// source spellings round-trip from its span. The keyword direction is the enum variant.
2232#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2233#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2234#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2235pub enum TriggerOrder {
2236    /// `FOLLOWS <other>` — fire immediately after the named trigger.
2237    Follows {
2238        /// The anchor trigger this one follows.
2239        anchor: Ident,
2240        /// Source location and node identity.
2241        meta: Meta,
2242    },
2243    /// `PRECEDES <other>` — fire immediately before the named trigger.
2244    Precedes {
2245        /// The anchor trigger this one precedes.
2246        anchor: Ident,
2247        /// Source location and node identity.
2248        meta: Meta,
2249    },
2250}
2251
2252/// `CREATE DATABASE [IF NOT EXISTS] <name>`.
2253///
2254/// Non-generic, like [`CreateSchema`]: a database is named by identifiers only. The
2255/// trailing `[WITH] <option> ...` list (`OWNER`, `TEMPLATE`, `ENCODING`, …) is a
2256/// deliberate follow-up — real migrations rarely spell it and it carries no
2257/// expressions the shape must compare.
2258#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2259#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2260#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2261pub struct CreateDatabase {
2262    /// The MySQL `IF NOT EXISTS` existence guard (the [`CreateTable`] precedent). Gated
2263    /// for acceptance by
2264    /// [`ExistenceGuards::create_database_if_not_exists`](crate::dialect::ExistenceGuards):
2265    /// PostgreSQL has no `CREATE DATABASE IF NOT EXISTS`, so the flag is off there and
2266    /// the guard surfaces as a clean parse error.
2267    pub if_not_exists: bool,
2268    /// Name referenced by this syntax.
2269    pub name: ObjectName,
2270    /// Source location and node identity.
2271    pub meta: Meta,
2272}
2273
2274/// `CREATE [OR REPLACE] FUNCTION <name> ( [<param> [, ...]] ) [RETURNS <type>]
2275/// [<option> ...]`.
2276///
2277/// The routine body has two disjoint grammatical homes on the [`FunctionBody`] axis. An
2278/// opaque source *string* is introduced by the order-independent `AS` option, spelled either
2279/// single-quoted (`AS 'SELECT 1'`) or dollar-quoted (`AS $$ … $$` / `AS $tag$ … $tag$`) where
2280/// the dialect enables [`dollar_quoted_strings`](crate::dialect::StringLiteralSyntax); the
2281/// delimiter and verbatim body text round-trip from the body [`Literal`]'s span. A *live* SQL
2282/// body — a SQL-standard `RETURN <expr>` (PostgreSQL 14+ / standard `opt_routine_body`) — is
2283/// the trailing [`body`](Self::body) slot, which strictly follows the whole option list (proven
2284/// against the PG oracle: `LANGUAGE sql RETURN 1` accepts, `RETURN 1 LANGUAGE sql` rejects), so
2285/// it is a distinct parse position from `AS`, not another order-independent option. The two
2286/// homes share the [`FunctionBody`] *type* (the axis vocabulary the dollar-body sibling staged)
2287/// but not a grammatical slot.
2288///
2289/// The node is generic over `X` because a parameter or `RETURNS` type can be a host-owned
2290/// [`DataType::Other`] under a custom dialect, and the live
2291/// [`body`](Self::body) carries an [`Expr`]`<X>` (stock builtins pin `X = NoExt`). The M1
2292/// surface is the parameter list, an optional `RETURNS <type>`, the [`FunctionOption`] cluster
2293/// the corpus exercises (`LANGUAGE` / `AS` / null-call behaviour), and the trailing `RETURN`
2294/// body; the fuller volatility, security, parallelism, and `SET` option matrix — and the
2295/// statement-list `BEGIN ATOMIC … END` body that shares the trailing slot — are deliberate
2296/// follow-ups the open option list and the [`FunctionBody`] axis extend without a shape change.
2297#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2298#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2299#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2300pub struct CreateFunction<X: Extension = NoExt> {
2301    /// Whether the or replace form was present in the source.
2302    pub or_replace: bool,
2303    /// The optional MySQL `DEFINER = <user>` clause; `None` when omitted (always `None`
2304    /// for the PostgreSQL string-body routine, which has no definer prefix). Boxed because a
2305    /// [`Definer`] is comparatively large and the clause is rare, so a definer-less routine
2306    /// pays only a null pointer (the [`FunctionParam::default`] precedent).
2307    pub definer: Option<Box<Definer>>,
2308    /// Whether the MySQL `IF NOT EXISTS` guard was written (MySQL 8.0.29+); always `false`
2309    /// for PostgreSQL, which spells the intent `CREATE OR REPLACE`.
2310    pub if_not_exists: bool,
2311    /// Name referenced by this syntax.
2312    pub name: ObjectName,
2313    /// params in source order.
2314    pub params: ThinVec<FunctionParam<X>>,
2315    /// Optional returns for this syntax.
2316    pub returns: Option<DataType<X>>,
2317    /// Options supplied in source order.
2318    pub options: ThinVec<FunctionOption<X>>,
2319    /// The trailing SQL-standard routine body (`opt_routine_body`) — a `RETURN <expr>` live
2320    /// SQL expression that follows the entire option list. Boxed and optional because it is
2321    /// usually absent (an `AS`-string routine leaves it unset, paying one null pointer — the
2322    /// [`FunctionParam::default`] precedent); a [`FunctionBody::Definition`] string body never
2323    /// lands here (it rides the `AS` option instead).
2324    pub body: Option<Box<FunctionBody<X>>>,
2325    /// Source location and node identity.
2326    pub meta: Meta,
2327}
2328
2329/// One routine parameter: an optional argument [`mode`](Self::mode)
2330/// (`IN`/`OUT`/`INOUT`/`VARIADIC`), an optional name, its type (`b INT`), and an
2331/// optional default value (`b INT DEFAULT 0` / `b INT = 0`).
2332///
2333/// The three lead-in facets are independent: a bare type (`f(int)`) leaves both
2334/// `mode` and `name` unset, a mode may precede an unnamed type (`f(OUT int)`), and a
2335/// named parameter may carry a default. The name is PostgreSQL's `param_name` =
2336/// `type_function_name` production (parsed with the [type-name reservation
2337/// set](crate::dialect::FeatureSet::reserved_type_name)): a `type_func_name` keyword
2338/// like `left` is a legal parameter name, a `col_name` keyword like `int` is not — so
2339/// a lone `int` is unambiguously the type, mirroring how PostgreSQL resolves the
2340/// name-vs-type ambiguity. The default is the PostgreSQL `func_arg_with_default`
2341/// tail — boxed because it is usually absent and a [`FunctionParamDefault`] (an
2342/// [`Expr`]) is comparatively large, so a default-less parameter pays only a null
2343/// pointer (the [`ForeignKeyRef`] referential-action precedent). It is *parameter
2344/// metadata*: an ordinary function-**call** argument is a
2345/// [`FunctionArg`](crate::ast::FunctionArg) on the unrelated call path and is
2346/// unaffected.
2347#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2348#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2349#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2350pub struct FunctionParam<X: Extension = NoExt> {
2351    /// Mode selected by this syntax.
2352    pub mode: Option<FunctionParamMode>,
2353    /// Name referenced by this syntax.
2354    pub name: Option<Ident>,
2355    /// Data type named by this syntax.
2356    pub data_type: DataType<X>,
2357    /// Optional default for this syntax.
2358    pub default: Option<Box<FunctionParamDefault<X>>>,
2359    /// Source location and node identity.
2360    pub meta: Meta,
2361}
2362
2363/// A routine parameter's argument mode — PostgreSQL's `arg_class` prefix
2364/// (`func_arg: arg_class param_name func_type | …`). `IN` is the default and may be
2365/// omitted; `OUT`/`INOUT` mark output parameters; `VARIADIC` marks a trailing
2366/// array-spread parameter. A fieldless [`Copy`] tag whose span rides the owning
2367/// [`FunctionParam`] (the [`FunctionParamDefaultSpelling`] precedent): it records only
2368/// the written mode so it round-trips.
2369///
2370/// PostgreSQL admits the mode either before *or* after the name (`IN a int` and
2371/// `a IN int` both parse); this models the documented, canonical mode-first spelling
2372/// (which sqlparser-rs's `ArgMode` also targets) and adds [`Variadic`](Self::Variadic),
2373/// which sqlparser-rs omits but real PostgreSQL requires. The rarer name-first spelling
2374/// is a deliberate parse-surface boundary — no corpus exercises it and PostgreSQL itself
2375/// normalizes the two orders to one AST.
2376#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2377#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2378#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2379pub enum FunctionParamMode {
2380    /// `IN` — an input parameter (the default when omitted).
2381    In,
2382    /// `OUT` — an output parameter.
2383    Out,
2384    /// `INOUT` — an input/output parameter.
2385    InOut,
2386    /// `VARIADIC` — a trailing array-spread parameter.
2387    Variadic,
2388}
2389
2390/// A routine parameter's default-value clause: the [`spelling`](Self::spelling)
2391/// (`DEFAULT` keyword vs `=`) and the value [`Expr`]. PostgreSQL's grammar spells
2392/// one production two ways — `func_arg_with_default: func_arg DEFAULT a_expr |
2393/// func_arg '=' a_expr` — so the tag records which the source used and the value is
2394/// a live SQL expression reusing [`Expr`] rather than a re-derived literal grammar.
2395///
2396/// sqlparser-rs models the same clause as a bare `default_expr: Option<Expr>` and
2397/// always re-renders it as `= <expr>`, normalizing the `DEFAULT` spelling away; this
2398/// AST instead carries the fieldless [`FunctionParamDefaultSpelling`] tag so both
2399/// forms round-trip verbatim (the spelling-fidelity doctrine, as for
2400/// [`EqualsSpelling`](crate::ast::EqualsSpelling)).
2401#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2402#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2403#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2404pub struct FunctionParamDefault<X: Extension = NoExt> {
2405    /// Exact source spelling retained for faithful rendering.
2406    pub spelling: FunctionParamDefaultSpelling,
2407    /// Value supplied by this syntax.
2408    pub value: Expr<X>,
2409    /// Source location and node identity.
2410    pub meta: Meta,
2411}
2412
2413/// Surface spelling for a [`FunctionParamDefault`]: PostgreSQL admits both the
2414/// `DEFAULT` keyword and a bare `=` to introduce a routine parameter's default, two
2415/// spellings of the one `func_arg_with_default` production. A `Copy` tag whose span
2416/// rides the owning [`FunctionParamDefault`], like [`MacroSpelling`]; it records the
2417/// source form only so rendering round-trips exactly (a fidelity tag, not a validity
2418/// one — the parser accepts both spellings wherever defaults are gated on).
2419#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2420#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2421#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2422pub enum FunctionParamDefaultSpelling {
2423    /// The `DEFAULT <expr>` keyword form.
2424    Default,
2425    /// The `= <expr>` operator form.
2426    Equals,
2427}
2428
2429#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2430#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2431#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2432/// The SQL function option forms represented by the AST.
2433pub enum FunctionOption<X: Extension = NoExt> {
2434    /// A `LANGUAGE <name>` clause naming the routine's implementation language. The name is a
2435    /// `NonReservedWord_or_Sconst` ([`LanguageName`]) — a bare word or, on the PostgreSQL
2436    /// surface, an `Sconst` string (`LANGUAGE 'sql'`/`E'sql'`/`$$sql$$`) — shared with the
2437    /// [`DoArg::Language`](crate::ast::DoArg) argument.
2438    Language {
2439        /// Name referenced by this syntax.
2440        name: LanguageName,
2441        /// Source location and node identity.
2442        meta: Meta,
2443    },
2444    /// `AS <body>` — the *string* routine body, carried on the [`FunctionBody`] axis. The `AS`
2445    /// option only ever homes an opaque source string (plain `'…'` or dollar-quoted
2446    /// `$tag$…$tag$`), i.e. [`FunctionBody::Definition`]; the live `RETURN <expr>` body rides
2447    /// the trailing [`CreateFunction::body`] slot, a disjoint grammatical position.
2448    As {
2449        /// The routine's string body; see [`FunctionBody`].
2450        body: FunctionBody<X>,
2451        /// Source location and node identity.
2452        meta: Meta,
2453    },
2454    /// The null-call behaviour (`CALLED ON NULL INPUT` / `RETURNS NULL ON NULL INPUT`
2455    /// / `STRICT`).
2456    NullBehavior {
2457        /// The null-input behaviour; see [`FunctionNullBehavior`].
2458        behavior: FunctionNullBehavior,
2459        /// Source location and node identity.
2460        meta: Meta,
2461    },
2462    /// The MySQL `[NOT] DETERMINISTIC` routine characteristic — whether the routine
2463    /// always produces the same result for the same inputs. The `not` field records the negated
2464    /// spelling so `NOT DETERMINISTIC` round-trips distinctly from a
2465    /// routine that simply omits the characteristic. A stored-routine option (MySQL SQL/PSM),
2466    /// not a PostgreSQL `CREATE FUNCTION` clause.
2467    Deterministic {
2468        /// Whether the `NOT` prefix was written (`NOT DETERMINISTIC`).
2469        not: bool,
2470        /// Source location and node identity.
2471        meta: Meta,
2472    },
2473    /// The MySQL SQL-data-access routine characteristic (`CONTAINS SQL` / `NO SQL` /
2474    /// `READS SQL DATA` / `MODIFIES SQL DATA`) — the advisory declaration of how the
2475    /// routine body touches data.
2476    DataAccess {
2477        /// Which data-access class was written; see [`SqlDataAccess`].
2478        access: SqlDataAccess,
2479        /// Source location and node identity.
2480        meta: Meta,
2481    },
2482    /// The MySQL `SQL SECURITY {DEFINER | INVOKER}` routine characteristic — the privilege
2483    /// context the routine executes under.
2484    SqlSecurity {
2485        /// The security context; see [`SqlSecurityContext`].
2486        context: SqlSecurityContext,
2487        /// Source location and node identity.
2488        meta: Meta,
2489    },
2490    /// The MySQL `COMMENT '<string>'` routine characteristic — a stored descriptive comment.
2491    /// The PostgreSQL comment surface is the separate `COMMENT ON FUNCTION` statement, so
2492    /// this inline option is MySQL-only.
2493    Comment {
2494        /// The comment string literal.
2495        comment: Literal,
2496        /// Source location and node identity.
2497        meta: Meta,
2498    },
2499}
2500
2501/// The MySQL routine SQL-data-access characteristic — how the routine body is declared to
2502/// touch data. Advisory (the server does not enforce it), recorded so it round-trips. A
2503/// `Copy` tag whose span rides the owning [`FunctionOption::DataAccess`], like
2504/// [`FunctionNullBehavior`].
2505#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2506#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2507#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2508pub enum SqlDataAccess {
2509    /// `CONTAINS SQL` — the body contains SQL but neither reads nor writes data (the default).
2510    ContainsSql,
2511    /// `NO SQL` — the body contains no SQL.
2512    NoSql,
2513    /// `READS SQL DATA` — the body reads but does not modify data.
2514    ReadsSqlData,
2515    /// `MODIFIES SQL DATA` — the body may modify data.
2516    ModifiesSqlData,
2517}
2518
2519/// The MySQL `SQL SECURITY` context — whose privileges a routine runs under. A `Copy` tag
2520/// whose span rides the owning [`FunctionOption::SqlSecurity`], like [`SqlDataAccess`].
2521#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2522#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2523#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2524pub enum SqlSecurityContext {
2525    /// `SQL SECURITY DEFINER` — run under the routine definer's privileges (the default).
2526    Definer,
2527    /// `SQL SECURITY INVOKER` — run under the calling user's privileges.
2528    Invoker,
2529}
2530
2531/// The executable body of a [`CreateFunction`] routine — the body-representation **axis**.
2532///
2533/// Two body *kinds* are modelled today. [`Definition`](Self::Definition) is an opaque source
2534/// string introduced by the order-independent `AS` option (`AS 'SELECT 1'`, `AS $$ … $$`,
2535/// `AS $body$ … $body$`). [`Return`](Self::Return) is the SQL-standard *live* body
2536/// `RETURN <expr>` (PostgreSQL 14+ / standard `opt_routine_body`), a real SQL [`Expr`] that
2537/// rides the trailing [`CreateFunction::body`] slot — a *distinct* parse position from `AS`
2538/// (the `RETURN` body strictly follows the whole option list, oracle-proven). This is a
2539/// distinct enum — not a bare [`Literal`] field on [`FunctionOption::As`] — precisely so the
2540/// live body reuses one body vocabulary across both slots rather than reshaping the option.
2541///
2542/// The axis is body *kind* (opaque string vs. live SQL), **not** quote style: a plain `'…'`
2543/// and a dollar-quoted `$tag$…$tag$` body are both
2544/// [`LiteralKind::String`](crate::ast::LiteralKind) and differ only in source spelling. That
2545/// spelling — the delimiter tag and the verbatim body text — round-trips from the
2546/// [`Literal`]'s span; it is recovered from source, never normalized, so a dollar body
2547/// re-renders byte-for-byte.
2548///
2549/// The enum is generic over `X` because [`Return`](Self::Return) carries an [`Expr`]`<X>`; the
2550/// [`Definition`](Self::Definition) string variant is generic-free. A statement-list
2551/// `BEGIN ATOMIC … END` body (PostgreSQL, oracle-accepted) shares the trailing slot and slots
2552/// in here as a further variant carrying a routine-body statement list — deliberately deferred:
2553/// modelling it honestly needs a routine-body statement grammar (its inner `RETURN` is not a
2554/// top-level [`Statement`]), which is a separate surface from this single-`Expr` body.
2555#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2556#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2557#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2558pub enum FunctionBody<X: Extension = NoExt> {
2559    /// `AS <string>` — an opaque routine body in the target language, kept as the source
2560    /// [`Literal`] (plain-quoted or dollar-quoted) rather than re-parsed, because the body's
2561    /// grammar is the target language, not SQL.
2562    Definition {
2563        /// Definition text supplied by this syntax.
2564        definition: Literal,
2565        /// Source location and node identity.
2566        meta: Meta,
2567    },
2568    /// `RETURN <expr>` — the SQL-standard live expression body (`opt_routine_body`'s
2569    /// `ReturnStmt`). The [`Expr`] is boxed (the [`MacroBody`] precedent) so the common `AS`
2570    /// string body pays no [`Expr`] footprint on the shared [`FunctionOption::As`] path.
2571    Return {
2572        /// Expression evaluated by this syntax.
2573        expr: Box<Expr<X>>,
2574        /// Source location and node identity.
2575        meta: Meta,
2576    },
2577    /// A MySQL SQL/PSM routine body — one `routine_body` statement, usually the
2578    /// `BEGIN … END` compound block ([`Statement::Compound`])
2579    /// but any single body statement (a bare `SET`, a flow-control construct, a `RETURN`).
2580    /// This is the live statement-list body the [`FunctionBody`] axis doc anticipated; it
2581    /// rides the trailing [`CreateFunction::body`] slot exactly as [`Return`](Self::Return)
2582    /// does — a distinct grammatical position after the whole characteristic list. Boxed
2583    /// ([`Statement`] is large) so the common `AS`-string path pays no footprint.
2584    Block {
2585        /// The routine body statement (typically a compound block); parsed through the
2586        /// `parse_body_statement` seam so its inner grammar is the MySQL body sub-language.
2587        body: Box<Statement<X>>,
2588        /// Source location and node identity.
2589        meta: Meta,
2590    },
2591}
2592
2593/// How a routine handles a NULL argument. `Strict` is the exact shorthand for
2594/// `ReturnsNullOnNull`, kept distinct so the written spelling round-trips. A `Copy`
2595/// leaf enum whose span lives on the owning [`FunctionOption`], like [`DropBehavior`].
2596#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2597#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2598#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2599pub enum FunctionNullBehavior {
2600    /// `CALLED ON NULL INPUT` — run the routine even when an argument is null.
2601    CalledOnNull,
2602    /// `RETURNS NULL ON NULL INPUT` — return null without running when any argument is null.
2603    ReturnsNullOnNull,
2604    /// `STRICT` — shorthand for `RETURNS NULL ON NULL INPUT`.
2605    Strict,
2606}
2607
2608/// The MySQL `DEFINER = <user>` clause prefixing a routine (and, on other tickets, a
2609/// view/trigger/event) definition — the account the object's `SQL SECURITY DEFINER` context
2610/// resolves to.
2611///
2612/// This is the *measured minimal* account surface the routine family needs: the bare
2613/// `<user>[@<host>]` account name and the `CURRENT_USER [()]` self-reference, the two forms
2614/// the corpus and the oracle exercise. The full MySQL account-name axis — the quoting-nuance
2615/// matrix, role grants, and the `IDENTIFIED BY` authentication tail — is owned by
2616/// `parse-mysql-user-role-ddl`; this node deliberately stops at the account *reference* a
2617/// routine header carries, and that ticket widens it (or lifts it to a shared account node)
2618/// without reshaping the routine statements that hold it.
2619#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2620#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2621#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2622pub enum Definer {
2623    /// `DEFINER = <user>[@<host>]` — a named account. The `user` and optional `host` are
2624    /// each an [`Ident`] so both the bare (`root`) and quoted (`'root'@'localhost'`) source
2625    /// spellings round-trip from their spans.
2626    Account {
2627        /// The account user name.
2628        user: Ident,
2629        /// The optional `@<host>` part; `None` for a bare user with no host.
2630        host: Option<Ident>,
2631        /// Source location and node identity.
2632        meta: Meta,
2633    },
2634    /// `DEFINER = CURRENT_USER [()]` — the current user at definition time.
2635    CurrentUser {
2636        /// Whether the empty `()` call-style parentheses were written.
2637        parens: bool,
2638        /// Source location and node identity.
2639        meta: Meta,
2640    },
2641}
2642
2643/// `CREATE [DEFINER = <user>] PROCEDURE [IF NOT EXISTS] <name> ( [<param> [, …]] )
2644/// [<characteristic> …] <routine_body>` — the MySQL stored-procedure definition (SQL/PSM).
2645///
2646/// A distinct node from [`CreateFunction`]: a procedure has no `RETURNS` type, its body may
2647/// not contain `RETURN` (the routine family enforces `ER_SP_BADRETURN`), and it is invoked by
2648/// `CALL` rather than in an expression. It shares the routine *vocabulary* — the parameter
2649/// list reuses [`FunctionParam`] (with its `IN`/`OUT`/`INOUT` [`FunctionParamMode`]) and the
2650/// characteristic list reuses [`FunctionOption`] (`LANGUAGE`/`COMMENT`/`DETERMINISTIC`/data
2651/// access/`SQL SECURITY`) — but not a statement shape. The [`body`](Self::body) is a single
2652/// MySQL body statement (typically a [`Statement::Compound`]
2653/// block), parsed through the `parse_body_statement` seam and boxed because [`Statement`] is
2654/// large.
2655#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2656#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2657#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2658pub struct CreateProcedure<X: Extension = NoExt> {
2659    /// The optional `DEFINER = <user>` clause; `None` when omitted. Boxed (the rare fat
2660    /// field pays only a null pointer when absent), like [`CreateFunction::definer`].
2661    pub definer: Option<Box<Definer>>,
2662    /// Whether the `IF NOT EXISTS` guard was written (MySQL 8.0.29+).
2663    pub if_not_exists: bool,
2664    /// The procedure name.
2665    pub name: ObjectName,
2666    /// The parameter list, in source order (always parenthesized, possibly empty).
2667    pub params: ThinVec<FunctionParam<X>>,
2668    /// The routine characteristics, in source order (`LANGUAGE`/`COMMENT`/`DETERMINISTIC`/
2669    /// data-access/`SQL SECURITY`), carried on the shared [`FunctionOption`] axis.
2670    pub characteristics: ThinVec<FunctionOption<X>>,
2671    /// The routine body — one MySQL body statement, usually a compound block.
2672    pub body: Box<Statement<X>>,
2673    /// Source location and node identity.
2674    pub meta: Meta,
2675}
2676
2677/// Which routine kind an [`AlterRoutine`] targets — the `PROCEDURE`/`FUNCTION` object keyword.
2678/// A `Copy` surface tag whose span rides the owning [`AlterRoutine`], like
2679/// [`ShowRoutineKind`](crate::ast::ShowRoutineKind).
2680#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2681#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2682#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2683pub enum RoutineKind {
2684    /// `PROCEDURE`.
2685    Procedure,
2686    /// `FUNCTION`.
2687    Function,
2688}
2689
2690/// `ALTER {PROCEDURE | FUNCTION} <name> [<characteristic> …]` — the MySQL routine-characteristics
2691/// alteration (SQL/PSM). Characteristics only: unlike [`CreateProcedure`]/[`CreateFunction`]
2692/// there is no body and no parameter list — `ALTER` re-declares only the mutable characteristic
2693/// subset (`COMMENT` / `LANGUAGE` / data access / `SQL SECURITY`; the server rejects
2694/// `DETERMINISTIC` here, which the parser enforces). One node spans both routine kinds via
2695/// [`kind`](Self::kind); boxed by [`Statement::AlterRoutine`].
2696#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2697#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2698#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2699pub struct AlterRoutine<X: Extension = NoExt> {
2700    /// Whether this alters a `PROCEDURE` or a `FUNCTION`.
2701    pub kind: RoutineKind,
2702    /// The routine name.
2703    pub name: ObjectName,
2704    /// The re-declared characteristics, in source order (the `ALTER`-legal subset).
2705    pub characteristics: ThinVec<FunctionOption<X>>,
2706    /// Source location and node identity.
2707    pub meta: Meta,
2708}
2709
2710/// The `ON SCHEDULE <when>` clause of a MySQL `CREATE`/`ALTER EVENT` — the one-shot
2711/// `AT <timestamp>` form or the recurring `EVERY <interval> [STARTS …] [ENDS …]` form.
2712///
2713/// Both timestamp positions ([`At::at`](Self::At::at), [`Every::starts`](Self::Every::starts),
2714/// [`Every::ends`](Self::Every::ends)) are ordinary [`Expr`]s, so a `NOW() + INTERVAL 1 DAY`
2715/// offset rides the expression grammar with no special schedule handling. The recurring
2716/// interval is `<value> <unit>` where the unit reuses the shared [`IntervalFields`] vocabulary
2717/// (MySQL's `interval` production, rendered in underscore spelling — `DAY_HOUR`, `MINUTE_SECOND`).
2718#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2719#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2720#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2721pub enum EventSchedule<X: Extension = NoExt> {
2722    /// `AT <timestamp>` — a one-shot event fired once at the given time. The [`Expr`] is
2723    /// boxed to keep the schedule (and the enclosing event node) lean.
2724    At {
2725        /// The one-shot execution timestamp (any expression, e.g. `NOW() + INTERVAL 1 DAY`).
2726        at: Box<Expr<X>>,
2727        /// Source location and node identity.
2728        meta: Meta,
2729    },
2730    /// `EVERY <value> <unit> [STARTS <ts>] [ENDS <ts>]` — a recurring event. The `<value>
2731    /// <unit>` interval reuses the shared [`IntervalFields`] vocabulary; `STARTS`/`ENDS` are
2732    /// optional window bounds (each an ordinary expression).
2733    Every {
2734        /// The interval count (an expression, typically an integer literal).
2735        value: Box<Expr<X>>,
2736        /// The interval unit, in the shared [`IntervalFields`] vocabulary (MySQL `interval`).
2737        unit: IntervalFields,
2738        /// The optional `STARTS <ts>` window start.
2739        starts: Option<Box<Expr<X>>>,
2740        /// The optional `ENDS <ts>` window end.
2741        ends: Option<Box<Expr<X>>>,
2742        /// Source location and node identity.
2743        meta: Meta,
2744    },
2745}
2746
2747/// `ON COMPLETION [NOT] PRESERVE` — whether a MySQL event is retained after its last
2748/// execution. A `Copy` surface tag whose span rides the owning event node, like
2749/// [`RoutineKind`].
2750#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2751#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2752#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2753pub enum EventOnCompletion {
2754    /// `ON COMPLETION PRESERVE` — keep the event definition after its final run.
2755    Preserve,
2756    /// `ON COMPLETION NOT PRESERVE` — drop the event after its final run (the default).
2757    NotPreserve,
2758}
2759
2760/// The `ENABLE | DISABLE [ON SLAVE|REPLICA]` activation state of a MySQL event. A `Copy`
2761/// surface tag whose span rides the owning event node.
2762///
2763/// [`DisableOnReplica`](Self::DisableOnReplica) carries a [`ReplicaSpelling`] because MySQL
2764/// 8.4 still admits BOTH the deprecated `DISABLE ON SLAVE` and the current `DISABLE ON
2765/// REPLICA` (server-measured: both accept, `SLAVE` warns) and the source spelling must
2766/// round-trip.
2767#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2768#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2769#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2770pub enum EventStatus {
2771    /// `ENABLE` — the event is active.
2772    Enable,
2773    /// `DISABLE` — the event is inactive on every node.
2774    Disable,
2775    /// `DISABLE ON SLAVE` / `DISABLE ON REPLICA` — the event is active only on the source,
2776    /// disabled on replicas. The [`ReplicaSpelling`] records which keyword was written.
2777    DisableOnReplica(ReplicaSpelling),
2778}
2779
2780/// Which of MySQL's two interchangeable replica keywords a source used — the deprecated
2781/// `SLAVE` or the current `REPLICA`. Both parse on MySQL 8.4 (`SLAVE` with a deprecation
2782/// warning), so the spelling is a round-trip tag, not a semantic difference. A `Copy` leaf.
2783#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2784#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2785#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2786pub enum ReplicaSpelling {
2787    /// The deprecated `SLAVE` keyword (still admitted on MySQL 8.4 with a warning).
2788    Slave,
2789    /// The current `REPLICA` keyword.
2790    Replica,
2791}
2792
2793/// `CREATE [DEFINER = <user>] EVENT [IF NOT EXISTS] <name> ON SCHEDULE <schedule>
2794/// [ON COMPLETION [NOT] PRESERVE] [ENABLE | DISABLE [ON SLAVE|REPLICA]] [COMMENT '…']
2795/// DO <body>` — the MySQL scheduled-event definition (SQL/PSM, gated by
2796/// [`StatementDdlGates::compound_statements`](crate::dialect::StatementDdlGates::compound_statements),
2797/// the stored-program body surface).
2798///
2799/// The clause order after the name is fixed by the grammar (schedule, completion, status,
2800/// comment, `DO`) — server-measured: a comment before the status is a syntax error. The
2801/// [`body`](Self::body) is a single MySQL body statement (usually a [`Statement::Compound`]
2802/// block), parsed through the `parse_body_statement` seam and boxed because [`Statement`] is
2803/// large; an event body carries no return value, so `RETURN` inside it is rejected (as in a
2804/// procedure body — server `ER_SP_BADRETURN`). The [`Definer`] prefix reuses the shared
2805/// routine account reference.
2806#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2807#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2808#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2809pub struct CreateEvent<X: Extension = NoExt> {
2810    /// The optional `DEFINER = <user>` clause; `None` when omitted. Boxed like the routine
2811    /// definer (a rare fat field paying only a null pointer when absent).
2812    pub definer: Option<Box<Definer>>,
2813    /// Whether the `IF NOT EXISTS` guard was written.
2814    pub if_not_exists: bool,
2815    /// The event name (`db.event` or bare `event`).
2816    pub name: ObjectName,
2817    /// The `ON SCHEDULE` clause — the `AT` one-shot or `EVERY` recurring form.
2818    pub schedule: EventSchedule<X>,
2819    /// The optional `ON COMPLETION [NOT] PRESERVE` clause.
2820    pub on_completion: Option<EventOnCompletion>,
2821    /// The optional `ENABLE | DISABLE [ON SLAVE|REPLICA]` activation state.
2822    pub status: Option<EventStatus>,
2823    /// The optional `COMMENT '…'` description.
2824    pub comment: Option<Literal>,
2825    /// The event body — one MySQL body statement (the `DO <stmt>` clause).
2826    pub body: Box<Statement<X>>,
2827    /// Source location and node identity.
2828    pub meta: Meta,
2829}
2830
2831/// `ALTER [DEFINER = <user>] EVENT <name> [ON SCHEDULE <schedule>] [ON COMPLETION [NOT]
2832/// PRESERVE] [RENAME TO <name>] [ENABLE | DISABLE [ON SLAVE|REPLICA]] [COMMENT '…']
2833/// [DO <body>]` — the MySQL event alteration.
2834///
2835/// Every clause is optional, but the grammar requires **at least one** (server-measured: a
2836/// bare `ALTER EVENT e` is a syntax error), which the parser enforces. The clause order is
2837/// the same fixed order as [`CreateEvent`], with `RENAME TO` between the schedule/completion
2838/// and the status. Unlike [`CreateEvent`] the schedule and body are optional, and
2839/// [`on_completion`](Self::on_completion) may appear with or without a schedule.
2840#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2841#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2842#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2843pub struct AlterEvent<X: Extension = NoExt> {
2844    /// The optional `DEFINER = <user>` clause; `None` when omitted.
2845    pub definer: Option<Box<Definer>>,
2846    /// The event name.
2847    pub name: ObjectName,
2848    /// The optional `ON SCHEDULE` clause.
2849    pub schedule: Option<EventSchedule<X>>,
2850    /// The optional `ON COMPLETION [NOT] PRESERVE` clause.
2851    pub on_completion: Option<EventOnCompletion>,
2852    /// The optional `RENAME TO <name>` clause.
2853    pub rename_to: Option<ObjectName>,
2854    /// The optional `ENABLE | DISABLE [ON SLAVE|REPLICA]` activation state.
2855    pub status: Option<EventStatus>,
2856    /// The optional `COMMENT '…'` description.
2857    pub comment: Option<Literal>,
2858    /// The optional `DO <body>` re-definition (one MySQL body statement).
2859    pub body: Option<Box<Statement<X>>>,
2860    /// Source location and node identity.
2861    pub meta: Meta,
2862}
2863
2864/// `DROP EVENT [IF EXISTS] <name>` — the MySQL scheduled-event drop. Kept apart from the
2865/// generic [`Statement::Drop`] because MySQL's event drop names exactly ONE event
2866/// (server-measured: `DROP EVENT a, b` is a syntax error) and takes no `CASCADE`/`RESTRICT`
2867/// behaviour, unlike the shared name-list [`DropStatement`] grammar.
2868#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2869#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2870#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2871pub struct DropEvent {
2872    /// Whether the `IF EXISTS` guard was written.
2873    pub if_exists: bool,
2874    /// The single event name (`db.event` or bare `event`).
2875    pub name: ObjectName,
2876    /// Source location and node identity.
2877    pub meta: Meta,
2878}
2879
2880/// `DROP {DATABASE | SCHEMA} [IF EXISTS] <name>` — MySQL's single-database drop, where
2881/// `DATABASE` and `SCHEMA` are exact synonyms (the lexer folds `SCHEMA` onto `DATABASE`).
2882/// Kept apart from the generic name-list [`Statement::Drop`] because the form names exactly
2883/// ONE unqualified database — server-measured on mysql:8, `DROP DATABASE a, b` is
2884/// `ER_PARSE_ERROR`, `DROP DATABASE db.x` is `ER_PARSE_ERROR`, and it takes no
2885/// `CASCADE`/`RESTRICT` (both syntax errors) — unlike PostgreSQL/DuckDB's `DROP SCHEMA
2886/// <name> [, …] [CASCADE | RESTRICT]`, which keeps riding the shared [`DropStatement`]
2887/// grammar there. Gated by
2888/// [`StatementDdlGates::drop_database`](crate::dialect::StatementDdlGates::drop_database).
2889#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2890#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2891#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2892pub struct DropDatabase {
2893    /// Which of the two synonymous keywords was written; recorded so the source spelling
2894    /// round-trips verbatim (the tag pattern of [`TemporaryTableKind`], not a semantic
2895    /// difference — MySQL treats the two identically).
2896    pub spelling: DatabaseKeyword,
2897    /// Whether the `IF EXISTS` guard was written.
2898    pub if_exists: bool,
2899    /// The single database name — a bare unqualified identifier (`ident`; a dotted
2900    /// `db.x` is a server syntax error).
2901    pub name: Ident,
2902    /// Source location and node identity.
2903    pub meta: Meta,
2904}
2905
2906/// The keyword spelling of a database object — `DATABASE` or its exact synonym `SCHEMA`
2907/// (MySQL folds the two onto one grammar). Recorded on [`DropDatabase`] so the written
2908/// keyword round-trips.
2909#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2910#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2911#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2912pub enum DatabaseKeyword {
2913    /// The `DATABASE` spelling.
2914    Database,
2915    /// The `SCHEMA` spelling (a MySQL synonym for `DATABASE`).
2916    Schema,
2917}
2918
2919/// `DROP INDEX <name> ON <table> [ALGORITHM [=] {DEFAULT | INPLACE | INSTANT | COPY}]
2920/// [LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}]` — MySQL's index drop (`drop_index_stmt`,
2921/// `sql_yacc.yy`). Kept apart from the generic name-list [`Statement::Drop`] because it names
2922/// the owning table with a mandatory `ON <table>` (server-measured: `DROP INDEX i` with no
2923/// `ON` is `ER_PARSE_ERROR` on mysql:8) and carries the online-DDL `ALGORITHM`/`LOCK`
2924/// execution hints, neither of which the shared grammar has. The index name is a bare
2925/// identifier (`ident`; a dotted `i.j` is a syntax error) while the table is a possibly-dotted
2926/// [`ObjectName`] (`db.t` binds). Gated by
2927/// [`IndexAlterSyntax::index_drop_on_table`](crate::dialect::IndexAlterSyntax::index_drop_on_table).
2928#[derive(Clone, Debug, PartialEq, Eq, Hash)]
2929#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2930#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2931pub struct DropIndexOnTable {
2932    /// The dropped index — a bare unqualified identifier.
2933    pub name: Ident,
2934    /// The table the index belongs to (`ON <table>`); may be schema-qualified (`db.t`).
2935    pub table: ObjectName,
2936    /// The trailing `ALGORITHM`/`LOCK` execution hints in source order (`opt_index_lock_and_algorithm`).
2937    /// The grammar admits at most one of each and both orderings (`ALGORITHM … LOCK …` or
2938    /// `LOCK … ALGORITHM …`, server-measured: a repeated `ALGORITHM`/`LOCK` is `ER_PARSE_ERROR`),
2939    /// so this holds zero, one, or two entries and preserves the written order for round-trip.
2940    pub options: ThinVec<IndexLockAlgorithmOption>,
2941    /// Source location and node identity.
2942    pub meta: Meta,
2943}
2944
2945/// One entry of a `DROP INDEX … ON …` (or online `ALTER TABLE`) `opt_index_lock_and_algorithm`
2946/// tail — an `ALGORITHM` or a `LOCK` execution hint. Each records whether the optional `=`
2947/// (`opt_equal`) was written so the surface form round-trips verbatim.
2948#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2949#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2950#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2951pub enum IndexLockAlgorithmOption {
2952    /// `ALGORITHM [=] {DEFAULT | INPLACE | INSTANT | COPY}`.
2953    Algorithm {
2954        /// Whether the optional `=` was written between the keyword and the value.
2955        equals: bool,
2956        /// The chosen algorithm.
2957        value: IndexAlgorithm,
2958    },
2959    /// `LOCK [=] {DEFAULT | NONE | SHARED | EXCLUSIVE}`.
2960    Lock {
2961        /// Whether the optional `=` was written between the keyword and the value.
2962        equals: bool,
2963        /// The chosen lock level.
2964        value: IndexLock,
2965    },
2966}
2967
2968/// The online-DDL `ALGORITHM` value (`alter_algorithm_option_value`). `DEFAULT` is a keyword;
2969/// the rest are matched case-insensitively as identifiers, and an unknown value is a *binding*
2970/// reject (`ER_UNKNOWN_ALTER_ALGORITHM` 1800), not a syntax error — so only these four bind.
2971#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2972#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2973#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2974pub enum IndexAlgorithm {
2975    /// `DEFAULT` — let the server choose.
2976    Default,
2977    /// `INPLACE` — rebuild in place where supported.
2978    Inplace,
2979    /// `INSTANT` — metadata-only change where supported.
2980    Instant,
2981    /// `COPY` — rebuild via a table copy.
2982    Copy,
2983}
2984
2985/// The online-DDL `LOCK` value (`alter_lock_option_value`). `DEFAULT` is a keyword; the rest
2986/// are matched case-insensitively as identifiers, and an unknown value is a *binding* reject
2987/// (`ER_UNKNOWN_ALTER_LOCK` 1801), not a syntax error — so only these four bind.
2988#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2989#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
2990#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
2991pub enum IndexLock {
2992    /// `DEFAULT` — the server's default concurrency.
2993    Default,
2994    /// `NONE` — permit concurrent reads and writes.
2995    None,
2996    /// `SHARED` — permit concurrent reads only.
2997    Shared,
2998    /// `EXCLUSIVE` — permit no concurrent access.
2999    Exclusive,
3000}
3001
3002/// `CREATE [OR REPLACE] [TEMP|TEMPORARY] {MACRO | FUNCTION} [IF NOT EXISTS] <name>
3003/// (<param> [, ...]) AS <body>` — DuckDB's macro DDL (gated by
3004/// [`StatementDdlGates::create_macro`](crate::dialect::StatementDdlGates::create_macro)).
3005///
3006/// This is a distinct node from [`CreateFunction`], not a variant of it: a DuckDB
3007/// macro body is a *live* SQL expression or query (`AS x + 1` / `AS TABLE SELECT …`),
3008/// whereas a PostgreSQL/MySQL `CREATE FUNCTION` body is an opaque source *string* in a
3009/// target language (`AS 'RETURN 1'`). The two share only the `CREATE … (params) AS`
3010/// prefix; their bodies, parameter grammars (a macro parameter is a bare untyped name,
3011/// a routine parameter is `[name] <type>`), and option tails are disjoint.
3012///
3013/// DuckDB spells the same feature with either the `MACRO` keyword or `FUNCTION` as an
3014/// exact synonym; [`spelling`](Self::spelling) records which so the source keyword
3015/// round-trips (the tag pattern of [`TemporaryTableKind`], not a semantic difference).
3016/// Whole-statement gated to DuckDB (and Lenient): every other dialect either has no
3017/// macro grammar (`MACRO` is left unconsumed and surfaces as an unknown statement) or
3018/// spells `CREATE FUNCTION` as the incompatible string-body routine, so the gate is
3019/// behaviour-accurate, not merely a modelling limitation. Boxed by the enclosing
3020/// [`Statement::CreateMacro`] to keep the enum within its size budget.
3021///
3022/// The single-body surface — one `(params) AS body` per statement — is modelled; DuckDB
3023/// additionally accepts a comma-separated *overload* list (`… AS a, (x, y) AS b`), a
3024/// deliberate follow-up (no vendored corpus statement spells it) the shape would extend
3025/// by lifting `params`/`body` into an overload sequence.
3026#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3027#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3028#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3029pub struct CreateMacro<X: Extension = NoExt> {
3030    /// Whether the or replace form was present in the source.
3031    pub or_replace: bool,
3032    /// Optional temporary for this syntax.
3033    pub temporary: Option<TemporaryTableKind>,
3034    /// Exact source spelling retained for faithful rendering.
3035    pub spelling: MacroSpelling,
3036    /// Whether the if not exists form was present in the source.
3037    pub if_not_exists: bool,
3038    /// Name referenced by this syntax.
3039    pub name: ObjectName,
3040    /// params in source order.
3041    pub params: ThinVec<MacroParam<X>>,
3042    /// Statement or query body governed by this node.
3043    pub body: MacroBody<X>,
3044    /// Source location and node identity.
3045    pub meta: Meta,
3046}
3047
3048/// Which keyword introduced a [`CreateMacro`] — DuckDB accepts `MACRO` and `FUNCTION`
3049/// as exact synonyms. A `Copy` tag whose span rides the owning [`CreateMacro`], like
3050/// [`TemporaryTableKind`].
3051#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3052#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3053#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3054pub enum MacroSpelling {
3055    /// Source used the `MACRO` spelling.
3056    Macro,
3057    /// Source used the `FUNCTION` spelling.
3058    Function,
3059}
3060
3061/// One [`CreateMacro`] parameter: a bare untyped name with an optional `:= <expr>`
3062/// default (`CREATE MACRO m(a, b := 10) AS …`). Unlike a [`FunctionParam`], a macro
3063/// parameter carries no type — DuckDB macros are template-substituted, not typed — so
3064/// the name is mandatory and no `DataType` rides the node.
3065#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3066#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3067#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3068pub struct MacroParam<X: Extension = NoExt> {
3069    /// Name referenced by this syntax.
3070    pub name: Ident,
3071    /// Optional default for this syntax.
3072    pub default: Option<Expr<X>>,
3073    /// Source location and node identity.
3074    pub meta: Meta,
3075}
3076
3077/// A [`CreateMacro`] body: a scalar expression (`AS <expr>`) or a table-producing
3078/// query (`AS TABLE <query>`). The `TABLE` keyword is the DuckDB discriminant between a
3079/// scalar macro (returns one value) and a table macro (returns a relation); the body
3080/// grammar reuses [`Expr`] / [`Query`] rather than re-deriving either.
3081#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3082#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3083#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3084pub enum MacroBody<X: Extension = NoExt> {
3085    /// A scalar macro body — an expression returning a single value.
3086    Scalar {
3087        /// Expression evaluated by this syntax.
3088        expr: Box<Expr<X>>,
3089        /// Source location and node identity.
3090        meta: Meta,
3091    },
3092    /// A table macro body — a query returning a relation.
3093    Table {
3094        /// Query governed by this node.
3095        query: Box<Query<X>>,
3096        /// Source location and node identity.
3097        meta: Meta,
3098    },
3099}
3100
3101/// `CREATE [OR REPLACE] [TEMP|TEMPORARY] TYPE [IF NOT EXISTS] <name> AS <definition>` —
3102/// DuckDB's user-defined-type DDL (gated by
3103/// [`StatementDdlGates::create_type`](crate::dialect::StatementDdlGates::create_type)).
3104///
3105/// The name is optionally schema-qualified (`CREATE TYPE s1.mood AS …`). Its
3106/// [`definition`](Self::definition) is either the dedicated `ENUM` production (a label
3107/// list or a label-supplying query) or an alias to any other (possibly composite/nested)
3108/// data type — see [`CreateTypeDefinition`].
3109///
3110/// `OR REPLACE` and `IF NOT EXISTS` are mutually exclusive in DuckDB's grammar (under `OR
3111/// REPLACE` the parser reads `IF` as the type name), so the parser fills `if_not_exists`
3112/// only on the plain form; the two flags are never both set. Whole-statement gated to
3113/// DuckDB (and Lenient): every other dialect leaves `TYPE` unconsumed after `CREATE`,
3114/// where it surfaces as the `TABLE` expectation (an unknown statement). Boxed by the
3115/// enclosing [`Statement::CreateType`] to keep the enum
3116/// within its size budget.
3117#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3118#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3119#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3120pub struct CreateType<X: Extension = NoExt> {
3121    /// Whether the or replace form was present in the source.
3122    pub or_replace: bool,
3123    /// Optional temporary for this syntax.
3124    pub temporary: Option<TemporaryTableKind>,
3125    /// Whether the if not exists form was present in the source.
3126    pub if_not_exists: bool,
3127    /// Name referenced by this syntax.
3128    pub name: ObjectName,
3129    /// The type definition — an `ENUM` label list or a type alias; see [`CreateTypeDefinition`].
3130    pub definition: CreateTypeDefinition<X>,
3131    /// Source location and node identity.
3132    pub meta: Meta,
3133}
3134
3135/// The `AS <definition>` body of a [`CreateType`]. DuckDB's `CREATE TYPE` grammar has a
3136/// dedicated `ENUM` rule distinct from the general data-type grammar — its labels are
3137/// parser-restricted to string constants (`ENUM(1, 2)` is a *parse* error, unlike the
3138/// data-type-position `x::ENUM(...)` cast, which parses any modifier and only bind-rejects
3139/// a non-string) — so an enum definition is its own variant rather than a
3140/// [`DataType::Enum`](crate::ast::DataType). Every non-`ENUM` spelling (`STRUCT`/`MAP`/
3141/// `UNION`, an alias to a named type, arrays, `DECIMAL(p, s)`, …) reuses the shared data
3142/// type via [`Alias`](Self::Alias).
3143#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3144#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3145#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3146pub enum CreateTypeDefinition<X: Extension = NoExt> {
3147    /// `AS ENUM ('a', 'b', …)` — a labelled enumeration. The label list may be empty
3148    /// (`ENUM ()`, which DuckDB accepts) and each label is a string literal.
3149    Enum {
3150        /// labels in source order.
3151        labels: ThinVec<Literal>,
3152        /// Source location and node identity.
3153        meta: Meta,
3154    },
3155    /// `AS ENUM (<query>)` — the enum labels are drawn from a query's single column
3156    /// (`ENUM (SELECT DISTINCT month FROM …)`); the parenthesized query reuses [`Query`].
3157    EnumFromQuery {
3158        /// Query governed by this node.
3159        query: Box<Query<X>>,
3160        /// Source location and node identity.
3161        meta: Meta,
3162    },
3163    /// `AS <type>` — an alias to another type: a scalar (`AS VARCHAR`), a composite/nested
3164    /// constructor (`AS STRUCT(…)` / `AS MAP(…)` / `AS UNION(…)`), an array (`AS my_int[]`),
3165    /// or a named user type. Reuses the shared [`DataType`] grammar.
3166    Alias {
3167        /// Data type named by this syntax.
3168        data_type: DataType<X>,
3169        /// Source location and node identity.
3170        meta: Meta,
3171    },
3172}
3173
3174/// A `CREATE [TEMPORARY] SEQUENCE [IF NOT EXISTS] <name> [<option> ...]` sequence-generator
3175/// statement (SQL:2003 T176; PostgreSQL and DuckDB), gated by
3176/// [`StatementDdlGates::create_sequence`](crate::dialect::StatementDdlGates::create_sequence).
3177///
3178/// The trailing options are the SQL-standard sequence-generator core — `START [WITH]`,
3179/// `INCREMENT [BY]`, `MINVALUE`/`NO MINVALUE`, `MAXVALUE`/`NO MAXVALUE`, `CYCLE`/`NO CYCLE` —
3180/// which both engines' parsers accept in any order, so this single node is gated
3181/// per-dialect by the one flag rather than split into parallel PostgreSQL/DuckDB nodes
3182/// (ADR-0011). They reuse [`IdentityOption`], the identical option vocabulary the
3183/// `GENERATED … AS IDENTITY` column form already carries; the [`Cache`](IdentityOption::Cache)
3184/// variant is never produced here — DuckDB's `CREATE SEQUENCE` grammar rejects `CACHE`
3185/// (engine-measured), so admitting it would over-accept, and it is a PostgreSQL-only
3186/// extension left to a follow-up.
3187///
3188/// PostgreSQL's extended tails (`AS <type>`, `CACHE`, `OWNED BY`, `RESTART`) and DuckDB's
3189/// `OR REPLACE SEQUENCE` are deliberately unmodelled here: each is one engine's extension,
3190/// not the shared standard core, and none is exercised by the DuckDB corpus.
3191#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3192#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3193#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3194pub struct CreateSequence<X: Extension = NoExt> {
3195    /// Optional temporary for this syntax.
3196    pub temporary: Option<TemporaryTableKind>,
3197    /// Whether the if not exists form was present in the source.
3198    pub if_not_exists: bool,
3199    /// Name referenced by this syntax.
3200    pub name: ObjectName,
3201    /// Options supplied in source order.
3202    pub options: ThinVec<IdentityOption<X>>,
3203    /// Source location and node identity.
3204    pub meta: Meta,
3205}
3206
3207/// A `CREATE EXTENSION [IF NOT EXISTS] <name> [WITH] [SCHEMA s] [VERSION v] [CASCADE]`
3208/// statement (PostgreSQL; gated by
3209/// [`StatementDdlGates::extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl)).
3210///
3211/// Non-generic: an extension is named by a bare identifier and its options carry only
3212/// names and version strings — no expressions or extension nodes. The `WITH` keyword is
3213/// optional sugar (PostgreSQL's `opt_with`); [`with`](Self::with) records whether it was
3214/// written so it round-trips. The options are order-independent and repeatable in the
3215/// grammar (`create_extension_opt_list`), kept here in source order.
3216#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3217#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3218#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3219pub struct CreateExtension {
3220    /// Whether the if not exists form was present in the source.
3221    pub if_not_exists: bool,
3222    /// The extension name.
3223    pub name: Ident,
3224    /// Whether the optional `WITH` keyword preceded the option list.
3225    pub with: bool,
3226    /// Options supplied in source order.
3227    pub options: ThinVec<CreateExtensionOption>,
3228    /// Source location and node identity.
3229    pub meta: Meta,
3230}
3231
3232/// One `CREATE EXTENSION` option (`create_extension_opt_item`). PostgreSQL's grammar
3233/// carries a `FROM <old_version>` item whose action is a parse-time
3234/// `FEATURE_NOT_SUPPORTED` reject, so that form is not modelled here.
3235#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3236#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3237#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3238pub enum CreateExtensionOption {
3239    /// `SCHEMA <name>` — the schema the extension's objects are installed into.
3240    Schema {
3241        /// The target schema name.
3242        name: Ident,
3243        /// Source location and node identity.
3244        meta: Meta,
3245    },
3246    /// `VERSION <version>` — the version to install.
3247    Version {
3248        /// Version value supplied by this syntax.
3249        version: ExtensionVersion,
3250        /// Source location and node identity.
3251        meta: Meta,
3252    },
3253    /// `CASCADE` — install missing required extensions too.
3254    Cascade {
3255        /// Source location and node identity.
3256        meta: Meta,
3257    },
3258}
3259
3260/// An extension version value (PostgreSQL's `NonReservedWord_or_Sconst`): a bare word or
3261/// a string constant. PostgreSQL folds both to the same string internally, but the
3262/// surface spelling is kept distinct so it round-trips.
3263#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3264#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3265#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3266pub enum ExtensionVersion {
3267    /// A bare non-reserved word (`VERSION v1`).
3268    Word {
3269        /// Identifier-form version value.
3270        word: Ident,
3271        /// Source location and node identity.
3272        meta: Meta,
3273    },
3274    /// A string constant (`VERSION '1.0'`).
3275    String {
3276        /// Value supplied by this syntax.
3277        value: Literal,
3278        /// Source location and node identity.
3279        meta: Meta,
3280    },
3281}
3282
3283/// An `ALTER EXTENSION <name> ...` statement, gated by
3284/// [`StatementDdlGates::extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl).
3285///
3286/// Unifies PostgreSQL's two `ALTER EXTENSION` productions under one node, since both
3287/// share the `ALTER EXTENSION <name>` head: the `UPDATE [TO <version>]` version bump
3288/// (`AlterExtensionStmt`) and the `ADD`/`DROP <member>` membership change
3289/// (`AlterExtensionContentsStmt`). PostgreSQL's `ALTER EXTENSION <name> SET SCHEMA
3290/// <schema>` is a separate `AlterObjectSchemaStmt` production (shared with every other
3291/// relocatable object), not an `AlterExtensionStmt`, so it is out of this node's scope.
3292#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3293#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3294#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3295pub struct AlterExtension<X: Extension = NoExt> {
3296    /// The extension name.
3297    pub name: Ident,
3298    /// The change applied to the extension.
3299    pub action: AlterExtensionAction<X>,
3300    /// Source location and node identity.
3301    pub meta: Meta,
3302}
3303
3304/// The change an [`AlterExtension`] applies.
3305#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3306#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3307#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3308pub enum AlterExtensionAction<X: Extension = NoExt> {
3309    /// `UPDATE [TO <version>]` — bump the installed version (`AlterExtensionStmt`).
3310    Update {
3311        /// The target version, when a `TO` clause was written.
3312        version: Option<ExtensionVersion>,
3313        /// Source location and node identity.
3314        meta: Meta,
3315    },
3316    /// `ADD <member>` (`add` is `true`) or `DROP <member>` — change extension membership
3317    /// (`AlterExtensionContentsStmt`).
3318    Change {
3319        /// `true` for `ADD`, `false` for `DROP`.
3320        add: bool,
3321        /// The member object added to or dropped from the extension.
3322        member: ObjectReference<X>,
3323        /// Source location and node identity.
3324        meta: Meta,
3325    },
3326}
3327
3328/// An `ALTER DATABASE [IF EXISTS] <name> SET ALIAS TO <alias>` statement (DuckDB), gated by
3329/// [`StatementDdlGates::alter_database`](crate::dialect::StatementDdlGates::alter_database).
3330///
3331/// DuckDB's sole `AlterDatabaseStmt` production re-aliases an attached database
3332/// (`third_party/libpg_query/grammar/statements/alter_database.y` at the pinned v1.5.4
3333/// commit `08e34c447b`). The grammar reduces `SET <ident> TO <name>`, but its action rejects
3334/// any keyword but `ALIAS`, so only `SET ALIAS TO` is a parse-accept (engine-measured: `SET
3335/// FOO TO` is a syntax error, and `ALTER DATABASE d RENAME TO e` is likewise a syntax error —
3336/// DuckDB has no `RENAME` form for a database). The change is held in an
3337/// [`AlterDatabaseAction`] enum so a sibling dialect (MySQL's charset/collation `ALTER
3338/// DATABASE`) can add its own action variants without reshaping this node.
3339#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3340#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3341#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3342pub struct AlterDatabase {
3343    /// Whether the `IF EXISTS` existence guard was present in the source.
3344    pub if_exists: bool,
3345    /// The database name.
3346    pub name: Ident,
3347    /// The change applied to the database.
3348    pub action: AlterDatabaseAction,
3349    /// Source location and node identity.
3350    pub meta: Meta,
3351}
3352
3353/// The change an [`AlterDatabase`] applies.
3354#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3355#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3356#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3357pub enum AlterDatabaseAction {
3358    /// `SET ALIAS TO <alias>` — re-alias the attached database.
3359    SetAlias {
3360        /// The new alias.
3361        new_name: Ident,
3362        /// Source location and node identity.
3363        meta: Meta,
3364    },
3365}
3366
3367/// An `ALTER SEQUENCE [IF EXISTS] <name> <option>...` statement (DuckDB), gated by
3368/// [`StatementDdlGates::alter_sequence`](crate::dialect::StatementDdlGates::alter_sequence).
3369///
3370/// DuckDB's `AlterSeqStmt` production changes a sequence generator's options
3371/// (`third_party/libpg_query/grammar/statements/alter_sequence.y` at the pinned v1.5.4
3372/// commit). Its `SeqOptList` is the same option grammar `CREATE SEQUENCE` reduces, so the
3373/// shared core is carried through the reused [`IdentityOption`] axis
3374/// ([`AlterSequenceOption::Common`]); the ALTER-only leads DuckDB additionally parses
3375/// (`RESTART`, `AS <type>`, `OWNED BY`, `SEQUENCE NAME`) are their own variants. Options
3376/// are kept in source order. `SET SCHEMA` is *not* an option here — it is DuckDB's separate
3377/// [`AlterObjectSchema`] production, dispatched before this one.
3378#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3379#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3380#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3381pub struct AlterSequence<X: Extension = NoExt> {
3382    /// Whether the `IF EXISTS` existence guard was present in the source.
3383    pub if_exists: bool,
3384    /// The sequence name.
3385    pub name: ObjectName,
3386    /// Options supplied in source order.
3387    pub options: ThinVec<AlterSequenceOption<X>>,
3388    /// Source location and node identity.
3389    pub meta: Meta,
3390}
3391
3392/// One `ALTER SEQUENCE` option (DuckDB's `SeqOptElem`).
3393#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3394#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3395#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3396pub enum AlterSequenceOption<X: Extension = NoExt> {
3397    /// A shared sequence-generator option `CREATE SEQUENCE` also accepts (`START [WITH]`,
3398    /// `INCREMENT [BY]`, `MIN`/`MAXVALUE`, `NO MIN`/`MAXVALUE`, `CYCLE`/`NO CYCLE`, `CACHE`),
3399    /// reusing the [`IdentityOption`] axis.
3400    Common {
3401        /// The reused shared option.
3402        option: IdentityOption<X>,
3403        /// Source location and node identity.
3404        meta: Meta,
3405    },
3406    /// `RESTART [[WITH] <n>]` — reset the sequence's current value (bare = restart to its
3407    /// start value).
3408    Restart {
3409        /// The restart value, when a `[WITH] <n>` tail was written.
3410        value: Option<Expr<X>>,
3411        /// Source location and node identity.
3412        meta: Meta,
3413    },
3414    /// `AS <type>` — change the sequence's integer type.
3415    As {
3416        /// The sequence's new integer type.
3417        data_type: DataType<X>,
3418        /// Source location and node identity.
3419        meta: Meta,
3420    },
3421    /// `OWNED BY {<column> | NONE}` — tie the sequence's lifetime to a column, or detach it
3422    /// (`None` renders `NONE`).
3423    OwnedBy {
3424        /// The owning column, or `None` for `OWNED BY NONE`.
3425        owner: Option<ObjectName>,
3426        /// Source location and node identity.
3427        meta: Meta,
3428    },
3429    /// `SEQUENCE NAME <name>` — the internal rename form libpg_query documents as pg_dump-only.
3430    SequenceName {
3431        /// The new internal name.
3432        name: ObjectName,
3433        /// Source location and node identity.
3434        meta: Meta,
3435    },
3436}
3437
3438/// An `ALTER {TABLE | VIEW | SEQUENCE} [IF EXISTS] <name> SET SCHEMA <schema>` statement
3439/// (DuckDB's `AlterObjectSchemaStmt`), gated by
3440/// [`StatementDdlGates::alter_object_set_schema`](crate::dialect::StatementDdlGates::alter_object_set_schema).
3441///
3442/// Relocates a relocatable object to another schema
3443/// (`third_party/libpg_query/grammar/statements/alter_schema.y` at the pinned v1.5.4 commit).
3444/// DuckDB 1.5.4's binder rejects this with `Not implemented: T_AlterObjectSchemaStmt`, but the
3445/// production is parse-reachable (engine-measured via `json_serialize_sql`), and PARSE-level
3446/// parity is the modelled bar — analogous to PostgreSQL's grammar-present, engine-unimplemented
3447/// `CreateAssertionStmt`. The object kind is one of [`SchemaRelocationObject`]'s three.
3448#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3449#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3450#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3451pub struct AlterObjectSchema {
3452    /// Which relocatable object kind was named.
3453    pub object_type: SchemaRelocationObject,
3454    /// Whether the `IF EXISTS` existence guard was present in the source.
3455    pub if_exists: bool,
3456    /// The object's current (schema-qualified) name.
3457    pub name: ObjectName,
3458    /// The destination schema.
3459    pub new_schema: Ident,
3460    /// Source location and node identity.
3461    pub meta: Meta,
3462}
3463
3464/// The relocatable object kind an [`AlterObjectSchema`] moves — DuckDB's `AlterObjectSchemaStmt`
3465/// admits exactly these three object heads.
3466#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3467#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3468#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3469pub enum SchemaRelocationObject {
3470    /// `ALTER TABLE … SET SCHEMA`.
3471    Table,
3472    /// `ALTER VIEW … SET SCHEMA`.
3473    View,
3474    /// `ALTER SEQUENCE … SET SCHEMA`.
3475    Sequence,
3476}
3477
3478/// A reference to a database object by kind and kind-appropriate signature — the shared
3479/// object-reference axis for PostgreSQL object-membership DDL.
3480///
3481/// PostgreSQL's `ALTER EXTENSION … ADD|DROP <member>` grammar
3482/// (`AlterExtensionContentsStmt`) names a member object by a kind keyword plus a
3483/// signature whose shape depends on the kind: a bare or qualified name, a routine
3484/// argument-type signature, an aggregate/operator signature, a cast type pair, a type
3485/// name, or a transform. The sibling object-DDL heads name objects the same way — the
3486/// `parse-pg-alter-object-depends` head (`ALTER … DEPENDS ON EXTENSION`) reuses
3487/// [`Routine`](Self::Routine) and [`Named`](Self::Named) for its `FUNCTION`/`PROCEDURE`/
3488/// `ROUTINE`, `MATERIALIZED VIEW`, and `INDEX` objects, and `parse-pg-drop-transform`
3489/// reuses [`Transform`](Self::Transform) — so the axis lives here once rather than being
3490/// re-derived per head.
3491///
3492/// [`Trigger`](Self::Trigger) is the one shape no `ALTER EXTENSION` member takes: its
3493/// `TRIGGER <name> ON <table>` form is exclusive to the `DEPENDS ON EXTENSION` head
3494/// (`ALTER EXTENSION … ADD TRIGGER` is a reject), so it was added to the axis additively
3495/// when that head's measured grammar demanded it, keeping every object-DDL head on one
3496/// shared reference type.
3497#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3498#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3499#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3500pub enum ObjectReference<X: Extension = NoExt> {
3501    /// A kind keyword and a name. [`kind`](ObjectRefKind) records whether the name may be
3502    /// schema-qualified: PostgreSQL's `object_type_any_name` kinds (`TABLE`, `VIEW`, …)
3503    /// accept a dotted `any_name`, its `object_type_name` kinds (`SCHEMA`, `ROLE`, …)
3504    /// only a single `name`.
3505    Named {
3506        /// The object kind; see [`ObjectRefKind`].
3507        kind: ObjectRefKind,
3508        /// The referenced name.
3509        name: ObjectName,
3510        /// Source location and node identity.
3511        meta: Meta,
3512    },
3513    /// `FUNCTION`/`PROCEDURE`/`ROUTINE <name>[(<argtypes>)]`
3514    /// (PostgreSQL's `function_with_argtypes`; the argument list is optional).
3515    Routine {
3516        /// Which routine kind; see [`RoutineObjectKind`].
3517        kind: RoutineObjectKind,
3518        /// The routine name and optional argument-type signature.
3519        signature: RoutineSignature<X>,
3520        /// Source location and node identity.
3521        meta: Meta,
3522    },
3523    /// `AGGREGATE <name>(<aggr_args>)`.
3524    Aggregate {
3525        /// The aggregate name.
3526        name: ObjectName,
3527        /// The aggregate argument signature.
3528        args: AggregateArgs<X>,
3529        /// Source location and node identity.
3530        meta: Meta,
3531    },
3532    /// `OPERATOR [<schema>.]<sym>(<left>, <right>)` (PostgreSQL's `operator_with_argtypes`).
3533    Operator {
3534        /// The optional schema qualification (empty for an unqualified operator).
3535        schema: ObjectName,
3536        /// The symbolic operator spelling, interned exact-case.
3537        op: Symbol,
3538        /// The operand types.
3539        args: OperatorArgs<X>,
3540        /// Source location and node identity.
3541        meta: Meta,
3542    },
3543    /// `OPERATOR CLASS`/`OPERATOR FAMILY <name> USING <access_method>`.
3544    OperatorClass {
3545        /// `true` for `OPERATOR FAMILY`, `false` for `OPERATOR CLASS`.
3546        family: bool,
3547        /// The operator class/family name.
3548        name: ObjectName,
3549        /// The `USING <access_method>` index access method.
3550        access_method: Ident,
3551        /// Source location and node identity.
3552        meta: Meta,
3553    },
3554    /// `CAST (<from> AS <to>)`.
3555    Cast {
3556        /// The cast's source type.
3557        from: DataType<X>,
3558        /// The cast's target type.
3559        to: DataType<X>,
3560        /// Source location and node identity.
3561        meta: Meta,
3562    },
3563    /// `DOMAIN`/`TYPE <typename>` — a type-named object.
3564    Type {
3565        /// `true` for the `DOMAIN` keyword, `false` for `TYPE` (both name a type via a
3566        /// `Typename`).
3567        domain: bool,
3568        /// The type name (a `Typename`, possibly schema-qualified).
3569        name: DataType<X>,
3570        /// Source location and node identity.
3571        meta: Meta,
3572    },
3573    /// `TRANSFORM FOR <typename> LANGUAGE <language>`.
3574    Transform {
3575        /// The type the transform is `FOR`.
3576        type_name: DataType<X>,
3577        /// The transform's procedural language name.
3578        language: Ident,
3579        /// Source location and node identity.
3580        meta: Meta,
3581    },
3582    /// `TRIGGER <name> ON <table>` — a trigger named by its bare name plus the table it
3583    /// is defined on (PostgreSQL's `ALTER TRIGGER name ON qualified_name`). Unlike the
3584    /// other object kinds, a trigger is not schema-qualifiable on its own name (it is a
3585    /// `name`, a single `ColId`); the `table` carries the qualification.
3586    ///
3587    /// This variant is reached only by the `DEPENDS ON EXTENSION` head — no `ALTER
3588    /// EXTENSION` member is a trigger — so `parse_object_reference` never constructs it.
3589    Trigger {
3590        /// The trigger's bare name.
3591        name: Ident,
3592        /// The table the trigger is defined on (a `qualified_name`).
3593        table: ObjectName,
3594        /// Source location and node identity.
3595        meta: Meta,
3596    },
3597}
3598
3599/// A member-object kind naming an object by a bare or qualified name — the
3600/// name-only shapes of [`ObjectReference::Named`], covering PostgreSQL's
3601/// `object_type_any_name` and `object_type_name` productions.
3602#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3603#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3604#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3605pub enum ObjectRefKind {
3606    /// `TABLE`.
3607    Table,
3608    /// `SEQUENCE`.
3609    Sequence,
3610    /// `VIEW`.
3611    View,
3612    /// `MATERIALIZED VIEW`.
3613    MaterializedView,
3614    /// `INDEX`.
3615    Index,
3616    /// `FOREIGN TABLE`.
3617    ForeignTable,
3618    /// `COLLATION`.
3619    Collation,
3620    /// `CONVERSION`.
3621    Conversion,
3622    /// `STATISTICS`.
3623    Statistics,
3624    /// `TEXT SEARCH PARSER`.
3625    TextSearchParser,
3626    /// `TEXT SEARCH DICTIONARY`.
3627    TextSearchDictionary,
3628    /// `TEXT SEARCH TEMPLATE`.
3629    TextSearchTemplate,
3630    /// `TEXT SEARCH CONFIGURATION`.
3631    TextSearchConfiguration,
3632    /// `ACCESS METHOD`.
3633    AccessMethod,
3634    /// `EVENT TRIGGER`.
3635    EventTrigger,
3636    /// `EXTENSION`.
3637    Extension,
3638    /// `FOREIGN DATA WRAPPER`.
3639    ForeignDataWrapper,
3640    /// `[PROCEDURAL] LANGUAGE`. The optional `PROCEDURAL` keyword is exact-synonym sugar
3641    /// (PostgreSQL's `opt_procedural`); the canonical render emits `LANGUAGE`.
3642    Language,
3643    /// `PUBLICATION`.
3644    Publication,
3645    /// `SCHEMA`.
3646    Schema,
3647    /// `SERVER` (a foreign server).
3648    Server,
3649    /// `DATABASE`.
3650    Database,
3651    /// `ROLE`.
3652    Role,
3653    /// `TABLESPACE`.
3654    Tablespace,
3655}
3656
3657impl ObjectRefKind {
3658    /// Whether this kind names a schema-qualifiable object (PostgreSQL's
3659    /// `object_type_any_name` group), so its name may be a dotted `any_name`. The
3660    /// remaining kinds are `object_type_name`, which take only a single `name`.
3661    pub fn schema_qualifiable(self) -> bool {
3662        matches!(
3663            self,
3664            Self::Table
3665                | Self::Sequence
3666                | Self::View
3667                | Self::MaterializedView
3668                | Self::Index
3669                | Self::ForeignTable
3670                | Self::Collation
3671                | Self::Conversion
3672                | Self::Statistics
3673                | Self::TextSearchParser
3674                | Self::TextSearchDictionary
3675                | Self::TextSearchTemplate
3676                | Self::TextSearchConfiguration
3677        )
3678    }
3679}
3680
3681/// The argument signature of an `AGGREGATE` member (PostgreSQL's `aggr_args`).
3682///
3683/// Argument names and modes are dropped, keeping only the type list — the same
3684/// simplification [`RoutineSignature`] applies to `function_with_argtypes`.
3685#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3686#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3687#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3688pub enum AggregateArgs<X: Extension = NoExt> {
3689    /// `(*)` — the zero-argument wildcard.
3690    Star {
3691        /// Source location and node identity.
3692        meta: Meta,
3693    },
3694    /// A direct argument-type list, optionally followed by an ordered-set `ORDER BY`
3695    /// list: `(<types>)`, `(ORDER BY <types>)` (empty `direct`), or
3696    /// `(<types> ORDER BY <types>)`.
3697    Types {
3698        /// Direct argument types (empty for the leading `ORDER BY` form).
3699        direct: ThinVec<DataType<X>>,
3700        /// Ordered-set `ORDER BY` argument types, when the clause is present.
3701        order_by: Option<ThinVec<DataType<X>>>,
3702        /// Source location and node identity.
3703        meta: Meta,
3704    },
3705}
3706
3707/// The operand types of an `OPERATOR` member (PostgreSQL's `oper_argtypes`): a binary or
3708/// unary operator's left and right operand types, where a `NONE` operand (a unary
3709/// operator's missing side) is `None`. At least one side is always present.
3710#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3711#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3712#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3713pub struct OperatorArgs<X: Extension = NoExt> {
3714    /// The left operand type, or `None` for a left-unary operator's `NONE`.
3715    pub left: Option<DataType<X>>,
3716    /// The right operand type, or `None` for a right-unary operator's `NONE`.
3717    pub right: Option<DataType<X>>,
3718    /// Source location and node identity.
3719    pub meta: Meta,
3720}
3721
3722/// An `ALTER <object> [NO] DEPENDS ON EXTENSION <extension>` statement (PostgreSQL's
3723/// `AlterObjectDependsStmt`), gated by
3724/// [`StatementDdlGates::extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl).
3725///
3726/// Records or clears a dependency of a database object on an extension, so that dropping
3727/// the extension cascades to the object (and `pg_dump` skips it). PostgreSQL admits only
3728/// four object heads here — `FUNCTION`/`PROCEDURE`/`ROUTINE` (a `function_with_argtypes`),
3729/// `TRIGGER <name> ON <table>`, `MATERIALIZED VIEW`, and `INDEX` — captured by the shared
3730/// [`ObjectReference`] axis, so the wider `object_type` keyword set (`TABLE`, `VIEW`,
3731/// `SEQUENCE`, …) that `object` could otherwise spell is out of the parsed grammar.
3732///
3733/// The construct references an extension but operates on a non-extension object; it shares
3734/// the [`extension_ddl`](crate::dialect::StatementDdlGates::extension_ddl) gate rather than
3735/// carrying its own, because it is meaningless without the extension system and its dialect
3736/// boundary (PostgreSQL on, everything else off) is identical to extension DDL's — a
3737/// dialect that has extensions has this, one that does not (DuckDB's `INSTALL`/`LOAD`) has
3738/// neither.
3739#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3740#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3741#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3742pub struct AlterObjectDepends<X: Extension = NoExt> {
3743    /// The object whose extension dependency is being set — one of the four
3744    /// [`ObjectReference`] shapes the `DEPENDS` grammar admits.
3745    pub object: ObjectReference<X>,
3746    /// Whether the `NO DEPENDS` negation form was written (PostgreSQL's `opt_no`), which
3747    /// removes the recorded dependency instead of adding it.
3748    pub no: bool,
3749    /// The extension the object depends on (a bare `name`, not schema-qualifiable).
3750    pub extension: Ident,
3751    /// Source location and node identity.
3752    pub meta: Meta,
3753}
3754
3755/// A MySQL `CREATE SERVER <name> FOREIGN DATA WRAPPER <wrapper> OPTIONS ( <option> [, ...] )`
3756/// federated-server definition (`sql_yacc.yy` `CREATE SERVER_SYM …`), gated by
3757/// [`StatementDdlGates::server_definition`](crate::dialect::StatementDdlGates::server_definition).
3758///
3759/// Registers a remote server for the `FEDERATED` storage engine. The server name and the
3760/// `FOREIGN DATA WRAPPER` name are each an `ident_or_text` (a bare/backtick identifier or a
3761/// quoted string, folded to an [`Ident`] whose quote style round-trips). The `OPTIONS` list is
3762/// the fixed [`ServerOption`] keyword set and is non-empty — an empty `OPTIONS ()` is
3763/// `ER_PARSE_ERROR` on mysql:8.4.10. Shares its gate and the [`ServerOption`] axis with
3764/// [`AlterServer`] (the two differ only in whether a wrapper is named); [`DropServer`] disposes
3765/// of the same object.
3766#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3767#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3768#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3769pub struct CreateServer {
3770    /// The server name (`ident_or_text`).
3771    pub name: Ident,
3772    /// The `FOREIGN DATA WRAPPER` name (`ident_or_text`) — the storage-engine wrapper the
3773    /// server uses.
3774    pub wrapper: Ident,
3775    /// The `OPTIONS` list, in source order; non-empty (`server_options_list`).
3776    pub options: ThinVec<ServerOption>,
3777    /// Source location and node identity.
3778    pub meta: Meta,
3779}
3780
3781/// A MySQL `ALTER SERVER <name> OPTIONS ( <option> [, ...] )` federated-server change
3782/// (`sql_yacc.yy` `ALTER SERVER_SYM …`), gated by
3783/// [`StatementDdlGates::server_definition`](crate::dialect::StatementDdlGates::server_definition).
3784///
3785/// Re-sets connection options on an existing server. Unlike [`CreateServer`] it names no
3786/// `FOREIGN DATA WRAPPER` (the wrapper is fixed at creation), but the `OPTIONS` list is the
3787/// same non-empty [`ServerOption`] grammar.
3788#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3789#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3790#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3791pub struct AlterServer {
3792    /// The server name (`ident_or_text`).
3793    pub name: Ident,
3794    /// The `OPTIONS` list, in source order; non-empty (`server_options_list`).
3795    pub options: ThinVec<ServerOption>,
3796    /// Source location and node identity.
3797    pub meta: Meta,
3798}
3799
3800/// A MySQL `DROP SERVER [IF EXISTS] <name>` federated-server drop (`sql_yacc.yy`
3801/// `drop_server_stmt`), gated by
3802/// [`StatementDdlGates::server_definition`](crate::dialect::StatementDdlGates::server_definition).
3803///
3804/// Removes a single server; no comma list (`DROP SERVER a, b` is `ER_PARSE_ERROR` on
3805/// mysql:8.4.10). The name is an `ident_or_text`.
3806#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3807#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3808#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3809pub struct DropServer {
3810    /// Whether the `IF EXISTS` guard was written (`drop_server_stmt`'s `if_exists`).
3811    pub if_exists: bool,
3812    /// The server name (`ident_or_text`).
3813    pub name: Ident,
3814    /// Source location and node identity.
3815    pub meta: Meta,
3816}
3817
3818/// One `CREATE`/`ALTER SERVER` option — a fixed keyword ([`ServerOptionKind`]) and its value
3819/// (`sql_yacc.yy` `server_option`). Every option carries exactly one value: the string-valued
3820/// keywords (`HOST`/`DATABASE`/`USER`/`PASSWORD`/`SOCKET`/`OWNER`) take a `TEXT_STRING_sys`
3821/// string literal, and `PORT` takes a `ulong_num` unsigned-integer literal — the parser holds
3822/// each to its measured value type (`HOST 123` and `PORT '3306'` are both `ER_PARSE_ERROR` on
3823/// mysql:8.4.10). The value is a [`Literal`] whose [`kind`](Literal::kind) records which.
3824#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3825#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3826#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3827pub struct ServerOption {
3828    /// Which fixed option keyword this is.
3829    pub kind: ServerOptionKind,
3830    /// The option value — a string literal for every kind but [`ServerOptionKind::Port`],
3831    /// which carries an unsigned-integer literal.
3832    pub value: Literal,
3833    /// Source location and node identity.
3834    pub meta: Meta,
3835}
3836
3837/// The fixed keyword of a [`ServerOption`] (`sql_yacc.yy` `server_option`). A surface tag (no
3838/// `meta` — its span rides the owning [`ServerOption`]). Only these seven keywords are
3839/// options; any other (`FOO 'bar'`) is `ER_PARSE_ERROR` on mysql:8.4.10.
3840#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
3841#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3842#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3843pub enum ServerOptionKind {
3844    /// `HOST '<host>'` — the remote host name.
3845    Host,
3846    /// `DATABASE '<db>'` — the remote default database.
3847    Database,
3848    /// `USER '<user>'` — the connection user.
3849    User,
3850    /// `PASSWORD '<password>'` — the connection password.
3851    Password,
3852    /// `SOCKET '<socket>'` — the remote unix socket path.
3853    Socket,
3854    /// `OWNER '<owner>'` — the server owner.
3855    Owner,
3856    /// `PORT <n>` — the remote TCP port (an unsigned-integer value, not a string).
3857    Port,
3858}
3859
3860/// A MySQL `ALTER INSTANCE <action>` server-instance administration statement (`sql_yacc.yy`
3861/// `alter_instance_stmt`), gated by
3862/// [`StatementDdlGates::alter_instance`](crate::dialect::StatementDdlGates::alter_instance).
3863///
3864/// A single instance-wide maintenance action (rotate an encryption master key, reload TLS
3865/// material or the keyring, toggle the InnoDB redo log) — see [`AlterInstanceAction`]. Unlike
3866/// the server and database DDL this names no object.
3867#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3868#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3869#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3870pub struct AlterInstance {
3871    /// The instance action performed.
3872    pub action: AlterInstanceAction,
3873    /// Source location and node identity.
3874    pub meta: Meta,
3875}
3876
3877/// The action of an [`AlterInstance`] (`sql_yacc.yy` `alter_instance_action`). The `INNODB`,
3878/// `BINLOG`, and `REDO_LOG` words are matched as identifiers by the server (`is_identifier`),
3879/// so a wrong word is `ER_PARSE_ERROR` (`ROTATE FOO MASTER KEY`, `ENABLE INNODB FOO`); this
3880/// models only the measured accepted set.
3881#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3882#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3883#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3884pub enum AlterInstanceAction {
3885    /// `ROTATE INNODB MASTER KEY` — rotate the InnoDB tablespace-encryption master key.
3886    RotateInnodbMasterKey {
3887        /// Source location and node identity.
3888        meta: Meta,
3889    },
3890    /// `ROTATE BINLOG MASTER KEY` — rotate the binary-log encryption master key.
3891    RotateBinlogMasterKey {
3892        /// Source location and node identity.
3893        meta: Meta,
3894    },
3895    /// `RELOAD TLS [FOR CHANNEL <channel>] [NO ROLLBACK ON ERROR]` — reconfigure the TLS
3896    /// context. The optional `FOR CHANNEL` names a bare `ident` (a quoted `'ch'` is
3897    /// `ER_PARSE_ERROR` on mysql:8.4.10, unlike `FLUSH … FOR CHANNEL`'s string); the default
3898    /// is to roll back the active context on error, and `NO ROLLBACK ON ERROR` suppresses it.
3899    ReloadTls {
3900        /// The `FOR CHANNEL <channel>` name (a bare `ident`), or `None`.
3901        channel: Option<Ident>,
3902        /// Whether `NO ROLLBACK ON ERROR` was written.
3903        no_rollback_on_error: bool,
3904        /// Source location and node identity.
3905        meta: Meta,
3906    },
3907    /// `RELOAD KEYRING` — reload keyring component options from the manifest.
3908    ReloadKeyring {
3909        /// Source location and node identity.
3910        meta: Meta,
3911    },
3912    /// `ENABLE INNODB REDO_LOG` — re-enable redo logging.
3913    EnableInnodbRedoLog {
3914        /// Source location and node identity.
3915        meta: Meta,
3916    },
3917    /// `DISABLE INNODB REDO_LOG` — disable redo logging (for bulk load).
3918    DisableInnodbRedoLog {
3919        /// Source location and node identity.
3920        meta: Meta,
3921    },
3922}
3923
3924/// A MySQL `CREATE [OR REPLACE] SPATIAL REFERENCE SYSTEM [IF NOT EXISTS] <srid> <attributes>`
3925/// spatial-reference-system definition (`sql_yacc.yy` `create_srs_stmt`), gated by
3926/// [`StatementDdlGates::spatial_reference_system`](crate::dialect::StatementDdlGates::spatial_reference_system).
3927///
3928/// `OR REPLACE` and `IF NOT EXISTS` are mutually exclusive: the grammar has one branch for each
3929/// (`CREATE OR REPLACE …` and `CREATE … IF NOT EXISTS …`), so `CREATE OR REPLACE … IF NOT EXISTS`
3930/// is `ER_PARSE_ERROR` on mysql:8.4.10. The `<srid>` is a `real_ulonglong_num` unsigned integer
3931/// (decimal or `0x` hex; a negative or fractional value is `ER_PARSE_ERROR`, an out-of-`u64`-range
3932/// value is the post-parse `ER_DATA_OUT_OF_RANGE`). Every attribute is optional and the list is
3933/// order-free at the grammar level — a missing NAME/DEFINITION is the post-parse semantic reject
3934/// `ER_SRS_MISSING_MANDATORY_ATTRIBUTE`, and a repeated attribute is
3935/// `ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS`, neither a syntax error — so [`attributes`](Self::attributes)
3936/// keeps the source order and admits repeats, matching the recognized grammar rather than the
3937/// narrower semantic rule.
3938#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3939#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3940#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3941pub struct CreateSpatialReferenceSystem {
3942    /// Whether the `OR REPLACE` branch was written (mutually exclusive with
3943    /// [`if_not_exists`](Self::if_not_exists)).
3944    pub or_replace: bool,
3945    /// Whether the `IF NOT EXISTS` guard was written (mutually exclusive with
3946    /// [`or_replace`](Self::or_replace)).
3947    pub if_not_exists: bool,
3948    /// The SRID — a `real_ulonglong_num` unsigned-integer literal (decimal or `0x` hex).
3949    pub srid: Literal,
3950    /// The attribute list, in source order (order-free grammar; repeats admitted).
3951    pub attributes: ThinVec<SrsAttribute>,
3952    /// Source location and node identity.
3953    pub meta: Meta,
3954}
3955
3956/// One `CREATE SPATIAL REFERENCE SYSTEM` attribute (`sql_yacc.yy` `srs_attributes`). The
3957/// attributes are order-free and each independently optional; a repeat of the same attribute is
3958/// a post-parse semantic reject (`ER_SRS_MULTIPLE_ATTRIBUTE_DEFINITIONS`), not a syntax error,
3959/// so the parser accepts the recognized grammar and preserves whatever the source wrote.
3960#[derive(Clone, Debug, PartialEq, Eq, Hash)]
3961#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
3962#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
3963pub enum SrsAttribute {
3964    /// `NAME '<name>'` — the SRS name (a `TEXT_STRING_sys_nonewline` string literal).
3965    Name {
3966        /// The name string.
3967        value: Literal,
3968        /// Source location and node identity.
3969        meta: Meta,
3970    },
3971    /// `DEFINITION '<wkt>'` — the WKT coordinate-system definition string.
3972    Definition {
3973        /// The definition string.
3974        value: Literal,
3975        /// Source location and node identity.
3976        meta: Meta,
3977    },
3978    /// `ORGANIZATION '<org>' IDENTIFIED BY <id>` — the authoring organization and its numeric
3979    /// coordinate-system id. `IDENTIFIED BY` is mandatory (`ORGANIZATION 'o'` alone is
3980    /// `ER_PARSE_ERROR`) and the id is a `real_ulonglong_num` integer (`IDENTIFIED BY 'x'` is
3981    /// `ER_PARSE_ERROR`).
3982    Organization {
3983        /// The organization name string.
3984        organization: Literal,
3985        /// The `IDENTIFIED BY` numeric coordinate-system id (unsigned-integer literal).
3986        identifier: Literal,
3987        /// Source location and node identity.
3988        meta: Meta,
3989    },
3990    /// `DESCRIPTION '<text>'` — the free-text description string.
3991    Description {
3992        /// The description string.
3993        value: Literal,
3994        /// Source location and node identity.
3995        meta: Meta,
3996    },
3997}
3998
3999/// A MySQL `DROP SPATIAL REFERENCE SYSTEM [IF EXISTS] <srid>` spatial-reference-system drop
4000/// (`sql_yacc.yy` `drop_srs_stmt`), gated by
4001/// [`StatementDdlGates::spatial_reference_system`](crate::dialect::StatementDdlGates::spatial_reference_system).
4002///
4003/// Removes a single SRS by id; no comma list (`DROP SPATIAL REFERENCE SYSTEM 1, 2` is
4004/// `ER_PARSE_ERROR` on mysql:8.4.10). The `<srid>` is a `real_ulonglong_num` unsigned integer.
4005#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4006#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4007#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4008pub struct DropSpatialReferenceSystem {
4009    /// Whether the `IF EXISTS` guard was written (`drop_srs_stmt`'s `if_exists`).
4010    pub if_exists: bool,
4011    /// The SRID — a `real_ulonglong_num` unsigned-integer literal.
4012    pub srid: Literal,
4013    /// Source location and node identity.
4014    pub meta: Meta,
4015}
4016
4017/// A MySQL `CREATE RESOURCE GROUP <name> TYPE [=] {SYSTEM | USER} [VCPU …] [THREAD_PRIORITY …]
4018/// [ENABLE | DISABLE]` resource-group definition (`sql_yacc.yy` `create_resource_group_stmt`),
4019/// gated by [`StatementDdlGates::resource_group`](crate::dialect::StatementDdlGates::resource_group).
4020///
4021/// `TYPE` is mandatory (a bare `CREATE RESOURCE GROUP g` is `ER_PARSE_ERROR` on mysql:8.4.10) and
4022/// the option train is fixed-order — `VCPU`, then `THREAD_PRIORITY`, then `ENABLE`/`DISABLE`; any
4023/// permutation (`… ENABLE VCPU …`) is `ER_PARSE_ERROR`. `CREATE` admits no `FORCE` (`ENABLE FORCE`
4024/// is `ER_PARSE_ERROR`), unlike [`AlterResourceGroup`] and [`DropResourceGroup`], which share the
4025/// same [`resource_group`](crate::dialect::StatementDdlGates::resource_group) gate.
4026#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4027#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4028#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4029pub struct CreateResourceGroup {
4030    /// The resource-group name (`ident`).
4031    pub name: Ident,
4032    /// Whether the `TYPE =` spelling (vs bare `TYPE`) was written (`opt_equal`).
4033    pub type_equals: bool,
4034    /// The group type (`SYSTEM`/`USER`), mandatory.
4035    pub group_type: ResourceGroupType,
4036    /// The optional `VCPU [=] <ranges>` CPU-affinity clause.
4037    pub vcpu: Option<ResourceGroupVcpu>,
4038    /// The optional `THREAD_PRIORITY [=] <n>` clause.
4039    pub thread_priority: Option<ResourceGroupThreadPriority>,
4040    /// The optional `ENABLE`/`DISABLE` state clause.
4041    pub state: Option<ResourceGroupState>,
4042    /// Source location and node identity.
4043    pub meta: Meta,
4044}
4045
4046/// A MySQL `ALTER RESOURCE GROUP <name> [VCPU …] [THREAD_PRIORITY …] [ENABLE | DISABLE] [FORCE]`
4047/// resource-group change (`sql_yacc.yy` `alter_resource_group_stmt`), gated by
4048/// [`StatementDdlGates::resource_group`](crate::dialect::StatementDdlGates::resource_group).
4049///
4050/// Every clause is optional — a bare `ALTER RESOURCE GROUP g` grammar-accepts on mysql:8.4.10.
4051/// The trailing `FORCE` is an *independent* optional (`opt_force`), not a suffix of
4052/// `ENABLE`/`DISABLE`: `ALTER RESOURCE GROUP g FORCE`, with neither state keyword, grammar-accepts
4053/// — so [`force`](Self::force) is a peer of [`state`](Self::state), not nested inside it. Shares
4054/// the [`ResourceGroupVcpu`]/[`ResourceGroupThreadPriority`]/[`ResourceGroupState`] axes with
4055/// [`CreateResourceGroup`]; `CREATE` differs only in requiring `TYPE` and forbidding `FORCE`.
4056#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4057#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4058#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4059pub struct AlterResourceGroup {
4060    /// The resource-group name (`ident`).
4061    pub name: Ident,
4062    /// The optional `VCPU [=] <ranges>` CPU-affinity clause.
4063    pub vcpu: Option<ResourceGroupVcpu>,
4064    /// The optional `THREAD_PRIORITY [=] <n>` clause.
4065    pub thread_priority: Option<ResourceGroupThreadPriority>,
4066    /// The optional `ENABLE`/`DISABLE` state clause.
4067    pub state: Option<ResourceGroupState>,
4068    /// Whether the trailing `FORCE` was written (`opt_force`, independent of
4069    /// [`state`](Self::state)).
4070    pub force: bool,
4071    /// Source location and node identity.
4072    pub meta: Meta,
4073}
4074
4075/// A MySQL `DROP RESOURCE GROUP <name> [FORCE]` resource-group drop (`sql_yacc.yy`
4076/// `drop_resource_group_stmt`), gated by
4077/// [`StatementDdlGates::resource_group`](crate::dialect::StatementDdlGates::resource_group).
4078///
4079/// Removes a single group by name; the optional trailing `FORCE` (`opt_force`) evicts running
4080/// threads.
4081#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4082#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4083#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4084pub struct DropResourceGroup {
4085    /// The resource-group name (`ident`).
4086    pub name: Ident,
4087    /// Whether the trailing `FORCE` was written (`opt_force`).
4088    pub force: bool,
4089    /// Source location and node identity.
4090    pub meta: Meta,
4091}
4092
4093/// The `TYPE` of a [`CreateResourceGroup`] (`sql_yacc.yy` `resource_group_types`): the fixed
4094/// `SYSTEM`/`USER` keyword set. A surface tag (no `meta` — its span rides the owning node); any
4095/// other word is `ER_PARSE_ERROR`.
4096#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4097#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4098#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4099pub enum ResourceGroupType {
4100    /// `SYSTEM` — a system resource group.
4101    System,
4102    /// `USER` — a user resource group.
4103    User,
4104}
4105
4106/// The `ENABLE`/`DISABLE` state clause of a resource-group `CREATE`/`ALTER`
4107/// (`opt_resource_group_enable_disable`). A surface tag (no `meta` — its span rides the owning
4108/// node).
4109#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4110#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4111#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4112pub enum ResourceGroupState {
4113    /// `ENABLE` — enable the group.
4114    Enable,
4115    /// `DISABLE` — disable the group.
4116    Disable,
4117}
4118
4119/// The `VCPU [=] <range> [, <range> …]` CPU-affinity clause of a resource-group `CREATE`/`ALTER`
4120/// (`sql_yacc.yy` `VCPU_SYM opt_equal vcpu_range_spec_list`). The `[=]` presence
4121/// ([`equals`](Self::equals)) and the non-empty [`ranges`](Self::ranges) list round-trip. The
4122/// separator between ranges is `opt_comma` (a comma *or* whitespace both parse), so the parse is
4123/// separator-insensitive and the list is rendered canonically comma-separated.
4124#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4125#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4126#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4127pub struct ResourceGroupVcpu {
4128    /// Whether the `VCPU =` spelling (vs bare `VCPU`) was written (`opt_equal`).
4129    pub equals: bool,
4130    /// The CPU-id range list, in source order; non-empty (`vcpu_range_spec_list`).
4131    pub ranges: ThinVec<VcpuRange>,
4132    /// Source location and node identity.
4133    pub meta: Meta,
4134}
4135
4136/// One `vcpu_num_or_range`: a single CPU id ([`end`](Self::end) `None`) or an inclusive
4137/// `start-end` range. Each bound is a `NUM` unsigned-integer literal; a value beyond the host CPU
4138/// count is a post-parse semantic reject (`ER_RESOURCE_GROUP_…`), not a syntax error, so the
4139/// parser accepts any integer bounds.
4140#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4141#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4142#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4143pub struct VcpuRange {
4144    /// The single id, or the inclusive-range start.
4145    pub start: Literal,
4146    /// The inclusive-range end (`start-end`), or `None` for a single id.
4147    pub end: Option<Literal>,
4148    /// Source location and node identity.
4149    pub meta: Meta,
4150}
4151
4152/// The `THREAD_PRIORITY [=] <n>` clause of a resource-group `CREATE`/`ALTER`
4153/// (`opt_resource_group_priority`). The value is a `signed_num` — an optionally negative integer
4154/// (`THREAD_PRIORITY = -5` grammar-accepts on mysql:8.4.10) — so the sign is carried apart from
4155/// the magnitude literal (`-` is a separate token). The `[=]` presence and the sign round-trip.
4156#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4157#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4158#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4159pub struct ResourceGroupThreadPriority {
4160    /// Whether the `THREAD_PRIORITY =` spelling (vs bare `THREAD_PRIORITY`) was written
4161    /// (`opt_equal`).
4162    pub equals: bool,
4163    /// Whether a leading `-` was written (`signed_num`'s negative branch).
4164    pub negative: bool,
4165    /// The magnitude — a `NUM` unsigned-integer literal.
4166    pub value: Literal,
4167    /// Source location and node identity.
4168    pub meta: Meta,
4169}
4170
4171/// A MySQL `ALTER {DATABASE | SCHEMA} [<name>] <option> …` schema-option change (`sql_yacc.yy`
4172/// `alter_database_stmt`), gated by
4173/// [`StatementDdlGates::alter_database_options`](crate::dialect::StatementDdlGates::alter_database_options).
4174///
4175/// A distinct node — and gate — from DuckDB's [`AlterDatabase`] `SET ALIAS` relocation: the two
4176/// share only the `ALTER DATABASE` head, and their grammars are disjoint (DuckDB requires a name
4177/// and a single `SET ALIAS TO`; MySQL's name is optional — `ALTER DATABASE CHARACTER SET utf8`
4178/// binds the default schema — and it takes a non-empty, repeatable list of charset/collation/
4179/// encryption/read-only [`AlterDatabaseOption`]s). Modelling them apart keeps each behaviour's
4180/// invariants in the type rather than as XOR-populated fields on one node. The `DATABASE`/
4181/// `SCHEMA` [`spelling`](Self::spelling) is an exact synonym recorded for round-trip.
4182#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4183#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4184#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4185pub struct AlterDatabaseOptions {
4186    /// Which of the synonymous `DATABASE`/`SCHEMA` keywords was written.
4187    pub spelling: DatabaseKeyword,
4188    /// The database name, or `None` for the unqualified `ALTER DATABASE <option> …` form that
4189    /// targets the session's default schema (`ident_or_empty`; a dotted `d.x` is
4190    /// `ER_PARSE_ERROR`, so a bare [`Ident`]).
4191    pub name: Option<Ident>,
4192    /// The option list, in source order; non-empty (`alter_database_options`).
4193    pub options: ThinVec<AlterDatabaseOption>,
4194    /// Source location and node identity.
4195    pub meta: Meta,
4196}
4197
4198/// One `ALTER {DATABASE | SCHEMA}` option (`sql_yacc.yy` `alter_database_option`). The `[=]`
4199/// ([`opt_equal`]) and, where admitted, the leading `[DEFAULT]` are recorded so the surface
4200/// form round-trips verbatim. The value grammars are fixed; a value outside the set is a parse
4201/// or bind reject on mysql:8.4.10 (`READ ONLY 2` is `ER_PARSE_ERROR`; `ENCRYPTION 'X'` binds
4202/// then rejects `ER_WRONG_VALUE`, so any string parses).
4203///
4204/// [`opt_equal`]: https://dev.mysql.com/doc/refman/8.4/en/alter-database.html
4205#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4206#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4207#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4208pub enum AlterDatabaseOption {
4209    /// `[DEFAULT] {CHARACTER SET | CHARSET} [=] <charset>` — the default character set. The
4210    /// charset name is `charset_name` (`ident_or_text`, or the reserved `BINARY`), folded to
4211    /// an [`Ident`].
4212    CharacterSet {
4213        /// Whether the leading `DEFAULT` was written.
4214        default: bool,
4215        /// Which of the `CHARACTER SET`/`CHARSET` synonyms was written.
4216        keyword: CharsetKeyword,
4217        /// Whether the optional `=` was written.
4218        equals: bool,
4219        /// The character-set name.
4220        charset: Ident,
4221        /// Source location and node identity.
4222        meta: Meta,
4223    },
4224    /// `[DEFAULT] COLLATE [=] <collation>` — the default collation. The name is
4225    /// `collation_name` (`ident_or_text`, or the reserved `BINARY`), folded to an [`Ident`].
4226    Collate {
4227        /// Whether the leading `DEFAULT` was written.
4228        default: bool,
4229        /// Whether the optional `=` was written.
4230        equals: bool,
4231        /// The collation name.
4232        collation: Ident,
4233        /// Source location and node identity.
4234        meta: Meta,
4235    },
4236    /// `[DEFAULT] ENCRYPTION [=] '<Y|N>'` — the default tablespace encryption. Any string
4237    /// literal parses; the `Y`/`N` restriction is a bind-time check (`ER_WRONG_VALUE`), so the
4238    /// value is held verbatim as a [`Literal`].
4239    Encryption {
4240        /// Whether the leading `DEFAULT` was written.
4241        default: bool,
4242        /// Whether the optional `=` was written.
4243        equals: bool,
4244        /// The encryption value (a string literal).
4245        value: Literal,
4246        /// Source location and node identity.
4247        meta: Meta,
4248    },
4249    /// `READ ONLY [=] {DEFAULT | 0 | 1}` — the schema read-only flag. Takes no leading
4250    /// `DEFAULT` prefix (unlike the charset/collation/encryption options — `DEFAULT READ ONLY`
4251    /// is `ER_PARSE_ERROR`); the value is a [`ReadOnlyValue`] `ternary_option`, and any other
4252    /// number (`READ ONLY 2`) is `ER_PARSE_ERROR`.
4253    ReadOnly {
4254        /// Whether the optional `=` was written.
4255        equals: bool,
4256        /// The read-only value.
4257        value: ReadOnlyValue,
4258        /// Source location and node identity.
4259        meta: Meta,
4260    },
4261}
4262
4263/// Which of the synonymous `CHARACTER SET`/`CHARSET` keywords introduced an
4264/// [`AlterDatabaseOption::CharacterSet`]. A surface tag (no `meta`) recording the spelling for
4265/// round-trip; the two are semantically identical.
4266#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4267#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4268#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4269pub enum CharsetKeyword {
4270    /// The two-word `CHARACTER SET` spelling.
4271    CharacterSet,
4272    /// The one-word `CHARSET` synonym.
4273    Charset,
4274}
4275
4276/// The value of an [`AlterDatabaseOption::ReadOnly`] — MySQL's `ternary_option`. A surface tag
4277/// (no `meta`); `0`/`1` are the boolean settings and `DEFAULT` inherits the global default.
4278/// Any other number is `ER_PARSE_ERROR` on mysql:8.4.10.
4279#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4280#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4281#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4282pub enum ReadOnlyValue {
4283    /// `DEFAULT` — inherit the global read-only default.
4284    Default,
4285    /// `0` — read-write.
4286    Off,
4287    /// `1` — read-only.
4288    On,
4289}
4290
4291/// The binary unit suffix on a MySQL storage-DDL size literal (`size_number`'s `IDENT_sys`
4292/// form): `K`, `M`, or `G`. MySQL folds `<digits><suffix>` to a byte count by shifting the
4293/// number left 10/20/30 bits, so the tag names the multiplier a downstream converter would
4294/// otherwise re-decode from the spelling. Case is *not* carried here — the exact source
4295/// letter (`16m` vs `16M`) round-trips from the enclosing [`SizeLiteral`]'s span.
4296#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4297#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4298#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4299pub enum SizeUnit {
4300    /// `K` — the value is shifted left 10 bits (× 1024).
4301    Kilo,
4302    /// `M` — the value is shifted left 20 bits (× 1024²).
4303    Mega,
4304    /// `G` — the value is shifted left 30 bits (× 1024³).
4305    Giga,
4306}
4307
4308/// A MySQL storage-DDL size literal (`size_number`): a byte count written either as a plain
4309/// integer (`134217728`) or as an integer with a binary unit suffix (`128M`, `2G`, `16k`).
4310///
4311/// MySQL lexes the suffixed form as a *single* `IDENT_sys` token (an unquoted identifier may
4312/// begin with digits), so the digits and the suffix letter must abut with no space — `16 M`
4313/// is a syntax error. This crate's tokenizer instead splits `16M` into a number token and an
4314/// adjacent word; the parser rejoins them by span adjacency, so [`meta`](Self::meta) spans the
4315/// whole literal and renders verbatim. [`unit`](Self::unit) records the multiplier as
4316/// structured metadata (`None` for a bare byte count); like a [`Literal`], the numeric value
4317/// and the exact spelling round-trip from the span, so the value carries only the surface tag
4318/// a consumer cannot otherwise recover.
4319#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4320#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4321#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4322pub struct SizeLiteral {
4323    /// The binary unit suffix, or `None` for a bare integer byte count; see [`SizeUnit`].
4324    pub unit: Option<SizeUnit>,
4325    /// Source location and node identity. Spans the whole literal (digits + any suffix),
4326    /// which is what round-trips the exact spelling.
4327    pub meta: Meta,
4328}
4329
4330/// Which storage-DDL option keyword carries a [`SizeLiteral`] value — the `size_number`-valued
4331/// members of MySQL's shared `ts_option_*` family, grouped here because they share the exact
4332/// `<KEYWORD> [=] <size>` shape and differ only in the keyword.
4333#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
4334#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4335#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4336pub enum TablespaceSizeOption {
4337    /// `INITIAL_SIZE` — the file's initial size.
4338    InitialSize,
4339    /// `AUTOEXTEND_SIZE` — the auto-extension increment.
4340    AutoextendSize,
4341    /// `MAX_SIZE` — the file's maximum size.
4342    MaxSize,
4343    /// `EXTENT_SIZE` — the extent size.
4344    ExtentSize,
4345    /// `UNDO_BUFFER_SIZE` — the UNDO buffer size (logfile group).
4346    UndoBufferSize,
4347    /// `REDO_BUFFER_SIZE` — the REDO buffer size (logfile group).
4348    RedoBufferSize,
4349    /// `FILE_BLOCK_SIZE` — the file block size (tablespace).
4350    FileBlockSize,
4351}
4352
4353/// One option in a MySQL tablespace / logfile-group statement — a member of the server's shared
4354/// `ts_option_*` family (every one of these DDL statements builds one
4355/// `PT_alter_tablespace_option` list). The full universe is modelled here as one axis; each
4356/// statement context accepts only its own subset (e.g. `UNDO TABLESPACE` takes `ENGINE` alone,
4357/// `ALTER TABLESPACE` excludes `FILE_BLOCK_SIZE`), enforced by the parser rather than the type,
4358/// so an out-of-context option ends the list and surfaces as a clean parse error — matching the
4359/// live server's per-context grammar.
4360///
4361/// Non-generic: every value is a size literal, an integer/string [`Literal`], an [`Ident`], or a
4362/// flag — no expressions or extension nodes. Each option records whether its optional `=`
4363/// (`opt_equal`) was written so the surface form round-trips.
4364#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4365#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4366#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4367pub enum TablespaceOption {
4368    /// A `size_number`-valued option (`INITIAL_SIZE [=] 128M`, …); see [`TablespaceSizeOption`].
4369    Size {
4370        /// Which size option keyword this is.
4371        kind: TablespaceSizeOption,
4372        /// Whether the optional `=` was written between the keyword and the value.
4373        equals: bool,
4374        /// The size value.
4375        size: SizeLiteral,
4376        /// Source location and node identity.
4377        meta: Meta,
4378    },
4379    /// `NODEGROUP [=] <n>` — a plain integer node-group id (`real_ulong_num`, never suffixed).
4380    Nodegroup {
4381        /// Whether the optional `=` was written between the keyword and the value.
4382        equals: bool,
4383        /// The node-group id.
4384        value: Literal,
4385        /// Source location and node identity.
4386        meta: Meta,
4387    },
4388    /// `[STORAGE] ENGINE [=] <name>` — the storage engine (`ident_or_text`: a bare identifier or
4389    /// a quoted string, folded to an [`Ident`] whose quote style round-trips).
4390    Engine {
4391        /// Whether the optional leading `STORAGE` noise keyword was written (`opt_storage`).
4392        storage: bool,
4393        /// Whether the optional `=` was written between `ENGINE` and the value.
4394        equals: bool,
4395        /// The engine name.
4396        name: Ident,
4397        /// Source location and node identity.
4398        meta: Meta,
4399    },
4400    /// `WAIT` or `NO_WAIT` — the NDB completion-wait flag (`ts_option_wait`).
4401    Wait {
4402        /// `true` for `NO_WAIT`, `false` for `WAIT`.
4403        negated: bool,
4404        /// Source location and node identity.
4405        meta: Meta,
4406    },
4407    /// `COMMENT [=] '<text>'` — a free-text comment string.
4408    Comment {
4409        /// Whether the optional `=` was written between the keyword and the value.
4410        equals: bool,
4411        /// The comment string.
4412        value: Literal,
4413        /// Source location and node identity.
4414        meta: Meta,
4415    },
4416    /// `ENCRYPTION [=] '<y_or_n>'` — the tablespace encryption flag string (`'Y'`/`'N'`).
4417    Encryption {
4418        /// Whether the optional `=` was written between the keyword and the value.
4419        equals: bool,
4420        /// The encryption flag string.
4421        value: Literal,
4422        /// Source location and node identity.
4423        meta: Meta,
4424    },
4425    /// `ENGINE_ATTRIBUTE [=] '<json>'` — the engine-specific JSON attribute string.
4426    EngineAttribute {
4427        /// Whether the optional `=` was written between the keyword and the value.
4428        equals: bool,
4429        /// The JSON attribute string.
4430        value: Literal,
4431        /// Source location and node identity.
4432        meta: Meta,
4433    },
4434}
4435
4436/// A MySQL `CREATE [UNDO] TABLESPACE <name> [ADD DATAFILE '<f>'] [USE LOGFILE GROUP <lg>]
4437/// [<option>...]` statement (gated by
4438/// [`StatementDdlGates::tablespace_ddl`](crate::dialect::StatementDdlGates::tablespace_ddl)).
4439///
4440/// One node for both the InnoDB/NDB tablespace (`Sql_cmd_create_tablespace`) and the
4441/// `UNDO`-tablespace variant (`Sql_cmd_create_undo_tablespace`), distinguished by
4442/// [`undo`](Self::undo). The two differ only in the leading `UNDO` keyword, whether the datafile
4443/// is mandatory (it is for `UNDO`, optional otherwise), and the accepted option set (`UNDO`
4444/// admits `ENGINE` alone) — all enforced by the parser. The `USE LOGFILE GROUP` clause is
4445/// NDB-only and never appears on the `UNDO` form.
4446#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4447#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4448#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4449pub struct CreateTablespace {
4450    /// Whether this is the `CREATE UNDO TABLESPACE` variant.
4451    pub undo: bool,
4452    /// The tablespace name.
4453    pub name: Ident,
4454    /// `ADD DATAFILE '<file>'`. Mandatory (`Some`) for the `UNDO` form; optional otherwise.
4455    pub datafile: Option<Literal>,
4456    /// `USE LOGFILE GROUP <name>` (NDB, regular tablespace only).
4457    pub use_logfile_group: Option<Ident>,
4458    /// Options supplied in source order.
4459    pub options: ThinVec<TablespaceOption>,
4460    /// Source location and node identity.
4461    pub meta: Meta,
4462}
4463
4464/// The undo-tablespace access state set by `ALTER UNDO TABLESPACE <name> SET {ACTIVE | INACTIVE}`
4465/// (`undo_tablespace_state`).
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 UndoTablespaceState {
4470    /// `SET ACTIVE`.
4471    Active,
4472    /// `SET INACTIVE`.
4473    Inactive,
4474}
4475
4476/// A MySQL `ALTER [UNDO] TABLESPACE <name> <action>` statement (gated by
4477/// [`StatementDdlGates::tablespace_ddl`](crate::dialect::StatementDdlGates::tablespace_ddl)).
4478///
4479/// The four regular productions (`ADD`/`DROP DATAFILE`, `RENAME TO`, a bare option list) and the
4480/// `UNDO` production (`SET {ACTIVE | INACTIVE}`) share the `ALTER [UNDO] TABLESPACE <name>` head
4481/// and are unified under [`AlterTablespaceAction`]. The action itself distinguishes the `UNDO`
4482/// form (only it carries [`SetState`](AlterTablespaceAction::SetState)), so no separate `undo`
4483/// flag is needed.
4484#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4485#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4486#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4487pub struct AlterTablespace {
4488    /// The tablespace name.
4489    pub name: Ident,
4490    /// The change applied to the tablespace.
4491    pub action: AlterTablespaceAction,
4492    /// Source location and node identity.
4493    pub meta: Meta,
4494}
4495
4496/// The change an [`AlterTablespace`] applies.
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 AlterTablespaceAction {
4501    /// `ADD DATAFILE '<f>' [<option>...]`.
4502    AddDatafile {
4503        /// The datafile path.
4504        datafile: Literal,
4505        /// The trailing alter-tablespace options in source order.
4506        options: ThinVec<TablespaceOption>,
4507        /// Source location and node identity.
4508        meta: Meta,
4509    },
4510    /// `DROP DATAFILE '<f>' [<option>...]`.
4511    DropDatafile {
4512        /// The datafile path.
4513        datafile: Literal,
4514        /// The trailing alter-tablespace options in source order.
4515        options: ThinVec<TablespaceOption>,
4516        /// Source location and node identity.
4517        meta: Meta,
4518    },
4519    /// `RENAME TO <new_name>` — takes no options (a trailing option is a parse error).
4520    Rename {
4521        /// The new tablespace name.
4522        new_name: Ident,
4523        /// Source location and node identity.
4524        meta: Meta,
4525    },
4526    /// A bare (non-empty) option list — `ALTER TABLESPACE <name> <option> [, <option>]...`
4527    /// with no `ADD`/`DROP`/`RENAME` lead.
4528    Options {
4529        /// The options in source order (at least one).
4530        options: ThinVec<TablespaceOption>,
4531        /// Source location and node identity.
4532        meta: Meta,
4533    },
4534    /// `SET {ACTIVE | INACTIVE} [<option>...]` — the `ALTER UNDO TABLESPACE` form (the trailing
4535    /// options are the `UNDO` subset, `ENGINE` alone).
4536    SetState {
4537        /// The new access state.
4538        state: UndoTablespaceState,
4539        /// The trailing undo-tablespace options in source order.
4540        options: ThinVec<TablespaceOption>,
4541        /// Source location and node identity.
4542        meta: Meta,
4543    },
4544}
4545
4546/// A MySQL `DROP [UNDO] TABLESPACE <name> [<option>...]` statement (gated by
4547/// [`StatementDdlGates::tablespace_ddl`](crate::dialect::StatementDdlGates::tablespace_ddl)).
4548///
4549/// One node for `DROP TABLESPACE` (`Sql_cmd_drop_tablespace`) and `DROP UNDO TABLESPACE`
4550/// (`Sql_cmd_drop_undo_tablespace`), distinguished by [`undo`](Self::undo): the regular form
4551/// accepts `ENGINE`/`WAIT` options, the `UNDO` form `ENGINE` alone (parser-enforced).
4552#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4553#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4554#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4555pub struct DropTablespace {
4556    /// Whether this is the `DROP UNDO TABLESPACE` variant.
4557    pub undo: bool,
4558    /// The tablespace name.
4559    pub name: Ident,
4560    /// Options supplied in source order.
4561    pub options: ThinVec<TablespaceOption>,
4562    /// Source location and node identity.
4563    pub meta: Meta,
4564}
4565
4566/// A MySQL `CREATE LOGFILE GROUP <name> ADD UNDOFILE '<f>' [<option>...]` statement (NDB; gated
4567/// by
4568/// [`StatementDdlGates::logfile_group_ddl`](crate::dialect::StatementDdlGates::logfile_group_ddl)).
4569///
4570/// The `ADD UNDOFILE` clause is mandatory (a bare `CREATE LOGFILE GROUP <name>` is a syntax
4571/// error). The option set is the logfile-group subset of the shared `ts_option_*` family
4572/// (`INITIAL_SIZE`, `UNDO_BUFFER_SIZE`, `REDO_BUFFER_SIZE`, `NODEGROUP`, `ENGINE`, `WAIT`,
4573/// `COMMENT`), parser-enforced.
4574#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4575#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4576#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4577pub struct CreateLogfileGroup {
4578    /// The logfile-group name.
4579    pub name: Ident,
4580    /// The mandatory `ADD UNDOFILE '<file>'` path.
4581    pub undofile: Literal,
4582    /// Options supplied in source order.
4583    pub options: ThinVec<TablespaceOption>,
4584    /// Source location and node identity.
4585    pub meta: Meta,
4586}
4587
4588/// A MySQL `ALTER LOGFILE GROUP <name> ADD UNDOFILE '<f>' [<option>...]` statement (NDB; gated by
4589/// [`StatementDdlGates::logfile_group_ddl`](crate::dialect::StatementDdlGates::logfile_group_ddl)).
4590///
4591/// Like [`CreateLogfileGroup`] the `ADD UNDOFILE` clause is mandatory; the accepted option set is
4592/// the narrower alter subset (`INITIAL_SIZE`, `ENGINE`, `WAIT`), parser-enforced.
4593#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4594#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4595#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4596pub struct AlterLogfileGroup {
4597    /// The logfile-group name.
4598    pub name: Ident,
4599    /// The mandatory `ADD UNDOFILE '<file>'` path.
4600    pub undofile: Literal,
4601    /// Options supplied in source order.
4602    pub options: ThinVec<TablespaceOption>,
4603    /// Source location and node identity.
4604    pub meta: Meta,
4605}
4606
4607/// A MySQL `DROP LOGFILE GROUP <name> [<option>...]` statement (NDB; gated by
4608/// [`StatementDdlGates::logfile_group_ddl`](crate::dialect::StatementDdlGates::logfile_group_ddl)).
4609///
4610/// The option set is `ENGINE`/`WAIT` (`opt_drop_ts_options`, shared with `DROP TABLESPACE`).
4611#[derive(Clone, Debug, PartialEq, Eq, Hash)]
4612#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
4613#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
4614pub struct DropLogfileGroup {
4615    /// The logfile-group name.
4616    pub name: Ident,
4617    /// Options supplied in source order.
4618    pub options: ThinVec<TablespaceOption>,
4619    /// Source location and node identity.
4620    pub meta: Meta,
4621}