Skip to main content

uqa_sql/schema/columns/
addition.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind an added column against current defaults, CHECKs, generated expressions, and foreign keys.
8use crate::{
9    assignment::columns::ColumnCatalogError,
10    ast::{ColumnDef, ForeignKey, TableKeyConstraint},
11    schema::{
12        foreign_keys::ForeignKeyDefinitionContext, SchemaBindingContext, SchemaExpressionCatalog,
13    },
14    semantics::conflict::InferenceBindingScope,
15    SQLError,
16};
17pub trait AddedColumnKeys {
18    fn try_key_constraints(
19        &self,
20        table: &str,
21    ) -> Result<Vec<TableKeyConstraint>, ColumnCatalogError>;
22    fn try_foreign_keys(&self, table: &str) -> Result<Vec<ForeignKey>, ColumnCatalogError>;
23}
24pub struct AddedColumnAnalysisContext<'a> {
25    pub keys: &'a dyn AddedColumnKeys,
26    pub schema: &'a dyn SchemaExpressionCatalog,
27    pub bindings: &'a dyn InferenceBindingScope,
28    pub foreign_keys: ForeignKeyDefinitionContext<'a>,
29}
30fn ddl_storage_error(action: &str, error: ColumnCatalogError) -> SQLError {
31    crate::catalog::errors::storage_error(action, error.as_ref())
32}
33pub fn bind_added_column(
34    context: &AddedColumnAnalysisContext<'_>,
35    table: &str,
36    qualifier: &str,
37    column: &mut ColumnDef,
38) -> Result<(), SQLError> {
39    if let Some(default) = &mut column.default {
40        let binding = context.bindings.binding_scope()?;
41        crate::schema::defaults::validate_default_expression(
42            &SchemaBindingContext {
43                catalog: context.schema,
44                binding: &binding.context(),
45            },
46            default,
47            &column.ty,
48        )?;
49    }
50    let mut candidate_columns = context
51        .foreign_keys
52        .columns
53        .try_describe_table(table)
54        .map_err(|error| ddl_storage_error("ALTER TABLE ADD COLUMN", error))?
55        .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
56    candidate_columns.push(column.clone());
57    let check_columns = candidate_columns.clone();
58    if let Some(check) = &mut column.check {
59        let binding = context.bindings.binding_scope()?;
60        crate::schema::constraints::validate_check_expression(
61            &SchemaBindingContext {
62                catalog: context.schema,
63                binding: &binding.context(),
64            },
65            table,
66            qualifier,
67            &check_columns,
68            check,
69        )?;
70        crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
71            context.schema,
72            check,
73            None,
74        )?;
75        candidate_columns
76            .last_mut()
77            .expect("new column candidate exists")
78            .check
79            .clone_from(&column.check);
80    }
81    let key_constraints = context
82        .keys
83        .try_key_constraints(table)
84        .map_err(|error| ddl_storage_error("ALTER TABLE ADD COLUMN", error))?;
85    let foreign_keys = context
86        .keys
87        .try_foreign_keys(table)
88        .map_err(|error| ddl_storage_error("ALTER TABLE ADD COLUMN", error))?;
89    crate::schema::generated::prepare_generated_columns(
90        context.schema,
91        qualifier,
92        &mut candidate_columns,
93        &key_constraints,
94        &foreign_keys,
95    )?;
96    column.generated = candidate_columns
97        .last()
98        .and_then(|candidate| candidate.generated.clone());
99    if let Some(reference) = column.references.clone() {
100        let mut foreign_key = crate::schema::foreign_keys::column_foreign_key(column, &reference);
101        crate::schema::foreign_keys::validate_foreign_key_definition_with_local_state(
102            &context.foreign_keys,
103            table,
104            Some(&candidate_columns),
105            None,
106            &mut foreign_key,
107        )?;
108        let [referenced_column] = foreign_key.ref_columns.as_slice() else {
109            return Err(SQLError::Internal(
110                "column FOREIGN KEY did not resolve exactly one referenced column".into(),
111            ));
112        };
113        let Some(reference) = column.references.as_mut() else {
114            return Err(SQLError::Internal(
115                "column FOREIGN KEY disappeared during validation".into(),
116            ));
117        };
118        reference.table = foreign_key.ref_table;
119        reference.column = Some(referenced_column.clone());
120    }
121    Ok(())
122}