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