Skip to main content

uqa_sql/compiler/
foreign_tables.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Analyze a foreign-table definition in declaration order after execution resolves its creation namespace.
8
9use super::{range_var_name, NodeEnum, Result, SQLError};
10use crate::ast::{ColumnType, CreateForeignTable, DeferredCreateForeignTable};
11use crate::schema::table_creation::column_declarations::{
12    check_foreign_column_declaration, check_serial_array, foreign_table_constraint_error,
13};
14use crate::type_resolution::{resolve_declared_column_type, FunctionTypeResolver};
15
16/// Resolve each column's type and clauses, and each table constraint, in written order. Execution calls this only after checking the creation namespace and an `IF NOT EXISTS` target.
17pub fn resolve_deferred_create_foreign_table(
18    deferred: &DeferredCreateForeignTable,
19    types: &dyn FunctionTypeResolver,
20) -> Result<CreateForeignTable> {
21    let parsed = crate::parser::parse(&deferred.definition_sql)?;
22    let [raw] = parsed.protobuf.stmts.as_slice() else {
23        return Err(SQLError::Internal(
24            "deferred CREATE FOREIGN TABLE did not contain exactly one statement".into(),
25        ));
26    };
27    let node = raw
28        .stmt
29        .as_deref()
30        .and_then(|node| node.node.as_ref())
31        .ok_or_else(|| SQLError::Internal("deferred CREATE FOREIGN TABLE is empty".into()))?;
32    let NodeEnum::CreateForeignTableStmt(statement) = node else {
33        return Err(SQLError::Internal(
34            "deferred CREATE FOREIGN TABLE changed statement kind".into(),
35        ));
36    };
37    let base = statement
38        .base_stmt
39        .as_ref()
40        .ok_or_else(|| SQLError::Internal("CREATE FOREIGN TABLE without base".into()))?;
41    let relation = base
42        .relation
43        .as_ref()
44        .ok_or_else(|| SQLError::Internal("CREATE FOREIGN TABLE without relation".into()))?;
45    if base.if_not_exists != deferred.if_not_exists
46        || range_var_name(relation) != deferred.name
47        || statement.servername != deferred.server_name
48    {
49        return Err(SQLError::Internal(
50            "deferred CREATE FOREIGN TABLE changed target identity".into(),
51        ));
52    }
53    super::validate_create_table_envelope(base, "CREATE FOREIGN TABLE")?;
54    let column_types = analyze_elements(base, types, &relation.relname)?;
55    // The supported declaration is lowered only after its ordered checks; ordinary-table key lowering must never preempt a foreign-table diagnostic.
56    let mut table = super::relations::compile_create_foreign_table(statement)?;
57    if column_types.len() != table.columns.len() {
58        return Err(SQLError::Internal(
59            "foreign-table declaration lost an analyzed column".into(),
60        ));
61    }
62    for (column, ty) in table.columns.iter_mut().zip(column_types) {
63        column.ty = ty;
64    }
65    Ok(table)
66}
67
68fn analyze_elements(
69    statement: &pg_query::protobuf::CreateStmt,
70    types: &dyn FunctionTypeResolver,
71    table: &str,
72) -> Result<Vec<ColumnType>> {
73    use pg_query::protobuf::ConstrType;
74    let mut column_types = Vec::new();
75    for element in &statement.table_elts {
76        match element.node.as_ref() {
77            Some(NodeEnum::ColumnDef(column)) => {
78                let declaration = super::tree::compile_column_declaration(column)?;
79                check_serial_array(&declaration)?;
80                let ty = super::types::compile_type_name(column)?;
81                let ty = resolve_declared_column_type(types, &ty)?;
82                check_foreign_column_declaration(&declaration, &column.colname, table)?;
83                column_types.push(ty);
84            }
85            Some(NodeEnum::Constraint(constraint)) => {
86                let kind = match constraint.contype() {
87                    ConstrType::ConstrPrimary => "primary key",
88                    ConstrType::ConstrUnique => "unique",
89                    ConstrType::ConstrForeign => "foreign key",
90                    ConstrType::ConstrExclusion => "exclusion",
91                    ConstrType::ConstrCheck | ConstrType::ConstrNotnull => continue,
92                    other => {
93                        return Err(SQLError::Unsupported(format!(
94                            "table constraint {other:?} is not supported"
95                        )));
96                    }
97                };
98                return Err(foreign_table_constraint_error(kind));
99            }
100            Some(other) => {
101                return Err(SQLError::Unsupported(format!(
102                    "CREATE FOREIGN TABLE element {other:?} is not supported"
103                )));
104            }
105            None => {
106                return Err(SQLError::Internal(
107                    "CREATE FOREIGN TABLE contains an empty element".into(),
108                ));
109            }
110        }
111    }
112    Ok(column_types)
113}
114
115pub(super) fn not_null_declarations(
116    table: &crate::ast::CreateTable,
117) -> Result<Vec<crate::ast::NotNullDeclaration>> {
118    use crate::ast::{DeclaredElement, NotNullDeclaration};
119    let mut declarations = Vec::new();
120    let mut columns = table.columns.iter();
121    for element in &table.element_order {
122        match element {
123            DeclaredElement::Column(declaration) => {
124                let column = columns.next().ok_or_else(|| {
125                    SQLError::Internal("foreign-table declaration lost a column".into())
126                })?;
127                let before = declarations.len();
128                declarations.extend(
129                    declaration
130                        .clauses
131                        .iter()
132                        .filter(|clause| clause.kind == crate::ast::ColumnClauseKind::NotNull)
133                        .map(|clause| NotNullDeclaration {
134                            column: column.name.clone(),
135                            name: clause.name.clone(),
136                            no_inherit: clause.no_inherit,
137                            explicit: true,
138                        }),
139                );
140                if column.not_null && declarations.len() == before {
141                    declarations.push(NotNullDeclaration {
142                        column: column.name.clone(),
143                        name: None,
144                        no_inherit: false,
145                        explicit: false,
146                    });
147                }
148            }
149            DeclaredElement::NotNull(declaration) => declarations.push(declaration.clone()),
150            DeclaredElement::PrimaryKey { .. } | DeclaredElement::DeferrableKey => {
151                return Err(SQLError::Internal(
152                    "foreign-table declaration retained an unsupported key".into(),
153                ));
154            }
155        }
156    }
157    Ok(declarations)
158}
159
160/// Retain a foreign-column deletion list in written order before catalog binding.
161pub(super) fn column_drops(
162    commands: &[pg_query::protobuf::Node],
163) -> Option<Vec<crate::ast::DropColumnAction>> {
164    if commands.is_empty() {
165        return None;
166    }
167    commands
168        .iter()
169        .map(|node| {
170            let Some(NodeEnum::AlterTableCmd(command)) = node.node.as_ref() else {
171                return None;
172            };
173            (command.subtype() == pg_query::protobuf::AlterTableType::AtDropColumn).then(|| {
174                crate::ast::DropColumnAction {
175                    name: command.name.clone(),
176                    if_exists: command.missing_ok,
177                    cascade: matches!(
178                        command.behavior(),
179                        pg_query::protobuf::DropBehavior::DropCascade
180                    ),
181                }
182            })
183        })
184        .collect()
185}