Skip to main content

crabka_pgparser/
ast.rs

1//! The crabgresql AST for the SP2 slice.
2
3use crabka_pgtypes::{ColumnType, Datum};
4
5#[rustfmt::skip]
6#[derive(Debug, Clone, PartialEq)]
7pub enum Statement {
8    /// A `PostgreSQL` command that is recognized deliberately but cannot be
9    /// executed by the Gres architecture. Metadata lives on [`RefusalCommand`]
10    /// so parser, session, and compatibility tooling share one contract.
11    CompatibilityRefusal(RefusalCommand),
12    CreateTable {
13        name: String,
14        columns: Vec<ColumnDef>,
15        constraints: Vec<TableConstraint>,
16        sharded: bool,
17        sharding: Option<ShardingSpec>,
18    },
19    CreateIndex {
20        name: String,
21        table: String,
22        columns: Vec<String>,
23        unique: bool,
24        placement: IndexPlacement,
25    },
26    DropIndex {
27        name: String,
28        if_exists: bool,
29    },
30    CreateView {
31        name: String,
32        /// Exact query text following `AS`, retained for durable catalog storage.
33        definition: String,
34        /// Parsed definition used by the executor to validate the view schema.
35        query: QueryExpr,
36    },
37    DropTable {
38        name: String,
39    },
40    DropView {
41        name: String,
42        if_exists: bool,
43    },
44    /// Bounded `ALTER TABLE` rename support. Column renames are parsed so they
45    /// can fail with a clear unsupported-feature error until dependencies can be
46    /// rewritten safely.
47    AlterTableRename {
48        table: String,
49        rename: AlterTableRename,
50    },
51    Insert {
52        table: String,
53        columns: Option<Vec<String>>,
54        rows: Vec<Vec<Expr>>,
55        returning: Option<Vec<SelectItem>>,
56    },
57    Query(QueryExpr),
58    Begin {
59        isolation: Option<IsolationLevel>,
60    },
61    Commit,
62    Rollback,
63    Update {
64        table: String,
65        assignments: Vec<(String, Expr)>,
66        filter: Option<Expr>,
67        returning: Option<Vec<SelectItem>>,
68    },
69    Delete {
70        table: String,
71        filter: Option<Expr>,
72        returning: Option<Vec<SelectItem>>,
73    },
74    /// SP37: `SET [LOCAL] <name> = <value>` / `SET <name> TO <value>` / `SET TIME ZONE ...`.
75    Set {
76        local: bool,
77        name: String,
78        value: SetValue,
79    },
80    /// SP37: `SHOW <name>` / `SHOW TIME ZONE`.
81    Show {
82        name: String,
83    },
84    /// SP37: `RESET <name>`.
85    Reset {
86        target: ResetTarget,
87    },
88    /* SQL parity matrix row: CREATE ROLE / CREATE USER. */ CreateRole {
89        name: String,
90        can_login: bool,
91    },
92    /* SQL parity matrix row: DROP ROLE / DROP USER. */ DropRole {
93        name: String,
94    },
95    /* SQL parity matrix row: GRANT. */ GrantTablePrivileges {
96        privileges: Vec<String>,
97        table: String,
98        grantees: Vec<String>,
99    },
100    /* SQL parity matrix row: REVOKE. */ RevokeTablePrivileges {
101        privileges: Vec<String>,
102        table: String,
103        grantees: Vec<String>,
104    },
105    /* SQL parity matrix row: SET ROLE. */ SetRole {
106        role: Option<String>,
107    },
108    // SP40: FDW DDL
109    /// `CREATE FOREIGN DATA WRAPPER <name> OPTIONS (…)`
110    CreateFdw {
111        name: String,
112        options: OptionList,
113    },
114    /// `DROP FOREIGN DATA WRAPPER [IF EXISTS] <name>`
115    DropFdw {
116        name: String,
117        if_exists: bool,
118    },
119    /// `CREATE SERVER <name> FOREIGN DATA WRAPPER <wrapper> OPTIONS (…)`
120    CreateServer {
121        name: String,
122        wrapper: String,
123        options: OptionList,
124    },
125    /// `ALTER SERVER <name> OPTIONS (…)`
126    AlterServer {
127        name: String,
128        options: OptionList,
129    },
130    /// `DROP SERVER [IF EXISTS] <name>`
131    DropServer {
132        name: String,
133        if_exists: bool,
134    },
135    /// `CREATE USER MAPPING FOR <user> SERVER <server> OPTIONS (…)`
136    CreateUserMapping {
137        user: String,
138        server: String,
139        options: OptionList,
140    },
141    /// `ALTER USER MAPPING FOR <user> SERVER <server> OPTIONS (…)`
142    AlterUserMapping {
143        user: String,
144        server: String,
145        options: OptionList,
146    },
147    /// `DROP USER MAPPING [IF EXISTS] FOR <user> SERVER <server>`
148    DropUserMapping {
149        user: String,
150        server: String,
151        if_exists: bool,
152    },
153    /// `CREATE FOREIGN TABLE <name> (<col> <type>, …) SERVER <server> OPTIONS (…)`
154    CreateForeignTable {
155        name: String,
156        columns: Vec<ColumnDef>,
157        server: String,
158        options: OptionList,
159    },
160    /// `DROP FOREIGN TABLE [IF EXISTS] <name>`
161    DropForeignTable {
162        name: String,
163        if_exists: bool,
164    },
165    /// `IMPORT FOREIGN SCHEMA <remote_schema> [LIMIT TO | EXCEPT (<tables>)] FROM SERVER <server> [INTO <local_schema>]`
166    ImportForeignSchema {
167        remote_schema: String,
168        selector: ImportSelector,
169        server: String,
170        into_schema: String,
171    },
172}
173
174/// Stable, typed metadata for commands that parse normally and then fail clear.
175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum RefusalCommand {
177    AlterDatabase,
178    CreateDatabase,
179    DropDatabase,
180    AlterExtension,
181    DropExtension,
182    PrepareTransaction,
183    CommitPrepared,
184    RollbackPrepared,
185    AlterServer,
186    AlterUserMapping,
187    NonGoal(NonGoalCommand),
188}
189
190impl RefusalCommand {
191    #[must_use]
192    pub const fn command_name(self) -> &'static str {
193        match self {
194            Self::AlterDatabase => "ALTER DATABASE",
195            Self::CreateDatabase => "CREATE DATABASE",
196            Self::DropDatabase => "DROP DATABASE",
197            Self::AlterExtension => "ALTER EXTENSION",
198            Self::DropExtension => "DROP EXTENSION",
199            Self::PrepareTransaction => "PREPARE TRANSACTION",
200            Self::CommitPrepared => "COMMIT PREPARED",
201            Self::RollbackPrepared => "ROLLBACK PREPARED",
202            Self::AlterServer => "ALTER SERVER",
203            Self::AlterUserMapping => "ALTER USER MAPPING",
204            Self::NonGoal(command) => command.command_name(),
205        }
206    }
207
208    #[must_use]
209    pub const fn sqlstate(self) -> &'static str {
210        match self {
211            Self::PrepareTransaction | Self::CommitPrepared | Self::RollbackPrepared => "55000",
212            _ => "0A000",
213        }
214    }
215
216    #[must_use]
217    pub const fn message(self) -> &'static str {
218        match self {
219            Self::AlterDatabase | Self::CreateDatabase | Self::DropDatabase => {
220                "database lifecycle is managed by tenant provisioning"
221            }
222            Self::AlterExtension | Self::DropExtension => {
223                "extension lifecycle is not supported; use built-in compatibility shims"
224            }
225            Self::PrepareTransaction | Self::CommitPrepared | Self::RollbackPrepared => {
226                "SQL-level prepared transactions are not available"
227            }
228            Self::AlterServer => "ALTER SERVER is not supported",
229            Self::AlterUserMapping => "ALTER USER MAPPING is not supported",
230            Self::NonGoal(command) => command.message(),
231        }
232    }
233}
234
235impl Statement {
236    /// Return the centralized refusal contract for richer refusal AST variants.
237    #[must_use]
238    pub const fn compatibility_refusal(&self) -> Option<RefusalCommand> {
239        match self {
240            Self::CompatibilityRefusal(command) => Some(*command),
241            Self::AlterServer { .. } => Some(RefusalCommand::AlterServer),
242            Self::AlterUserMapping { .. } => Some(RefusalCommand::AlterUserMapping),
243            _ => None,
244        }
245    }
246}
247
248/// Architectural non-goal commands tracked by the `PostgreSQL` compatibility matrix.
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub enum NonGoalCommand {
251    AlterConversion,
252    AlterLanguage,
253    AlterLargeObject,
254    AlterOperator,
255    AlterOperatorClass,
256    AlterOperatorFamily,
257    AlterPublication,
258    AlterRule,
259    AlterSubscription,
260    AlterTablespace,
261    AlterTextSearchParser,
262    AlterTextSearchTemplate,
263    CreateAccessMethod,
264    CreateConversion,
265    CreateLanguage,
266    CreateOperator,
267    CreateOperatorClass,
268    CreateOperatorFamily,
269    CreatePublication,
270    CreateRule,
271    CreateSubscription,
272    CreateTablespace,
273    CreateTextSearchParser,
274    CreateTextSearchTemplate,
275    CreateTransform,
276    DropAccessMethod,
277    DropConversion,
278    DropLanguage,
279    DropOperator,
280    DropOperatorClass,
281    DropOperatorFamily,
282    DropPublication,
283    DropRule,
284    DropSubscription,
285    DropTablespace,
286    DropTextSearchParser,
287    DropTextSearchTemplate,
288    DropTransform,
289    Load,
290    SecurityLabel,
291}
292
293impl NonGoalCommand {
294    #[must_use]
295    pub const fn command_name(self) -> &'static str {
296        match self {
297            Self::AlterConversion => "ALTER CONVERSION",
298            Self::AlterLanguage => "ALTER LANGUAGE",
299            Self::AlterLargeObject => "ALTER LARGE OBJECT",
300            Self::AlterOperator => "ALTER OPERATOR",
301            Self::AlterOperatorClass => "ALTER OPERATOR CLASS",
302            Self::AlterOperatorFamily => "ALTER OPERATOR FAMILY",
303            Self::AlterPublication => "ALTER PUBLICATION",
304            Self::AlterRule => "ALTER RULE",
305            Self::AlterSubscription => "ALTER SUBSCRIPTION",
306            Self::AlterTablespace => "ALTER TABLESPACE",
307            Self::AlterTextSearchParser => "ALTER TEXT SEARCH PARSER",
308            Self::AlterTextSearchTemplate => "ALTER TEXT SEARCH TEMPLATE",
309            Self::CreateAccessMethod => "CREATE ACCESS METHOD",
310            Self::CreateConversion => "CREATE CONVERSION",
311            Self::CreateLanguage => "CREATE LANGUAGE",
312            Self::CreateOperator => "CREATE OPERATOR",
313            Self::CreateOperatorClass => "CREATE OPERATOR CLASS",
314            Self::CreateOperatorFamily => "CREATE OPERATOR FAMILY",
315            Self::CreatePublication => "CREATE PUBLICATION",
316            Self::CreateRule => "CREATE RULE",
317            Self::CreateSubscription => "CREATE SUBSCRIPTION",
318            Self::CreateTablespace => "CREATE TABLESPACE",
319            Self::CreateTextSearchParser => "CREATE TEXT SEARCH PARSER",
320            Self::CreateTextSearchTemplate => "CREATE TEXT SEARCH TEMPLATE",
321            Self::CreateTransform => "CREATE TRANSFORM",
322            Self::DropAccessMethod => "DROP ACCESS METHOD",
323            Self::DropConversion => "DROP CONVERSION",
324            Self::DropLanguage => "DROP LANGUAGE",
325            Self::DropOperator => "DROP OPERATOR",
326            Self::DropOperatorClass => "DROP OPERATOR CLASS",
327            Self::DropOperatorFamily => "DROP OPERATOR FAMILY",
328            Self::DropPublication => "DROP PUBLICATION",
329            Self::DropRule => "DROP RULE",
330            Self::DropSubscription => "DROP SUBSCRIPTION",
331            Self::DropTablespace => "DROP TABLESPACE",
332            Self::DropTextSearchParser => "DROP TEXT SEARCH PARSER",
333            Self::DropTextSearchTemplate => "DROP TEXT SEARCH TEMPLATE",
334            Self::DropTransform => "DROP TRANSFORM",
335            Self::Load => "LOAD",
336            Self::SecurityLabel => "SECURITY LABEL",
337        }
338    }
339
340    #[must_use]
341    pub const fn message(self) -> &'static str {
342        match self {
343            Self::AlterConversion | Self::CreateConversion | Self::DropConversion => {
344                "conversion objects are unavailable on the UTF-8-only server"
345            }
346            Self::AlterLanguage | Self::CreateLanguage | Self::DropLanguage => {
347                "only built-in procedural languages are available"
348            }
349            Self::AlterLargeObject => "large objects are unavailable; use bytea storage",
350            Self::AlterOperator
351            | Self::AlterOperatorClass
352            | Self::AlterOperatorFamily
353            | Self::CreateOperator
354            | Self::CreateOperatorClass
355            | Self::CreateOperatorFamily
356            | Self::DropOperator
357            | Self::DropOperatorClass
358            | Self::DropOperatorFamily => "C-bound operator objects are not supported",
359            Self::AlterPublication
360            | Self::AlterSubscription
361            | Self::CreatePublication
362            | Self::CreateSubscription
363            | Self::DropPublication
364            | Self::DropSubscription => "physical replication SQL is not supported",
365            Self::AlterRule | Self::CreateRule | Self::DropRule => {
366                "the legacy rewrite rule system is not supported"
367            }
368            Self::AlterTablespace | Self::CreateTablespace | Self::DropTablespace => {
369                "tablespaces are not part of the chapter storage model"
370            }
371            Self::AlterTextSearchParser
372            | Self::AlterTextSearchTemplate
373            | Self::CreateTextSearchParser
374            | Self::CreateTextSearchTemplate
375            | Self::DropTextSearchParser
376            | Self::DropTextSearchTemplate => "C-bound text search objects are not supported",
377            Self::CreateAccessMethod | Self::DropAccessMethod => {
378                "C-bound access methods are not supported"
379            }
380            Self::CreateTransform | Self::DropTransform => {
381                "C-bound transform objects are not supported"
382            }
383            Self::Load => "C-bound code loading is not supported",
384            Self::SecurityLabel => "C-bound security labels are not supported",
385        }
386    }
387}
388
389/// One bounded `PostgreSQL` 18 syntax representative for an architectural refusal.
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub struct NonGoalRefusalSpec {
392    pub command: RefusalCommand,
393    pub identity: crate::command::CommandIdentity,
394    pub representative_sql: &'static str,
395}
396
397macro_rules! non_goal_specs {
398    ($(($variant:ident, $sql:literal)),+ $(,)?) => {
399        pub const NON_GOAL_REFUSALS: &[NonGoalRefusalSpec] = &[
400            $(NonGoalRefusalSpec {
401                command: RefusalCommand::NonGoal(NonGoalCommand::$variant),
402                identity: crate::command::CommandIdentity::$variant,
403                representative_sql: $sql,
404            }),+
405        ];
406    };
407}
408
409non_goal_specs!(
410    (AlterConversion, "ALTER CONVERSION conv RENAME TO conv2"),
411    (AlterLanguage, "ALTER LANGUAGE lang RENAME TO lang2"),
412    (AlterLargeObject, "ALTER LARGE OBJECT 1 OWNER TO postgres"),
413    (
414        AlterOperator,
415        "ALTER OPERATOR +(integer, integer) OWNER TO postgres"
416    ),
417    (
418        AlterOperatorClass,
419        "ALTER OPERATOR CLASS opc USING btree RENAME TO opc2"
420    ),
421    (
422        AlterOperatorFamily,
423        "ALTER OPERATOR FAMILY opf USING btree RENAME TO opf2"
424    ),
425    (AlterPublication, "ALTER PUBLICATION pub ADD TABLE t"),
426    (AlterRule, "ALTER RULE r ON t RENAME TO r2"),
427    (AlterSubscription, "ALTER SUBSCRIPTION sub DISABLE"),
428    (AlterTablespace, "ALTER TABLESPACE ts RENAME TO ts2"),
429    (
430        AlterTextSearchParser,
431        "ALTER TEXT SEARCH PARSER p RENAME TO p2"
432    ),
433    (
434        AlterTextSearchTemplate,
435        "ALTER TEXT SEARCH TEMPLATE t RENAME TO t2"
436    ),
437    (
438        CreateAccessMethod,
439        "CREATE ACCESS METHOD am TYPE INDEX HANDLER handler_fn"
440    ),
441    (
442        CreateConversion,
443        "CREATE CONVERSION conv FOR 'UTF8' TO 'LATIN1' FROM func"
444    ),
445    (CreateLanguage, "CREATE LANGUAGE lang"),
446    (
447        CreateOperator,
448        "CREATE OPERATOR === (FUNCTION = int4eq, LEFTARG = integer, RIGHTARG = integer)"
449    ),
450    (
451        CreateOperatorClass,
452        "CREATE OPERATOR CLASS opc FOR TYPE integer USING btree AS OPERATOR 1 < (integer, integer)"
453    ),
454    (
455        CreateOperatorFamily,
456        "CREATE OPERATOR FAMILY opf USING btree"
457    ),
458    (CreatePublication, "CREATE PUBLICATION pub"),
459    (
460        CreateRule,
461        "CREATE RULE r AS ON SELECT TO t DO INSTEAD NOTHING"
462    ),
463    (
464        CreateSubscription,
465        "CREATE SUBSCRIPTION sub CONNECTION 'host=x' PUBLICATION pub"
466    ),
467    (CreateTablespace, "CREATE TABLESPACE ts LOCATION '/tmp/ts'"),
468    (
469        CreateTextSearchParser,
470        "CREATE TEXT SEARCH PARSER p (START = f, GETTOKEN = f, END = f, LEXTYPES = f)"
471    ),
472    (
473        CreateTextSearchTemplate,
474        "CREATE TEXT SEARCH TEMPLATE t (LEXIZE = f)"
475    ),
476    (
477        CreateTransform,
478        "CREATE TRANSFORM FOR integer LANGUAGE sql (FROM SQL WITH FUNCTION f(integer), TO SQL WITH FUNCTION f(integer))"
479    ),
480    (DropAccessMethod, "DROP ACCESS METHOD am"),
481    (DropConversion, "DROP CONVERSION conv"),
482    (DropLanguage, "DROP LANGUAGE lang"),
483    (DropOperator, "DROP OPERATOR +(integer, integer)"),
484    (DropOperatorClass, "DROP OPERATOR CLASS opc USING btree"),
485    (DropOperatorFamily, "DROP OPERATOR FAMILY opf USING btree"),
486    (DropPublication, "DROP PUBLICATION pub"),
487    (DropRule, "DROP RULE r ON t"),
488    (DropSubscription, "DROP SUBSCRIPTION sub"),
489    (DropTablespace, "DROP TABLESPACE ts"),
490    (DropTextSearchParser, "DROP TEXT SEARCH PARSER p"),
491    (DropTextSearchTemplate, "DROP TEXT SEARCH TEMPLATE t"),
492    (DropTransform, "DROP TRANSFORM FOR integer LANGUAGE sql"),
493    (Load, "LOAD 'lib'"),
494    (SecurityLabel, "SECURITY LABEL ON TABLE t IS 'label'"),
495);
496
497#[derive(Debug, Clone, PartialEq, Eq)]
498pub enum AlterTableRename {
499    Table { new_name: String },
500    Column { column: String, new_name: String },
501}
502
503#[derive(Debug, Clone, PartialEq, Eq)]
504pub struct CopyStmt {
505    pub table: String,
506    pub columns: Option<Vec<String>>,
507    pub format: CopyFormat,
508}
509
510pub const COPY_FROM_STDIN_SENTINEL: &str = "__copy_from_stdin";
511
512#[derive(Debug, Clone, Copy, PartialEq, Eq)]
513pub enum CopyFormat {
514    Text,
515    Csv,
516}
517
518#[derive(Debug, Clone, PartialEq, Eq)]
519pub enum ResetTarget {
520    Name(String),
521    All,
522}
523
524#[derive(Debug, Clone, Copy, PartialEq, Eq)]
525pub enum IndexPlacement {
526    Local,
527    Global,
528}
529
530#[derive(Debug, Clone, PartialEq, Eq)]
531pub enum ShardingSpec {
532    Hash(HashShardingSpec),
533}
534
535#[derive(Debug, Clone, PartialEq, Eq)]
536pub struct HashShardingSpec {
537    pub columns: Vec<String>,
538    pub buckets: u32,
539    pub co_location_group: Option<String>,
540}
541
542/// A key-value option list for FDW DDL: `OPTIONS (key 'value', …)`.
543pub type OptionList = Vec<(String, String)>;
544
545/// The optional table-filter for `IMPORT FOREIGN SCHEMA`.
546#[derive(Debug, Clone, PartialEq, Eq)]
547pub enum ImportSelector {
548    /// Import all tables (no filter clause).
549    All,
550    /// `LIMIT TO (table, …)` — import only the listed tables.
551    LimitTo(Vec<String>),
552    /// `EXCEPT (table, …)` — import all tables except the listed ones.
553    Except(Vec<String>),
554}
555
556/// SP37: the right-hand side of a `SET` (or the value form of `SET TIME ZONE`).
557/// `Default` is `SET ... = DEFAULT` / `SET TIME ZONE { DEFAULT | LOCAL }` (resets
558/// the parameter to its built-in default); `Value` is a literal/identifier value.
559#[derive(Debug, Clone, PartialEq, Eq)]
560pub enum SetValue {
561    Default,
562    Value(String),
563}
564
565/// Transaction isolation levels supported by SP4.
566#[derive(Debug, Clone, Copy, PartialEq, Eq)]
567pub enum IsolationLevel {
568    ReadCommitted,
569    RepeatableRead,
570}
571
572#[derive(Debug, Clone, PartialEq)]
573pub struct ColumnDef {
574    pub name: String,
575    pub ty: ColumnType,
576    pub serial: Option<SerialKind>,
577    pub constraints: Vec<ColumnConstraint>,
578}
579
580#[derive(Debug, Clone, Copy, PartialEq, Eq)]
581pub enum SerialKind {
582    Serial,
583    BigSerial,
584}
585
586#[derive(Debug, Clone, PartialEq, Eq, Default)]
587pub struct SequenceOptions {
588    pub start: Option<i64>,
589    pub increment: Option<i64>,
590    pub min: Option<i64>,
591    pub max: Option<i64>,
592    pub cache: Option<i64>,
593    pub cycle: Option<bool>,
594}
595
596#[derive(Debug, Clone, PartialEq)]
597pub enum ColumnConstraint {
598    NotNull,
599    Default(Expr),
600    PrimaryKey,
601    Unique,
602    Check(Expr),
603}
604
605#[derive(Debug, Clone, PartialEq)]
606pub enum TableConstraint {
607    PrimaryKey(Vec<String>),
608    Unique(Vec<String>),
609    Check(Expr),
610}
611
612#[derive(Debug, Clone, Copy, PartialEq, Eq)]
613pub enum RowLockStrength {
614    ForUpdate,
615    ForShare,
616}
617
618#[derive(Debug, Clone, PartialEq)]
619pub struct SelectStmt {
620    pub projection: Vec<SelectItem>,
621    /// SP33: the FROM clause — a list of join trees. Empty for a FROM-less SELECT;
622    /// the comma form (`FROM a, b`) is a `Vec<TableExpr>` with len > 1 (implicit
623    /// cross join).
624    pub from: Vec<TableExpr>,
625    pub filter: Option<Expr>,
626    /// SP28: `SELECT DISTINCT` — dedup the projected output rows.
627    pub distinct: bool,
628    /// SP27: `GROUP BY <expr-list>` (empty when absent).
629    pub group_by: Vec<Expr>,
630    /// SP27: `HAVING <predicate>` (evaluated per group).
631    pub having: Option<Expr>,
632    pub order_by: Vec<OrderItem>,
633    pub limit: Option<i64>,
634    /// SP28: `OFFSET <n>` — skip the first `n` output rows (before LIMIT).
635    pub offset: Option<i64>,
636    pub locking: Option<RowLockStrength>,
637}
638
639/// A complete row-producing SQL query expression. The body may be a lone SELECT,
640/// a lone VALUES list, or a set-operation tree. The tail applies to the complete
641/// query expression.
642#[derive(Debug, Clone, PartialEq)]
643pub struct QueryExpr {
644    pub with: Option<WithClause>,
645    pub body: SetExpr,
646    pub order_by: Vec<OrderItem>,
647    pub limit: Option<i64>,
648    pub offset: Option<i64>,
649    pub locking: Option<RowLockStrength>,
650}
651
652/// SP39: a VALUES row constructor list. Every row is non-empty; cross-row arity
653/// is checked during executor analysis so it gets `PostgreSQL`'s analysis SQLSTATE.
654#[derive(Debug, Clone, PartialEq)]
655pub struct ValuesStmt {
656    pub rows: Vec<Vec<Expr>>,
657}
658
659#[derive(Debug, Clone, PartialEq)]
660pub struct WithClause {
661    pub recursive: bool,
662    pub ctes: Vec<Cte>,
663}
664
665#[derive(Debug, Clone, PartialEq)]
666pub struct Cte {
667    pub name: String,
668    pub columns: Option<Vec<String>>,
669    pub query: QueryExpr,
670}
671
672/// SP39: query bodies that may appear as set-operation leaves or derived tables.
673#[derive(Debug, Clone, PartialEq)]
674pub enum QueryBody {
675    Select(Box<SelectStmt>),
676    Values(ValuesStmt),
677    Nested(Box<QueryExpr>),
678}
679
680/// SP38: a node in the set-operation tree. A `Query` leaf is one query block; a
681/// `SetOp` combines two sub-trees. INTERSECT binds tighter than UNION/EXCEPT;
682/// UNION/EXCEPT are left-associative (the parser encodes this in the tree shape).
683#[derive(Debug, Clone, PartialEq)]
684pub enum SetExpr {
685    Query(QueryBody),
686    SetOp {
687        op: SetOp,
688        /// `true` for `… ALL …` (keep duplicates); `false` for the default
689        /// (duplicate-eliminating) form.
690        all: bool,
691        left: Box<SetExpr>,
692        right: Box<SetExpr>,
693    },
694}
695
696#[derive(Debug, Clone, Copy, PartialEq, Eq)]
697pub enum SetOp {
698    Union,
699    Intersect,
700    Except,
701}
702
703#[derive(Debug, Clone, PartialEq)]
704pub enum SelectItem {
705    Wildcard,
706    /// SP33: `a.*` — every column of one table in scope.
707    QualifiedWildcard(String),
708    Expr {
709        expr: Expr,
710        alias: Option<String>,
711    },
712}
713
714/// SP33: one entry in the FROM clause — a base table, a derived table
715/// (subquery), or a join of two table-exprs. The comma form (`FROM a, b`) is a
716/// `Vec<TableExpr>` with len > 1 (implicit cross join).
717#[derive(Debug, Clone, PartialEq)]
718pub enum TableExpr {
719    Table {
720        name: String,
721        alias: Option<String>,
722    },
723    Derived {
724        subquery: QueryExpr,
725        alias: String, // PG requires a derived table to be aliased
726        columns: Option<Vec<String>>,
727    },
728    Join {
729        left: Box<TableExpr>,
730        right: Box<TableExpr>,
731        kind: JoinKind,
732        constraint: JoinConstraint,
733    },
734}
735
736#[derive(Debug, Clone, Copy, PartialEq, Eq)]
737pub enum JoinKind {
738    Inner,
739    Left,
740    Right,
741    Full,
742    Cross,
743}
744
745#[derive(Debug, Clone, PartialEq)]
746pub enum JoinConstraint {
747    On(Expr),
748    Using(Vec<String>),
749    Natural,
750    None, // CROSS JOIN / comma
751}
752
753#[derive(Debug, Clone, PartialEq)]
754pub struct OrderItem {
755    pub expr: Expr,
756    pub asc: bool,
757}
758
759#[derive(Debug, Clone, PartialEq)]
760pub enum Expr {
761    IntLiteral(String),
762    /// SP32: a decimal/exponent literal. `PostgreSQL` types these as `numeric`
763    /// (SP30 typed them `float8`; SP32 introduced `numeric`, so a bare `1.5`/`1e3`
764    /// is now scale-faithful `numeric` — `float8` requires an explicit cast).
765    NumericLiteral(String),
766    StringLiteral(String),
767    BoolLiteral(bool),
768    NullLiteral,
769    /// SP33: a column reference, optionally table-qualified (`a.col`). `table` is
770    /// `None` for a bare `col`.
771    Column {
772        table: Option<String>,
773        name: String,
774    },
775    Param(u32),
776    /// `DEFAULT` in INSERT/UPDATE value position. The executor replaces this with
777    /// the target column's catalog default or NULL.
778    Default,
779    Unary {
780        op: UnaryOp,
781        expr: Box<Expr>,
782    },
783    Binary {
784        op: BinaryOp,
785        left: Box<Expr>,
786        right: Box<Expr>,
787    },
788    /// SP27: a function call, e.g. `count(*)`, `sum(a + 1)`, `count(DISTINCT x)`.
789    /// Whether a name is an aggregate (vs. an unknown/undefined function) is
790    /// decided by the executor, not the parser.
791    Func(FuncCall),
792    /// SP28: `expr IS [NOT] NULL`. Never evaluates to NULL itself.
793    IsNull {
794        expr: Box<Expr>,
795        negated: bool,
796    },
797    /// SP28: `expr [NOT] IN (e1, e2, …)` — value-list membership (not a subquery).
798    InList {
799        expr: Box<Expr>,
800        list: Vec<Expr>,
801        negated: bool,
802    },
803    /// SP28: `expr [NOT] BETWEEN low AND high` (bounds inclusive).
804    Between {
805        expr: Box<Expr>,
806        low: Box<Expr>,
807        high: Box<Expr>,
808        negated: bool,
809    },
810    /// SP28: `expr [NOT] LIKE pat` / `[NOT] ILIKE pat`. `%`/`_` wildcards with a
811    /// `\` escape; `case_insensitive` is the ILIKE form.
812    Like {
813        expr: Box<Expr>,
814        pattern: Box<Expr>,
815        negated: bool,
816        case_insensitive: bool,
817    },
818    /// SP28: a `CASE` expression. `operand` is `Some` for the simple form
819    /// (`CASE x WHEN v THEN r …`) and `None` for the searched form
820    /// (`CASE WHEN cond THEN r …`). `whens` is non-empty (parser-enforced).
821    Case {
822        operand: Option<Box<Expr>>,
823        whens: Vec<(Expr, Expr)>,
824        else_result: Option<Box<Expr>>,
825    },
826    /// SP31: an explicit cast — `CAST(expr AS ty)` or `expr::ty`. The target type
827    /// is resolved to a [`ColumnType`] by the parser (an unknown type name is a
828    /// parse error); the executor performs the value conversion.
829    Cast {
830        expr: Box<Expr>,
831        ty: ColumnType,
832    },
833    /// SP34: a scalar subquery `(SELECT …)` — one row, one column, usable as an
834    /// expression. Resolved (uncorrelated) to `Const` by the executor pre-pass.
835    ScalarSubquery(Box<QueryExpr>),
836    /// SP34: `EXISTS (SELECT …)` — true iff the subquery returns ≥1 row. `NOT
837    /// EXISTS` is the prefix `NOT` wrapping this.
838    Exists(Box<QueryExpr>),
839    /// SP34: `expr [NOT] IN (SELECT …)` — subquery membership (single-column subquery).
840    InSubquery {
841        expr: Box<Expr>,
842        subquery: Box<QueryExpr>,
843        negated: bool,
844    },
845    /// SP34: `expr op ANY|SOME|ALL (SELECT …)`. `all` is the `ALL` form; `ANY`/`SOME`
846    /// are `all == false`. The subquery is single-column.
847    Quantified {
848        expr: Box<Expr>,
849        op: BinaryOp,
850        all: bool,
851        subquery: Box<QueryExpr>,
852    },
853    /// SP34: an executor-produced literal — a resolved subquery folded to a value
854    /// carrying its static type. The parser NEVER emits this; `ty` matters because a
855    /// zero-row scalar subquery is a typed NULL.
856    Const {
857        value: Datum,
858        ty: ColumnType,
859    },
860}
861
862/// SP27: a parsed function call. `name` is lowercased by the lexer.
863#[derive(Debug, Clone, PartialEq)]
864pub struct FuncCall {
865    pub name: String,
866    /// `true` for `f(DISTINCT …)`. `ALL` (the default) parses to `false`.
867    pub distinct: bool,
868    pub args: FuncArgs,
869}
870
871/// SP27: a function call's argument list. `Star` is the `f(*)` form (only
872/// `count(*)` is meaningful); `Exprs` is a (possibly empty) positional list.
873#[derive(Debug, Clone, PartialEq)]
874pub enum FuncArgs {
875    Star,
876    Exprs(Vec<Expr>),
877}
878
879#[derive(Debug, Clone, Copy, PartialEq, Eq)]
880pub enum UnaryOp {
881    Not,
882    Neg,
883}
884
885#[derive(Debug, Clone, Copy, PartialEq, Eq)]
886pub enum BinaryOp {
887    Add,
888    Sub,
889    Mul,
890    Div,
891    /// SP29: `||` string concatenation.
892    Concat,
893    Eq,
894    Ne,
895    Lt,
896    Le,
897    Gt,
898    Ge,
899    And,
900    Or,
901}