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    RenameRule {
832        from: String,
833        to: String,
834    },
835    SetTriggerEnableMode {
836        name: Option<String>,
837        user_only: bool,
838        mode: EventEnableMode,
839    },
840    SetRuleEnableMode {
841        name: String,
842        mode: EventEnableMode,
843    },
844    SetDefault {
845        name: String,
846        default: Expr,
847    },
848    DropDefault {
849        name: String,
850    },
851    SetExpression {
852        name: String,
853        expression: Expr,
854    },
855    DropExpression {
856        name: String,
857    },
858    SetNotNull {
859        name: String,
860    },
861    DropNotNull {
862        name: String,
863    },
864    AlterColumnType {
865        name: String,
866        ty: ColumnType,
867        #[serde(default, skip_serializing_if = "Option::is_none")]
868        using: Option<Expr>,
869    },
870}
871
872#[derive(Debug, Clone, Serialize, Deserialize)]
873pub struct InsertStmt {
874    pub table: String,
875    /// SQL-visible target relation name: explicit alias, otherwise the local relation name.
876    pub target_qualifier: String,
877    #[serde(default = "default_include_descendants")]
878    pub include_descendants: bool,
879    pub columns: Vec<String>,
880    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
881    pub with: Vec<CTE>,
882    /// Inline `VALUES (...) (...)` rows. Empty when the statement is
883    /// an `INSERT ... SELECT` form; in that case `select_source` is
884    /// populated with the underlying SELECT.
885    pub rows: Vec<Vec<ValueExpr>>,
886    /// Populated when the statement is `INSERT INTO t (...) SELECT ...`.
887    /// The engine materialises the inner select first and then writes
888    /// each row through the standard INSERT path.
889    pub select_source: Option<Box<SelectStmt>>,
890    /// `ON CONFLICT (...) DO ...` clause. `None` for plain
891    /// `INSERT INTO ... VALUES ...` without conflict handling.
892    pub on_conflict: Option<OnConflict>,
893    /// `RETURNING ...` projection list. Empty when absent.
894    pub returning: Vec<Projection>,
895    /// `PostgreSQL` 18 names for the old and new row images visible to
896    /// `RETURNING`. The defaults are `old` and `new`.
897    pub returning_aliases: ReturningAliases,
898}
899
900#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
901pub struct ReturningAliases {
902    pub old: String,
903    pub new: String,
904    #[serde(default)]
905    pub old_explicit: bool,
906    #[serde(default)]
907    pub new_explicit: bool,
908}
909
910impl Default for ReturningAliases {
911    fn default() -> Self {
912        Self {
913            old: "old".into(),
914            new: "new".into(),
915            old_explicit: false,
916            new_explicit: false,
917        }
918    }
919}
920
921#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
922pub struct OnConflict {
923    /// Conflict target columns parsed from the `ON CONFLICT (col, ...)`
924    /// list. Empty when the clause uses `ON CONFLICT DO NOTHING` with
925    /// no target.
926    pub conflict_columns: Vec<String>,
927    pub action: OnConflictAction,
928}
929
930#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
931pub enum OnConflictAction {
932    /// `DO NOTHING` -- skip conflicting rows silently.
933    Nothing,
934    /// `DO UPDATE SET col = expr [, ...] [WHERE pred]` -- apply the
935    /// listed assignments to the existing row when the conflict
936    /// target matches.
937    Update {
938        assignments: Vec<(String, Expr)>,
939        r#where: Option<Expr>,
940    },
941}
942
943#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
944pub struct SelectStmt {
945    pub projections: Vec<Projection>,
946    /// Rows owned by a `VALUES` query body. `PostgreSQL` represents `VALUES`
947    /// through the same query node used for `SELECT`, so nested query bodies
948    /// such as CTEs and set-operation branches must retain them here.
949    #[serde(default, skip_serializing_if = "Vec::is_empty")]
950    pub values: Vec<Vec<Expr>>,
951    pub from: Option<FromClause>,
952    pub r#where: Option<Expr>,
953    pub group_by: Vec<Expr>,
954    /// Expanded GROUPING SETS / ROLLUP / CUBE specification. When
955    /// non-empty the executor produces one row per grouping set;
956    /// `group_by` is treated as a single grouping set in that case.
957    /// Each inner Vec lists the grouping-key expressions for that
958    /// set (an empty inner Vec means the global grand-total bucket).
959    pub grouping_sets: Vec<Vec<Expr>>,
960    /// `GROUP BY DISTINCT` -- remove duplicate grouping sets after grouping expressions have been resolved against their input types.
961    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
962    pub group_distinct: bool,
963    /// `HAVING <expr>`. Evaluated against each aggregated row and
964    /// filters out groups whose predicate is falsy. Mirrors PG's
965    /// `havingClause`.
966    pub having: Option<Expr>,
967    pub order_by: Vec<OrderBy>,
968    /// `LIMIT <expr>`. Stored as an expression so `LIMIT $1` and any
969    /// other constant-folding integer expression resolves at execute
970    /// time. `None` means no LIMIT clause was supplied.
971    pub limit: Option<Expr>,
972    /// `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.
973    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
974    pub with_ties: bool,
975    /// `OFFSET <expr>`. Same shape as [`SelectStmt::limit`].
976    pub offset: Option<Expr>,
977    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
978    pub with: Vec<CTE>,
979    /// Optional set operation: `Some` for UNION / INTERSECT / EXCEPT.
980    /// Parsed statements carry both operands in [`SetOp`]; `left` remains
981    /// optional only for backward-compatible deserialization.
982    pub set_op: Option<Box<SetOp>>,
983    /// `SELECT DISTINCT` -- de-duplicate the final result rows. Set by
984    /// the compiler whenever the parsed `distinct_clause` is non-empty.
985    pub distinct: bool,
986    /// `SELECT DISTINCT ON (<expr>, ...)` keys. Empty for plain
987    /// `SELECT DISTINCT`.
988    pub distinct_on: Vec<Expr>,
989    /// `FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE }` row-locking clauses, in source order. Empty when the query does not lock rows.
990    #[serde(default, skip_serializing_if = "Vec::is_empty")]
991    pub locking: Vec<LockingClause>,
992}
993
994#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
995pub struct SetOp {
996    pub kind: SetOpKind,
997    pub all: bool,
998    /// Explicit left-hand subtree. Parsed set operations are left-associative,
999    /// so a chain such as `a UNION b UNION c` carries `(a UNION b)` here
1000    /// instead of flattening it back to only `a`.
1001    #[serde(default, skip_serializing_if = "Option::is_none")]
1002    pub left: Option<Box<SelectStmt>>,
1003    pub right: SelectStmt,
1004    /// `ORDER BY` applied to the combined `lhs <op> rhs` result.
1005    /// Distinct from the LHS / RHS branches' own `ORDER BY`.
1006    pub combined_order_by: Vec<OrderBy>,
1007    /// `LIMIT` applied to the combined result. `None` means no
1008    /// outer LIMIT clause was supplied.
1009    pub combined_limit: Option<Expr>,
1010    /// Whether the combined set-operation limit is `FETCH ... WITH TIES`.
1011    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1012    pub combined_with_ties: bool,
1013    /// `OFFSET` applied to the combined result.
1014    pub combined_offset: Option<Expr>,
1015}
1016
1017#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1018pub enum SetOpKind {
1019    Union,
1020    Intersect,
1021    Except,
1022}
1023
1024/// `DISCARD` target. Mirrors `PostgreSQL`'s `DiscardMode`.
1025#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1026pub enum DiscardTarget {
1027    All,
1028    Plans,
1029    Sequences,
1030    Temp,
1031}
1032
1033#[derive(Debug, Clone, Serialize, Deserialize)]
1034pub struct UpdateStmt {
1035    pub table: String,
1036    pub target_qualifier: String,
1037    #[serde(default = "default_include_descendants")]
1038    pub include_descendants: bool,
1039    pub assignments: Vec<(String, Expr)>,
1040    pub r#where: Option<Expr>,
1041    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
1042    pub with: Vec<CTE>,
1043    /// `UPDATE t SET ... FROM other [JOIN ...]` -- the engine joins
1044    /// the target with this clause before applying the assignments.
1045    pub from: Option<FromClause>,
1046    /// `RETURNING ...` projection list. Empty when absent.
1047    pub returning: Vec<Projection>,
1048    pub returning_aliases: ReturningAliases,
1049}
1050
1051#[derive(Debug, Clone, Serialize, Deserialize)]
1052pub struct DeleteStmt {
1053    pub table: String,
1054    pub target_qualifier: String,
1055    #[serde(default = "default_include_descendants")]
1056    pub include_descendants: bool,
1057    pub r#where: Option<Expr>,
1058    /// Common table expressions defined with `WITH [RECURSIVE] ...`.
1059    pub with: Vec<CTE>,
1060    /// `DELETE FROM t USING other [JOIN ...]` -- the engine joins
1061    /// the target with this clause and deletes target rows whose
1062    /// joined image satisfies WHERE.
1063    pub using: Option<FromClause>,
1064    /// `RETURNING ...` projection list. Empty when absent.
1065    pub returning: Vec<Projection>,
1066    pub returning_aliases: ReturningAliases,
1067}
1068
1069#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1070pub struct SetConstraintName {
1071    pub catalog: Option<String>,
1072    pub schema: Option<String>,
1073    pub name: String,
1074}
1075
1076/// 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.
1077#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1078pub struct VacuumOption {
1079    pub name: String,
1080    pub value: Option<VacuumOptionValue>,
1081}
1082
1083#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1084pub enum VacuumOptionValue {
1085    Boolean(bool),
1086    Integer(i32),
1087    String(String),
1088}
1089
1090/// One relation (and optional ANALYZE column list) named by `VACUUM`.
1091#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1092pub struct VacuumTarget {
1093    pub catalog: Option<String>,
1094    pub table: String,
1095    #[serde(default = "default_include_descendants")]
1096    pub include_descendants: bool,
1097    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1098    pub columns: Vec<String>,
1099}
1100
1101#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1102pub struct VacuumStmt {
1103    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1104    pub options: Vec<VacuumOption>,
1105    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1106    pub targets: Vec<VacuumTarget>,
1107}
1108
1109#[derive(Debug, Clone, Serialize, Deserialize)]
1110pub enum Statement {
1111    CreateTable(CreateTable),
1112    CreateIndex(CreateIndex),
1113    Insert(InsertStmt),
1114    /// `SelectStmt` is the largest variant by far (CTEs + set-ops + n-ary
1115    /// expression trees), so we box it to keep the enum's stack footprint
1116    /// proportional to the smaller variants.
1117    Select(Box<SelectStmt>),
1118    Update(UpdateStmt),
1119    Delete(DeleteStmt),
1120    Drop(DropStmt),
1121    AlterTable(AlterTableStmt),
1122    AlterViewOptions(AlterViewOptionsStmt),
1123    /// `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).
1124    CreateView {
1125        name: String,
1126        #[serde(default)]
1127        column_names: Vec<String>,
1128        body: Box<SelectStmt>,
1129        or_replace: bool,
1130        #[serde(default)]
1131        persistence: RelationPersistence,
1132        /// Validated `PostgreSQL` view reloptions in declaration order.
1133        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1134        options: Vec<(String, String)>,
1135    },
1136    /// `CREATE MATERIALIZED VIEW ... AS SELECT ... [WITH [NO] DATA]`.
1137    CreateMaterializedView {
1138        name: String,
1139        #[serde(default)]
1140        column_names: Vec<String>,
1141        #[serde(default)]
1142        if_not_exists: bool,
1143        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1144        with_no_data: bool,
1145        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1146        options: Vec<(String, String)>,
1147        body: Box<SelectStmt>,
1148    },
1149    /// `REFRESH MATERIALIZED VIEW [CONCURRENTLY] name [WITH [NO] DATA]`.
1150    RefreshMaterializedView {
1151        name: String,
1152        concurrently: bool,
1153        with_no_data: bool,
1154    },
1155    /// `CREATE SCHEMA [IF NOT EXISTS] name`. This AST entry records the
1156    /// command for the engine's durable schema catalog and namespace
1157    /// resolver.
1158    CreateSchema {
1159        name: String,
1160        if_not_exists: bool,
1161    },
1162    /// `SET <name> [TO|=] <value>` - runtime parameter assignment.
1163    /// The engine gives `search_path` resolution semantics and stores other
1164    /// parameters in the logical session for subsequent `SHOW` statements.
1165    SetVariable {
1166        name: String,
1167        value: String,
1168    },
1169    /// `RESET <name>` restores one runtime parameter to its session default.
1170    ResetVariable {
1171        name: String,
1172    },
1173    /// `RESET ALL` restores every resettable runtime parameter.
1174    ResetAllVariables,
1175    /// `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.
1176    SetConstraints {
1177        constraints: Vec<SetConstraintName>,
1178        deferred: bool,
1179    },
1180    /// `SHOW <variable>` - return the runtime parameter as one
1181    /// `(name -> value)` row.
1182    ShowVariable {
1183        name: String,
1184    },
1185    /// `DISCARD [ALL|PLANS|SEQUENCES|TEMP|TEMPORARY]` - clear session state.
1186    /// The engine resets session variables, prepared statements, sequence state, and the current session's temporary relations as requested.
1187    Discard {
1188        target: DiscardTarget,
1189    },
1190    /// `LOAD 'library'` - load a shared library into the session. The
1191    /// engine embeds its extension surface, so libraries it provides
1192    /// natively (Apache AGE) load as no-ops and unknown libraries fail
1193    /// like a missing `$libdir` file.
1194    Load {
1195        library: String,
1196    },
1197    /// `EXPLAIN ...`. Carries the inner statement so the engine can
1198    /// emit the planner output.
1199    Explain {
1200        analyze: bool,
1201        verbose: bool,
1202        format: Option<String>,
1203        body: Box<Statement>,
1204    },
1205    /// `ANALYZE [table]`. The engine refreshes per-column statistics
1206    /// for cardinality estimation; the AST simply records the target.
1207    Analyze {
1208        table: Option<String>,
1209    },
1210    /// `VACUUM [options] [relations]`. Execution enforces `PostgreSQL`'s transaction-block restriction before validating options and dispatching storage maintenance.
1211    Vacuum(VacuumStmt),
1212    /// `TRUNCATE TABLE t1, t2 ...`. Wipes the listed table hierarchies unless
1213    /// a target uses `ONLY`.
1214    Truncate {
1215        tables: Vec<TruncateTarget>,
1216        cascade: bool,
1217        #[serde(default)]
1218        restart_identity: bool,
1219    },
1220    /// `BEGIN` / `COMMIT` / `ROLLBACK` / `SAVEPOINT name`.
1221    Transaction(TransactionStmt),
1222    /// `DECLARE name [BINARY] [SCROLL] CURSOR [WITH HOLD] FOR query`.
1223    DeclareCursor(DeclareCursorStmt),
1224    /// `FETCH` or `MOVE` over a named SQL cursor.
1225    FetchCursor(FetchCursorStmt),
1226    /// `CLOSE name` or `CLOSE ALL`. `None` represents `ALL`.
1227    CloseCursor {
1228        name: Option<String>,
1229    },
1230    /// `CREATE SEQUENCE name [START n] [INCREMENT n]`.
1231    CreateSequence(CreateSequence),
1232    /// `ALTER SEQUENCE name [RESTART [WITH n]] [INCREMENT [BY] n]
1233    /// [START [WITH] n]`.
1234    AlterSequence(AlterSequence),
1235    /// `CREATE TABLE name AS SELECT ...`.
1236    CreateTableAs {
1237        name: String,
1238        if_not_exists: bool,
1239        #[serde(default, skip_serializing_if = "Vec::is_empty")]
1240        column_names: Vec<String>,
1241        #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1242        with_no_data: bool,
1243        #[serde(default)]
1244        persistence: RelationPersistence,
1245        #[serde(default)]
1246        on_commit: OnCommitAction,
1247        body: Box<SelectStmt>,
1248    },
1249    /// `PREPARE name AS <inner>`.
1250    Prepare {
1251        name: String,
1252        body: Box<Statement>,
1253    },
1254    /// `EXECUTE name (param1, param2, ...)`.
1255    Execute {
1256        name: String,
1257        params: Vec<Expr>,
1258    },
1259    /// `DEALLOCATE name | DEALLOCATE ALL`. `None` means ALL.
1260    Deallocate {
1261        name: Option<String>,
1262    },
1263    /// `SELECT * FROM (VALUES ...) [AS alias]` -- a standalone VALUES
1264    /// statement (also reachable from a SET-OP body).
1265    Values {
1266        rows: Vec<Vec<Expr>>,
1267    },
1268    /// `CREATE SERVER name FOREIGN DATA WRAPPER type OPTIONS (...)`.
1269    CreateForeignServer(CreateForeignServer),
1270    /// `CREATE FOREIGN TABLE name (...) SERVER server OPTIONS (...)`.
1271    CreateForeignTable(CreateForeignTable),
1272    /// `MERGE INTO target USING source ON cond WHEN MATCHED THEN ...
1273    /// WHEN NOT MATCHED THEN ...`. SQL:2003 conditional UPSERT.
1274    Merge(MergeStmt),
1275    /// `CREATE [OR REPLACE] FUNCTION | PROCEDURE ...`. Boxed: the
1276    /// definition (parameters + body source) dwarfs other variants.
1277    CreateFunction(Box<CreateFunction>),
1278    /// `DROP FUNCTION | PROCEDURE [IF EXISTS] name[(args)] [, ...]`.
1279    DropFunction(DropFunctionStmt),
1280    /// `ALTER FUNCTION | PROCEDURE | ROUTINE name[(input_types)]` volatility and null-input attributes.
1281    AlterRoutine(AlterRoutineStmt),
1282    AlterRoutineOwner(AlterRoutineOwnerStmt),
1283    GrantRoutine(GrantRoutineStmt),
1284    CreateRole(CreateRoleStmt),
1285    AlterRole(AlterRoleStmt),
1286    DropRole(DropRoleStmt),
1287    /// `CREATE [OR REPLACE] TRIGGER ... ON relation`.
1288    CreateTrigger(CreateTrigger),
1289    /// `DROP TRIGGER [IF EXISTS] name ON relation`.
1290    DropTrigger(DropTrigger),
1291    /// `CREATE [OR REPLACE] RULE ... ON relation`.
1292    CreateRule(CreateRule),
1293    /// `DROP RULE [IF EXISTS] name ON relation`.
1294    DropRule(DropRule),
1295    /// `DO [LANGUAGE lang] $$ ... $$` - anonymous code block.
1296    DoBlock {
1297        language: String,
1298        body: String,
1299    },
1300    /// `CALL proc(args)` - procedure invocation. `OUT` / `INOUT`
1301    /// parameters shape the result row.
1302    Call {
1303        name: String,
1304        args: Vec<Expr>,
1305    },
1306}
1307
1308#[derive(Debug, Clone, Serialize, Deserialize)]
1309pub struct TruncateTarget {
1310    pub table: String,
1311    #[serde(default = "default_include_descendants")]
1312    pub include_descendants: bool,
1313}
1314
1315#[derive(Debug, Clone, Serialize, Deserialize)]
1316pub struct MergeStmt {
1317    pub target: String,
1318    pub target_qualifier: String,
1319    pub target_alias: Option<String>,
1320    #[serde(default = "default_include_descendants")]
1321    pub include_descendants: bool,
1322    pub source: FromClause,
1323    pub join_condition: Expr,
1324    pub when_clauses: Vec<MergeWhen>,
1325    /// `MERGE ... RETURNING ...` projection list. Empty when absent.
1326    pub returning: Vec<Projection>,
1327    pub returning_aliases: ReturningAliases,
1328}
1329
1330#[derive(Debug, Clone, Serialize, Deserialize)]
1331pub enum MergeWhen {
1332    /// `WHEN MATCHED [AND <cond>] THEN UPDATE SET ...`.
1333    UpdateMatched {
1334        condition: Option<Expr>,
1335        assignments: Vec<(String, Expr)>,
1336    },
1337    /// `WHEN MATCHED [AND <cond>] THEN DELETE`.
1338    DeleteMatched { condition: Option<Expr> },
1339    /// `WHEN NOT MATCHED BY SOURCE [AND <cond>] THEN UPDATE SET ...`.
1340    UpdateNotMatchedBySource {
1341        condition: Option<Expr>,
1342        assignments: Vec<(String, Expr)>,
1343    },
1344    /// `WHEN NOT MATCHED BY SOURCE [AND <cond>] THEN DELETE`.
1345    DeleteNotMatchedBySource { condition: Option<Expr> },
1346    /// `WHEN NOT MATCHED [AND <cond>] THEN INSERT (cols) VALUES (vals)`.
1347    InsertNotMatched {
1348        condition: Option<Expr>,
1349        columns: Vec<String>,
1350        values: Vec<Expr>,
1351    },
1352    /// `WHEN MATCHED [AND <cond>] THEN DO NOTHING`.
1353    NothingMatched { condition: Option<Expr> },
1354    /// `WHEN NOT MATCHED [AND <cond>] THEN DO NOTHING`.
1355    NothingNotMatched { condition: Option<Expr> },
1356    /// `WHEN NOT MATCHED BY SOURCE [AND <cond>] THEN DO NOTHING`.
1357    NothingNotMatchedBySource { condition: Option<Expr> },
1358}
1359
1360#[derive(Debug, Clone, Serialize, Deserialize)]
1361pub struct CreateForeignServer {
1362    pub name: String,
1363    pub fdw_type: String,
1364    pub options: Vec<(String, String)>,
1365    pub if_not_exists: bool,
1366}
1367
1368#[derive(Debug, Clone, Serialize, Deserialize)]
1369pub struct CreateForeignTable {
1370    pub name: String,
1371    pub server_name: String,
1372    pub columns: Vec<ColumnDef>,
1373    pub options: Vec<(String, String)>,
1374    pub if_not_exists: bool,
1375}
1376
1377#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1378pub enum TransactionIsolationLevel {
1379    ReadUncommitted,
1380    ReadCommitted,
1381    RepeatableRead,
1382    Serializable,
1383}
1384
1385impl TransactionIsolationLevel {
1386    #[must_use]
1387    pub const fn as_str(self) -> &'static str {
1388        match self {
1389            Self::ReadUncommitted => "read uncommitted",
1390            Self::ReadCommitted => "read committed",
1391            Self::RepeatableRead => "repeatable read",
1392            Self::Serializable => "serializable",
1393        }
1394    }
1395}
1396
1397#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
1398pub struct TransactionCharacteristics {
1399    pub isolation: Option<TransactionIsolationLevel>,
1400    pub read_only: Option<bool>,
1401    pub deferrable: Option<bool>,
1402}
1403
1404#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1405pub enum TransactionStmt {
1406    Begin,
1407    BeginWithCharacteristics(TransactionCharacteristics),
1408    Commit,
1409    CommitAndChain,
1410    Rollback,
1411    RollbackAndChain,
1412    SetCharacteristics(TransactionCharacteristics),
1413    SetSessionCharacteristics(TransactionCharacteristics),
1414    SetSnapshot(String),
1415    Savepoint(String),
1416    ReleaseSavepoint(String),
1417    RollbackToSavepoint(String),
1418}
1419
1420#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1421pub enum CursorDirection {
1422    Forward,
1423    Backward,
1424    Absolute,
1425    Relative,
1426}
1427
1428#[derive(Debug, Clone, Serialize, Deserialize)]
1429pub struct DeclareCursorStmt {
1430    pub name: String,
1431    pub binary: bool,
1432    /// `None` lets the query determine scrollability, while `Some(true)` and `Some(false)` represent explicit `SCROLL` and `NO SCROLL`.
1433    pub scroll: Option<bool>,
1434    pub hold: bool,
1435    pub query: Box<SelectStmt>,
1436}
1437
1438#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1439pub struct FetchCursorStmt {
1440    pub name: String,
1441    pub direction: CursorDirection,
1442    /// `PostgreSQL` uses `i64::MAX` for `ALL`; negative counts reverse `FORWARD` and `BACKWARD`.
1443    pub count: i64,
1444    pub move_only: bool,
1445}
1446
1447#[cfg(test)]
1448mod tests;