Skip to main content

uqa_sql/schema/table_creation/
declaration.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Bind complete table declarations around their sequence and catalog publication boundaries.
8use crate::ast::{ColumnDef, ColumnType, CreateTable, Expr};
9use crate::schema::constraints::validate_foreign_key_definition;
10use crate::schema::foreign_keys::{resolve_foreign_key_parent, ForeignKeyDefinitionContext};
11use crate::schema::indexes::names::IndexNameCatalog;
12use crate::schema::inheritance::InheritanceContext;
13use crate::schema::{SchemaBindingContext, SchemaExpressionCatalog};
14use crate::semantics::conflict::InferenceBindingScope;
15use crate::type_resolution::FunctionTypeResolver;
16use crate::SQLError;
17
18pub struct CreateTableAnalysisContext<'a> {
19    pub types: &'a dyn FunctionTypeResolver,
20    pub schema: &'a dyn SchemaExpressionCatalog,
21    pub bindings: &'a dyn InferenceBindingScope,
22    pub inheritance: InheritanceContext<'a>,
23    pub index_names: &'a dyn IndexNameCatalog,
24    pub foreign_keys: ForeignKeyDefinitionContext<'a>,
25}
26
27pub fn prepare_create_table_declaration(
28    context: &CreateTableAnalysisContext<'_>,
29    c: &mut CreateTable,
30) -> Result<(), SQLError> {
31    for column in &mut c.columns {
32        column.ty =
33            crate::type_resolution::resolve_declared_column_type(context.types, &column.ty)?;
34    }
35    super::super::inheritance::prepare_create_table_hierarchy(&context.inheritance, c)?;
36    super::super::indexes::names::name_constraint_indexes(
37        context.index_names,
38        &c.name,
39        &mut c.key_constraints,
40    )?;
41    bind_create_table_relation_references(context.foreign_keys.catalog, c)?;
42    Ok(())
43}
44
45pub fn validate_create_table_expressions(
46    context: &CreateTableAnalysisContext<'_>,
47    c: &mut CreateTable,
48) -> Result<(), SQLError> {
49    let check_columns = c.columns.clone();
50    for column in &mut c.columns {
51        if let Some(default) = &mut column.default {
52            validate_default_expression(context, default, &column.ty)?;
53        }
54        if let Some(check) = &mut column.check {
55            validate_check_expression(context, &c.name, &c.qualifier, &check_columns, check)?;
56            crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
57                context.schema,
58                check,
59                None,
60            )?;
61        }
62    }
63    for check in &mut c.checks {
64        validate_check_expression(
65            context,
66            &c.name,
67            &c.qualifier,
68            &check_columns,
69            &mut check.expr,
70        )?;
71        crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
72            context.schema,
73            &check.expr,
74            None,
75        )?;
76    }
77    super::super::check_inheritance::merge_create_checks(c)?;
78    for foreign_key in &mut c.foreign_keys {
79        if !foreign_key.period {
80            continue;
81        }
82        let self_reference = foreign_key.ref_table == c.name
83            || foreign_key.ref_table == c.qualifier
84            || c.name
85                .rsplit_once('.')
86                .is_some_and(|(_, local_name)| local_name == foreign_key.ref_table);
87        if self_reference {
88            validate_foreign_key_definition(
89                &c.name,
90                &c.columns,
91                &c.name,
92                &c.columns,
93                &c.key_constraints,
94                foreign_key,
95            )?;
96            foreign_key.ref_table.clone_from(&c.name);
97        } else {
98            let (canonical, parent_columns, parent_keys) =
99                resolve_foreign_key_parent(&context.foreign_keys, &foreign_key.ref_table)?;
100            validate_foreign_key_definition(
101                &c.name,
102                &c.columns,
103                &canonical,
104                &parent_columns,
105                &parent_keys,
106                foreign_key,
107            )?;
108            foreign_key.ref_table = canonical;
109        }
110    }
111    super::super::generated::prepare_generated_columns(
112        context.schema,
113        &c.qualifier,
114        &mut c.columns,
115        &c.key_constraints,
116        &c.foreign_keys,
117    )?;
118    Ok(())
119}
120
121pub fn bind_created_table_foreign_keys(
122    context: &ForeignKeyDefinitionContext<'_>,
123    c: &mut CreateTable,
124    registered_columns: &mut [ColumnDef],
125) -> Result<(), SQLError> {
126    for column in registered_columns {
127        let Some(reference) = column.references.clone() else {
128            continue;
129        };
130        let mut foreign_key = super::super::foreign_keys::column_foreign_key(column, &reference);
131        super::super::foreign_keys::validate_bound_foreign_key_definition_with_local_state(
132            context,
133            &c.name,
134            None,
135            Some(&c.key_constraints),
136            &mut foreign_key,
137        )?;
138        let [referenced_column] = foreign_key.ref_columns.as_slice() else {
139            return Err(SQLError::Internal(
140                "column FOREIGN KEY did not resolve exactly one referenced column".into(),
141            ));
142        };
143        let Some(reference) = column.references.as_mut() else {
144            return Err(SQLError::Internal(
145                "column FOREIGN KEY disappeared during validation".into(),
146            ));
147        };
148        reference.referenced_key = foreign_key.referenced_key;
149        reference.table = foreign_key.ref_table;
150        reference.column = Some(referenced_column.clone());
151    }
152    for foreign_key in &mut c.foreign_keys {
153        super::super::foreign_keys::validate_bound_foreign_key_definition_with_local_state(
154            context,
155            &c.name,
156            None,
157            Some(&c.key_constraints),
158            foreign_key,
159        )?;
160    }
161    Ok(())
162}
163
164fn validate_default_expression(
165    context: &CreateTableAnalysisContext<'_>,
166    expression: &mut Expr,
167    target: &ColumnType,
168) -> Result<(), SQLError> {
169    let binding = context.bindings.binding_scope()?;
170    super::super::defaults::validate_default_expression(
171        &SchemaBindingContext {
172            catalog: context.schema,
173            binding: &binding.context(),
174        },
175        expression,
176        target,
177    )
178}
179
180fn validate_check_expression(
181    context: &CreateTableAnalysisContext<'_>,
182    table: &str,
183    qualifier: &str,
184    columns: &[ColumnDef],
185    expression: &mut Expr,
186) -> Result<(), SQLError> {
187    let binding = context.bindings.binding_scope()?;
188    super::super::constraints::validate_check_expression(
189        &SchemaBindingContext {
190            catalog: context.schema,
191            binding: &binding.context(),
192        },
193        table,
194        qualifier,
195        columns,
196        expression,
197    )
198}
199
200fn bind_create_table_relation_references(
201    catalog: &dyn super::super::foreign_keys::ForeignKeyDefinitionCatalog,
202    table: &mut CreateTable,
203) -> Result<(), SQLError> {
204    let table_name = table.name.clone();
205    let qualifier = table.qualifier.clone();
206    for column in &mut table.columns {
207        if let Some(reference) = column.references.as_mut() {
208            bind_create_table_reference(catalog, &table_name, &qualifier, &mut reference.table)?;
209        }
210    }
211    for foreign_key in &mut table.foreign_keys {
212        bind_create_table_reference(catalog, &table_name, &qualifier, &mut foreign_key.ref_table)?;
213    }
214    Ok(())
215}
216
217fn bind_create_table_reference(
218    catalog: &dyn super::super::foreign_keys::ForeignKeyDefinitionCatalog,
219    table: &str,
220    qualifier: &str,
221    reference: &mut String,
222) -> Result<(), SQLError> {
223    let self_reference = reference == table
224        || reference == qualifier
225        || table
226            .rsplit_once('.')
227            .is_some_and(|(_, local_name)| local_name == reference);
228    if self_reference {
229        table.clone_into(reference);
230        return Ok(());
231    }
232    *reference = catalog.resolve_table_reference(reference)?;
233    Ok(())
234}