Skip to main content

uqa_sql/schema/
foreign_tables.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Foreign-table declaration validation over fresh schema binding scopes and loaded sequence names.
8use crate::schema::constraint_metadata::ConstraintMetadataResult;
9use crate::schema::dependencies::regclass::SchemaReferenceCatalog;
10use crate::schema::sequences::implicit_ownership::StoredSequenceNames;
11use crate::schema::{SchemaBindingContext, SchemaExpressionCatalog};
12use crate::semantics::conflict::InferenceBindingScope;
13use crate::type_resolution::FunctionTypeResolver;
14use crate::{
15    ast::{ColumnDef, ColumnType, Expr, TableCheck},
16    SQLError,
17};
18use uqa_core::RelationIdentity;
19
20pub struct ForeignSchemaContext<'a> {
21    pub types: &'a dyn FunctionTypeResolver,
22    pub schema: &'a dyn SchemaExpressionCatalog,
23    pub bindings: &'a dyn InferenceBindingScope,
24    pub references: &'a dyn SchemaReferenceCatalog,
25    pub sequences: &'a dyn StoredSequenceNames,
26    pub allocate_identity: fn(&str) -> ConstraintMetadataResult<[u8; 16]>,
27}
28
29pub fn validate_foreign_table_schema_envelope(columns: &[ColumnDef]) -> Result<(), SQLError> {
30    let mut names = std::collections::BTreeSet::new();
31    for column in columns {
32        if !names.insert(column.name.as_str()) {
33            return Err(SQLError::Routine {
34                sqlstate: "42701".into(),
35                message: format!("column \"{}\" specified more than once", column.name),
36            });
37        }
38        crate::schema::columns::validate_postgres_column_name(&column.name)?;
39        crate::schema::columns::validate_postgres_relation_column_type(&column.name, &column.ty)?;
40        if column.primary_key || column.unique {
41            let kind = if column.primary_key {
42                "primary key"
43            } else {
44                "unique"
45            };
46            return Err(SQLError::Unsupported(format!(
47                "{kind} constraints are not supported on foreign tables"
48            )));
49        }
50        if column.references.is_some() {
51            return Err(SQLError::Unsupported(
52                "foreign key constraints are not supported on foreign tables".into(),
53            ));
54        }
55    }
56    Ok(())
57}
58impl ForeignSchemaContext<'_> {
59    pub fn prepare_foreign_table_schema(
60        &self,
61        table_name: &str,
62        columns: &mut [ColumnDef],
63        checks: &mut Vec<TableCheck>,
64    ) -> Result<(), SQLError> {
65        self.prepare_foreign_table_schema_inner(table_name, columns, checks, false)
66    }
67    pub fn prepare_stored_foreign_table_schema(
68        &self,
69        table_name: &str,
70        columns: &mut [ColumnDef],
71        checks: &mut Vec<TableCheck>,
72    ) -> Result<(), SQLError> {
73        validate_foreign_table_schema_envelope(columns)?;
74        self.prepare_foreign_table_schema_inner(table_name, columns, checks, true)
75    }
76    fn prepare_foreign_table_schema_inner(
77        &self,
78        table_name: &str,
79        columns: &mut [ColumnDef],
80        checks: &mut Vec<TableCheck>,
81        stored: bool,
82    ) -> Result<(), SQLError> {
83        let relation = RelationIdentity::from_legacy_name(table_name).map_err(|error| {
84            SQLError::Internal(format!("decode foreign table `{table_name}`: {error}"))
85        })?;
86        let qualifier = relation.name.clone();
87        let check_columns = columns.to_vec();
88        for column in columns.iter_mut() {
89            if let Some(default) = &mut column.default {
90                prepare_foreign_table_sequence_references(
91                    self.references,
92                    self.sequences,
93                    default,
94                    stored,
95                )?;
96                validate_default_expression(self, default, &column.ty)?;
97            }
98            if let Some(check) = &mut column.check {
99                prepare_foreign_table_sequence_references(
100                    self.references,
101                    self.sequences,
102                    check,
103                    stored,
104                )?;
105                validate_check_expression(self, table_name, &qualifier, &check_columns, check)?;
106                crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
107                    self.schema,
108                    check,
109                    None,
110                )?;
111            }
112            if let Some(generated) = &mut column.generated {
113                prepare_foreign_table_sequence_references(
114                    self.references,
115                    self.sequences,
116                    &mut generated.expression,
117                    stored,
118                )?;
119            }
120        }
121        for check in checks.iter_mut() {
122            prepare_foreign_table_sequence_references(
123                self.references,
124                self.sequences,
125                &mut check.expr,
126                stored,
127            )?;
128            validate_check_expression(
129                self,
130                table_name,
131                &qualifier,
132                &check_columns,
133                &mut check.expr,
134            )?;
135            crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
136                self.schema,
137                &check.expr,
138                None,
139            )?;
140        }
141        crate::schema::generated::prepare_generated_columns(
142            self.schema,
143            &qualifier,
144            columns,
145            &[],
146            &[],
147        )?;
148        let mut constraints = crate::ast::TableConstraintSet {
149            checks: std::mem::take(checks),
150            ..crate::ast::TableConstraintSet::default()
151        };
152        let mut allocate = self.allocate_identity;
153        crate::schema::constraint_metadata::materialize_constraint_metadata(
154            &relation,
155            columns,
156            &mut constraints,
157            &mut allocate,
158        )
159        .map_err(|error| SQLError::Internal(error.to_string()))?;
160        *checks = constraints.checks;
161        Ok(())
162    }
163}
164fn validate_default_expression(
165    context: &ForeignSchemaContext<'_>,
166    expression: &mut Expr,
167    target: &ColumnType,
168) -> Result<(), SQLError> {
169    let binding = context.bindings.binding_scope()?;
170    crate::schema::defaults::validate_default_expression(
171        &SchemaBindingContext {
172            catalog: context.schema,
173            binding: &binding.context(),
174        },
175        expression,
176        target,
177    )
178}
179fn validate_check_expression(
180    context: &ForeignSchemaContext<'_>,
181    table: &str,
182    qualifier: &str,
183    columns: &[ColumnDef],
184    expression: &mut Expr,
185) -> Result<(), SQLError> {
186    let binding = context.bindings.binding_scope()?;
187    crate::schema::constraints::validate_check_expression(
188        &SchemaBindingContext {
189            catalog: context.schema,
190            binding: &binding.context(),
191        },
192        table,
193        qualifier,
194        columns,
195        expression,
196    )
197}
198
199fn prepare_foreign_table_sequence_references(
200    references: &dyn SchemaReferenceCatalog,
201    sequences: &dyn StoredSequenceNames,
202    expression: &mut crate::ast::Expr,
203    stored: bool,
204) -> Result<(), SQLError> {
205    crate::schema::dependencies::regclass::bind_schema_regclass_constants(
206        references, expression, stored,
207    )
208    .map_err(|error| SQLError::Internal(error.to_string()))?;
209    let result = if stored {
210        crate::schema::dependencies::rewrites::rewrite_sequence_function_references(
211            expression,
212            &mut |reference| {
213                *reference = sequences.stored_sequence_name(reference)?;
214                Ok(())
215            },
216        )
217    } else {
218        crate::schema::dependencies::regclass::bind_sequence_references_in_expr(
219            references, expression,
220        )
221    };
222    result.map_err(|error| SQLError::Internal(error.to_string()))
223}
224
225#[cfg(test)]
226mod tests;
227
228pub mod dependencies;