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
23pub struct CreateTableAnalysisContext<'a> {
24    pub types: &'a dyn FunctionTypeResolver,
25    pub schema: &'a dyn SchemaExpressionCatalog,
26    pub bindings: &'a dyn InferenceBindingScope,
27    pub inheritance: InheritanceContext<'a>,
28    pub index_names: &'a dyn IndexNameCatalog,
29    pub foreign_keys: ForeignKeyDefinitionContext<'a>,
30}
31
32/// 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.
33pub fn transform_create_table(
34    context: &CreateTableAnalysisContext<'_>,
35    c: &mut CreateTable,
36) -> Result<(), SQLError> {
37    use crate::ast::DeclaredElement;
38    let target = super::column_declarations::ColumnDeclarationTarget {
39        table: &c.qualifier,
40        partitioned: c.hierarchy.partition_spec.is_some(),
41        partition: c.hierarchy.partition_bound.is_some(),
42    };
43    let lost =
44        || SQLError::Internal("the written order of CREATE TABLE elements lost a column".into());
45    let mut columns = c.columns.iter_mut();
46    let mut deferrable_key = false;
47    let mut declarations = Vec::new();
48    let mut primary_keys = Vec::new();
49    for element in &c.element_order {
50        match element {
51            DeclaredElement::Column(declaration) => {
52                let column = columns.next().ok_or_else(lost)?;
53                super::column_declarations::check_serial_array(declaration)?;
54                if !c.untyped_columns.contains(&column.name) {
55                    column.ty = crate::type_resolution::resolve_declared_column_type(
56                        context.types,
57                        &column.ty,
58                    )?;
59                }
60                deferrable_key |= super::column_declarations::check_column_declaration(
61                    declaration,
62                    &column.name,
63                    target,
64                )?;
65                // A column's NOT NULL clause, or the constraint its PRIMARY KEY, SERIAL or identity implies, joins the constraints in the column's position.
66                if column.not_null {
67                    declarations.push(NotNullDeclaration {
68                        column: column.name.clone(),
69                        name: column.not_null_name.clone(),
70                        no_inherit: column.not_null_no_inherit,
71                        explicit: column.not_null_explicit,
72                    });
73                }
74            }
75            DeclaredElement::NotNull(declaration) => {
76                if target.partitioned && declaration.no_inherit {
77                    return Err(SQLError::Routine {
78                        sqlstate: "0A000".into(),
79                        message: "not-null constraints on partitioned tables cannot be NO INHERIT"
80                            .into(),
81                    });
82                }
83                declarations.push(declaration.clone());
84            }
85            DeclaredElement::PrimaryKey { columns } => primary_keys.push(columns.clone()),
86            DeclaredElement::DeferrableKey => deferrable_key = true,
87        }
88    }
89    if columns.next().is_some() {
90        return Err(lost());
91    }
92    if deferrable_key {
93        return Err(SQLError::Unsupported(
94            "CREATE TABLE: DEFERRABLE PRIMARY KEY and UNIQUE constraints are not supported".into(),
95        ));
96    }
97    c.element_order.clear();
98    super::keys::transform_declared_keys(&context.inheritance, c)?;
99    // `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.
100    for columns in primary_keys {
101        for column in columns {
102            match declarations
103                .iter()
104                .find(|declaration| declaration.column == column)
105            {
106                Some(declaration) if declaration.no_inherit => {
107                    return Err(SQLError::Routine {
108                        sqlstate: "42601".into(),
109                        message: format!(
110                            "conflicting NO INHERIT declaration for not-null constraint on column \"{column}\""
111                        ),
112                    });
113                }
114                Some(_) => {}
115                None => declarations.push(NotNullDeclaration {
116                    column,
117                    name: None,
118                    no_inherit: false,
119                    explicit: false,
120                }),
121            }
122        }
123    }
124    c.not_null_declarations = declarations;
125    Ok(())
126}
127
128/// 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.
129pub fn prepare_create_table_declaration(
130    context: &CreateTableAnalysisContext<'_>,
131    c: &mut CreateTable,
132    notices: &mut Vec<crate::SQLNotice>,
133) -> Result<InheritedDefinitions, SQLError> {
134    let declared = c.key_constraints.len();
135    let declared_foreign_keys = c.foreign_keys.len();
136    let parents =
137        super::super::inheritance::merge_create_table_hierarchy(&context.inheritance, c, notices)?;
138    let keys = c.key_constraints.len() - declared;
139    let foreign_keys = c.foreign_keys.len() - declared_foreign_keys;
140    super::keys::declare_primary_key_not_null(&mut c.columns, &c.key_constraints[keys..]);
141    for column in &c.columns {
142        context.types.require_type_usage(&column.ty)?;
143    }
144    super::validate_create_table_columns(c)?;
145    let unique_indexes = match c.hierarchy.parents.first() {
146        Some(parent) if c.hierarchy.is_partition() && c.hierarchy.partition_spec.is_some() => {
147            context
148                .inheritance
149                .catalog
150                .unique_index_keys(parent)
151                .map_err(|error| SQLError::Internal(format!("read parent indexes: {error}")))?
152        }
153        _ => Vec::new(),
154    };
155    Ok(InheritedDefinitions {
156        expressions: parents.expressions,
157        not_nulls: parents.not_nulls,
158        keys,
159        foreign_keys,
160        unique_indexes,
161    })
162}
163
164/// `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.
165pub fn define_inherited_expressions(
166    c: &mut CreateTable,
167    inherited: &InheritedDefinitions,
168    allocate: &mut CatalogIdentityAllocator<'_>,
169) -> Result<(), SQLError> {
170    for column in &mut c.columns {
171        if inherited.expressions.contains(&column.name) {
172            allocate_expression_identity(column, allocate)?;
173        }
174    }
175    for check in c.checks.iter_mut().filter(|check| !check.is_local) {
176        materialize_check_identity(&mut check.object_id, &mut check.catalog_oid, allocate)
177            .map_err(ConstraintMetadataError::into_sql_error)?;
178    }
179    Ok(())
180}
181
182/// `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.
183fn allocate_expression_identity(
184    column: &mut ColumnDef,
185    allocate: &mut CatalogIdentityAllocator<'_>,
186) -> Result<(), SQLError> {
187    if column.object_id.is_none() {
188        column.object_id = Some(
189            allocate
190                .allocate_object_id("column")
191                .map_err(ConstraintMetadataError::into_sql_error)?,
192        );
193    }
194    materialize_default_oid(column, allocate).map_err(ConstraintMetadataError::into_sql_error)?;
195    Ok(())
196}
197
198/// Bind the partition bound and key of the created relation, which `DefineRelation` computes once the relation exists.
199pub fn bind_create_table_partitioning(
200    context: &CreateTableAnalysisContext<'_>,
201    c: &mut CreateTable,
202) -> Result<(), SQLError> {
203    super::super::inheritance::bind_create_table_partitioning(&context.inheritance, c)
204}
205
206/// 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.
207pub fn define_create_table_defaults(
208    context: &CreateTableAnalysisContext<'_>,
209    c: &mut CreateTable,
210    allocate: &mut CatalogIdentityAllocator<'_>,
211) -> Result<(), SQLError> {
212    let binding = context.bindings.binding_scope()?;
213    let schema = SchemaBindingContext {
214        catalog: context.schema,
215        binding: &binding.context(),
216    };
217    let snapshot = c.columns.clone();
218    for index in 0..c.columns.len() {
219        let column = &mut c.columns[index];
220        if let Some(default) = &mut column.default {
221            if !super::super::defaults::validate_default_expression(
222                &schema,
223                default,
224                &column.ty,
225                &column.name,
226            )? {
227                column.default = None;
228            }
229        }
230        super::super::generated::prepare_generated_column(
231            &schema,
232            &c.qualifier,
233            &snapshot,
234            &mut c.columns,
235            index,
236            &c.foreign_keys,
237        )?;
238        allocate_expression_identity(&mut c.columns[index], allocate)?;
239    }
240    Ok(())
241}
242
243/// 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.
244pub fn clone_create_table_parent_keys<'a>(
245    context: &CreateTableAnalysisContext<'a>,
246    c: &mut CreateTable,
247    inherited: &InheritedDefinitions,
248    allocate: &mut CatalogIdentityAllocator<'_>,
249) -> Result<ConstraintIndexNamer<'a>, SQLError> {
250    super::keys::clone_parent_keys(context.index_names, c, inherited, allocate)
251}
252
253/// 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.
254pub fn cloned_constraint_names(
255    c: &CreateTable,
256    inherited: &InheritedDefinitions,
257) -> BTreeSet<String> {
258    c.key_constraints[..inherited.keys]
259        .iter()
260        .filter_map(|key| key.name.clone())
261        .chain(
262            c.foreign_keys[..inherited.foreign_keys]
263                .iter()
264                .filter_map(|foreign_key| foreign_key.name.clone()),
265        )
266        .collect()
267}
268
269/// Define every declared key before foreign-key binding so a foreign key can reference its own table's keys.
270pub fn define_create_table_constraints(
271    c: &mut CreateTable,
272    indexes: ConstraintIndexNamer<'_>,
273    inherited: &InheritedDefinitions,
274    allocate: &mut CatalogIdentityAllocator<'_>,
275) -> Result<(), SQLError> {
276    super::keys::define_declared_keys(indexes, c, inherited, allocate)
277}
278
279pub(super) fn validate_check_expression(
280    context: &CreateTableAnalysisContext<'_>,
281    table: &str,
282    qualifier: &str,
283    columns: &[ColumnDef],
284    expression: &mut Expr,
285) -> Result<(), SQLError> {
286    let binding = context.bindings.binding_scope()?;
287    super::super::constraints::validate_check_expression(
288        &SchemaBindingContext {
289            catalog: context.schema,
290            binding: &binding.context(),
291        },
292        table,
293        qualifier,
294        columns,
295        expression,
296    )
297}