Skip to main content

uqa_sql/
ast.rs

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