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        if !crate::schema::defaults::validate_default_expression(
42            &SchemaBindingContext {
43                catalog: context.schema,
44                binding: &binding.context(),
45            },
46            default,
47            &column.ty,
48            &column.name,
49        )? {
50            column.default = None;
51        }
52    }
53    let mut candidate_columns = context
54        .foreign_keys
55        .columns
56        .try_describe_table(table)
57        .map_err(|error| ddl_storage_error("ALTER TABLE ADD COLUMN", error))?
58        .ok_or_else(|| SQLError::UnknownTable(table.to_string()))?;
59    candidate_columns.push(column.clone());
60    let check_columns = candidate_columns.clone();
61    if let Some(check) = &mut column.check {
62        let binding = context.bindings.binding_scope()?;
63        crate::schema::constraints::validate_check_expression(
64            &SchemaBindingContext {
65                catalog: context.schema,
66                binding: &binding.context(),
67            },
68            table,
69            qualifier,
70            &check_columns,
71            check,
72        )?;
73        crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
74            context.schema,
75            check,
76            None,
77        )?;
78        candidate_columns
79            .last_mut()
80            .expect("new column candidate exists")
81            .check
82            .clone_from(&column.check);
83    }
84    let foreign_keys = context
85        .keys
86        .try_foreign_keys(table)
87        .map_err(|error| ddl_storage_error("ALTER TABLE ADD COLUMN", error))?;
88    let binding = context.bindings.binding_scope()?;
89    crate::schema::generated::prepare_generated_columns(
90        &SchemaBindingContext {
91            catalog: context.schema,
92            binding: &binding.context(),
93        },
94        qualifier,
95        &mut candidate_columns,
96        &foreign_keys,
97    )?;
98    column.generated = candidate_columns
99        .last()
100        .and_then(|candidate| candidate.generated.clone());
101    if let Some(reference) = column.references.clone() {
102        let mut foreign_key = crate::schema::foreign_keys::column_foreign_key(column, &reference);
103        crate::schema::foreign_keys::validate_foreign_key_definition_with_local_state(
104            &context.foreign_keys,
105            table,
106            Some(&candidate_columns),
107            None,
108            &mut foreign_key,
109        )?;
110        let [referenced_column] = foreign_key.ref_columns.as_slice() else {
111            return Err(SQLError::Internal(
112                "column FOREIGN KEY did not resolve exactly one referenced column".into(),
113            ));
114        };
115        let Some(reference) = column.references.as_mut() else {
116            return Err(SQLError::Internal(
117                "column FOREIGN KEY disappeared during validation".into(),
118            ));
119        };
120        reference.table = foreign_key.ref_table;
121        reference.column = Some(referenced_column.clone());
122    }
123    Ok(())
124}