Skip to main content

uqa_sql/
ast.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Internal SQL AST. Lifts the relevant subset of the `libpg_query`
8//! protobuf tree into a Rust enum the compiler walks. Statements not
9//! yet supported parse cleanly but compile to
10//! [`crate::SQLError::Unsupported`].
11
12use serde::{Deserialize, Serialize};
13
14mod acl_role_specification;
15mod assignment_target;
16mod constraints;
17mod cte;
18mod domains;
19mod events;
20mod expressions;
21mod from;
22mod function_binding;
23mod indexes;
24mod interval;
25mod locking;
26mod namespaces;
27mod ranges;
28mod relation_hierarchy;
29mod relation_lifecycle;
30mod role_specification;
31mod routine_security;
32mod routines;
33mod sequence;
34mod types;
35
36pub use acl_role_specification::AclRoleSpecification;
37pub use assignment_target::{AssignmentStep, AssignmentTarget};
38pub use constraints::*;
39pub use cte::*;
40pub use domains::*;
41pub use events::*;
42pub use expressions::*;
43pub use from::*;
44pub use function_binding::*;
45pub use indexes::*;
46pub use interval::*;
47pub use locking::*;
48pub use namespaces::*;
49pub use ranges::*;
50pub use relation_hierarchy::*;
51pub use relation_lifecycle::*;
52pub use role_specification::RoleSpecification;
53pub use routine_security::*;
54pub use routines::*;
55pub use sequence::*;
56pub use types::*;
57
58const fn default_include_descendants() -> bool {
59    true
60}
61
62const fn default_true() -> bool {
63    true
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
67pub enum GeneratedColumnKind {
68    Virtual,
69    Stored,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct GeneratedColumn {
74    pub kind: GeneratedColumnKind,
75    pub expression: Box<Expr>,
76    #[serde(default, skip_serializing_if = "Vec::is_empty")]
77    pub function_dependencies: Vec<GeneratedFunctionDependency>,
78}
79
80#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
81pub struct IndexColumnOrder {
82    pub descending: bool,
83    pub nulls_first: bool,
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct CreateIndex {
88    #[serde(default)]
89    pub included_columns: Vec<String>,
90    #[serde(default)]
91    pub column_order: Vec<IndexColumnOrder>,
92    #[serde(default)]
93    pub predicate: Option<Box<Expr>>,
94    pub name: Option<String>,
95    pub table: String,
96    /// `gin`, `btree`, `ivf`, `hnsw`, `rtree`, ...
97    pub access_method: String,
98    pub columns: Vec<IndexKey>,
99    #[serde(default)]
100    pub unique: bool,
101    #[serde(default)]
102    pub nulls_not_distinct: bool,
103    /// `CREATE INDEX IF NOT EXISTS`.
104    pub if_not_exists: bool,
105    /// Storage parameters from `WITH (k = v, ...)`. Stored verbatim;
106    /// known keys (`analyzer`, `lists`, `probes`, ...)
107    /// are interpreted by the engine.
108    pub options: Vec<(String, String)>,
109    /// Explicit option namespaces retained for declaration-time validation.
110    #[serde(default, skip_serializing_if = "Vec::is_empty")]
111    pub option_namespaces: Vec<String>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct DropStmt {
116    pub kind: DropKind,
117    pub names: Vec<String>,
118    pub if_exists: bool,
119    pub cascade: bool,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
123pub enum DropKind {
124    Table,
125    ForeignTable,
126    Index,
127    View,
128    MaterializedView,
129    Schema,
130    Sequence,
131    Domain,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct AlterTableStmt {
136    pub table: String,
137    /// Local SQL relation identifier used while binding new or replaced generation expressions.
138    pub qualifier: String,
139    pub if_exists: bool,
140    /// Whether the target omitted `ONLY` and therefore allows recursive ALTER behavior.
141    #[serde(default = "default_true")]
142    pub recurse: bool,
143    pub actions: Vec<AlterTableAction>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147#[expect(
148    clippy::large_enum_variant,
149    reason = "preserves the stable AST serde shape"
150)]
151pub enum AlterTableAction {
152    AddInheritance {
153        parent: String,
154    },
155    DropInheritance {
156        parent: String,
157    },
158    AttachPartition {
159        partition: String,
160        bound: PartitionBound,
161    },
162    DetachPartition {
163        partition: String,
164        concurrently: bool,
165        finalize: bool,
166    },
167    AddColumn {
168        column: ColumnDef,
169        #[serde(default)]
170        checks: Vec<TableCheck>,
171        #[serde(default)]
172        key_constraints: Vec<TableKeyConstraint>,
173        if_not_exists: bool,
174    },
175    AddKeyConstraint {
176        constraint: TableKeyConstraint,
177    },
178    AddCheckConstraint {
179        constraint: TableCheck,
180    },
181    AddForeignKeyConstraint {
182        constraint: ForeignKey,
183    },
184    AddNotNullConstraint {
185        name: Option<String>,
186        column: String,
187        validated: bool,
188        no_inherit: bool,
189    },
190    ValidateConstraint {
191        name: String,
192    },
193    AlterConstraint {
194        name: String,
195        enforceability: Option<bool>,
196        deferrability: Option<(bool, bool)>,
197        no_inherit: Option<bool>,
198    },
199    DropConstraint {
200        name: String,
201        if_exists: bool,
202        cascade: bool,
203    },
204    DropColumn {
205        name: String,
206        if_exists: bool,
207        cascade: bool,
208    },
209    RenameColumn {
210        from: String,
211        to: String,
212    },
213    RenameTable {
214        to: String,
215    },
216    RenameTrigger {
217        from: String,
218        to: String,
219    },
220    RenameConstraint {
221        from: String,
222        to: String,
223    },
224    RenameRule {
225        from: String,
226        to: String,
227    },
228    SetPersistence {
229        persistence: RelationPersistence,
230    },
231    ChangeOwner {
232        owner: RoleSpecification,
233    },
234    SetSchema {
235        schema: String,
236    },
237    SetTriggerEnableMode {
238        name: Option<String>,
239        user_only: bool,
240        mode: EventEnableMode,
241    },
242    SetRuleEnableMode {
243        name: String,
244        mode: EventEnableMode,
245    },
246    SetDefault {
247        name: String,
248        default: Expr,
249    },
250    DropDefault {
251        name: String,
252    },
253    SetExpression {
254        name: String,
255        expression: Expr,
256    },
257    DropExpression {
258        name: String,
259    },
260    SetNotNull {
261        name: String,
262    },
263    DropNotNull {
264        name: String,
265    },
266    AlterColumnType {
267        name: String,
268        ty: ColumnType,
269        #[serde(default, skip_serializing_if = "Option::is_none")]
270        using: Option<Expr>,
271    },
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
275pub struct InsertStmt {
276    pub table: String,
277    /// Whether `table` is a stored catalog identity rather than a name to resolve in the executing session.
278    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
279    pub target_relation_bound: bool,
280    /// SQL-visible target relation name: explicit alias, otherwise the local relation name.
281    pub target_qualifier: String,
282    #[serde(default = "default_include_descendants")]
283    pub include_descendants: bool,
284    pub columns: Vec<AssignmentTarget>,
285    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
286    pub with: Vec<CTE>,
287    /// Inline `VALUES (...) (...)` rows. `DEFAULT VALUES` is represented by one empty row; the vector itself is empty only for `INSERT ... SELECT`, whose query is in `select_source`.
288    pub rows: Vec<Vec<ValueExpr>>,
289    /// Populated when the statement is `INSERT INTO t (...) SELECT ...`.
290    /// The engine materialises the inner select first and then writes
291    /// each row through the standard INSERT path.
292    pub select_source: Option<Box<SelectStmt>>,
293    /// `ON CONFLICT (...) DO ...` clause. `None` for plain
294    /// `INSERT INTO ... VALUES ...` without conflict handling.
295    pub on_conflict: Option<OnConflict>,
296    /// `RETURNING ...` projection list. Empty when absent.
297    pub returning: Vec<Projection>,
298    /// `PostgreSQL` 18 names for the old and new row images visible to
299    /// `RETURNING`. The defaults are `old` and `new`.
300    pub returning_aliases: ReturningAliases,
301}
302
303#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
304pub struct ReturningAliases {
305    pub old: String,
306    pub new: String,
307    #[serde(default)]
308    pub old_explicit: bool,
309    #[serde(default)]
310    pub new_explicit: bool,
311}
312
313impl Default for ReturningAliases {
314    fn default() -> Self {
315        Self {
316            old: "old".into(),
317            new: "new".into(),
318            old_explicit: false,
319            new_explicit: false,
320        }
321    }
322}
323
324#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
325pub struct OnConflict {
326    #[serde(default)]
327    pub predicate: Option<Box<Expr>>,
328    #[serde(default)]
329    pub constraint: Option<String>,
330    /// Conflict target columns parsed from the `ON CONFLICT (col, ...)`
331    /// list. Empty when the clause uses `ON CONFLICT DO NOTHING` with
332    /// no target.
333    pub conflict_columns: Vec<String>,
334    #[serde(default)]
335    pub expressions: Vec<Expr>,
336    pub action: OnConflictAction,
337}
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
340pub enum OnConflictAction {
341    /// `DO NOTHING` -- skip conflicting rows silently.
342    Nothing,
343    /// `DO UPDATE SET col = expr [, ...] [WHERE pred]` -- apply the
344    /// listed assignments to the existing row when the conflict
345    /// target matches.
346    Update {
347        assignments: Vec<(AssignmentTarget, Expr)>,
348        r#where: Option<Box<Expr>>,
349    },
350}
351
352#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353pub struct SelectStmt {
354    pub projections: Vec<Projection>,
355    /// Rows owned by a `VALUES` query body. `PostgreSQL` represents `VALUES`
356    /// through the same query node used for `SELECT`, so nested query bodies
357    /// such as CTEs and set-operation branches must retain them here.
358    #[serde(default, skip_serializing_if = "Vec::is_empty")]
359    pub values: Vec<Vec<Expr>>,
360    pub from: Option<FromClause>,
361    pub r#where: Option<Expr>,
362    pub group_by: Vec<Expr>,
363    /// Expanded GROUPING SETS / ROLLUP / CUBE specification. When
364    /// non-empty the executor produces one row per grouping set;
365    /// `group_by` is treated as a single grouping set in that case.
366    /// Each inner Vec lists the grouping-key expressions for that
367    /// set (an empty inner Vec means the global grand-total bucket).
368    pub grouping_sets: Vec<Vec<Expr>>,
369    /// `GROUP BY DISTINCT` -- remove duplicate grouping sets after grouping expressions have been resolved against their input types.
370    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
371    pub group_distinct: bool,
372    /// `HAVING <expr>`. Evaluated against each aggregated row and
373    /// filters out groups whose predicate is falsy. Mirrors PG's
374    /// `havingClause`.
375    pub having: Option<Expr>,
376    pub order_by: Vec<OrderBy>,
377    /// `LIMIT <expr>`. Stored as an expression so `LIMIT $1` and any
378    /// other constant-folding integer expression resolves at execute
379    /// time. `None` means no LIMIT clause was supplied.
380    pub limit: Option<Expr>,
381    /// `FETCH ... WITH TIES`. The row-count expression remains in [`Self::limit`]; this flag extends the boundary through every row whose complete `ORDER BY` key equals the last requested row.
382    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
383    pub with_ties: bool,
384    /// `OFFSET <expr>`. Same shape as [`SelectStmt::limit`].
385    pub offset: Option<Expr>,
386    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
387    pub with: Vec<CTE>,
388    /// Optional set operation: `Some` for UNION / INTERSECT / EXCEPT.
389    /// Parsed statements carry both operands in [`SetOp`]; `left` remains
390    /// optional only for backward-compatible deserialization.
391    pub set_op: Option<Box<SetOp>>,
392    /// `SELECT DISTINCT` -- de-duplicate the final result rows. Set by
393    /// the compiler whenever the parsed `distinct_clause` is non-empty.
394    pub distinct: bool,
395    /// `SELECT DISTINCT ON (<expr>, ...)` keys. Empty for plain
396    /// `SELECT DISTINCT`.
397    pub distinct_on: Vec<Expr>,
398    /// `FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE }` row-locking clauses, in source order. Empty when the query does not lock rows.
399    #[serde(default, skip_serializing_if = "Vec::is_empty")]
400    pub locking: Vec<LockingClause>,
401}
402
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
404pub struct SetOp {
405    pub kind: SetOpKind,
406    pub all: bool,
407    /// Explicit left-hand subtree. Parsed set operations are left-associative,
408    /// so a chain such as `a UNION b UNION c` carries `(a UNION b)` here
409    /// instead of flattening it back to only `a`.
410    #[serde(default, skip_serializing_if = "Option::is_none")]
411    pub left: Option<Box<SelectStmt>>,
412    pub right: SelectStmt,
413    /// `ORDER BY` applied to the combined `lhs <op> rhs` result.
414    /// Distinct from the LHS / RHS branches' own `ORDER BY`.
415    pub combined_order_by: Vec<OrderBy>,
416    /// `LIMIT` applied to the combined result. `None` means no
417    /// outer LIMIT clause was supplied.
418    pub combined_limit: Option<Expr>,
419    /// Whether the combined set-operation limit is `FETCH ... WITH TIES`.
420    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
421    pub combined_with_ties: bool,
422    /// `OFFSET` applied to the combined result.
423    pub combined_offset: Option<Expr>,
424}
425
426#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
427pub enum SetOpKind {
428    Union,
429    Intersect,
430    Except,
431}
432
433/// `DISCARD` target. Mirrors `PostgreSQL`'s `DiscardMode`.
434#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
435pub enum DiscardTarget {
436    All,
437    Plans,
438    Sequences,
439    Temp,
440}
441
442#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
443pub struct UpdateStmt {
444    pub table: String,
445    /// Whether `table` is a stored catalog identity rather than a name to resolve in the executing session.
446    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
447    pub target_relation_bound: bool,
448    pub target_qualifier: String,
449    #[serde(default = "default_include_descendants")]
450    pub include_descendants: bool,
451    pub assignments: Vec<(AssignmentTarget, Expr)>,
452    pub r#where: Option<Expr>,
453    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
454    pub with: Vec<CTE>,
455    /// `UPDATE t SET ... FROM other [JOIN ...]` -- the engine joins
456    /// the target with this clause before applying the assignments.
457    pub from: Option<FromClause>,
458    /// `RETURNING ...` projection list. Empty when absent.
459    pub returning: Vec<Projection>,
460    pub returning_aliases: ReturningAliases,
461}
462
463#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
464pub struct DeleteStmt {
465    pub table: String,
466    /// Whether `table` is a stored catalog identity rather than a name to resolve in the executing session.
467    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
468    pub target_relation_bound: bool,
469    pub target_qualifier: String,
470    #[serde(default = "default_include_descendants")]
471    pub include_descendants: bool,
472    pub r#where: Option<Expr>,
473    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
474    pub with: Vec<CTE>,
475    /// `DELETE FROM t USING other [JOIN ...]` -- the engine joins
476    /// the target with this clause and deletes target rows whose
477    /// joined image satisfies WHERE.
478    pub using: Option<FromClause>,
479    /// `RETURNING ...` projection list. Empty when absent.
480    pub returning: Vec<Projection>,
481    pub returning_aliases: ReturningAliases,
482}
483
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485pub struct SetConstraintName {
486    pub catalog: Option<String>,
487    pub schema: Option<String>,
488    pub name: String,
489}
490
491/// One parser-normalized `VACUUM` option. Execution validates options before rejecting transaction blocks, then resolves relation targets, matching `PostgreSQL`'s diagnostic order.
492#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
493pub struct VacuumOption {
494    pub name: String,
495    pub value: Option<VacuumOptionValue>,
496}
497
498#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
499pub enum VacuumOptionValue {
500    Boolean(bool),
501    Integer(i32),
502    String(String),
503}
504
505/// One relation (and optional ANALYZE column list) named by `VACUUM`.
506#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
507pub struct VacuumTarget {
508    pub catalog: Option<String>,
509    pub table: String,
510    #[serde(default = "default_include_descendants")]
511    pub include_descendants: bool,
512    #[serde(default, skip_serializing_if = "Vec::is_empty")]
513    pub columns: Vec<String>,
514}
515
516#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
517pub struct VacuumStmt {
518    #[serde(default, skip_serializing_if = "Vec::is_empty")]
519    pub options: Vec<VacuumOption>,
520    #[serde(default, skip_serializing_if = "Vec::is_empty")]
521    pub targets: Vec<VacuumTarget>,
522}
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
525pub enum Statement {
526    CreateDomain(CreateDomain),
527    CreateTable(CreateTable),
528    CreateTableIfNotExists(DeferredCreateTable),
529    CreateIndex(CreateIndex),
530    RenameIndex(RenameIndexStmt),
531    Insert(InsertStmt),
532    /// `SelectStmt` is the largest variant by far (CTEs + set-ops + n-ary
533    /// expression trees), so we box it to keep the enum's stack footprint
534    /// proportional to the smaller variants.
535    Select(Box<SelectStmt>),
536    Update(UpdateStmt),
537    Delete(DeleteStmt),
538    Drop(DropStmt),
539    AlterTable(AlterTableStmt),
540    AlterForeignTable(AlterForeignTableStmt),
541    AlterView(AlterViewStmt),
542    /// `CREATE [OR REPLACE] VIEW name [(column_name, ...)] AS SELECT ...`. The body is the underlying `SelectStmt`; views are materialised lazily on every reference (no row caching).
543    CreateView {
544        name: String,
545        #[serde(default)]
546        column_names: Vec<String>,
547        body: Box<SelectStmt>,
548        or_replace: bool,
549        #[serde(default)]
550        persistence: RelationPersistence,
551        /// Validated `PostgreSQL` view reloptions in declaration order.
552        #[serde(default, skip_serializing_if = "Vec::is_empty")]
553        options: Vec<(String, String)>,
554    },
555    /// `CREATE MATERIALIZED VIEW ... AS SELECT ... [WITH [NO] DATA]`.
556    CreateMaterializedView {
557        name: String,
558        #[serde(default)]
559        column_names: Vec<String>,
560        #[serde(default)]
561        if_not_exists: bool,
562        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
563        with_no_data: bool,
564        #[serde(default, skip_serializing_if = "Vec::is_empty")]
565        options: Vec<(String, String)>,
566        body: Box<SelectStmt>,
567    },
568    /// `REFRESH MATERIALIZED VIEW [CONCURRENTLY] name [WITH [NO] DATA]`.
569    RefreshMaterializedView {
570        name: String,
571        concurrently: bool,
572        with_no_data: bool,
573    },
574    /// `CREATE SCHEMA [IF NOT EXISTS] [name] [AUTHORIZATION role]`; an omitted name binds to the resolved owner.
575    CreateSchema {
576        name: Option<String>,
577        if_not_exists: bool,
578        #[serde(default, skip_serializing_if = "Option::is_none")]
579        authorization: Option<SchemaAuthorization>,
580    },
581    AlterSchemaOwner {
582        name: String,
583        new_owner: RoleSpecification,
584    },
585    /// `NOTIFY channel [, 'payload']` queues one asynchronous notification for delivery when the outer transaction commits.
586    Notify {
587        channel: String,
588        payload: String,
589    },
590    /// `LISTEN channel` transactionally subscribes the current SQL session.
591    Listen {
592        channel: String,
593    },
594    /// `UNLISTEN channel | *` transactionally removes one or every subscription. `None` represents `*`.
595    Unlisten {
596        channel: Option<String>,
597    },
598    /// `SET <name> [TO|=] <value>` - runtime parameter assignment.
599    /// The engine gives `search_path` resolution semantics and stores other
600    /// parameters in the logical session for subsequent `SHOW` statements.
601    SetVariable {
602        name: String,
603        value: String,
604        #[serde(default)]
605        local: bool,
606        #[serde(default)]
607        is_default: bool,
608    },
609    /// `RESET <name>` restores one runtime parameter to its session default.
610    ResetVariable {
611        name: String,
612    },
613    /// `RESET ALL` restores every resettable runtime parameter.
614    ResetAllVariables,
615    /// `SET CONSTRAINTS { ALL | name [, ...] } { DEFERRED | IMMEDIATE }`. An empty constraint list represents `ALL`; qualified names retain their SQL spelling so execution can apply schema-search semantics.
616    SetConstraints {
617        constraints: Vec<SetConstraintName>,
618        deferred: bool,
619    },
620    /// `SHOW <variable>` - return the runtime parameter as one
621    /// `(name -> value)` row.
622    ShowVariable {
623        name: String,
624    },
625    /// `DISCARD [ALL|PLANS|SEQUENCES|TEMP|TEMPORARY]` - clear session state.
626    /// The engine resets session variables, prepared statements, sequence state, and the current session's temporary relations as requested.
627    Discard {
628        target: DiscardTarget,
629    },
630    /// `LOAD 'library'` - load a shared library into the session. The
631    /// engine embeds its extension surface, so libraries it provides
632    /// natively (Apache AGE) load as no-ops and unknown libraries fail
633    /// like a missing `$libdir` file.
634    Load {
635        library: String,
636    },
637    /// `EXPLAIN ...`. Carries the inner statement so the engine can
638    /// emit the planner output.
639    Explain {
640        analyze: bool,
641        verbose: bool,
642        format: Option<String>,
643        body: Box<Statement>,
644    },
645    /// `ANALYZE [table]`. The engine refreshes per-column statistics
646    /// for cardinality estimation; the AST simply records the target.
647    Analyze {
648        table: Option<String>,
649    },
650    /// `VACUUM [options] [relations]`. Execution validates options, rejects transaction blocks, then resolves targets and dispatches storage maintenance in `PostgreSQL` order.
651    Vacuum(VacuumStmt),
652    /// `LOCK [TABLE] [ONLY] name [IN mode MODE] [NOWAIT]`.
653    LockTable(LockTableStmt),
654    /// `TRUNCATE TABLE t1, t2 ...`. Wipes the listed table hierarchies unless
655    /// a target uses `ONLY`.
656    Truncate {
657        tables: Vec<TruncateTarget>,
658        cascade: bool,
659        #[serde(default)]
660        restart_identity: bool,
661    },
662    /// `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT name`.
663    Transaction(TransactionStmt),
664    /// `DECLARE name [BINARY] [SCROLL] CURSOR [WITH HOLD] FOR query`.
665    DeclareCursor(DeclareCursorStmt),
666    /// `FETCH` or `MOVE` over a named SQL cursor.
667    FetchCursor(FetchCursorStmt),
668    /// `CLOSE name` or `CLOSE ALL`. `None` represents `ALL`.
669    CloseCursor {
670        name: Option<String>,
671    },
672    /// `CREATE SEQUENCE name [START n] [INCREMENT n]`.
673    CreateSequence(CreateSequence),
674    /// `ALTER SEQUENCE name [RESTART [WITH n]] [INCREMENT [BY] n]
675    /// [START [WITH] n]`.
676    AlterSequence(AlterSequence),
677    /// `CREATE TABLE name AS SELECT ...`.
678    CreateTableAs {
679        name: String,
680        if_not_exists: bool,
681        #[serde(default, skip_serializing_if = "Vec::is_empty")]
682        column_names: Vec<String>,
683        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
684        with_no_data: bool,
685        #[serde(default)]
686        persistence: RelationPersistence,
687        #[serde(default)]
688        on_commit: OnCommitAction,
689        body: Box<SelectStmt>,
690    },
691    /// `PREPARE name AS <inner>`.
692    Prepare {
693        name: String,
694        #[serde(default)]
695        parameter_types: Vec<ColumnType>,
696        body: Box<Statement>,
697    },
698    /// `EXECUTE name (param1, param2, ...)`.
699    Execute {
700        name: String,
701        params: Vec<Expr>,
702    },
703    /// `DEALLOCATE name | DEALLOCATE ALL`. `None` means ALL.
704    Deallocate {
705        name: Option<String>,
706    },
707    /// `SELECT * FROM (VALUES ...) [AS alias]` -- a standalone VALUES
708    /// statement (also reachable from a SET-OP body).
709    Values {
710        rows: Vec<Vec<Expr>>,
711    },
712    /// `CREATE SERVER name FOREIGN DATA WRAPPER type OPTIONS (...)`.
713    CreateForeignServer(CreateForeignServer),
714    /// `CREATE FOREIGN TABLE name (...) SERVER server OPTIONS (...)`.
715    CreateForeignTable(CreateForeignTable),
716    /// `CREATE FOREIGN TABLE IF NOT EXISTS` retains its raw-parser declaration until execution can check the shared relation namespace.
717    CreateForeignTableIfNotExists(DeferredCreateForeignTable),
718    /// `MERGE INTO target USING source ON cond WHEN MATCHED THEN ...
719    /// WHEN NOT MATCHED THEN ...`. SQL:2003 conditional UPSERT.
720    Merge(MergeStmt),
721    /// `CREATE [OR REPLACE] FUNCTION | PROCEDURE ...`. Boxed: the
722    /// definition (parameters + body source) dwarfs other variants.
723    CreateFunction(Box<CreateFunction>),
724    /// `DROP FUNCTION | PROCEDURE [IF EXISTS] name[(args)] [, ...]`.
725    DropFunction(DropFunctionStmt),
726    /// `ALTER FUNCTION | PROCEDURE | ROUTINE name[(input_types)]` volatility and null-input attributes.
727    AlterRoutine(AlterRoutineStmt),
728    AlterRoutineOwner(AlterRoutineOwnerStmt),
729    RenameRoutine(RenameRoutineStmt),
730    GrantRoutine(GrantRoutineStmt),
731    GrantTable(GrantTableStmt),
732    GrantSequence(GrantSequenceStmt),
733    GrantDatabase(GrantDatabaseStmt),
734    GrantSchema(GrantSchemaStmt),
735    GrantRole(GrantRoleStmt),
736    CreateRole(CreateRoleStmt),
737    AlterRole(AlterRoleStmt),
738    RenameRole(RenameRoleStmt),
739    DropRole(DropRoleStmt),
740    /// `CREATE [OR REPLACE] TRIGGER ... ON relation`.
741    CreateTrigger(CreateTrigger),
742    /// `DROP TRIGGER [IF EXISTS] name ON relation`.
743    DropTrigger(DropTrigger),
744    /// `CREATE [OR REPLACE] RULE ... ON relation`.
745    CreateRule(CreateRule),
746    /// `DROP RULE [IF EXISTS] name ON relation`.
747    DropRule(DropRule),
748    /// `DO [LANGUAGE lang] $$ ... $$` - anonymous code block.
749    DoBlock {
750        language: String,
751        body: String,
752    },
753    /// `CALL proc(args)` - procedure invocation. `OUT` / `INOUT`
754    /// parameters shape the result row.
755    Call {
756        name: String,
757        args: Vec<Expr>,
758    },
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
762pub struct TruncateTarget {
763    pub table: String,
764    #[serde(default = "default_include_descendants")]
765    pub include_descendants: bool,
766}
767
768#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
769pub struct MergeTargetColumnBinding {
770    pub object_id: [u8; 16],
771    /// Domain identities used by non-DEFAULT assignment coercions, including after target deletion.
772    #[serde(default, skip_serializing_if = "std::collections::BTreeSet::is_empty")]
773    pub domain_dependencies: std::collections::BTreeSet<u32>,
774}
775
776#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
777pub struct MergeStmt {
778    #[serde(default)]
779    pub with: Vec<CTE>,
780    pub target: String,
781    pub target_qualifier: String,
782    pub target_alias: Option<String>,
783    /// Creation-bound write targets in a stored body. Removed identities retain their expressions and dependencies but receive no writes.
784    #[serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")]
785    pub target_column_bindings: std::collections::BTreeMap<String, MergeTargetColumnBinding>,
786    #[serde(default = "default_include_descendants")]
787    pub include_descendants: bool,
788    pub source: FromClause,
789    pub join_condition: Expr,
790    pub when_clauses: Vec<MergeWhen>,
791    /// `MERGE ... RETURNING ...` projection list. Empty when absent.
792    pub returning: Vec<Projection>,
793    pub returning_aliases: ReturningAliases,
794}
795
796#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
797pub enum MergeWhen {
798    /// `WHEN MATCHED [AND <cond>] THEN UPDATE SET ...`.
799    UpdateMatched {
800        condition: Option<Expr>,
801        assignments: Vec<(AssignmentTarget, Expr)>,
802    },
803    /// `WHEN MATCHED [AND <cond>] THEN DELETE`.
804    DeleteMatched { condition: Option<Expr> },
805    /// `WHEN NOT MATCHED BY SOURCE [AND <cond>] THEN UPDATE SET ...`.
806    UpdateNotMatchedBySource {
807        condition: Option<Expr>,
808        assignments: Vec<(AssignmentTarget, Expr)>,
809    },
810    /// `WHEN NOT MATCHED BY SOURCE [AND <cond>] THEN DELETE`.
811    DeleteNotMatchedBySource { condition: Option<Expr> },
812    /// `WHEN NOT MATCHED [AND <cond>] THEN INSERT (cols) VALUES (vals)`.
813    InsertNotMatched {
814        condition: Option<Expr>,
815        columns: Vec<AssignmentTarget>,
816        values: Vec<Expr>,
817    },
818    /// `WHEN MATCHED [AND <cond>] THEN DO NOTHING`.
819    NothingMatched { condition: Option<Expr> },
820    /// `WHEN NOT MATCHED [AND <cond>] THEN DO NOTHING`.
821    NothingNotMatched { condition: Option<Expr> },
822    /// `WHEN NOT MATCHED BY SOURCE [AND <cond>] THEN DO NOTHING`.
823    NothingNotMatchedBySource { condition: Option<Expr> },
824}
825
826#[derive(Debug, Clone, Serialize, Deserialize)]
827pub struct CreateForeignServer {
828    pub name: String,
829    pub fdw_type: String,
830    pub options: Vec<(String, String)>,
831    pub if_not_exists: bool,
832}
833
834#[derive(Debug, Clone, Serialize, Deserialize)]
835pub struct CreateForeignTable {
836    pub name: String,
837    pub server_name: String,
838    pub columns: Vec<ColumnDef>,
839    #[serde(default)]
840    pub checks: Vec<TableCheck>,
841    pub options: Vec<(String, String)>,
842    pub if_not_exists: bool,
843}
844
845/// A syntactically valid `CREATE FOREIGN TABLE IF NOT EXISTS` whose definition must be analyzed only when its target name is free.
846#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
847pub struct DeferredCreateForeignTable {
848    pub name: String,
849    pub server_name: String,
850    pub definition_sql: String,
851}
852
853#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
854pub enum TransactionIsolationLevel {
855    ReadUncommitted,
856    ReadCommitted,
857    RepeatableRead,
858    Serializable,
859}
860
861impl TransactionIsolationLevel {
862    #[must_use]
863    pub const fn as_str(self) -> &'static str {
864        match self {
865            Self::ReadUncommitted => "read uncommitted",
866            Self::ReadCommitted => "read committed",
867            Self::RepeatableRead => "repeatable read",
868            Self::Serializable => "serializable",
869        }
870    }
871}
872
873#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
874pub struct TransactionCharacteristics {
875    pub isolation: Option<TransactionIsolationLevel>,
876    pub read_only: Option<bool>,
877    pub deferrable: Option<bool>,
878}
879
880#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
881pub enum TransactionStmt {
882    Begin,
883    BeginWithCharacteristics(TransactionCharacteristics),
884    Commit,
885    CommitAndChain,
886    Rollback,
887    RollbackAndChain,
888    SetCharacteristics(TransactionCharacteristics),
889    SetSessionCharacteristics(TransactionCharacteristics),
890    SetSnapshot(String),
891    Savepoint(String),
892    ReleaseSavepoint(String),
893    RollbackToSavepoint(String),
894}
895
896#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
897pub enum CursorDirection {
898    Forward,
899    Backward,
900    Absolute,
901    Relative,
902}
903
904#[derive(Debug, Clone, Serialize, Deserialize)]
905pub struct DeclareCursorStmt {
906    pub name: String,
907    pub binary: bool,
908    /// `None` lets the query determine scrollability, while `Some(true)` and `Some(false)` represent explicit `SCROLL` and `NO SCROLL`.
909    pub scroll: Option<bool>,
910    pub hold: bool,
911    pub query: Box<SelectStmt>,
912}
913
914#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
915pub struct FetchCursorStmt {
916    pub name: String,
917    pub direction: CursorDirection,
918    /// `PostgreSQL` uses `i64::MAX` for `ALL`; negative counts reverse `FORWARD` and `BACKWARD`.
919    pub count: i64,
920    pub move_only: bool,
921}
922
923#[cfg(test)]
924mod tests;