Skip to main content

uqa_sql/schema/
keys.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Declaration and identity rules for newly added PRIMARY KEY and UNIQUE constraints.
8use crate::{
9    ast::{ColumnDef, TableKeyConstraint, TableKeyConstraintKind},
10    SQLError,
11};
12/// The relation that ALTER TABLE adds a key to.
13pub struct AddedKeyRelation<'a> {
14    pub table: &'a str,
15    pub columns: &'a [ColumnDef],
16    pub keys: &'a [TableKeyConstraint],
17    /// The relation's own partition key, when it is partitioned.
18    pub partition: Option<&'a crate::ast::PartitionSpec>,
19}
20
21/// Validate a key that ALTER TABLE adds, before its index is named: its declaration as `transformIndexConstraint` checks it, a primary key's columns as the NOT NULL constraints that `ATPrepAddPrimaryKey` adds before the index, and then the checks of `DefineIndex`, which resolves the key's columns.
22pub fn validate_added_key(
23    relation: &AddedKeyRelation<'_>,
24    key: &TableKeyConstraint,
25) -> Result<(), SQLError> {
26    if let Some(column) = key
27        .columns
28        .iter()
29        .enumerate()
30        .find_map(|(position, column)| key.columns[..position].contains(column).then_some(column))
31    {
32        return Err(definition::repeated_key_column(key.kind, column));
33    }
34    let column = |name: &str| relation.columns.iter().find(|column| column.name == name);
35    let system = definition::is_system_column;
36    if key.without_overlaps {
37        if let Some(period) = key.columns.last() {
38            if let Some(found) = column(period) {
39                definition::validate_overlaps_column(period, Some(&found.ty))?;
40            } else if system(period) {
41                definition::validate_overlaps_column(period, None)?;
42            }
43        }
44    }
45    definition::validate_overlaps_key_length(key)?;
46    if key.kind == TableKeyConstraintKind::PrimaryKey {
47        for name in &key.columns {
48            if column(name).is_some() {
49                continue;
50            }
51            if system(name) {
52                return Err(SQLError::Routine {
53                    sqlstate: "0A000".into(),
54                    message: format!("cannot add not-null constraint on system column \"{name}\""),
55                });
56            }
57            let local = uqa_core::RelationIdentity::from_legacy_name(relation.table)
58                .map_err(SQLError::Internal)?;
59            return Err(SQLError::Routine {
60                sqlstate: "42703".into(),
61                message: format!(
62                    "column \"{name}\" of relation \"{}\" does not exist",
63                    local.name
64                ),
65            });
66        }
67    }
68    definition::validate_key_definition(
69        &definition::KeyRelation {
70            table: relation.table,
71            columns: relation.columns,
72            partition: relation.partition,
73            has_primary_key: relation
74                .keys
75                .iter()
76                .any(|existing| existing.kind == TableKeyConstraintKind::PrimaryKey),
77        },
78        key,
79    )
80}
81
82/// The keys that ALTER TABLE ADD COLUMN declares on the new column, transformed as `transformIndexConstraints` transforms the statement: a second primary key fails with the relation's name as written, and a key repeating an earlier key's index is dropped.
83pub fn transform_column_keys(
84    relation: &str,
85    keys: &[TableKeyConstraint],
86) -> Result<Vec<TableKeyConstraint>, SQLError> {
87    if keys
88        .iter()
89        .filter(|key| key.kind == TableKeyConstraintKind::PrimaryKey)
90        .count()
91        > 1
92    {
93        return Err(definition::multiple_primary_keys(relation));
94    }
95    Ok(definition::index_order(keys.to_vec()))
96}
97
98/// Apply the NOT NULL requirement of a primary key to the stored column candidate.
99pub fn apply_primary_key_columns(
100    table: &str,
101    constraint: &TableKeyConstraint,
102    columns: &mut [ColumnDef],
103) -> Result<(), String> {
104    if constraint.kind == TableKeyConstraintKind::PrimaryKey {
105        for key_column in &constraint.columns {
106            let column = columns
107                .iter_mut()
108                .find(|column| column.name == *key_column)
109                .ok_or_else(|| {
110                    format!("column `{key_column}` does not exist on table `{table}`")
111                })?;
112            column.not_null = true;
113        }
114    }
115    Ok(())
116}
117
118pub mod definition;
119
120#[cfg(test)]
121mod tests;