Skip to main content

uqa_sql/schema/
foreign_servers.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! `PostgreSQL` foreign-server declaration options and deletion authority.
8
9use crate::{
10    catalog::{
11        foreign_server::ForeignServerDefinition,
12        roles::{guards::RoleCatalogGuards, role_inherits, RoleReferenceNames},
13    },
14    SQLError, SQLNotice,
15};
16use std::collections::BTreeMap;
17
18/// `transformGenericOptions` checks duplicate names before `optionListToArray` rejects an equals sign in a name.
19pub fn creation_options(
20    options: &[(String, String)],
21) -> Result<BTreeMap<String, String>, SQLError> {
22    let mut result = BTreeMap::new();
23    for (name, value) in options {
24        if result.insert(name.clone(), value.clone()).is_some() {
25            return Err(SQLError::Routine {
26                sqlstate: "42710".into(),
27                message: format!("option \"{name}\" provided more than once"),
28            });
29        }
30    }
31    if let Some((name, _)) = options.iter().find(|(name, _)| name.contains('=')) {
32        return Err(SQLError::Routine {
33            sqlstate: "22023".into(),
34            message: format!("invalid option name \"{name}\": must not contain \"=\""),
35        });
36    }
37    Ok(result)
38}
39
40/// Check the current role against the selected server's exact owner incarnation. Execution repeats this check on the freshly resolved definition after acquiring its object lock.
41pub fn ensure_drop_authority(
42    server: &ForeignServerDefinition,
43    session: &dyn RoleReferenceNames,
44    catalog: &dyn RoleCatalogGuards,
45) -> Result<(), SQLError> {
46    let current = session.current_role();
47    let roles = catalog.role_definitions();
48    let memberships = catalog.role_memberships();
49    if role_inherits(&roles, &memberships, &current, &server.metadata.owner) {
50        return Ok(());
51    }
52    Err(SQLError::Routine {
53        sqlstate: "42501".into(),
54        message: format!("must be owner of foreign server {}", server.name),
55    })
56}
57
58pub fn missing_server(name: &str) -> SQLError {
59    SQLError::Routine {
60        sqlstate: "42704".into(),
61        message: format!("server \"{name}\" does not exist"),
62    }
63}
64
65pub fn missing_server_notice(name: &str) -> SQLNotice {
66    SQLNotice::notice(format!("server \"{name}\" does not exist, skipping"))
67}
68
69#[cfg(test)]
70mod tests;