Skip to main content

uqa_sql/schema/table_creation/
keys.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! The PRIMARY KEY and UNIQUE constraints of a new table. `transformIndexConstraints` validates the declared keys before the relation exists, and `DefineIndex` checks and names each key's index after `DefineRelation` has cloned the keys of a partition's parent.
8
9use crate::ast::{ColumnDef, ColumnType, CreateTable, TableKeyConstraint, TableKeyConstraintKind};
10use crate::schema::columns::POSTGRES_SYSTEM_COLUMNS;
11use crate::schema::constraint_metadata::{
12    identity::materialize_key_identity, materialize_foreign_key_identity, CatalogIdentityAllocator,
13    ConstraintMetadataError,
14};
15use crate::schema::indexes::names::{ConstraintIndexNamer, IndexNameCatalog};
16use crate::schema::indexes::unique::{
17    validate_partitioned_key_constraint, validate_partitioned_unique_key, PartitionedUniqueKey,
18};
19use crate::schema::inheritance::InheritanceContext;
20use crate::schema::keys::definition::{
21    index_order, missing_key_column, multiple_primary_keys, repeated_key_column,
22    validate_key_definition, validate_overlaps_column, validate_overlaps_key_length, KeyRelation,
23};
24use crate::SQLError;
25
26#[cfg(test)]
27mod tests;
28
29/// What a new table takes from its parents before its own definitions: the columns whose expressions the parents give, and the keys and foreign keys a partition clones from its parent.
30#[derive(Debug, Clone, PartialEq, Default)]
31pub struct InheritedDefinitions {
32    /// The columns whose default or generation expression a parent gives and the statement does not replace, which `heap_create_with_catalog` stores with the relation.
33    pub expressions: Vec<String>,
34    /// The NOT NULL constraints the parents give, in parent order, which `AddRelationNotNullConstraints` creates after the declared ones.
35    pub not_nulls: Vec<super::not_nulls::InheritedNotNull>,
36    /// The parent's keys, which precede the declared keys of the table.
37    pub keys: usize,
38    /// The parent's foreign keys, which precede the declared foreign keys of the table.
39    pub foreign_keys: usize,
40    /// The key attributes of the parent's unique indexes that no key constraint owns.
41    pub unique_indexes: Vec<Vec<crate::ast::IndexKey>>,
42}
43
44/// Validate the keys a CREATE TABLE declares, in declaration order, then order them as `transformIndexConstraints` does: the primary key first, and a key whose index would repeat an earlier key's index dropped, giving its name to that key when the earlier key has none.
45pub fn transform_declared_keys(
46    context: &InheritanceContext<'_>,
47    table: &mut CreateTable,
48) -> Result<(), SQLError> {
49    let mut columns = KeyColumns {
50        context,
51        declared: &table.columns,
52        parents: &table.hierarchy.parents,
53        inherited: Vec::new(),
54    };
55    let mut primary_key = false;
56    for key in &table.key_constraints {
57        if key.kind == TableKeyConstraintKind::PrimaryKey {
58            if primary_key {
59                return Err(multiple_primary_keys(&table.qualifier));
60            }
61            primary_key = true;
62        }
63        validate_declared_key(&mut columns, key)?;
64    }
65    table.key_constraints = index_order(std::mem::take(&mut table.key_constraints));
66    // The column flags follow the keys that survive; `define_declared_keys` sets them again.
67    for column in &mut table.columns {
68        column.primary_key = false;
69        column.unique = false;
70    }
71    Ok(())
72}
73
74/// The columns a declared key may name: the statement's columns, the system columns, and then each parent's columns, a parent being read only when a key names a column that no earlier source has.
75struct KeyColumns<'s, 'c> {
76    context: &'s InheritanceContext<'c>,
77    declared: &'s [ColumnDef],
78    parents: &'s [String],
79    inherited: Vec<Vec<ColumnDef>>,
80}
81
82enum KeyColumn {
83    Column(ColumnType),
84    System,
85}
86
87impl KeyColumns<'_, '_> {
88    fn find(&mut self, name: &str) -> Result<Option<KeyColumn>, SQLError> {
89        if let Some(column) = self.declared.iter().find(|column| column.name == name) {
90            return Ok(Some(KeyColumn::Column(column.ty.clone())));
91        }
92        if POSTGRES_SYSTEM_COLUMNS.contains(&name) {
93            return Ok(Some(KeyColumn::System));
94        }
95        for (position, requested) in self.parents.iter().enumerate() {
96            if position == self.inherited.len() {
97                let parent = self.context.catalog.resolve_parent(requested)?;
98                let columns = self
99                    .context
100                    .partitions
101                    .catalog
102                    .try_describe_table(&parent)
103                    .map_err(|error| {
104                        SQLError::Internal(format!("read inherited row type: {error}"))
105                    })?
106                    .ok_or_else(|| SQLError::UnknownTable(parent.clone()))?;
107                self.inherited.push(columns);
108            }
109            if let Some(column) = self.inherited[position]
110                .iter()
111                .find(|column| column.name == name)
112            {
113                return Ok(Some(KeyColumn::Column(column.ty.clone())));
114            }
115        }
116        Ok(None)
117    }
118}
119
120fn validate_declared_key(
121    columns: &mut KeyColumns<'_, '_>,
122    key: &TableKeyConstraint,
123) -> Result<(), SQLError> {
124    for (position, name) in key.columns.iter().enumerate() {
125        let Some(column) = columns.find(name)? else {
126            return Err(missing_key_column(name));
127        };
128        if key.columns[..position].contains(name) {
129            return Err(repeated_key_column(key.kind, name));
130        }
131        if key.without_overlaps && position + 1 == key.columns.len() {
132            let ty = match &column {
133                KeyColumn::Column(ty) => Some(ty),
134                KeyColumn::System => None,
135            };
136            validate_overlaps_column(name, ty)?;
137        }
138    }
139    validate_overlaps_key_length(key)?;
140    for name in &key.included_columns {
141        if columns.find(name)?.is_none() {
142            return Err(missing_key_column(name));
143        }
144    }
145    Ok(())
146}
147
148/// A declared primary key makes each of its columns NOT NULL through a constraint of the new table: an inherited NOT NULL becomes local and takes the name generated for the new table, as `AddRelationNotNullConstraints` merges the declaration with the inherited constraint.
149pub fn declare_primary_key_not_null(columns: &mut [ColumnDef], keys: &[TableKeyConstraint]) {
150    for key in keys
151        .iter()
152        .filter(|key| key.kind == TableKeyConstraintKind::PrimaryKey)
153    {
154        for name in &key.columns {
155            let Some(column) = columns.iter_mut().find(|column| column.name == *name) else {
156                continue;
157            };
158            if column.not_null && column.not_null_is_local {
159                continue;
160            }
161            if column.not_null {
162                column.not_null_name = None;
163                column.not_null_identity = None;
164            }
165            column.not_null = true;
166            column.not_null_is_local = true;
167            column.not_null_validated = true;
168        }
169    }
170}
171
172/// The keys a partition clones from its parent, which `DefineRelation` creates before the table's CHECK constraints: each unique index cloned from the parent is checked against the table's own partition key, and each cloned key's index is named, a name the CHECKs the table inherits hold being taken, and takes its OIDs; then `CloneForeignKeyConstraints` creates the partition's copies of the parent's foreign keys. The namer goes on to name the declared keys.
173pub fn clone_parent_keys<'a>(
174    catalog: &'a dyn IndexNameCatalog,
175    table: &mut CreateTable,
176    inherited: &InheritedDefinitions,
177    allocate: &mut CatalogIdentityAllocator<'_>,
178) -> Result<ConstraintIndexNamer<'a>, SQLError> {
179    let mut indexes = ConstraintIndexNamer::new(catalog, &table.name)?;
180    indexes.occupy(
181        table
182            .checks
183            .iter()
184            .filter(|check| !check.is_local)
185            .filter_map(|check| check.name.clone()),
186    );
187    let partition = table.hierarchy.partition_spec.as_ref();
188    for key in &mut table.key_constraints[..inherited.keys] {
189        if let Some(partition) = partition {
190            validate_partitioned_key_constraint(&table.name, key, partition)?;
191        }
192        indexes.name(key)?;
193        materialize_key_identity(key, allocate).map_err(ConstraintMetadataError::into_sql_error)?;
194    }
195    for foreign_key in &mut table.foreign_keys[..inherited.foreign_keys] {
196        materialize_foreign_key_identity(
197            &mut foreign_key.object_id,
198            &mut foreign_key.catalog_identity,
199            allocate,
200        )
201        .map_err(ConstraintMetadataError::into_sql_error)?;
202    }
203    if let Some(partition) = partition {
204        for keys in &inherited.unique_indexes {
205            let columns = keys
206                .iter()
207                .map(crate::ast::IndexKey::column)
208                .collect::<Vec<_>>();
209            validate_partitioned_unique_key(
210                &table.name,
211                &PartitionedUniqueKey {
212                    constraint_type: TableKeyConstraintKind::Unique,
213                    columns: &columns,
214                    without_overlaps: false,
215                },
216                partition,
217            )?;
218        }
219    }
220    Ok(indexes)
221}
222
223/// Define the keys the table declares as `DefineIndex` does once the table's CHECK and NOT NULL constraints exist: each key, primary key first, against an inherited primary key, the partition key and its index attributes. Each key's index is named after its checks, so a later key's errors follow an earlier key's name conflict, and no index takes a name a constraint of the table holds; the index then takes its OID and the constraint its own.
224pub fn define_declared_keys(
225    mut indexes: ConstraintIndexNamer<'_>,
226    table: &mut CreateTable,
227    inherited: &InheritedDefinitions,
228    allocate: &mut CatalogIdentityAllocator<'_>,
229) -> Result<(), SQLError> {
230    indexes.occupy(
231        table
232            .columns
233            .iter()
234            .flat_map(|column| [column.not_null_name.clone(), column.check_name.clone()])
235            .chain(table.checks.iter().map(|check| check.name.clone()))
236            .flatten(),
237    );
238    let inherited_primary_key = table.key_constraints[..inherited.keys]
239        .iter()
240        .any(|key| key.kind == TableKeyConstraintKind::PrimaryKey);
241    let partition = table.hierarchy.partition_spec.as_ref();
242    let declared = &mut table.key_constraints[inherited.keys..];
243    for key in declared.iter_mut() {
244        validate_key_definition(
245            &KeyRelation {
246                table: &table.name,
247                columns: &table.columns,
248                partition,
249                has_primary_key: inherited_primary_key,
250            },
251            key,
252        )?;
253        indexes.name(key)?;
254        materialize_key_identity(key, allocate).map_err(ConstraintMetadataError::into_sql_error)?;
255    }
256    mark_single_column_keys(&mut table.columns, &table.key_constraints[inherited.keys..]);
257    Ok(())
258}
259
260/// Single-column keys also flag their columns for the scalar key paths that read the flags.
261fn mark_single_column_keys(columns: &mut [ColumnDef], keys: &[TableKeyConstraint]) {
262    for key in keys {
263        let [name] = key.columns.as_slice() else {
264            continue;
265        };
266        let Some(column) = columns.iter_mut().find(|column| column.name == *name) else {
267            continue;
268        };
269        match key.kind {
270            TableKeyConstraintKind::PrimaryKey => column.primary_key = true,
271            TableKeyConstraintKind::Unique => column.unique = true,
272        }
273    }
274}