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    let local_columns = registered_columns.to_vec();
127    for column in registered_columns {
128        let Some(reference) = column.references.clone() else {
129            continue;
130        };
131        let mut foreign_key = super::super::foreign_keys::column_foreign_key(column, &reference);
132        super::super::foreign_keys::validate_bound_foreign_key_definition_with_local_state(
133            context,
134            &c.name,
135            Some(&local_columns),
136            Some(&c.key_constraints),
137            &mut foreign_key,
138        )?;
139        let [referenced_column] = foreign_key.ref_columns.as_slice() else {
140            return Err(SQLError::Internal(
141                "column FOREIGN KEY did not resolve exactly one referenced column".into(),
142            ));
143        };
144        let Some(reference) = column.references.as_mut() else {
145            return Err(SQLError::Internal(
146                "column FOREIGN KEY disappeared during validation".into(),
147            ));
148        };
149        reference.referenced_key = foreign_key.referenced_key;
150        reference.referenced_index = foreign_key.referenced_index;
151        reference.table = foreign_key.ref_table;
152        reference.column = Some(referenced_column.clone());
153    }
154    for foreign_key in &mut c.foreign_keys {
155        super::super::foreign_keys::validate_bound_foreign_key_definition_with_local_state(
156            context,
157            &c.name,
158            Some(&local_columns),
159            Some(&c.key_constraints),
160            foreign_key,
161        )?;
162    }
163    Ok(())
164}
165
166fn validate_default_expression(
167    context: &CreateTableAnalysisContext<'_>,
168    expression: &mut Expr,
169    target: &ColumnType,
170) -> Result<(), SQLError> {
171    let binding = context.bindings.binding_scope()?;
172    super::super::defaults::validate_default_expression(
173        &SchemaBindingContext {
174            catalog: context.schema,
175            binding: &binding.context(),
176        },
177        expression,
178        target,
179    )
180}
181
182fn validate_check_expression(
183    context: &CreateTableAnalysisContext<'_>,
184    table: &str,
185    qualifier: &str,
186    columns: &[ColumnDef],
187    expression: &mut Expr,
188) -> Result<(), SQLError> {
189    let binding = context.bindings.binding_scope()?;
190    super::super::constraints::validate_check_expression(
191        &SchemaBindingContext {
192            catalog: context.schema,
193            binding: &binding.context(),
194        },
195        table,
196        qualifier,
197        columns,
198        expression,
199    )
200}
201
202fn bind_create_table_relation_references(
203    catalog: &dyn super::super::foreign_keys::ForeignKeyDefinitionCatalog,
204    table: &mut CreateTable,
205) -> Result<(), SQLError> {
206    let table_name = table.name.clone();
207    let qualifier = table.qualifier.clone();
208    for column in &mut table.columns {
209        if let Some(reference) = column.references.as_mut() {
210            bind_create_table_reference(catalog, &table_name, &qualifier, &mut reference.table)?;
211        }
212    }
213    for foreign_key in &mut table.foreign_keys {
214        bind_create_table_reference(catalog, &table_name, &qualifier, &mut foreign_key.ref_table)?;
215    }
216    Ok(())
217}
218
219fn bind_create_table_reference(
220    catalog: &dyn super::super::foreign_keys::ForeignKeyDefinitionCatalog,
221    table: &str,
222    qualifier: &str,
223    reference: &mut String,
224) -> Result<(), SQLError> {
225    let self_reference = reference == table
226        || reference == qualifier
227        || table
228            .rsplit_once('.')
229            .is_some_and(|(_, local_name)| local_name == reference);
230    if self_reference {
231        table.clone_into(reference);
232        return Ok(());
233    }
234    *reference = catalog.resolve_table_reference(reference)?;
235    Ok(())
236}