Skip to main content

uqa_sql/ast/
constraints.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Column and table constraint nodes shared by CREATE and ALTER TABLE.
8
9use serde::{Deserialize, Serialize};
10
11use super::{
12    deserialize_auto_increment, AutoIncrement, ColumnType, Expr, GeneratedColumn, OnCommitAction,
13    PartitionBound, PartitionSpec, RelationPersistence, TableHierarchy,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[allow(clippy::struct_excessive_bools)]
18pub struct ColumnDef {
19    pub name: String,
20    pub ty: ColumnType,
21    /// Durable identity of this catalog column. Logical names can change while a fixed transaction snapshot continues to address the same column.
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub object_id: Option<[u8; 16]>,
24    /// Value exposed for physical rows captured before this column was added. This is the catalog equivalent of `PostgreSQL`'s `attmissingval`.
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub missing_value: Option<uqa_core::Value>,
27    pub primary_key: bool,
28    pub not_null: bool,
29    /// Whether `NOT NULL` was declared as its own constraint instead of being
30    /// implied by `PRIMARY KEY` or an auto-incrementing identity.
31    #[serde(default)]
32    pub not_null_explicit: bool,
33    /// Durable `PostgreSQL` 18 `NOT NULL` constraint name. Parsing leaves an
34    /// unnamed declaration as `None`; table registration assigns and persists
35    /// `PostgreSQL`'s generated name before the constraint becomes visible.
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub not_null_name: Option<String>,
38    /// Whether the named `NOT NULL` constraint has been validated against
39    /// every pre-existing row. `NOT VALID` still enforces future writes.
40    #[serde(default = "default_true")]
41    pub not_null_validated: bool,
42    /// Durable `NO INHERIT` state for `PostgreSQL` 18 named `NOT NULL`
43    /// constraints.
44    #[serde(default)]
45    pub not_null_no_inherit: bool,
46    /// Sequence provenance for `SERIAL` / `BIGSERIAL` and identity columns. The custom decoder accepts the legacy boolean representation written by releases that merged both SQL features into one table counter.
47    #[serde(
48        default,
49        deserialize_with = "deserialize_auto_increment",
50        skip_serializing_if = "Option::is_none"
51    )]
52    pub auto_increment: Option<AutoIncrement>,
53    /// `UNIQUE` column constraint -- the engine rejects an INSERT
54    /// whose value for this column already exists in another row.
55    #[serde(default)]
56    pub unique: bool,
57    /// `DEFAULT <expr>`. Evaluated at INSERT time when the column is
58    /// not present in the row tuple. Persisted in catalog metadata so
59    /// reopened engines keep the same INSERT semantics.
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub default: Option<Expr>,
62    /// `PostgreSQL` 18 generated-column definition. Stored values are refreshed
63    /// on every row write; virtual values are evaluated from the physical row
64    /// only when a logical row is read.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub generated: Option<GeneratedColumn>,
67    /// `CHECK (<expr>)` column-level constraint. Evaluated at INSERT
68    /// (and UPDATE-replace) time against the row being written.
69    #[serde(default, skip_serializing_if = "Option::is_none")]
70    pub check: Option<Expr>,
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub check_name: Option<String>,
73    #[serde(default = "default_true")]
74    pub check_enforced: bool,
75    #[serde(default = "default_true")]
76    pub check_validated: bool,
77    #[serde(default)]
78    pub check_no_inherit: bool,
79    /// Column-level `REFERENCES parent[(col)]` foreign key. An omitted column is resolved to the referenced primary key before publication.
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub references: Option<ForeignKeyRef>,
82}
83
84/// `REFERENCES table[(column)]` reference target.
85#[derive(Debug, Clone, Serialize, Deserialize)]
86#[allow(clippy::struct_excessive_bools)]
87pub struct ForeignKeyRef {
88    #[serde(default, skip_serializing_if = "Option::is_none")]
89    pub referenced_key: Option<String>,
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub name: Option<String>,
92    /// Durable identity of the catalog constraint object. The engine assigns
93    /// this when the constraint is published; parsed SQL leaves it unset.
94    #[serde(default, skip_serializing_if = "Option::is_none")]
95    pub object_id: Option<[u8; 16]>,
96    pub table: String,
97    #[serde(default, skip_serializing_if = "Option::is_none")]
98    pub column: Option<String>,
99    #[serde(default)]
100    pub on_update: ForeignKeyAction,
101    #[serde(default)]
102    pub on_delete: ForeignKeyAction,
103    #[serde(default)]
104    pub match_type: ForeignKeyMatch,
105    #[serde(default = "default_true")]
106    pub enforced: bool,
107    #[serde(default = "default_true")]
108    pub validated: bool,
109    #[serde(default)]
110    pub deferrable: bool,
111    #[serde(default)]
112    pub initially_deferred: bool,
113    /// `REFERENCES table (..., PERIOD column)` temporal coverage semantics.
114    #[serde(default)]
115    pub period: bool,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct CreateTable {
120    pub name: String,
121    /// Local SQL relation identifier used while binding expressions declared inside the table definition.
122    pub qualifier: String,
123    pub columns: Vec<ColumnDef>,
124    /// `CREATE TABLE IF NOT EXISTS` - silently ignore the statement
125    /// when a table with this name already exists.
126    pub if_not_exists: bool,
127    /// Table-level `CHECK (...)` constraints. Each entry is an
128    /// expression that must evaluate truthy against every row.
129    #[allow(dead_code)]
130    pub checks: Vec<TableCheck>,
131    /// Table-level `FOREIGN KEY (col, ...) REFERENCES parent(col, ...)`.
132    pub foreign_keys: Vec<ForeignKey>,
133    /// Every declared `PRIMARY KEY` / `UNIQUE` constraint, including
134    /// column-level declarations. Keeping the typed key (rather than only
135    /// setting per-column flags) preserves composite-key and `NULLS NOT
136    /// DISTINCT` semantics through planning and catalog persistence.
137    #[serde(default)]
138    pub key_constraints: Vec<TableKeyConstraint>,
139    /// `PostgreSQL` relation persistence selected by `TEMPORARY` or `UNLOGGED`.
140    #[serde(default)]
141    pub persistence: RelationPersistence,
142    /// Transaction-end behavior for temporary tables.
143    #[serde(default)]
144    pub on_commit: OnCommitAction,
145    /// Direct inheritance and declarative-partitioning metadata. The engine
146    /// resolves parent names and merges their row types atomically at create
147    /// time, then persists the canonical hierarchy with the table schema.
148    #[serde(default)]
149    pub hierarchy: TableHierarchy,
150}
151
152/// A syntactically valid `CREATE TABLE IF NOT EXISTS` whose definition must be analyzed only after execution has established that the target relation does not already exist.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct DeferredCreateTable {
155    pub name: String,
156    pub persistence: RelationPersistence,
157    pub definition_sql: String,
158}
159
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
161pub enum TableKeyConstraintKind {
162    PrimaryKey,
163    Unique,
164}
165
166/// A table key whose columns are compared as one tuple.
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168pub struct TableKeyConstraint {
169    pub name: Option<String>,
170    pub kind: TableKeyConstraintKind,
171    pub columns: Vec<String>,
172    /// `PostgreSQL` UNIQUE keys normally treat every NULL-containing tuple as
173    /// distinct. `UNIQUE NULLS NOT DISTINCT` opts into NULL equality.
174    #[serde(default)]
175    pub nulls_not_distinct: bool,
176    /// The final key column is a range or multirange compared by overlap.
177    #[serde(default)]
178    pub without_overlaps: bool,
179}
180
181/// Durable table-level constraints that do not fit in `ColumnDef`.
182///
183/// `serde(default)` on the catalog field containing this structure keeps
184/// databases written before constraint persistence backward compatible.
185#[derive(Debug, Clone, Default, Serialize, Deserialize)]
186pub struct TableConstraintSet {
187    #[serde(default)]
188    pub checks: Vec<TableCheck>,
189    #[serde(default)]
190    pub foreign_keys: Vec<ForeignKey>,
191    #[serde(default)]
192    pub key_constraints: Vec<TableKeyConstraint>,
193    /// Stored alongside the table definition so reopen preserves `pg_class.relpersistence` for unlogged tables.
194    #[serde(default)]
195    pub persistence: RelationPersistence,
196    /// Permanent and unlogged tables always use the default. Temporary tables are session-local and therefore never write this field to disk.
197    #[serde(default)]
198    pub on_commit: OnCommitAction,
199    /// Durable relation hierarchy and partition-bound metadata.
200    #[serde(default)]
201    pub hierarchy: TableHierarchy,
202}
203
204/// `CHECK (expr)` constraint with an optional name (`CONSTRAINT <name>
205/// CHECK (...)`).
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct TableCheck {
208    pub name: Option<String>,
209    pub expr: Expr,
210    #[serde(default = "default_true")]
211    pub enforced: bool,
212    #[serde(default = "default_true")]
213    pub validated: bool,
214    #[serde(default)]
215    pub no_inherit: bool,
216    /// Runtime form of the bound CHECK retained after `DETACH PARTITION ... CONCURRENTLY`.
217    #[serde(default, skip_serializing_if = "Option::is_none")]
218    pub partition_constraint: Option<DetachedPartitionConstraint>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct DetachedPartitionConstraint {
223    pub spec: PartitionSpec,
224    pub bound: PartitionBound,
225}
226
227/// Table-level foreign key. Compilation preserves an omitted referenced column list as empty; validation fills it from the primary key before publication.
228#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
229#[allow(clippy::struct_excessive_bools)]
230pub struct ForeignKey {
231    /// Name of the selected unique index in the referenced relation namespace.
232    #[serde(default, skip_serializing_if = "Option::is_none")]
233    pub referenced_key: Option<String>,
234    pub name: Option<String>,
235    /// Durable identity of the catalog constraint object. The engine assigns
236    /// this when the constraint is published; parsed SQL leaves it unset.
237    #[serde(default, skip_serializing_if = "Option::is_none")]
238    pub object_id: Option<[u8; 16]>,
239    pub local_columns: Vec<String>,
240    pub ref_table: String,
241    pub ref_columns: Vec<String>,
242    #[serde(default)]
243    pub on_update: ForeignKeyAction,
244    #[serde(default)]
245    pub on_delete: ForeignKeyAction,
246    /// Optional column subset for `ON DELETE SET NULL (...)` and
247    /// `ON DELETE SET DEFAULT (...)`. Empty means every local FK
248    /// column participates.
249    #[serde(default)]
250    pub on_delete_set_columns: Vec<String>,
251    #[serde(default)]
252    pub match_type: ForeignKeyMatch,
253    #[serde(default = "default_true")]
254    pub enforced: bool,
255    #[serde(default = "default_true")]
256    pub validated: bool,
257    #[serde(default)]
258    pub deferrable: bool,
259    #[serde(default)]
260    pub initially_deferred: bool,
261    /// The final local and referenced columns use `PostgreSQL` PERIOD coverage.
262    #[serde(default)]
263    pub period: bool,
264}
265
266const fn default_true() -> bool {
267    true
268}
269
270#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
271pub enum ForeignKeyAction {
272    #[default]
273    NoAction,
274    Restrict,
275    Cascade,
276    SetNull,
277    SetDefault,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
281pub enum ForeignKeyMatch {
282    #[default]
283    Simple,
284    Full,
285}