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