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