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