linkrs_query/executor/expression/functions/
udf.rs1pub mod dynamic;
8pub mod error;
9pub mod loader;
10pub mod plugin;
11
12pub use dynamic::{execute_isolated, DynamicPlugin};
13pub use error::UdfError;
14pub use loader::{LoadedPlugin, UdfLoader};
15pub use plugin::{
16 check_arity, SharedPlugin, UdfAbiVersionFn, UdfCreateFn, UdfDestroyFn, UdfPlugin,
17 UDF_ABI_VERSION, UDF_ABI_VERSION_SYMBOL, UDF_CREATE_SYMBOL, UDF_DESTROY_SYMBOL,
18};
19
20#[cfg(test)]
21mod tests {
22 use super::*;
23 use crate::executor::expression::{ExpressionError, ExpressionErrorType};
24 use linkrs_core::Value;
25
26 struct Double;
27
28 impl UdfPlugin for Double {
29 fn name(&self) -> &str {
30 "test_double"
31 }
32
33 fn description(&self) -> &str {
34 "doubles an integer"
35 }
36
37 fn min_arity(&self) -> usize {
38 1
39 }
40
41 fn max_arity(&self) -> usize {
42 1
43 }
44
45 fn is_pure(&self) -> bool {
46 true
47 }
48
49 fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
50 match &args[0] {
51 Value::Int(v) => Ok(Value::Int(v * 2)),
52 other => Err(ExpressionError::new(
53 ExpressionErrorType::TypeError,
54 format!("expected int, got {other:?}"),
55 )),
56 }
57 }
58 }
59
60 struct Panic;
61
62 impl UdfPlugin for Panic {
63 fn name(&self) -> &str {
64 "test_panic"
65 }
66
67 fn description(&self) -> &str {
68 "always panics"
69 }
70
71 fn min_arity(&self) -> usize {
72 0
73 }
74
75 fn max_arity(&self) -> usize {
76 0
77 }
78
79 fn is_pure(&self) -> bool {
80 false
81 }
82
83 fn execute(&self, _args: &[Value]) -> Result<Value, ExpressionError> {
84 panic!("boom");
85 }
86 }
87
88 #[test]
89 fn test_isolated_execution_success() {
90 let plugin = Double;
91 let result = execute_isolated(&plugin, &[Value::Int(21)]).expect("execution failed");
92 assert_eq!(result, Value::Int(42));
93 }
94
95 #[test]
96 fn test_isolated_execution_arity_checked() {
97 let plugin = Double;
98 let err = execute_isolated(&plugin, &[]).unwrap_err();
99 assert_eq!(err.error_type, ExpressionErrorType::InvalidArgumentCount);
100 }
101
102 #[test]
103 fn test_isolated_execution_panic_contained() {
104 let plugin = Panic;
105 let err = execute_isolated(&plugin, &[]).unwrap_err();
106 assert_eq!(err.error_type, ExpressionErrorType::FunctionExecutionError);
107 assert!(err.message.contains("test_panic"));
108 }
109
110 #[test]
111 fn test_loader_rejects_missing_file() {
112 let err = UdfLoader::load(std::path::Path::new("/nonexistent/udf_plugin.so")).unwrap_err();
113 assert!(matches!(err, UdfError::InvalidPath(_, _)));
114 }
115
116 #[test]
117 fn test_loader_rejects_wrong_extension() {
118 let dir = std::env::temp_dir();
119 let path = dir.join("linkrs_udf_probe.txt");
120 std::fs::write(&path, b"not a library").expect("write probe file");
121 let err = UdfLoader::load(&path).unwrap_err();
122 std::fs::remove_file(&path).ok();
123 assert!(matches!(err, UdfError::InvalidPath(_, _)));
124 }
125
126 #[test]
127 fn test_install_from_source_rejects_remote() {
128 let err = UdfLoader::install_from_source("https://example.com/udf.so").unwrap_err();
129 assert!(matches!(err, UdfError::RepoDownloadUnsupported(_)));
130 }
131}