linkrs_query/executor/expression/functions/registry/
udf.rs1use super::super::udf::{LoadedPlugin, SharedPlugin, UdfError, UdfLoader};
2use super::super::CustomFunction;
3use super::FunctionRegistry;
4use std::path::{Path, PathBuf};
5use std::sync::Arc;
6use std::time::SystemTime;
7
8pub struct LoadedExtension {
14 pub name: String,
16 pub path: PathBuf,
18 pub loaded_mtime: Option<SystemTime>,
20 pub plugin: SharedPlugin,
22}
23
24impl std::fmt::Debug for LoadedExtension {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 f.debug_struct("LoadedExtension")
27 .field("name", &self.name)
28 .field("path", &self.path)
29 .finish()
30 }
31}
32#[derive(Debug, Clone, PartialEq)]
34pub struct DynamicUdfInfo {
35 pub name: String,
36 pub path: String,
37 pub description: String,
38 pub min_arity: usize,
39 pub max_arity: usize,
40 pub is_pure: bool,
41}
42
43impl FunctionRegistry {
44 pub fn dynamic_udf_mtime(&self, name: &str) -> Option<SystemTime> {
46 self.dynamic_libraries
47 .get(&name.to_uppercase())
48 .and_then(|ext| ext.loaded_mtime)
49 }
50 pub fn load_dynamic_udf(&mut self, path: &Path) -> Result<String, UdfError> {
55 let loaded = UdfLoader::load(path)?;
56 self.register_loaded_plugin(loaded)
57 }
58
59 pub fn install_dynamic_udf(&mut self, source: &str) -> Result<String, UdfError> {
64 let loaded = UdfLoader::install_from_source(source)?;
65 self.register_loaded_plugin(loaded)
66 }
67 fn register_loaded_plugin(&mut self, loaded: LoadedPlugin) -> Result<String, UdfError> {
68 let name = loaded.plugin.name().to_string();
69 let upper = name.to_uppercase();
70 if self.builtin_functions.contains_key(&upper) {
71 return Err(UdfError::BuiltinConflict(name));
72 }
73 if let Some(existing) = self.dynamic_libraries.get(&upper) {
74 return Err(UdfError::AlreadyLoaded(
75 name,
76 existing.path.to_string_lossy().to_string(),
77 ));
78 }
79 if let Some(existing) = self.custom_functions.get(&upper) {
80 if !existing.is_dynamic() {
81 return Err(UdfError::AlreadyLoaded(
82 name,
83 "custom function registry".to_string(),
84 ));
85 }
86 }
87 let func = CustomFunction::new_dynamic(Arc::clone(&loaded.plugin));
88 self.register_custom_full(func);
89 self.dynamic_libraries.insert(
90 upper,
91 LoadedExtension {
92 name: name.clone(),
93 path: loaded.path,
94 loaded_mtime: loaded.loaded_mtime,
95 plugin: loaded.plugin,
96 },
97 );
98 Ok(name)
99 }
100 pub fn register_dynamic_plugin(&mut self, plugin: SharedPlugin) -> Result<String, UdfError> {
105 let name = plugin.name().to_string();
106 if name.trim().is_empty() {
107 return Err(UdfError::InvalidName("<in-process>".to_string()));
108 }
109 let upper = name.to_uppercase();
110 if self.builtin_functions.contains_key(&upper) {
111 return Err(UdfError::BuiltinConflict(name));
112 }
113 if self.dynamic_libraries.contains_key(&upper) {
114 let existing = &self.dynamic_libraries[&upper];
115 return Err(UdfError::AlreadyLoaded(
116 name,
117 existing.path.to_string_lossy().to_string(),
118 ));
119 }
120 let func = CustomFunction::new_dynamic(Arc::clone(&plugin));
121 self.register_custom_full(func);
122 self.dynamic_libraries.insert(
123 upper,
124 LoadedExtension {
125 name: name.clone(),
126 path: PathBuf::from("<in-process>"),
127 loaded_mtime: None,
128 plugin,
129 },
130 );
131 Ok(name)
132 }
133 pub fn unload_dynamic_udf(&mut self, name: &str) -> Result<(), UdfError> {
135 let upper = name.to_uppercase();
136 let extension = self
137 .dynamic_libraries
138 .remove(&upper)
139 .ok_or_else(|| UdfError::NotLoaded(name.to_string()))?;
140 let _ = extension;
141 let remove = self
144 .custom_functions
145 .get(&upper)
146 .map(|f| f.is_dynamic())
147 .unwrap_or(false);
148 if remove {
149 self.custom_functions.remove(&upper);
150 }
151 Ok(())
152 }
153 pub fn reload_dynamic_udf(&mut self, name: &str) -> Result<bool, UdfError> {
159 let upper = name.to_uppercase();
160 let current_mtime = self
161 .dynamic_libraries
162 .get(&upper)
163 .ok_or_else(|| UdfError::NotLoaded(name.to_string()))?
164 .loaded_mtime;
165 let path = self.dynamic_libraries[&upper].path.clone();
166 if path.to_string_lossy() == "<in-process>" {
167 return Ok(false);
168 }
169 let current_file_mtime = std::fs::metadata(&path)
170 .ok()
171 .and_then(|m| m.modified().ok());
172 if current_mtime.is_some() && current_mtime == current_file_mtime {
173 return Ok(false);
174 }
175 let loaded = UdfLoader::load(&path)?;
178 let func = CustomFunction::new_dynamic(Arc::clone(&loaded.plugin));
179 let registered_name = loaded.plugin.name().to_string();
180 self.register_custom_full(func);
181 self.dynamic_libraries.insert(
182 upper,
183 LoadedExtension {
184 name: registered_name,
185 path: loaded.path,
186 loaded_mtime: loaded.loaded_mtime,
187 plugin: loaded.plugin,
188 },
189 );
190 Ok(true)
191 }
192 pub fn is_dynamic(&self, name: &str) -> bool {
194 self.dynamic_libraries.contains_key(&name.to_uppercase())
195 }
196 pub fn list_dynamic_udfs(&self) -> Vec<DynamicUdfInfo> {
198 let mut infos: Vec<DynamicUdfInfo> = self
199 .dynamic_libraries
200 .values()
201 .map(|ext| DynamicUdfInfo {
202 name: ext.name.clone(),
203 path: ext.path.to_string_lossy().to_string(),
204 description: ext.plugin.description().to_string(),
205 min_arity: ext.plugin.min_arity(),
206 max_arity: ext.plugin.max_arity(),
207 is_pure: ext.plugin.is_pure(),
208 })
209 .collect();
210 infos.sort_by(|a, b| a.name.cmp(&b.name));
211 infos
212 }
213}