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, CreateTable, Expr, NotNullDeclaration};
9use crate::schema::constraint_metadata::{
10    identity::materialize_default_oid, materialize_check_identity, CatalogIdentityAllocator,
11    ConstraintMetadataError,
12};
13use crate::schema::foreign_keys::ForeignKeyDefinitionContext;
14use crate::schema::indexes::names::{ConstraintIndexNamer, IndexNameCatalog};
15use crate::schema::inheritance::InheritanceContext;
16pub use crate::schema::table_creation::keys::InheritedDefinitions;
17use crate::schema::{SchemaBindingContext, SchemaExpressionCatalog};
18use crate::semantics::conflict::InferenceBindingScope;
19use crate::type_resolution::FunctionTypeResolver;
20use crate::SQLError;
21use std::collections::BTreeSet;
22
23#[cfg(test)]
24mod tests;
25
26pub struct CreateTableAnalysisContext<'a> {
27    pub types: &'a dyn FunctionTypeResolver,
28    pub schema: &'a dyn SchemaExpressionCatalog,
29    pub bindings: &'a dyn InferenceBindingScope,
30    pub inheritance: InheritanceContext<'a>,
31    pub index_names: &'a dyn IndexNameCatalog,
32    pub foreign_keys: ForeignKeyDefinitionContext<'a>,
33}
34
35/// Materialize the declared row layout before binding its expressions. Column identities and attribute numbers belong to the relation; defaults and constraints acquire their own identities only when they are defined.
36pub fn declared_row_columns(
37    columns: &mut [ColumnDef],
38    allocate: &mut CatalogIdentityAllocator<'_>,
39) -> Result<Vec<ColumnDef>, SQLError> {
40    crate::catalog::relation_attributes::materialize(columns, &[])?;
41    columns
42        .iter_mut()
43        .map(|column| {
44            if column.object_id.is_none() {
45                column.object_id = Some(
46                    allocate
47                        .allocate_object_id("column")
48                        .map_err(ConstraintMetadataError::into_sql_error)?,
49                );
50            }
51            let mut declared = ColumnDef::nullable(column.name.clone(), column.ty.clone());
52            declared.object_id = column.object_id;
53            declared.attribute_number = column.attribute_number;
54            declared.collation_oid = column.collation_oid;
55            Ok(declared)
56        })
57        .collect()
58}
59
60/// Examine the statement's elements in written order as `transformCreateStmt` does while it analyzes the statement, before the relation's name is checked: each column's type and then its clauses, and each NOT NULL table constraint; then validate the declared keys.
61pub fn transform_create_table(
62    context: &CreateTableAnalysisContext<'_>,
63    c: &mut CreateTable,
64) -> Result<(), SQLError> {
65    use crate::ast::DeclaredElement;
66    let target = super::column_declarations::ColumnDeclarationTarget {
67        table: &c.qualifier,
68        partitioned: c.hierarchy.partition_spec.is_some(),
69        partition: c.hierarchy.partition_bound.is_some(),
70    };
71    let lost =
72        || SQLError::Internal("the written order of CREATE TABLE elements lost a column".into());
73    let mut columns = c.columns.iter_mut();
74    let mut deferrable_key = false;
75    let mut declarations = Vec::new();
76    let mut primary_keys = Vec::new();
77    for element in &c.element_order {
78        match element {
79            DeclaredElement::Column(declaration) => {
80                let column = columns.next().ok_or_else(lost)?;
81                super::column_declarations::check_serial_array(declaration)?;
82                if !c.untyped_columns.contains(&column.name) {
83                    column.ty = crate::type_resolution::resolve_declared_column_type(
84                        context.types,
85                        &column.ty,
86                    )?;
87                    column.collation_oid = Some(crate::catalog::collations::declarations::select(
88                        context.types,
89                        &column.ty,
90                        declaration.collation.as_deref(),
91                    )?);
92                }
93                deferrable_key |= super::column_declarations::check_column_declaration(
94                    declaration,
95                    &column.name,
96                    target,
97                )?;
98                // A column's NOT NULL clause, or the constraint its PRIMARY KEY, SERIAL or identity implies, joins the constraints in the column's position.
99                if column.not_null {
100                    declarations.push(NotNullDeclaration {
101                        column: column.name.clone(),
102                        name: column.not_null_name.clone(),
103                        no_inherit: column.not_null_no_inherit,
104                        explicit: column.not_null_explicit,
105                    });
106                }
107            }
108            DeclaredElement::NotNull(declaration) => {
109                if target.partitioned && declaration.no_inherit {
110                    return Err(SQLError::Routine {
111                        sqlstate: "0A000".into(),
112                        message: "not-null constraints on partitioned tables cannot be NO INHERIT"
113                            .into(),
114                    });
115                }
116                declarations.push(declaration.clone());
117            }
118            DeclaredElement::PrimaryKey { columns } => primary_keys.push(columns.clone()),
119            DeclaredElement::DeferrableKey => deferrable_key = true,
120        }
121    }
122    if columns.next().is_some() {
123        return Err(lost());
124    }
125    if deferrable_key {
126        return Err(SQLError::Unsupported(
127            "CREATE TABLE: DEFERRABLE PRIMARY KEY and UNIQUE constraints are not supported".into(),
128        ));
129    }
130    c.element_order.clear();
131    super::keys::transform_declared_keys(&context.inheritance, c)?;
132    // `transformIndexConstraints` makes each column of a table PRIMARY KEY NOT NULL once every element is examined: a NO INHERIT declaration of the column conflicts, and a column without a declaration takes a generated constraint.
133    for columns in primary_keys {
134        for column in columns {
135            match declarations
136                .iter()
137                .find(|declaration| declaration.column == column)
138            {
139                Some(declaration) if declaration.no_inherit => {
140                    return Err(SQLError::Routine {
141                        sqlstate: "42601".into(),
142                        message: format!(
143                            "conflicting NO INHERIT declaration for not-null constraint on column \"{column}\""
144                        ),
145                    });
146                }
147                Some(_) => {}
148                None => declarations.push(NotNullDeclaration {
149                    column,
150                    name: None,
151                    no_inherit: false,
152                    explicit: false,
153                }),
154            }
155        }
156    }
157    c.not_null_declarations = declarations;
158    Ok(())
159}
160
161/// Describe the new table's columns before its name is claimed: the parents' columns merge ahead of the local ones as `MergeAttributes` merges them, the declared primary key's columns take their NOT NULL constraints, `BuildDescForRelation` requires `USAGE` on every column's type, and `CheckAttributeNamesTypes` rejects system column names and pseudo-types.
162pub fn prepare_create_table_declaration(
163    context: &CreateTableAnalysisContext<'_>,
164    c: &mut CreateTable,
165    notices: &mut Vec<crate::SQLNotice>,
166) -> Result<InheritedDefinitions, SQLError> {
167    let declared = c.key_constraints.len();
168    let declared_foreign_keys = c.foreign_keys.len();
169    let parents =
170        super::super::inheritance::merge_create_table_hierarchy(&context.inheritance, c, notices)?;
171    let keys = c.key_constraints.len() - declared;
172    let foreign_keys = c.foreign_keys.len() - declared_foreign_keys;
173    super::keys::declare_primary_key_not_null(&mut c.columns, &c.key_constraints[keys..]);
174    for column in &c.columns {
175        context.types.require_type_usage(&column.ty)?;
176    }
177    super::validate_create_table_columns(c)?;
178    let unique_indexes = match c.hierarchy.parents.first() {
179        Some(parent) if c.hierarchy.is_partition() && c.hierarchy.partition_spec.is_some() => {
180            context
181                .inheritance
182                .catalog
183                .unique_index_keys(parent)
184                .map_err(|error| SQLError::Internal(format!("read parent indexes: {error}")))?
185        }
186        _ => Vec::new(),
187    };
188    Ok(InheritedDefinitions {
189        expressions: parents.expressions,
190        not_nulls: parents.not_nulls,
191        keys,
192        foreign_keys,
193        unique_indexes,
194    })
195}
196
197/// `heap_create_with_catalog` stores what the parents give with the relation, before the statement's own expressions are analyzed: the inherited defaults and generation expressions in column order, then the inherited CHECK constraints in the order `MergeAttributes` collected them.
198pub fn define_inherited_expressions(
199    c: &mut CreateTable,
200    inherited: &InheritedDefinitions,
201    allocate: &mut CatalogIdentityAllocator<'_>,
202) -> Result<(), SQLError> {
203    for column in &mut c.columns {
204        if inherited.expressions.contains(&column.name) {
205            allocate_expression_identity(column, allocate)?;
206        }
207    }
208    for check in c.checks.iter_mut().filter(|check| !check.is_local) {
209        materialize_check_identity(&mut check.object_id, &mut check.catalog_oid, allocate)
210            .map_err(ConstraintMetadataError::into_sql_error)?;
211    }
212    Ok(())
213}
214
215/// `StoreAttrDefault`: a column's default or generation expression takes its `pg_attrdef` OID when it is stored; a column without one takes none. The column's own incarnation comes first.
216fn allocate_expression_identity(
217    column: &mut ColumnDef,
218    allocate: &mut CatalogIdentityAllocator<'_>,
219) -> Result<(), SQLError> {
220    if column.object_id.is_none() {
221        column.object_id = Some(
222            allocate
223                .allocate_object_id("column")
224                .map_err(ConstraintMetadataError::into_sql_error)?,
225        );
226    }
227    materialize_default_oid(column, allocate).map_err(ConstraintMetadataError::into_sql_error)?;
228    Ok(())
229}
230
231/// Bind the partition bound and key of the created relation, which `DefineRelation` computes once the relation exists.
232pub fn bind_create_table_partitioning(
233    context: &CreateTableAnalysisContext<'_>,
234    c: &mut CreateTable,
235) -> Result<(), SQLError> {
236    super::super::inheritance::bind_create_table_partitioning(&context.inheritance, c)
237}
238
239/// The defaults and generation expressions of the new table's columns, which `DefineRelation` transforms in column order before it binds the table's partitioning, since a partition key may name a generated column; each stored expression takes its `pg_attrdef` OID as `AddRelationNewConstraints` stores it.
240pub fn define_create_table_defaults(
241    context: &CreateTableAnalysisContext<'_>,
242    c: &mut CreateTable,
243    allocate: &mut CatalogIdentityAllocator<'_>,
244) -> Result<(), SQLError> {
245    let binding = context.bindings.binding_scope()?;
246    let schema = SchemaBindingContext {
247        catalog: context.schema,
248        binding: &binding.context(),
249    };
250    let snapshot = c.columns.clone();
251    for index in 0..c.columns.len() {
252        let column = &mut c.columns[index];
253        if let Some(default) = &mut column.default {
254            if !super::super::defaults::validate_default_expression(
255                &schema,
256                default,
257                &column.ty,
258                &column.name,
259            )? {
260                column.default = None;
261            }
262        }
263        super::super::generated::prepare_generated_column(
264            &schema,
265            &c.qualifier,
266            &snapshot,
267            &mut c.columns,
268            index,
269            &c.foreign_keys,
270        )?;
271        allocate_expression_identity(&mut c.columns[index], allocate)?;
272    }
273    Ok(())
274}
275
276/// The keys a partition clones from its parent, which `DefineRelation` creates once the table's own partition key is stored and before its CHECK constraints: each is checked against that partition key and its index named, beside the CHECKs the table inherits. The namer then names the declared keys.
277pub fn clone_create_table_parent_keys<'a>(
278    context: &CreateTableAnalysisContext<'a>,
279    c: &mut CreateTable,
280    inherited: &InheritedDefinitions,
281    allocate: &mut CatalogIdentityAllocator<'_>,
282) -> Result<ConstraintIndexNamer<'a>, SQLError> {
283    super::keys::clone_parent_keys(context.index_names, c, inherited, allocate)
284}
285
286/// The names of the constraints a new table holds before its CHECKs besides the ones it inherits: the keys and foreign keys a partition clones from its parent.
287pub fn cloned_constraint_names(
288    c: &CreateTable,
289    inherited: &InheritedDefinitions,
290) -> BTreeSet<String> {
291    c.key_constraints[..inherited.keys]
292        .iter()
293        .filter_map(|key| key.name.clone())
294        .chain(
295            c.foreign_keys[..inherited.foreign_keys]
296                .iter()
297                .filter_map(|foreign_key| foreign_key.name.clone()),
298        )
299        .collect()
300}
301
302/// Define every declared key before foreign-key binding so a foreign key can reference its own table's keys.
303pub fn define_create_table_constraints(
304    c: &mut CreateTable,
305    indexes: ConstraintIndexNamer<'_>,
306    inherited: &InheritedDefinitions,
307    allocate: &mut CatalogIdentityAllocator<'_>,
308) -> Result<(), SQLError> {
309    super::keys::define_declared_keys(indexes, c, inherited, allocate)
310}
311
312pub(super) fn validate_check_expression(
313    context: &CreateTableAnalysisContext<'_>,
314    table: &str,
315    qualifier: &str,
316    columns: &[ColumnDef],
317    expression: &mut Expr,
318) -> Result<(), SQLError> {
319    let binding = context.bindings.binding_scope()?;
320    super::super::constraints::validate_check_expression(
321        &SchemaBindingContext {
322            catalog: context.schema,
323            binding: &binding.context(),
324        },
325        table,
326        qualifier,
327        columns,
328        expression,
329    )
330}