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, ColumnType, ForeignKey, TableCheck, TableKeyConstraint},
10    SQLError,
11};
12pub fn validate_added_key_columns(
13    table: &str,
14    constraint: &TableKeyConstraint,
15    columns: &[ColumnDef],
16) -> Result<(), SQLError> {
17    let column_names: std::collections::BTreeSet<&str> =
18        columns.iter().map(|column| column.name.as_str()).collect();
19    for column in &constraint.columns {
20        if !column_names.contains(column.as_str()) {
21            return Err(SQLError::TypeMismatch(format!(
22                "ALTER TABLE ADD CONSTRAINT references unknown column `{column}`"
23            )));
24        }
25    }
26    if constraint.without_overlaps {
27        let period_column = constraint.columns.last().ok_or_else(|| {
28            SQLError::TypeMismatch(
29                "constraint using WITHOUT OVERLAPS needs at least two columns".into(),
30            )
31        })?;
32        let period_type = columns
33            .iter()
34            .find(|column| column.name == *period_column)
35            .map(|column| &column.ty)
36            .ok_or_else(|| SQLError::UnknownColumn(format!("{table}.{period_column}")))?;
37        if !matches!(
38            period_type,
39            ColumnType::Range(_) | ColumnType::Multirange(_)
40        ) {
41            return Err(SQLError::Routine {
42                sqlstate: "42804".into(),
43                message: format!(
44                    "column \"{period_column}\" in WITHOUT OVERLAPS is not a range or multirange type"
45                ),
46            });
47        }
48        if constraint.columns.len() < 2 {
49            return Err(SQLError::TypeMismatch(
50                "constraint using WITHOUT OVERLAPS needs at least two columns".into(),
51            ));
52        }
53    }
54
55    Ok(())
56}
57pub fn validate_added_key_identity(
58    table: &str,
59    constraint: &TableKeyConstraint,
60    existing_keys: &[TableKeyConstraint],
61    checks: &[TableCheck],
62    foreign_keys: &[ForeignKey],
63) -> Result<(), SQLError> {
64    if let Some(name) = constraint.name.as_deref() {
65        let check_name_exists = checks
66            .iter()
67            .any(|existing| existing.name.as_deref() == Some(name));
68        let foreign_name_exists = foreign_keys
69            .iter()
70            .any(|existing| existing.name.as_deref() == Some(name));
71        let key_name_exists = existing_keys
72            .iter()
73            .any(|existing| existing.name.as_deref() == Some(name));
74        if check_name_exists || foreign_name_exists || key_name_exists {
75            return Err(SQLError::TypeMismatch(format!(
76                "constraint `{name}` already exists on table `{table}`"
77            )));
78        }
79    }
80    if constraint.kind == crate::ast::TableKeyConstraintKind::PrimaryKey
81        && existing_keys
82            .iter()
83            .any(|existing| existing.kind == crate::ast::TableKeyConstraintKind::PrimaryKey)
84    {
85        return Err(SQLError::TypeMismatch(format!(
86            "multiple PRIMARY KEY constraints are not allowed on table `{table}`"
87        )));
88    }
89
90    Ok(())
91}
92
93/// Apply the NOT NULL requirement of a primary key to the stored column candidate.
94pub fn apply_primary_key_columns(
95    table: &str,
96    constraint: &TableKeyConstraint,
97    columns: &mut [ColumnDef],
98) -> Result<(), String> {
99    if constraint.kind == crate::ast::TableKeyConstraintKind::PrimaryKey {
100        for key_column in &constraint.columns {
101            let column = columns
102                .iter_mut()
103                .find(|column| column.name == *key_column)
104                .ok_or_else(|| {
105                    format!("column `{key_column}` does not exist on table `{table}`")
106                })?;
107            column.not_null = true;
108        }
109    }
110    Ok(())
111}