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 expressions;
15mod locking;
16
17pub use expressions::*;
18pub use locking::*;
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21pub enum ColumnType {
22    SmallInteger,
23    Integer,
24    BigInteger,
25    /// `PostgreSQL` object identifier (`pg_catalog.oid`).
26    Oid,
27    /// `PostgreSQL` transaction identifier (`pg_catalog.xid`).
28    Xid,
29    Boolean,
30    Text,
31    Name,
32    Uuid,
33    Varchar(Option<u32>),
34    /// Internal unconstrained `bpchar` type used after common-type selection.
35    Bpchar,
36    /// `PostgreSQL` blank-padded `CHARACTER(n)` / `CHAR(n)` (`bpchar`).
37    /// The length counts Unicode scalar values and defaults to one when the
38    /// declaration omits an explicit modifier.
39    Character(u32),
40    Real,
41    DoublePrecision,
42    /// `NUMERIC(precision, scale)` -- exact decimal storage. When
43    /// `scale` is `Some(s)` the engine rounds `INSERT` values to `s`
44    /// fractional digits. `precision` is captured for round-tripping
45    /// the catalog text but is not currently enforced.
46    Numeric {
47        precision: Option<u32>,
48        scale: Option<i32>,
49    },
50    /// `JSON` / `JSONB` columns store typed JSON values.
51    Json,
52    /// `JSONB` columns store typed JSON values with `PostgreSQL` JSONB operators.
53    JsonB,
54    /// `BYTEA` columns store opaque bytes.
55    Bytea,
56    /// `PostgreSQL`'s internal single-byte `"char"` catalog type.
57    InternalChar,
58    Regproc,
59    /// `PostgreSQL` relation object identifier (`pg_catalog.regclass`).
60    Regclass,
61    /// `PostgreSQL` namespace object identifier (`pg_catalog.regnamespace`).
62    Regnamespace,
63    Regtype,
64    PgNodeTree,
65    AclItem,
66    Int2Vector,
67    OidVector,
68    AnyArray,
69    /// `PostgreSQL`'s anonymous composite pseudo-type (OID 2249).
70    Record,
71    /// A `PostgreSQL` array whose elements retain their declared SQL type.
72    /// Nested array bounds are represented recursively.
73    Array(Box<ColumnType>),
74    /// `DATE` columns store days since 1970-01-01.
75    Date,
76    /// `TIME` columns store microseconds since midnight.
77    Time,
78    /// `TIME WITH TIME ZONE` columns store local time plus offset.
79    TimeTz,
80    /// `TIMESTAMP WITHOUT TIME ZONE` columns store naive microseconds
81    /// since 1970-01-01 00:00:00.
82    Timestamp,
83    /// `TIMESTAMP WITH TIME ZONE` columns store UTC microseconds since
84    /// 1970-01-01 00:00:00Z.
85    TimestampTz,
86    Interval,
87    /// `VECTOR(N)` columns store an `N`-dimensional `f32` embedding.
88    Vector(u32),
89    /// `TENSOR(N)` columns store an array of `N`-dimensional `f32`
90    /// embeddings. The row remains the retrieval identity; vector
91    /// indexes score against the best element in the tensor.
92    Tensor(u32),
93    /// A named `PostgreSQL` domain retaining both its own type identity and the
94    /// base type used for value conversion and operator selection.
95    Domain {
96        schema: String,
97        name: String,
98        oid: u32,
99        base: Box<ColumnType>,
100    },
101}
102
103pub(crate) fn builtin_array_element_name(type_name: &str) -> Option<&'static str> {
104    Some(match type_name {
105        "_bool" => "bool",
106        "_bytea" => "bytea",
107        "_char" => "\"char\"",
108        "_name" => "name",
109        "_int8" => "int8",
110        "_int2" => "int2",
111        "_int2vector" => "int2vector",
112        "_int4" => "int4",
113        "_regproc" => "regproc",
114        "_regclass" => "regclass",
115        "_text" => "text",
116        "_oid" => "oid",
117        "_oidvector" => "oidvector",
118        "_bpchar" => "bpchar",
119        "_varchar" => "varchar",
120        "_float4" => "float4",
121        "_float8" => "float8",
122        "_aclitem" => "aclitem",
123        "_date" => "date",
124        "_time" => "time",
125        "_timestamp" => "timestamp",
126        "_timestamptz" => "timestamptz",
127        "_interval" => "interval",
128        "_numeric" => "numeric",
129        "_timetz" => "timetz",
130        "_record" => "record",
131        "_uuid" => "uuid",
132        "_json" => "json",
133        "_jsonb" => "jsonb",
134        "_regtype" => "regtype",
135        "_xid" => "xid",
136        "_pg_node_tree" => "pg_node_tree",
137        _ => return None,
138    })
139}
140
141impl ColumnType {
142    #[must_use]
143    pub fn is_integer(&self) -> bool {
144        match self {
145            Self::SmallInteger | Self::Integer | Self::BigInteger | Self::Oid | Self::Xid => true,
146            Self::Domain { base, .. } => base.is_integer(),
147            _ => false,
148        }
149    }
150
151    #[must_use]
152    pub fn is_character_string(&self) -> bool {
153        match self {
154            Self::Text
155            | Self::Name
156            | Self::Varchar(_)
157            | Self::Bpchar
158            | Self::Character(_)
159            | Self::InternalChar
160            | Self::PgNodeTree
161            | Self::AclItem => true,
162            Self::Domain { base, .. } => base.is_character_string(),
163            _ => false,
164        }
165    }
166
167    /// Parse the canonical or accepted spelling of one implemented SQL type.
168    /// This is shared by expression binding and row-schema propagation so a
169    /// cast's declared type is not reconstructed from its runtime value.
170    pub fn from_sql_name(name: &str) -> Result<Self, crate::SQLError> {
171        let normalized = name.trim().to_ascii_lowercase();
172        if let Some(element) = builtin_array_element_name(&normalized) {
173            return Self::from_sql_name(element).map(|ty| Self::Array(Box::new(ty)));
174        }
175        if let Some(element) = normalized.strip_suffix("[]") {
176            return Self::from_sql_name(element).map(|ty| Self::Array(Box::new(ty)));
177        }
178        let (base, modifier) = normalized
179            .strip_suffix(')')
180            .and_then(|prefix| prefix.rsplit_once('('))
181            .map_or((normalized.as_str(), None), |(base, modifier)| {
182                (base.trim(), Some(modifier.trim()))
183            });
184        let base = base.strip_prefix("pg_catalog.").unwrap_or(base);
185        let character_length = || -> Result<Option<u32>, crate::SQLError> {
186            modifier
187                .map(|value| {
188                    value
189                        .parse::<u32>()
190                        .ok()
191                        .filter(|length| *length > 0)
192                        .ok_or_else(|| {
193                            crate::SQLError::TypeMismatch(format!(
194                                "character length must be greater than zero, got {value}"
195                            ))
196                        })
197                })
198                .transpose()
199        };
200        match base {
201            "smallint" | "int2" | "smallserial" | "serial2" => Ok(Self::SmallInteger),
202            "integer" | "int" | "int4" | "serial" | "serial4" => Ok(Self::Integer),
203            "bigint" | "int8" | "bigserial" | "serial8" => Ok(Self::BigInteger),
204            "oid" => Ok(Self::Oid),
205            "xid" => Ok(Self::Xid),
206            "boolean" | "bool" => Ok(Self::Boolean),
207            "text" => Ok(Self::Text),
208            "name" => Ok(Self::Name),
209            "uuid" => Ok(Self::Uuid),
210            "varchar" | "character varying" => Ok(Self::Varchar(character_length()?)),
211            "character" | "char" => Ok(Self::Character(character_length()?.unwrap_or(1))),
212            "bpchar" => Ok(character_length()?.map_or(Self::Bpchar, Self::Character)),
213            "real" | "float4" => Ok(Self::Real),
214            "double" | "double precision" | "float8" => Ok(Self::DoublePrecision),
215            "numeric" | "decimal" => {
216                let (precision, scale) = match modifier {
217                    None => (None, None),
218                    Some(modifier) => {
219                        let mut parts = modifier.split(',').map(str::trim);
220                        let precision = parts
221                            .next()
222                            .and_then(|value| value.parse::<u32>().ok())
223                            .ok_or_else(|| {
224                                crate::SQLError::TypeMismatch(format!(
225                                    "invalid numeric modifier `{modifier}`"
226                                ))
227                            })?;
228                        let scale = parts
229                            .next()
230                            .map(|value| value.parse::<i32>())
231                            .transpose()
232                            .map_err(|_| {
233                                crate::SQLError::TypeMismatch(format!(
234                                    "invalid numeric modifier `{modifier}`"
235                                ))
236                            })?
237                            .unwrap_or(0);
238                        if parts.next().is_some() {
239                            return Err(crate::SQLError::TypeMismatch(format!(
240                                "invalid numeric modifier `{modifier}`"
241                            )));
242                        }
243                        (Some(precision), Some(scale))
244                    }
245                };
246                Ok(Self::Numeric { precision, scale })
247            }
248            "json" => Ok(Self::Json),
249            "jsonb" => Ok(Self::JsonB),
250            "bytea" => Ok(Self::Bytea),
251            "\"char\"" => Ok(Self::InternalChar),
252            "regproc" => Ok(Self::Regproc),
253            "regclass" => Ok(Self::Regclass),
254            "regnamespace" => Ok(Self::Regnamespace),
255            "regtype" => Ok(Self::Regtype),
256            "pg_node_tree" => Ok(Self::PgNodeTree),
257            "aclitem" => Ok(Self::AclItem),
258            "int2vector" => Ok(Self::Int2Vector),
259            "oidvector" => Ok(Self::OidVector),
260            "anyarray" => Ok(Self::AnyArray),
261            "record" => Ok(Self::Record),
262            "date" => Ok(Self::Date),
263            "time" | "time without time zone" => Ok(Self::Time),
264            "timetz" | "time with time zone" => Ok(Self::TimeTz),
265            "timestamp" | "datetime" | "timestamp without time zone" => Ok(Self::Timestamp),
266            "timestamptz" | "timestamp with time zone" => Ok(Self::TimestampTz),
267            "interval" => Ok(Self::Interval),
268            "vector" => modifier
269                .and_then(|value| value.parse::<u32>().ok())
270                .filter(|dimension| *dimension > 0)
271                .map(Self::Vector)
272                .ok_or_else(|| crate::SQLError::TypeMismatch("VECTOR requires a dimension".into())),
273            "tensor" => modifier
274                .and_then(|value| value.parse::<u32>().ok())
275                .filter(|dimension| *dimension > 0)
276                .map(Self::Tensor)
277                .ok_or_else(|| crate::SQLError::TypeMismatch("TENSOR requires a dimension".into())),
278            other => Err(crate::SQLError::Unsupported(format!(
279                "SQL type `{other}` is not supported"
280            ))),
281        }
282    }
283
284    #[must_use]
285    pub fn sql_name(&self) -> String {
286        match self {
287            Self::SmallInteger => "smallint".into(),
288            Self::Integer => "integer".into(),
289            Self::BigInteger => "bigint".into(),
290            Self::Oid => "oid".into(),
291            Self::Xid => "xid".into(),
292            Self::Boolean => "boolean".into(),
293            Self::Text => "text".into(),
294            Self::Name => "name".into(),
295            Self::Uuid => "uuid".into(),
296            Self::Varchar(Some(length)) => format!("character varying({length})"),
297            Self::Varchar(None) => "character varying".into(),
298            Self::Bpchar => "bpchar".into(),
299            Self::Character(length) => format!("character({length})"),
300            Self::Real => "real".into(),
301            Self::DoublePrecision => "double precision".into(),
302            Self::Numeric {
303                precision: Some(precision),
304                scale: Some(scale),
305            } => format!("numeric({precision},{scale})"),
306            Self::Numeric { .. } => "numeric".into(),
307            Self::Json => "json".into(),
308            Self::JsonB => "jsonb".into(),
309            Self::Bytea => "bytea".into(),
310            Self::InternalChar => "\"char\"".into(),
311            Self::Regproc => "regproc".into(),
312            Self::Regclass => "regclass".into(),
313            Self::Regnamespace => "regnamespace".into(),
314            Self::Regtype => "regtype".into(),
315            Self::PgNodeTree => "pg_node_tree".into(),
316            Self::AclItem => "aclitem".into(),
317            Self::Int2Vector => "int2vector".into(),
318            Self::OidVector => "oidvector".into(),
319            Self::AnyArray => "anyarray".into(),
320            Self::Record => "record".into(),
321            Self::Array(element) => format!("{}[]", element.sql_name()),
322            Self::Date => "date".into(),
323            Self::Time => "time without time zone".into(),
324            Self::TimeTz => "time with time zone".into(),
325            Self::Timestamp => "timestamp without time zone".into(),
326            Self::TimestampTz => "timestamp with time zone".into(),
327            Self::Interval => "interval".into(),
328            Self::Vector(dimension) => format!("vector({dimension})"),
329            Self::Tensor(dimension) => format!("tensor({dimension})"),
330            Self::Domain { schema, name, .. } => format!("{schema}.{name}"),
331        }
332    }
333
334    /// Name emitted by `PostgreSQL`'s `regtype` output, including
335    /// `pg_typeof(...)`.
336    #[must_use]
337    pub fn regtype_name(&self) -> String {
338        match self {
339            Self::Varchar(_) => "character varying".into(),
340            Self::Bpchar | Self::Character(_) => "character".into(),
341            Self::Numeric { .. } => "numeric".into(),
342            Self::Vector(_) => "vector".into(),
343            Self::Tensor(_) => "tensor".into(),
344            Self::Domain { schema, name, .. } => format!("{schema}.{name}"),
345            Self::Array(element) => format!("{}[]", element.regtype_name()),
346            other => other.sql_name(),
347        }
348    }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
352pub enum GeneratedColumnKind {
353    Virtual,
354    Stored,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
358pub struct GeneratedColumn {
359    pub kind: GeneratedColumnKind,
360    pub expression: Box<Expr>,
361    #[serde(default, skip_serializing_if = "Vec::is_empty")]
362    pub function_dependencies: Vec<GeneratedFunctionDependency>,
363}
364
365#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
366pub struct FunctionBinding {
367    pub name: String,
368    pub argument_types: Vec<String>,
369}
370
371pub type GeneratedFunctionDependency = FunctionBinding;
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
374#[allow(clippy::struct_excessive_bools)]
375pub struct ColumnDef {
376    pub name: String,
377    pub ty: ColumnType,
378    pub primary_key: bool,
379    pub not_null: bool,
380    /// Whether `NOT NULL` was declared as its own constraint instead of being
381    /// implied by `PRIMARY KEY` or an auto-incrementing identity.
382    #[serde(default)]
383    pub not_null_explicit: bool,
384    /// Durable `PostgreSQL` 18 `NOT NULL` constraint name. Parsing leaves an
385    /// unnamed declaration as `None`; table registration assigns and persists
386    /// `PostgreSQL`'s generated name before the constraint becomes visible.
387    #[serde(default, skip_serializing_if = "Option::is_none")]
388    pub not_null_name: Option<String>,
389    /// `SERIAL` / `BIGSERIAL` columns auto-allocate from a per-table
390    /// monotonic counter when the value is omitted from `INSERT`.
391    #[serde(default)]
392    pub auto_increment: bool,
393    /// `UNIQUE` column constraint -- the engine rejects an INSERT
394    /// whose value for this column already exists in another row.
395    #[serde(default)]
396    pub unique: bool,
397    /// `DEFAULT <expr>`. Evaluated at INSERT time when the column is
398    /// not present in the row tuple. Persisted in catalog metadata so
399    /// reopened engines keep the same INSERT semantics.
400    #[serde(default, skip_serializing_if = "Option::is_none")]
401    pub default: Option<Expr>,
402    /// `PostgreSQL` 18 generated-column definition. Stored values are refreshed
403    /// on every row write; virtual values are evaluated from the physical row
404    /// only when a logical row is read.
405    #[serde(default, skip_serializing_if = "Option::is_none")]
406    pub generated: Option<GeneratedColumn>,
407    /// `CHECK (<expr>)` column-level constraint. Evaluated at INSERT
408    /// (and UPDATE-replace) time against the row being written.
409    #[serde(default, skip_serializing_if = "Option::is_none")]
410    pub check: Option<Expr>,
411    #[serde(default, skip_serializing_if = "Option::is_none")]
412    pub check_name: Option<String>,
413    #[serde(default = "default_true")]
414    pub check_enforced: bool,
415    /// `REFERENCES parent(col)` column-level FOREIGN KEY. The engine
416    /// rejects INSERT / UPDATE whose value is not present in the
417    /// referenced (table, column) pair.
418    #[serde(default, skip_serializing_if = "Option::is_none")]
419    pub references: Option<ForeignKeyRef>,
420}
421
422/// `REFERENCES table(column)` reference target.
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct ForeignKeyRef {
425    #[serde(default, skip_serializing_if = "Option::is_none")]
426    pub name: Option<String>,
427    pub table: String,
428    pub column: String,
429    #[serde(default)]
430    pub on_update: ForeignKeyAction,
431    #[serde(default)]
432    pub on_delete: ForeignKeyAction,
433    #[serde(default)]
434    pub match_type: ForeignKeyMatch,
435    #[serde(default = "default_true")]
436    pub enforced: bool,
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize)]
440pub struct CreateTable {
441    pub name: String,
442    /// Local SQL relation identifier used while binding expressions declared inside the table definition.
443    pub qualifier: String,
444    pub columns: Vec<ColumnDef>,
445    /// `CREATE TABLE IF NOT EXISTS` - silently ignore the statement
446    /// when a table with this name already exists.
447    pub if_not_exists: bool,
448    /// Table-level `CHECK (...)` constraints. Each entry is an
449    /// expression that must evaluate truthy against every row.
450    #[allow(dead_code)]
451    pub checks: Vec<TableCheck>,
452    /// Table-level `FOREIGN KEY (col, ...) REFERENCES parent(col, ...)`.
453    pub foreign_keys: Vec<ForeignKey>,
454    /// Every declared `PRIMARY KEY` / `UNIQUE` constraint, including
455    /// column-level declarations. Keeping the typed key (rather than only
456    /// setting per-column flags) preserves composite-key and `NULLS NOT
457    /// DISTINCT` semantics through planning and catalog persistence.
458    #[serde(default)]
459    pub key_constraints: Vec<TableKeyConstraint>,
460}
461
462#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
463pub enum TableKeyConstraintKind {
464    PrimaryKey,
465    Unique,
466}
467
468/// A table key whose columns are compared as one tuple.
469#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
470pub struct TableKeyConstraint {
471    pub name: Option<String>,
472    pub kind: TableKeyConstraintKind,
473    pub columns: Vec<String>,
474    /// `PostgreSQL` UNIQUE keys normally treat every NULL-containing tuple as
475    /// distinct. `UNIQUE NULLS NOT DISTINCT` opts into NULL equality.
476    #[serde(default)]
477    pub nulls_not_distinct: bool,
478}
479
480/// Durable table-level constraints that do not fit in `ColumnDef`.
481///
482/// `serde(default)` on the catalog field containing this structure keeps
483/// databases written before constraint persistence backward compatible.
484#[derive(Debug, Clone, Default, Serialize, Deserialize)]
485pub struct TableConstraintSet {
486    #[serde(default)]
487    pub checks: Vec<TableCheck>,
488    #[serde(default)]
489    pub foreign_keys: Vec<ForeignKey>,
490    #[serde(default)]
491    pub key_constraints: Vec<TableKeyConstraint>,
492}
493
494/// `CHECK (expr)` constraint with an optional name (`CONSTRAINT <name>
495/// CHECK (...)`).
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct TableCheck {
498    pub name: Option<String>,
499    pub expr: Expr,
500    #[serde(default = "default_true")]
501    pub enforced: bool,
502}
503
504/// Table-level foreign key. `local_columns.len()` matches
505/// `ref_columns.len()`; the engine joins on the position-aligned
506/// pairs.
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct ForeignKey {
509    pub name: Option<String>,
510    pub local_columns: Vec<String>,
511    pub ref_table: String,
512    pub ref_columns: Vec<String>,
513    #[serde(default)]
514    pub on_update: ForeignKeyAction,
515    #[serde(default)]
516    pub on_delete: ForeignKeyAction,
517    /// Optional column subset for `ON DELETE SET NULL (...)` and
518    /// `ON DELETE SET DEFAULT (...)`. Empty means every local FK
519    /// column participates.
520    #[serde(default)]
521    pub on_delete_set_columns: Vec<String>,
522    #[serde(default)]
523    pub match_type: ForeignKeyMatch,
524    #[serde(default = "default_true")]
525    pub enforced: bool,
526}
527
528const fn default_true() -> bool {
529    true
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
533pub enum ForeignKeyAction {
534    #[default]
535    NoAction,
536    Restrict,
537    Cascade,
538    SetNull,
539    SetDefault,
540}
541
542#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
543pub enum ForeignKeyMatch {
544    #[default]
545    Simple,
546    Full,
547}
548
549#[derive(Debug, Clone, Serialize, Deserialize)]
550pub struct CreateIndex {
551    pub name: Option<String>,
552    pub table: String,
553    /// `gin`, `btree`, `ivf`, `hnsw`, `rtree`, ...
554    pub access_method: String,
555    pub columns: Vec<String>,
556    /// `CREATE INDEX IF NOT EXISTS`.
557    pub if_not_exists: bool,
558    /// Storage parameters from `WITH (k = v, ...)`. Stored verbatim;
559    /// known keys (`analyzer`, `lists`, `probes`, ...)
560    /// are interpreted by the engine.
561    pub options: Vec<(String, String)>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct DropStmt {
566    pub kind: DropKind,
567    pub names: Vec<String>,
568    pub if_exists: bool,
569    pub cascade: bool,
570}
571
572#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
573pub enum DropKind {
574    Table,
575    Index,
576    View,
577    Schema,
578}
579
580/// Parameter mode of a `CREATE FUNCTION` / `CREATE PROCEDURE`
581/// argument. Mirrors `PostgreSQL`'s `FunctionParameterMode`.
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
583pub enum FunctionParamMode {
584    /// `IN` (also the default when no mode is written).
585    In,
586    /// `OUT` - shapes the result row, not part of a function's call
587    /// signature (but part of a procedure's).
588    Out,
589    /// `INOUT` - accepted as input and returned in the result row.
590    InOut,
591    /// `RETURNS TABLE (col type, ...)` column. Behaves like an `OUT`
592    /// parameter of a set-returning function.
593    Table,
594}
595
596/// One declared parameter of a user-defined function or procedure.
597#[derive(Debug, Clone, Serialize, Deserialize)]
598pub struct FunctionParam {
599    /// Parameter name. Empty for unnamed parameters (`f(integer)`),
600    /// which are only addressable as `$n`.
601    pub name: String,
602    /// Raw type name as written (last segment, lower-cased by the
603    /// compiler; e.g. `int4`, `text`, `numeric`).
604    pub type_name: String,
605    /// Parsed relation and column identity for `%TYPE`; ordinary types have no reference.
606    #[serde(default, skip_serializing_if = "Option::is_none")]
607    pub type_reference: Option<RoutineColumnTypeReference>,
608    pub mode: FunctionParamMode,
609    /// `DEFAULT <expr>` for trailing input parameters.
610    #[serde(default, skip_serializing_if = "Option::is_none")]
611    pub default: Option<Expr>,
612}
613
614/// Structured relation-column identity carried by a routine `%TYPE` declaration until catalog binding resolves it to a concrete SQL type.
615#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
616pub struct RoutineColumnTypeReference {
617    pub schema: Option<String>,
618    pub relation: String,
619    pub column: String,
620}
621
622impl RoutineColumnTypeReference {
623    pub fn new(schema: Option<String>, relation: String, column: String) -> Self {
624        Self {
625            schema,
626            relation,
627            column,
628        }
629    }
630
631    pub fn relation_reference(&self) -> String {
632        match self.schema.as_deref() {
633            Some(schema) => format!(
634                "{}.{}",
635                render_identifier_component(schema),
636                render_identifier_component(&self.relation)
637            ),
638            None => render_identifier_component(&self.relation),
639        }
640    }
641
642    pub fn type_reference(&self) -> String {
643        format!(
644            "{}.{}%type",
645            self.relation_reference(),
646            render_identifier_component(&self.column)
647        )
648    }
649}
650
651fn render_identifier_component(component: &str) -> String {
652    let can_render_bare = component
653        .bytes()
654        .enumerate()
655        .all(|(index, byte)| match byte {
656            b'a'..=b'z' | b'_' => true,
657            b'0'..=b'9' | b'$' => index != 0,
658            _ => false,
659        });
660    if can_render_bare && !component.is_empty() {
661        component.to_string()
662    } else {
663        format!("\"{}\"", component.replace('"', "\"\""))
664    }
665}
666
667/// Declared result shape of a user-defined function.
668#[derive(Debug, Clone, Serialize, Deserialize)]
669pub enum FunctionReturns {
670    /// Procedures and functions whose result is shaped purely by
671    /// `OUT` parameters carry no explicit `RETURNS` clause.
672    None,
673    /// `RETURNS <type>` - includes `RETURNS void` and `RETURNS record`.
674    Scalar { type_name: String },
675    /// `RETURNS SETOF <type>`.
676    SetOf { type_name: String },
677    /// `RETURNS TABLE (...)`. The column list lives in
678    /// [`CreateFunction::params`] as [`FunctionParamMode::Table`]
679    /// entries; this variant just records the set-returning shape.
680    Table,
681}
682
683/// `IMMUTABLE` / `STABLE` / `VOLATILE` marker.
684#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
685pub enum FunctionVolatility {
686    Immutable,
687    Stable,
688    #[default]
689    Volatile,
690}
691
692/// Body of a user-defined routine.
693#[derive(Debug, Clone, Serialize, Deserialize)]
694pub enum FunctionBody {
695    /// `AS $$ ... $$` - raw source text, parsed per language at
696    /// registration time.
697    Source(String),
698    /// SQL-standard body (`BEGIN ATOMIC ... END` / `RETURN expr`)
699    /// compiled straight to statements.
700    Statements(Vec<Statement>),
701}
702
703/// `CREATE [OR REPLACE] FUNCTION | PROCEDURE`.
704#[derive(Debug, Clone, Serialize, Deserialize)]
705pub struct CreateFunction {
706    pub name: String,
707    pub or_replace: bool,
708    pub is_procedure: bool,
709    pub params: Vec<FunctionParam>,
710    pub returns: FunctionReturns,
711    /// Parsed `%TYPE` identity for a scalar or set return declaration until registration resolves it.
712    #[serde(default, skip_serializing_if = "Option::is_none")]
713    pub return_type_reference: Option<RoutineColumnTypeReference>,
714    /// Lower-cased language name (`plpgsql`, `sql`).
715    pub language: String,
716    pub body: FunctionBody,
717    pub volatility: FunctionVolatility,
718    /// `STRICT` / `RETURNS NULL ON NULL INPUT` - the function is not
719    /// invoked when any input argument is NULL; the result is NULL.
720    pub strict: bool,
721}
722
723impl CreateFunction {
724    /// Number of call-signature parameters: `IN` + `INOUT` for
725    /// functions; every non-TABLE parameter for procedures (callers
726    /// pass placeholder arguments for procedure `OUT` parameters,
727    /// matching `PostgreSQL` 14+).
728    pub fn signature_arity(&self) -> usize {
729        self.params
730            .iter()
731            .filter(|p| self.is_signature_param(p))
732            .count()
733    }
734
735    /// Number of signature parameters without a `DEFAULT`.
736    pub fn required_arity(&self) -> usize {
737        self.params
738            .iter()
739            .filter(|p| self.is_signature_param(p) && p.default.is_none())
740            .count()
741    }
742
743    fn is_signature_param(&self, p: &FunctionParam) -> bool {
744        match p.mode {
745            FunctionParamMode::In | FunctionParamMode::InOut => true,
746            FunctionParamMode::Out => self.is_procedure,
747            FunctionParamMode::Table => false,
748        }
749    }
750
751    /// Signature parameters in declaration order.
752    pub fn signature_params(&self) -> Vec<&FunctionParam> {
753        self.params
754            .iter()
755            .filter(|p| self.is_signature_param(p))
756            .collect()
757    }
758
759    /// Parameters that shape the result row: `OUT` + `INOUT` +
760    /// `RETURNS TABLE` columns, in declaration order.
761    pub fn output_params(&self) -> Vec<&FunctionParam> {
762        self.params
763            .iter()
764            .filter(|p| {
765                matches!(
766                    p.mode,
767                    FunctionParamMode::Out | FunctionParamMode::InOut | FunctionParamMode::Table
768                )
769            })
770            .collect()
771    }
772
773    /// True when the routine produces a row set (`RETURNS SETOF` /
774    /// `RETURNS TABLE`).
775    pub fn returns_set(&self) -> bool {
776        matches!(
777            self.returns,
778            FunctionReturns::SetOf { .. } | FunctionReturns::Table
779        )
780    }
781}
782
783/// One `DROP FUNCTION` / `DROP PROCEDURE` target.
784#[derive(Debug, Clone, Serialize, Deserialize)]
785pub struct DropFunctionItem {
786    pub name: String,
787    /// `Some(types)` when the statement spelled an argument list
788    /// (`DROP FUNCTION f(int, int)` - matched by canonical argument
789    /// types); `None` for the bare-name form
790    /// (`DROP FUNCTION f`).
791    pub arg_types: Option<Vec<String>>,
792}
793
794/// `DROP FUNCTION [IF EXISTS] name[(argtypes)] [, ...]` and the
795/// `DROP PROCEDURE` equivalent.
796#[derive(Debug, Clone, Serialize, Deserialize)]
797pub struct DropFunctionStmt {
798    pub is_procedure: bool,
799    pub if_exists: bool,
800    #[serde(default)]
801    pub cascade: bool,
802    pub items: Vec<DropFunctionItem>,
803}
804
805#[derive(Debug, Clone, Serialize, Deserialize)]
806pub struct AlterTableStmt {
807    pub table: String,
808    /// Local SQL relation identifier used while binding new or replaced generation expressions.
809    pub qualifier: String,
810    pub if_exists: bool,
811    pub action: AlterTableAction,
812}
813
814#[derive(Debug, Clone, Serialize, Deserialize)]
815#[allow(clippy::large_enum_variant)]
816pub enum AlterTableAction {
817    AddColumn {
818        column: ColumnDef,
819        if_not_exists: bool,
820    },
821    AddKeyConstraint {
822        constraint: TableKeyConstraint,
823    },
824    DropColumn {
825        name: String,
826        if_exists: bool,
827        cascade: bool,
828    },
829    RenameColumn {
830        from: String,
831        to: String,
832    },
833    RenameTable {
834        to: String,
835    },
836    SetDefault {
837        name: String,
838        default: Expr,
839    },
840    DropDefault {
841        name: String,
842    },
843    SetExpression {
844        name: String,
845        expression: Expr,
846    },
847    DropExpression {
848        name: String,
849    },
850    SetNotNull {
851        name: String,
852    },
853    DropNotNull {
854        name: String,
855    },
856    AlterColumnType {
857        name: String,
858        ty: ColumnType,
859        #[serde(default, skip_serializing_if = "Option::is_none")]
860        using: Option<Expr>,
861    },
862}
863
864#[derive(Debug, Clone, Serialize, Deserialize)]
865pub struct InsertStmt {
866    pub table: String,
867    /// SQL-visible target relation name: explicit alias, otherwise the local relation name.
868    pub target_qualifier: String,
869    pub columns: Vec<String>,
870    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
871    pub with: Vec<CTE>,
872    /// Inline `VALUES (...) (...)` rows. Empty when the statement is
873    /// an `INSERT ... SELECT` form; in that case `select_source` is
874    /// populated with the underlying SELECT.
875    pub rows: Vec<Vec<ValueExpr>>,
876    /// Populated when the statement is `INSERT INTO t (...) SELECT ...`.
877    /// The engine materialises the inner select first and then writes
878    /// each row through the standard INSERT path.
879    pub select_source: Option<Box<SelectStmt>>,
880    /// `ON CONFLICT (...) DO ...` clause. `None` for plain
881    /// `INSERT INTO ... VALUES ...` without conflict handling.
882    pub on_conflict: Option<OnConflict>,
883    /// `RETURNING ...` projection list. Empty when absent.
884    pub returning: Vec<Projection>,
885    /// `PostgreSQL` 18 names for the old and new row images visible to
886    /// `RETURNING`. The defaults are `old` and `new`.
887    pub returning_aliases: ReturningAliases,
888}
889
890#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
891pub struct ReturningAliases {
892    pub old: String,
893    pub new: String,
894    #[serde(default)]
895    pub old_explicit: bool,
896    #[serde(default)]
897    pub new_explicit: bool,
898}
899
900impl Default for ReturningAliases {
901    fn default() -> Self {
902        Self {
903            old: "old".into(),
904            new: "new".into(),
905            old_explicit: false,
906            new_explicit: false,
907        }
908    }
909}
910
911#[derive(Debug, Clone, Serialize, Deserialize)]
912pub struct OnConflict {
913    /// Conflict target columns parsed from the `ON CONFLICT (col, ...)`
914    /// list. Empty when the clause uses `ON CONFLICT DO NOTHING` with
915    /// no target.
916    pub conflict_columns: Vec<String>,
917    pub action: OnConflictAction,
918}
919
920#[derive(Debug, Clone, Serialize, Deserialize)]
921pub enum OnConflictAction {
922    /// `DO NOTHING` -- skip conflicting rows silently.
923    Nothing,
924    /// `DO UPDATE SET col = expr [, ...] [WHERE pred]` -- apply the
925    /// listed assignments to the existing row when the conflict
926    /// target matches.
927    Update {
928        assignments: Vec<(String, Expr)>,
929        r#where: Option<Expr>,
930    },
931}
932
933#[derive(Debug, Clone, Serialize, Deserialize)]
934pub struct SelectStmt {
935    pub projections: Vec<Projection>,
936    /// Rows owned by a `VALUES` query body. `PostgreSQL` represents `VALUES`
937    /// through the same query node used for `SELECT`, so nested query bodies
938    /// such as CTEs and set-operation branches must retain them here.
939    #[serde(default, skip_serializing_if = "Vec::is_empty")]
940    pub values: Vec<Vec<Expr>>,
941    pub from: Option<FromClause>,
942    pub r#where: Option<Expr>,
943    pub group_by: Vec<Expr>,
944    /// Expanded GROUPING SETS / ROLLUP / CUBE specification. When
945    /// non-empty the executor produces one row per grouping set;
946    /// `group_by` is treated as a single grouping set in that case.
947    /// Each inner Vec lists the grouping-key expressions for that
948    /// set (an empty inner Vec means the global grand-total bucket).
949    pub grouping_sets: Vec<Vec<Expr>>,
950    /// `HAVING <expr>`. Evaluated against each aggregated row and
951    /// filters out groups whose predicate is falsy. Mirrors PG's
952    /// `havingClause`.
953    pub having: Option<Expr>,
954    pub order_by: Vec<OrderBy>,
955    /// `LIMIT <expr>`. Stored as an expression so `LIMIT $1` and any
956    /// other constant-folding integer expression resolves at execute
957    /// time. `None` means no LIMIT clause was supplied.
958    pub limit: Option<Expr>,
959    /// `OFFSET <expr>`. Same shape as [`SelectStmt::limit`].
960    pub offset: Option<Expr>,
961    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
962    pub with: Vec<CTE>,
963    /// Optional set operation: `Some` for UNION / INTERSECT / EXCEPT.
964    /// Parsed statements carry both operands in [`SetOp`]; `left` remains
965    /// optional only for backward-compatible deserialization.
966    pub set_op: Option<Box<SetOp>>,
967    /// `SELECT DISTINCT` -- de-duplicate the final result rows. Set by
968    /// the compiler whenever the parsed `distinct_clause` is non-empty.
969    pub distinct: bool,
970    /// `SELECT DISTINCT ON (<expr>, ...)` keys. Empty for plain
971    /// `SELECT DISTINCT`.
972    pub distinct_on: Vec<Expr>,
973    /// `FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE }` row-locking clauses, in source order. Empty when the query does not lock rows.
974    #[serde(default, skip_serializing_if = "Vec::is_empty")]
975    pub locking: Vec<LockingClause>,
976}
977
978#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct CTE {
980    pub name: String,
981    pub columns: Vec<String>,
982    pub recursive: bool,
983    pub query: Box<SelectStmt>,
984}
985
986#[derive(Debug, Clone, Serialize, Deserialize)]
987pub struct SetOp {
988    pub kind: SetOpKind,
989    pub all: bool,
990    /// Explicit left-hand subtree. Parsed set operations are left-associative,
991    /// so a chain such as `a UNION b UNION c` carries `(a UNION b)` here
992    /// instead of flattening it back to only `a`.
993    #[serde(default, skip_serializing_if = "Option::is_none")]
994    pub left: Option<Box<SelectStmt>>,
995    pub right: SelectStmt,
996    /// `ORDER BY` applied to the combined `lhs <op> rhs` result.
997    /// Distinct from the LHS / RHS branches' own `ORDER BY`.
998    pub combined_order_by: Vec<OrderBy>,
999    /// `LIMIT` applied to the combined result. `None` means no
1000    /// outer LIMIT clause was supplied.
1001    pub combined_limit: Option<Expr>,
1002    /// `OFFSET` applied to the combined result.
1003    pub combined_offset: Option<Expr>,
1004}
1005
1006#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1007pub enum SetOpKind {
1008    Union,
1009    Intersect,
1010    Except,
1011}
1012
1013#[derive(Debug, Clone, Serialize, Deserialize)]
1014pub enum FromClause {
1015    /// `FROM <table> [AS <alias>]`.
1016    Table {
1017        /// Durable catalog identity, including an explicit schema when present.
1018        name: String,
1019        /// Relation name visible to SQL column binding before an alias is applied.
1020        qualifier: String,
1021        alias: Option<String>,
1022    },
1023    /// `FROM left <kind> right ON predicate`. `lateral` is true when
1024    /// the right side is a LATERAL subquery / function -- the engine
1025    /// re-evaluates it for every left row.
1026    Join {
1027        left: Box<FromClause>,
1028        right: Box<FromClause>,
1029        kind: JoinKind,
1030        /// Boolean qualification supplied by `ON`. This is mutually
1031        /// exclusive with `using` and `natural` in parser-produced trees.
1032        on: Option<Expr>,
1033        /// `PostgreSQL` `USING (column, ...) [AS alias]` metadata. The column
1034        /// list must remain explicit until both input row types are known so
1035        /// binding can validate each side and construct the merged output.
1036        #[serde(default, skip_serializing_if = "Option::is_none")]
1037        using: Option<JoinUsing>,
1038        /// `NATURAL` derives its `USING` list from the visible columns of both
1039        /// input row types at binding time.
1040        #[serde(default)]
1041        natural: bool,
1042        #[allow(dead_code)]
1043        lateral: bool,
1044    },
1045    /// `FROM (VALUES (...)...) [AS <alias>(<col_aliases>)]`.
1046    Values {
1047        rows: Vec<Vec<Expr>>,
1048        alias: Option<String>,
1049        column_aliases: Vec<String>,
1050    },
1051    /// `FROM <fn>(<args>) [AS <alias>(<col_aliases>)]` -- e.g.
1052    /// `generate_series(1, 5)`, `unnest(arr)`, `regexp_split_to_table`,
1053    /// `json_each(...)`, `cypher(...) AS (col agtype, ...)`. The engine
1054    /// dispatches by name.
1055    Function {
1056        name: String,
1057        /// Local function identifier used as `PostgreSQL`'s default output column label. Kept separate from the catalog-qualified lookup name so quoted identifiers containing `.` remain indivisible.
1058        output_name: String,
1059        /// Catalog relation bound to a relation-aware table function.
1060        /// Kept separate from scalar arguments so name resolution,
1061        /// dependency tracking, and planning never treat it as text data.
1062        #[serde(default, skip_serializing_if = "Option::is_none")]
1063        relation: Option<String>,
1064        args: Vec<Expr>,
1065        alias: Option<String>,
1066        column_aliases: Vec<String>,
1067        /// Declared column types when the alias used a column
1068        /// definition list (`AS (col agtype, n int)`); empty when the
1069        /// alias only renamed columns. Type names are lowercased
1070        /// `PostgreSQL` internal names (`agtype`, `int4`, `text`, ...).
1071        #[serde(default)]
1072        column_types: Vec<String>,
1073    },
1074    /// `FROM (SELECT ...) AS <alias>` -- subquery as a relation.
1075    /// The body re-runs as if a CTE; the alias renames the result
1076    /// columns when supplied.
1077    Subquery {
1078        body: Box<SelectStmt>,
1079        alias: Option<String>,
1080        column_aliases: Vec<String>,
1081    },
1082}
1083
1084#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1085pub struct JoinUsing {
1086    pub columns: Vec<String>,
1087    #[serde(default, skip_serializing_if = "Option::is_none")]
1088    pub alias: Option<String>,
1089}
1090
1091impl FromClause {
1092    /// All table names referenced under this clause, in declaration
1093    /// order. Used by the compiler to resolve unqualified column refs.
1094    pub fn collect_tables(&self, out: &mut Vec<(String, Option<String>)>) {
1095        match self {
1096            FromClause::Table {
1097                name,
1098                qualifier,
1099                alias,
1100            } => out.push((
1101                name.clone(),
1102                Some(alias.as_ref().unwrap_or(qualifier).clone()),
1103            )),
1104            FromClause::Join { left, right, .. } => {
1105                left.collect_tables(out);
1106                right.collect_tables(out);
1107            }
1108            FromClause::Values { alias, .. }
1109            | FromClause::Function { alias, .. }
1110            | FromClause::Subquery { alias, .. } => {
1111                if let Some(a) = alias {
1112                    out.push((a.clone(), Some(a.clone())));
1113                }
1114            }
1115        }
1116    }
1117}
1118
1119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1120pub enum JoinKind {
1121    Inner,
1122    Left,
1123    Right,
1124    Full,
1125    Cross,
1126}
1127
1128/// `DISCARD` target. Mirrors `PostgreSQL`'s `DiscardMode`.
1129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1130pub enum DiscardTarget {
1131    All,
1132    Plans,
1133    Sequences,
1134    Temp,
1135}
1136
1137#[derive(Debug, Clone, Serialize, Deserialize)]
1138pub struct UpdateStmt {
1139    pub table: String,
1140    pub target_qualifier: String,
1141    pub assignments: Vec<(String, Expr)>,
1142    pub r#where: Option<Expr>,
1143    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
1144    pub with: Vec<CTE>,
1145    /// `UPDATE t SET ... FROM other [JOIN ...]` -- the engine joins
1146    /// the target with this clause before applying the assignments.
1147    pub from: Option<FromClause>,
1148    /// `RETURNING ...` projection list. Empty when absent.
1149    pub returning: Vec<Projection>,
1150    pub returning_aliases: ReturningAliases,
1151}
1152
1153#[derive(Debug, Clone, Serialize, Deserialize)]
1154pub struct DeleteStmt {
1155    pub table: String,
1156    pub target_qualifier: String,
1157    pub r#where: Option<Expr>,
1158    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
1159    pub with: Vec<CTE>,
1160    /// `DELETE FROM t USING other [JOIN ...]` -- the engine joins
1161    /// the target with this clause and deletes target rows whose
1162    /// joined image satisfies WHERE.
1163    pub using: Option<FromClause>,
1164    /// `RETURNING ...` projection list. Empty when absent.
1165    pub returning: Vec<Projection>,
1166    pub returning_aliases: ReturningAliases,
1167}
1168
1169#[derive(Debug, Clone, Serialize, Deserialize)]
1170pub enum Statement {
1171    CreateTable(CreateTable),
1172    CreateIndex(CreateIndex),
1173    Insert(InsertStmt),
1174    /// `SelectStmt` is the largest variant by far (CTEs + set-ops + n-ary
1175    /// expression trees), so we box it to keep the enum's stack footprint
1176    /// proportional to the smaller variants.
1177    Select(Box<SelectStmt>),
1178    Update(UpdateStmt),
1179    Delete(DeleteStmt),
1180    Drop(DropStmt),
1181    AlterTable(AlterTableStmt),
1182    /// `CREATE [OR REPLACE] VIEW name AS SELECT ...`. The body is the
1183    /// underlying `SelectStmt`; views are materialised lazily on every
1184    /// reference (no row caching).
1185    CreateView {
1186        name: String,
1187        body: Box<SelectStmt>,
1188        or_replace: bool,
1189    },
1190    /// `CREATE SCHEMA [IF NOT EXISTS] name`. This AST entry records the
1191    /// command for the engine's durable schema catalog and namespace
1192    /// resolver.
1193    CreateSchema {
1194        name: String,
1195        if_not_exists: bool,
1196    },
1197    /// `SET <name> [TO|=] <value>` - runtime parameter assignment.
1198    /// The engine gives `search_path` resolution semantics and stores other
1199    /// parameters in the logical session for subsequent `SHOW` statements.
1200    SetVariable {
1201        name: String,
1202        value: String,
1203    },
1204    /// `SHOW <variable>` - return the runtime parameter as one
1205    /// `(name -> value)` row.
1206    ShowVariable {
1207        name: String,
1208    },
1209    /// `DISCARD [ALL|PLANS|SEQUENCES|TEMP|TEMPORARY]` - clear session state.
1210    /// The engine resets
1211    /// session vars, prepared statements and sequences. `TEMP` is rejected
1212    /// until temporary tables are supported instead of being silently ignored.
1213    Discard {
1214        target: DiscardTarget,
1215    },
1216    /// `LOAD 'library'` - load a shared library into the session. The
1217    /// engine embeds its extension surface, so libraries it provides
1218    /// natively (Apache AGE) load as no-ops and unknown libraries fail
1219    /// like a missing `$libdir` file.
1220    Load {
1221        library: String,
1222    },
1223    /// `EXPLAIN ...`. Carries the inner statement so the engine can
1224    /// emit the planner output.
1225    Explain {
1226        analyze: bool,
1227        verbose: bool,
1228        format: Option<String>,
1229        body: Box<Statement>,
1230    },
1231    /// `ANALYZE [table]`. The engine refreshes per-column statistics
1232    /// for cardinality estimation; the AST simply records the target.
1233    Analyze {
1234        table: Option<String>,
1235    },
1236    /// `TRUNCATE TABLE t1, t2 ...`. Wipes the listed tables.
1237    Truncate {
1238        tables: Vec<String>,
1239        cascade: bool,
1240    },
1241    /// `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT name`.
1242    Transaction(TransactionStmt),
1243    /// `CREATE SEQUENCE name [START n] [INCREMENT n]`.
1244    CreateSequence(CreateSequence),
1245    /// `ALTER SEQUENCE name [RESTART [WITH n]] [INCREMENT [BY] n]
1246    /// [START [WITH] n]`.
1247    AlterSequence(AlterSequence),
1248    /// `CREATE TABLE name AS SELECT ...`.
1249    CreateTableAs {
1250        name: String,
1251        if_not_exists: bool,
1252        body: Box<SelectStmt>,
1253    },
1254    /// `PREPARE name AS <inner>`.
1255    Prepare {
1256        name: String,
1257        body: Box<Statement>,
1258    },
1259    /// `EXECUTE name (param1, param2, ...)`.
1260    Execute {
1261        name: String,
1262        params: Vec<Expr>,
1263    },
1264    /// `DEALLOCATE name | DEALLOCATE ALL`. `None` means ALL.
1265    Deallocate {
1266        name: Option<String>,
1267    },
1268    /// `SELECT * FROM (VALUES ...) [AS alias]` -- a standalone VALUES
1269    /// statement (also reachable from a SET-OP body).
1270    Values {
1271        rows: Vec<Vec<Expr>>,
1272    },
1273    /// `CREATE SERVER name FOREIGN DATA WRAPPER type OPTIONS (...)`.
1274    CreateForeignServer(CreateForeignServer),
1275    /// `CREATE FOREIGN TABLE name (...) SERVER server OPTIONS (...)`.
1276    CreateForeignTable(CreateForeignTable),
1277    /// `MERGE INTO target USING source ON cond WHEN MATCHED THEN ...
1278    /// WHEN NOT MATCHED THEN ...`. SQL:2003 conditional UPSERT.
1279    Merge(MergeStmt),
1280    /// `CREATE [OR REPLACE] FUNCTION | PROCEDURE ...`. Boxed: the
1281    /// definition (parameters + body source) dwarfs other variants.
1282    CreateFunction(Box<CreateFunction>),
1283    /// `DROP FUNCTION | PROCEDURE [IF EXISTS] name[(args)] [, ...]`.
1284    DropFunction(DropFunctionStmt),
1285    /// `DO [LANGUAGE lang] $$ ... $$` - anonymous code block.
1286    DoBlock {
1287        language: String,
1288        body: String,
1289    },
1290    /// `CALL proc(args)` - procedure invocation. `OUT` / `INOUT`
1291    /// parameters shape the result row.
1292    Call {
1293        name: String,
1294        args: Vec<Expr>,
1295    },
1296}
1297
1298#[derive(Debug, Clone, Serialize, Deserialize)]
1299pub struct MergeStmt {
1300    pub target: String,
1301    pub target_qualifier: String,
1302    pub target_alias: Option<String>,
1303    pub source: FromClause,
1304    pub join_condition: Expr,
1305    pub when_clauses: Vec<MergeWhen>,
1306    /// `MERGE ... RETURNING ...` projection list. Empty when absent.
1307    pub returning: Vec<Projection>,
1308    pub returning_aliases: ReturningAliases,
1309}
1310
1311#[derive(Debug, Clone, Serialize, Deserialize)]
1312pub enum MergeWhen {
1313    /// `WHEN MATCHED [AND <cond>] THEN UPDATE SET ...`.
1314    UpdateMatched {
1315        condition: Option<Expr>,
1316        assignments: Vec<(String, Expr)>,
1317    },
1318    /// `WHEN MATCHED [AND <cond>] THEN DELETE`.
1319    DeleteMatched { condition: Option<Expr> },
1320    /// `WHEN NOT MATCHED [AND <cond>] THEN INSERT (cols) VALUES (vals)`.
1321    InsertNotMatched {
1322        condition: Option<Expr>,
1323        columns: Vec<String>,
1324        values: Vec<Expr>,
1325    },
1326    /// `WHEN MATCHED [AND <cond>] THEN DO NOTHING`.
1327    NothingMatched { condition: Option<Expr> },
1328    /// `WHEN NOT MATCHED [AND <cond>] THEN DO NOTHING`.
1329    NothingNotMatched { condition: Option<Expr> },
1330}
1331
1332#[derive(Debug, Clone, Serialize, Deserialize)]
1333pub struct CreateForeignServer {
1334    pub name: String,
1335    pub fdw_type: String,
1336    pub options: Vec<(String, String)>,
1337    pub if_not_exists: bool,
1338}
1339
1340#[derive(Debug, Clone, Serialize, Deserialize)]
1341pub struct CreateForeignTable {
1342    pub name: String,
1343    pub server_name: String,
1344    pub columns: Vec<ColumnDef>,
1345    pub options: Vec<(String, String)>,
1346    pub if_not_exists: bool,
1347}
1348
1349#[derive(Debug, Clone, Serialize, Deserialize)]
1350pub struct CreateSequence {
1351    pub name: String,
1352    pub if_not_exists: bool,
1353    pub start: i64,
1354    pub increment: i64,
1355}
1356
1357/// Physical restart action carried by `ALTER SEQUENCE`.
1358#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1359pub enum SequenceRestart {
1360    /// No `RESTART` clause was specified.
1361    #[default]
1362    Unchanged,
1363    /// Bare `RESTART`; allocate the configured start value next.
1364    FromStart,
1365    /// `RESTART WITH value`; allocate the supplied value next.
1366    With(i64),
1367}
1368
1369fn deserialize_sequence_restart<'de, D>(deserializer: D) -> Result<SequenceRestart, D::Error>
1370where
1371    D: serde::Deserializer<'de>,
1372{
1373    #[derive(Deserialize)]
1374    enum Current {
1375        Unchanged,
1376        FromStart,
1377        With(i64),
1378    }
1379
1380    #[derive(Deserialize)]
1381    #[serde(untagged)]
1382    enum Representation {
1383        Current(Current),
1384        // Before SequenceRestart existed this field was
1385        // Option<Option<i64>>, serialized as null or an integer.
1386        Legacy(Option<i64>),
1387    }
1388
1389    Ok(match Representation::deserialize(deserializer)? {
1390        Representation::Current(Current::Unchanged) | Representation::Legacy(None) => {
1391            SequenceRestart::Unchanged
1392        }
1393        Representation::Current(Current::FromStart) => SequenceRestart::FromStart,
1394        Representation::Current(Current::With(value)) | Representation::Legacy(Some(value)) => {
1395            SequenceRestart::With(value)
1396        }
1397    })
1398}
1399
1400#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1401pub struct AlterSequence {
1402    pub name: String,
1403    /// `ALTER SEQUENCE IF EXISTS` suppresses only a missing sequence.
1404    #[serde(default)]
1405    pub if_exists: bool,
1406    /// `RESTART [WITH n]`, preserving omitted, bare, and explicit forms.
1407    #[serde(default, deserialize_with = "deserialize_sequence_restart")]
1408    pub restart: SequenceRestart,
1409    pub increment: Option<i64>,
1410    pub start: Option<i64>,
1411}
1412
1413#[derive(Debug, Clone, Serialize, Deserialize)]
1414pub enum TransactionStmt {
1415    Begin,
1416    Commit,
1417    Rollback,
1418    Savepoint(String),
1419    ReleaseSavepoint(String),
1420    RollbackToSavepoint(String),
1421}
1422
1423#[cfg(test)]
1424mod tests {
1425    use super::{AlterSequence, ColumnType, SequenceRestart};
1426
1427    #[test]
1428    fn regclass_scalar_and_array_names_preserve_type_identity() {
1429        assert_eq!(
1430            ColumnType::from_sql_name("pg_catalog.regclass").unwrap(),
1431            ColumnType::Regclass
1432        );
1433        assert_eq!(
1434            ColumnType::from_sql_name("_regclass").unwrap(),
1435            ColumnType::Array(Box::new(ColumnType::Regclass))
1436        );
1437        assert_eq!(ColumnType::Regclass.sql_name(), "regclass");
1438    }
1439
1440    #[test]
1441    fn regtype_output_omits_type_modifiers() {
1442        assert_eq!(
1443            ColumnType::Varchar(Some(7)).regtype_name(),
1444            "character varying"
1445        );
1446        assert_eq!(
1447            ColumnType::Numeric {
1448                precision: Some(10),
1449                scale: Some(2),
1450            }
1451            .regtype_name(),
1452            "numeric"
1453        );
1454        assert_eq!(ColumnType::Vector(3).regtype_name(), "vector");
1455        assert_eq!(
1456            ColumnType::Array(Box::new(ColumnType::Character(4))).regtype_name(),
1457            "character[]"
1458        );
1459    }
1460
1461    #[test]
1462    fn alter_sequence_restart_reads_legacy_and_current_serde_shapes() {
1463        let omitted: AlterSequence = serde_json::from_str(r#"{"name":"s"}"#).unwrap();
1464        assert_eq!(omitted.restart, SequenceRestart::Unchanged);
1465
1466        let legacy_none: AlterSequence =
1467            serde_json::from_str(r#"{"name":"s","restart":null}"#).unwrap();
1468        assert_eq!(legacy_none.restart, SequenceRestart::Unchanged);
1469
1470        let legacy_value: AlterSequence =
1471            serde_json::from_str(r#"{"name":"s","restart":7}"#).unwrap();
1472        assert_eq!(legacy_value.restart, SequenceRestart::With(7));
1473
1474        let current = AlterSequence {
1475            name: "s".into(),
1476            restart: SequenceRestart::FromStart,
1477            ..AlterSequence::default()
1478        };
1479        let round_trip: AlterSequence =
1480            serde_json::from_str(&serde_json::to_string(&current).unwrap()).unwrap();
1481        assert_eq!(round_trip.restart, SequenceRestart::FromStart);
1482    }
1483}