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