1use crate::{
10 ast::{ForeignWrapperFunctionOption, FunctionBinding},
11 catalog::roles::{memberships::role_is_superuser, RoleDefinition, RoleReference},
12 SQLError,
13};
14use std::collections::BTreeMap;
15
16#[derive(Clone)]
17pub struct ForeignWrapperRoutine {
18 pub binding: FunctionBinding,
19 pub oid: u32,
20 pub return_oid: i64,
21}
22
23#[derive(Default)]
24pub struct ForeignWrapperFunctions {
25 pub handler: Option<ForeignWrapperRoutine>,
26 pub validator: Option<ForeignWrapperRoutine>,
27}
28
29pub fn ensure_create_authority(
30 name: &str,
31 role: &RoleReference,
32 roles: &BTreeMap<String, RoleDefinition>,
33) -> Result<(), SQLError> {
34 if role_is_superuser(roles, role) {
35 return Ok(());
36 }
37 Err(SQLError::Diagnostic {
38 sqlstate: "42501".into(),
39 message: format!("permission denied to create foreign-data wrapper \"{name}\""),
40 detail: None,
41 hint: Some("Must be superuser to create a foreign-data wrapper.".into()),
42 })
43}
44
45pub fn duplicate_wrapper(name: &str) -> SQLError {
46 SQLError::Routine {
47 sqlstate: "42710".into(),
48 message: format!("foreign-data wrapper \"{name}\" already exists"),
49 }
50}
51
52pub fn ensure_drop_authority(
53 wrapper: &crate::catalog::foreign_wrapper::ForeignWrapperDefinition,
54 session: &dyn crate::catalog::roles::RoleReferenceNames,
55 catalog: &dyn crate::catalog::roles::guards::RoleCatalogGuards,
56) -> Result<(), SQLError> {
57 if crate::catalog::roles::role_inherits(
58 &catalog.role_definitions(),
59 &catalog.role_memberships(),
60 &session.current_role(),
61 &wrapper.owner,
62 ) {
63 return Ok(());
64 }
65 Err(SQLError::Routine {
66 sqlstate: "42501".into(),
67 message: format!("must be owner of foreign-data wrapper {}", wrapper.name),
68 })
69}
70
71pub fn missing_wrapper(name: &str) -> SQLError {
72 SQLError::Routine {
73 sqlstate: "42704".into(),
74 message: format!("foreign-data wrapper \"{name}\" does not exist"),
75 }
76}
77
78pub fn missing_wrapper_notice(name: &str) -> crate::SQLNotice {
79 crate::SQLNotice::notice(format!(
80 "foreign-data wrapper \"{name}\" does not exist, skipping"
81 ))
82}
83
84pub fn ensure_wrapper_usage(
85 wrapper: &crate::catalog::foreign_wrapper::ForeignWrapperDefinition,
86 role: &RoleReference,
87 catalog: &dyn crate::catalog::roles::guards::RoleCatalogGuards,
88) -> Result<(), SQLError> {
89 if matches!(wrapper.handler, crate::catalog::foreign_wrapper::ForeignWrapperHandler::Native(native) if wrapper.identity == native.reference())
91 || crate::catalog::roles::role_inherits(
92 &catalog.role_definitions(),
93 &catalog.role_memberships(),
94 role,
95 &wrapper.owner,
96 )
97 {
98 return Ok(());
99 }
100 Err(SQLError::Routine {
101 sqlstate: "42501".into(),
102 message: format!(
103 "permission denied for foreign-data wrapper {}",
104 wrapper.name
105 ),
106 })
107}
108
109pub fn bind_functions(
111 options: &[ForeignWrapperFunctionOption],
112 mut lookup: impl FnMut(&str, &[i64], &[String]) -> Result<ForeignWrapperRoutine, SQLError>,
113) -> Result<ForeignWrapperFunctions, SQLError> {
114 let mut result = ForeignWrapperFunctions::default();
115 let (mut handler_given, mut validator_given) = (false, false);
116 for option in options {
117 let (given, name, handler) = match option {
118 ForeignWrapperFunctionOption::Handler(name) => (&mut handler_given, name, true),
119 ForeignWrapperFunctionOption::Validator(name) => (&mut validator_given, name, false),
120 };
121 if *given {
122 return Err(SQLError::Routine {
123 sqlstate: "42601".into(),
124 message: "conflicting or redundant options".into(),
125 });
126 }
127 *given = true;
128 let Some(parts) = name else {
129 continue;
130 };
131 let name = function_name(parts)?;
132 if handler {
133 let function = lookup(&name, &[], &[])?;
134 if function.return_oid != 3115 {
135 return Err(SQLError::Routine {
136 sqlstate: "42809".into(),
137 message: format!("function {} must return type fdw_handler", parts.join(".")),
138 });
139 }
140 result.handler = Some(function);
141 } else {
142 result.validator = Some(lookup(
143 &name,
144 &[1009, 26],
145 &["text[]".into(), "oid".into()],
146 )?);
147 }
148 }
149 Ok(result)
150}
151
152fn function_name(parts: &[String]) -> Result<String, SQLError> {
153 let local = match parts {
154 [_] | [_, _] => parts,
155 [database, _, _] if database == crate::catalog::DATABASE_NAME => &parts[1..],
156 [_, _, _] => {
157 return Err(SQLError::Routine {
158 sqlstate: "0A000".into(),
159 message: format!(
160 "cross-database references are not implemented: {}",
161 parts.join(".")
162 ),
163 })
164 }
165 _ => {
166 return Err(SQLError::Routine {
167 sqlstate: "42601".into(),
168 message: format!(
169 "improper qualified name (too many dotted names): {}",
170 parts.join(".")
171 ),
172 })
173 }
174 };
175 Ok(local
176 .iter()
177 .map(|part| crate::compiler::render_relation_component(part))
178 .collect::<Vec<_>>()
179 .join("."))
180}
181
182pub fn creation_options(options: &[(String, String)]) -> Result<Vec<(String, String)>, SQLError> {
183 super::foreign_servers::creation_options(options)?;
184 Ok(options.to_vec())
185}
186
187#[cfg(test)]
188mod tests;