Skip to main content

uqa_sql/schema/
table_creation.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! CREATE TABLE column validation and CREATE TABLE AS result declarations.
8use crate::{
9    ast::{ColumnDef, CreateTable},
10    type_resolution::FunctionTypeResolver,
11    ColumnType, SQLError,
12};
13use std::collections::BTreeSet;
14
15/// `CreateTableAsRelExists` reports the written relation's unqualified name for both CTAS and materialized views.
16pub fn existing_create_as_target(
17    name: &str,
18    if_not_exists: bool,
19) -> Result<crate::SQLNotice, SQLError> {
20    let (_, relation) =
21        uqa_core::RelationIdentity::parse_reference(name).map_err(SQLError::Unsupported)?;
22    if if_not_exists {
23        Ok(
24            crate::SQLNotice::notice(format!("relation \"{relation}\" already exists, skipping"))
25                .with_sqlstate("42P07"),
26        )
27    } else {
28        Err(SQLError::Routine {
29            sqlstate: "42P07".into(),
30            message: format!("relation \"{relation}\" already exists"),
31        })
32    }
33}
34
35/// `CheckAttributeNamesTypes`: no column may take a system column's name, and then no column may have a pseudo-type.
36pub fn validate_create_table_columns(table: &CreateTable) -> Result<(), SQLError> {
37    validate_relation_column_names_and_types(&table.columns)
38}
39
40fn validate_relation_column_names_and_types(columns: &[ColumnDef]) -> Result<(), SQLError> {
41    for column in columns {
42        super::columns::validate_postgres_column_name(&column.name)?;
43    }
44    for column in columns {
45        super::columns::validate_postgres_relation_column_type(&column.name, &column.ty)?;
46    }
47    Ok(())
48}
49
50/// `DefineRelation` for a table whose columns a query defines: `MergeAttributes` rejects a repeated name, `BuildDescForRelation` requires `USAGE` on every column's type, and `CheckAttributeNamesTypes` rejects system column names and pseudo-types.
51pub fn validate_create_table_as_columns(
52    types: &dyn FunctionTypeResolver,
53    columns: &[ColumnDef],
54) -> Result<(), SQLError> {
55    let mut seen = BTreeSet::new();
56    for column in columns {
57        if !seen.insert(&column.name) {
58            return Err(SQLError::Routine {
59                sqlstate: "42701".into(),
60                message: format!("column \"{}\" specified more than once", column.name),
61            });
62        }
63    }
64    for column in columns {
65        types.require_type_usage(&column.ty)?;
66    }
67    validate_relation_column_names_and_types(columns)
68}
69
70/// The columns of a table that a query defines, named by the column list and then by the query, as `intorel_startup` names them: more names than the query has columns are rejected.
71pub fn create_table_as_columns(
72    query_schema: &crate::RowSchema,
73    column_names: &[String],
74) -> Result<Vec<ColumnDef>, SQLError> {
75    let names = query_schema
76        .columns()
77        .iter()
78        .enumerate()
79        .map(|(position, name)| {
80            column_names
81                .get(position)
82                .cloned()
83                .unwrap_or_else(|| name.clone())
84        })
85        .collect::<Vec<_>>();
86    let columns = names
87        .into_iter()
88        .enumerate()
89        .map(|(position, name)| {
90            let ty = query_schema
91                .column_type(position)
92                .cloned()
93                .unwrap_or(ColumnType::Text);
94            let collation = query_schema.column_collation(position).unwrap_or_else(|| {
95                u32::try_from(crate::catalog::type_metadata::pg_type_collation_oid(&ty))
96                    .expect("column collation OID")
97            });
98            if collation == 0 && super::composites::type_is_collatable(&ty) {
99                return Err(SQLError::Diagnostic {
100                    sqlstate: "42P22".into(),
101                    message: format!(
102                        "no collation was derived for column \"{name}\" with collatable type {}",
103                        ty.display_name()
104                    ),
105                    detail: None,
106                    hint: Some("Use the COLLATE clause to set the collation explicitly.".into()),
107                });
108            }
109            Ok(ColumnDef {
110                collation_oid: Some(collation),
111                name,
112                ty,
113                object_id: None,
114                attribute_number: None,
115                missing_value: None,
116                primary_key: false,
117                not_null: false,
118                not_null_explicit: false,
119                not_null_name: None,
120                not_null_identity: None,
121                not_null_validated: true,
122                not_null_no_inherit: false,
123                not_null_is_local: true,
124                auto_increment: None,
125                unique: false,
126                default: None,
127                generated: None,
128                check: None,
129                check_name: None,
130                check_enforced: true,
131                check_validated: true,
132                check_no_inherit: false,
133                check_is_local: true,
134                check_object_id: None,
135                check_catalog_oid: None,
136                default_catalog_oid: None,
137                references: None,
138            })
139        })
140        .collect::<Result<Vec<_>, SQLError>>()?;
141    if column_names.len() > query_schema.len() {
142        return Err(SQLError::Routine {
143            sqlstate: "42601".into(),
144            message: "too many column names were specified".into(),
145        });
146    }
147    Ok(columns)
148}
149
150pub mod checks;
151pub mod column_declarations;
152pub mod declaration;
153pub mod foreign_keys;
154pub mod keys;
155pub mod not_nulls;
156
157#[cfg(test)]
158mod tests;