1use super::roles::{identity::RoleSubject, RoleDefinition, RoleIdentity};
10use crate::{ast::FunctionBinding, SQLError};
11use serde::{Deserialize, Serialize};
12use std::collections::{BTreeMap, BTreeSet};
13
14pub type ForeignWrappers = BTreeMap<String, ForeignWrapperDefinition>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18pub enum NativeForeignWrapper {
19 Memory,
20 DuckDB,
21 Arrow,
22}
23
24impl NativeForeignWrapper {
25 pub const ALL: [Self; 3] = [Self::Memory, Self::DuckDB, Self::Arrow];
26
27 pub const fn handler_name(self) -> &'static str {
28 match self {
29 Self::Memory => "memory_fdw_handler",
30 Self::DuckDB => "duckdb_fdw_handler",
31 Self::Arrow => "arrow_fdw_handler",
32 }
33 }
34
35 pub fn handler_oid(self) -> i64 {
36 super::oids::stable_oid("native-fdw-handler", self.handler_name())
37 }
38
39 pub fn for_handler(oid: u32) -> Option<Self> {
40 Self::ALL
41 .into_iter()
42 .find(|native| native.handler_oid() == i64::from(oid))
43 }
44
45 pub const fn name(self) -> &'static str {
46 match self {
47 Self::Memory => "memory_fdw",
48 Self::DuckDB => "duckdb_fdw",
49 Self::Arrow => "arrow_fdw",
50 }
51 }
52
53 pub const fn reference(self) -> ForeignWrapperReference {
55 let (oid, tag) = match self {
56 Self::Memory => (1, 1),
57 Self::DuckDB => (2, 2),
58 Self::Arrow => (3, 3),
59 };
60 let mut object_id = *b"UQA native FDW\0\0";
61 object_id[15] = tag;
62 ForeignWrapperReference { oid, object_id }
63 }
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub enum ForeignWrapperHandler {
68 None,
69 Native(NativeForeignWrapper),
70 Function(ForeignWrapperFunction),
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
75#[serde(deny_unknown_fields)]
76pub struct ForeignWrapperFunction {
77 pub oid: u32,
78 pub binding: FunctionBinding,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(deny_unknown_fields)]
83pub struct ForeignWrapperReference {
84 pub oid: u32,
85 pub object_id: [u8; 16],
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
89#[serde(deny_unknown_fields)]
90pub struct ForeignWrapperDefinition {
91 pub name: String,
92 pub identity: ForeignWrapperReference,
93 pub owner: RoleIdentity,
94 pub handler: ForeignWrapperHandler,
95 pub validator: Option<ForeignWrapperFunction>,
96 pub options: Vec<(String, String)>,
98}
99
100impl ForeignWrapperDefinition {
101 pub fn require_handler(&self) -> Result<(), SQLError> {
102 if matches!(self.handler, ForeignWrapperHandler::None) {
103 return Err(SQLError::Routine {
104 sqlstate: "55000".into(),
105 message: format!("foreign-data wrapper \"{}\" has no handler", self.name),
106 });
107 }
108 Ok(())
109 }
110}
111
112pub fn native_wrappers() -> ForeignWrappers {
113 [
114 NativeForeignWrapper::Memory,
115 NativeForeignWrapper::DuckDB,
116 NativeForeignWrapper::Arrow,
117 ]
118 .into_iter()
119 .map(|native| {
120 let name = native.name().to_owned();
121 (
122 name.clone(),
123 ForeignWrapperDefinition {
124 name,
125 identity: native.reference(),
126 owner: RoleIdentity::BOOTSTRAP,
127 handler: ForeignWrapperHandler::Native(native),
128 validator: None,
129 options: Vec::new(),
130 },
131 )
132 })
133 .collect()
134}
135
136pub fn validate_functions(
138 wrappers: &ForeignWrappers,
139 routines: &crate::routines::lifecycle::RoutineRegistry,
140) -> Result<(), SQLError> {
141 for wrapper in wrappers.values() {
142 let handler = match &wrapper.handler {
143 ForeignWrapperHandler::Function(function) => Some(function),
144 _ => None,
145 };
146 for reference in handler.into_iter().chain(wrapper.validator.as_ref()) {
147 if reference.binding.builtin {
148 continue;
149 }
150 let Some(function) = routines
151 .values()
152 .flatten()
153 .find(|function| function.def.object_id == reference.binding.object_id)
154 else {
155 continue;
156 };
157 if function.def.catalog_oid != Some(reference.oid)
158 || function.def.is_procedure
159 || crate::routines::routine_signature_types(&function.def)
160 != reference.binding.argument_types
161 {
162 return Err(invalid(
163 &wrapper.name,
164 "live function disagrees with its retained identity",
165 ));
166 }
167 }
168 }
169 Ok(())
170}
171
172pub fn bound_wrapper<'a>(
174 wrappers: &'a ForeignWrappers,
175 name: &str,
176 reference: ForeignWrapperReference,
177) -> Result<&'a ForeignWrapperDefinition, SQLError> {
178 wrappers
179 .get(name)
180 .filter(|wrapper| wrapper.identity == reference)
181 .ok_or_else(|| SQLError::Routine {
182 sqlstate: "XX000".into(),
183 message: format!(
184 "cache lookup failed for foreign-data wrapper {}",
185 reference.oid
186 ),
187 })
188}
189
190pub fn validate_wrappers(
192 wrappers: &ForeignWrappers,
193 roles: &BTreeMap<String, RoleDefinition>,
194) -> Result<(), SQLError> {
195 let mut oids = BTreeSet::new();
196 let mut identities = BTreeSet::new();
197 for (name, wrapper) in wrappers {
198 if name.is_empty() || name.contains('\0') || name != &wrapper.name {
199 return Err(invalid(name, "invalid catalog name"));
200 }
201 let identity = wrapper.identity;
202 let valid_identity = identity.oid >= super::oids::FIRST_NORMAL_OBJECT_ID
203 || matches!(wrapper.handler, ForeignWrapperHandler::Native(native) if identity == native.reference());
204 if !valid_identity
205 || !oids.insert(identity.oid)
206 || identity.object_id == [0; 16]
207 || !identities.insert(identity.object_id)
208 {
209 return Err(invalid(name, "invalid or duplicate catalog identity"));
210 }
211 if !wrapper.owner.is_valid() || wrapper.owner.role_definition(roles).is_none() {
212 return Err(invalid(name, "owner references a missing or replaced role"));
213 }
214 let mut options = BTreeSet::new();
215 for (option, _) in &wrapper.options {
216 if option.is_empty() || option.contains(['\0', '=']) || !options.insert(option) {
217 return Err(invalid(name, "invalid or duplicate option name"));
218 }
219 }
220 let handler = match &wrapper.handler {
221 ForeignWrapperHandler::Function(function) => Some(function),
222 _ => None,
223 };
224 for reference in handler.into_iter().chain(wrapper.validator.as_ref()) {
225 let function = &reference.binding;
226 if reference.oid == 0
227 || (!function.builtin && reference.oid < super::oids::FIRST_NORMAL_OBJECT_ID)
228 || function.name.is_empty()
229 || function.resolution_error.is_some()
230 || function.dispatch.is_some()
231 || (!function.builtin && function.object_id.is_none_or(|id| id == [0; 16]))
232 {
233 return Err(invalid(name, "invalid bound function identity"));
234 }
235 }
236 }
237 Ok(())
238}
239
240fn invalid(name: &str, reason: &str) -> SQLError {
241 SQLError::Internal(format!("foreign-data wrapper `{name}`: {reason}"))
242}
243
244#[cfg(test)]
245mod tests;