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