linkrs_query/executor/expression/functions/
custom.rs1use crate::executor::expression::{ExpressionError, ExpressionErrorType};
6use linkrs_core::value_conversion::core_value_to_linkrs;
7use linkrs_core::Value;
8
9use std::ffi::c_void;
10
11use super::udf::{execute_isolated, SharedPlugin};
12use super::ExpressionFunction;
13
14pub struct CFunctionContext {
16 pub result: Option<Value>,
18 pub error: Option<String>,
20 pub aggregate_state: Option<Box<dyn std::any::Any + Send>>,
22 pub user_data: usize,
24 pub argc: usize,
26 pub argv: Vec<linkrs_core::types::c_api::linkrs_value_t>,
28}
29
30impl Default for CFunctionContext {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl CFunctionContext {
37 pub fn new() -> Self {
38 Self {
39 result: None,
40 error: None,
41 aggregate_state: None,
42 user_data: 0,
43 argc: 0,
44 argv: Vec::new(),
45 }
46 }
47
48 pub fn with_user_data(user_data: usize) -> Self {
49 Self {
50 result: None,
51 error: None,
52 aggregate_state: None,
53 user_data,
54 argc: 0,
55 argv: Vec::new(),
56 }
57 }
58
59 pub fn set_result(&mut self, value: Value) {
60 self.result = Some(value);
61 }
62
63 pub fn set_error(&mut self, error: String) {
64 self.error = Some(error);
65 }
66
67 pub fn set_aggregate_state<T: std::any::Any + Send + 'static>(&mut self, state: T) {
69 self.aggregate_state = Some(Box::new(state));
70 }
71
72 pub fn get_aggregate_state<T: std::any::Any + Send + 'static>(&self) -> Option<&T> {
74 self.aggregate_state.as_ref()?.downcast_ref::<T>()
75 }
76
77 pub fn get_aggregate_state_mut<T: std::any::Any + Send + 'static>(&mut self) -> Option<&mut T> {
79 self.aggregate_state.as_mut()?.downcast_mut::<T>()
80 }
81}
82
83pub type ScalarFunctionCallback =
85 extern "C" fn(*mut CFunctionContext, i32, *mut linkrs_core::types::c_api::linkrs_value_t);
86
87pub type AggregateStepCallback =
89 extern "C" fn(*mut CFunctionContext, i32, *mut linkrs_core::types::c_api::linkrs_value_t);
90
91pub type AggregateFinalCallback = extern "C" fn(*mut CFunctionContext);
93
94#[derive(Clone)]
96pub enum CustomFunctionImpl {
97 Rust(fn(&[Value]) -> Result<Value, ExpressionError>),
99 C {
101 scalar_callback: usize,
103 user_data: usize,
105 },
106 Aggregate {
108 step_callback: usize,
110 final_callback: usize,
112 user_data: usize,
114 },
115 Dynamic {
117 plugin: SharedPlugin,
119 },
120}
121
122impl std::fmt::Debug for CustomFunctionImpl {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 match self {
125 CustomFunctionImpl::Rust(_) => write!(f, "Rust closure"),
126 CustomFunctionImpl::C { .. } => write!(f, "C scalar callback"),
127 CustomFunctionImpl::Aggregate { .. } => write!(f, "C aggregate callback"),
128 CustomFunctionImpl::Dynamic { plugin } => {
129 write!(f, "Dynamic UDF '{}'", plugin.name())
130 }
131 }
132 }
133}
134
135#[derive(Debug, Clone)]
137pub struct CustomFunction {
138 pub name: String,
140 pub arity: usize,
142 pub is_variadic: bool,
144 pub description: String,
146 pub implementation: CustomFunctionImpl,
148}
149
150impl CustomFunction {
151 pub fn new_rust(
153 name: impl Into<String>,
154 arity: usize,
155 is_variadic: bool,
156 description: impl Into<String>,
157 implementation: fn(&[Value]) -> Result<Value, ExpressionError>,
158 ) -> Self {
159 Self {
160 name: name.into(),
161 arity,
162 is_variadic,
163 description: description.into(),
164 implementation: CustomFunctionImpl::Rust(implementation),
165 }
166 }
167
168 pub fn new_c(
170 name: impl Into<String>,
171 arity: usize,
172 is_variadic: bool,
173 description: impl Into<String>,
174 scalar_callback: ScalarFunctionCallback,
175 user_data: *mut c_void,
176 ) -> Self {
177 Self {
178 name: name.into(),
179 arity,
180 is_variadic,
181 description: description.into(),
182 implementation: CustomFunctionImpl::C {
183 scalar_callback: scalar_callback as usize,
184 user_data: user_data as usize,
185 },
186 }
187 }
188
189 pub fn new_c_aggregate(
191 name: impl Into<String>,
192 arity: usize,
193 is_variadic: bool,
194 description: impl Into<String>,
195 step_callback: AggregateStepCallback,
196 final_callback: AggregateFinalCallback,
197 user_data: *mut c_void,
198 ) -> Self {
199 Self {
200 name: name.into(),
201 arity,
202 is_variadic,
203 description: description.into(),
204 implementation: CustomFunctionImpl::Aggregate {
205 step_callback: step_callback as usize,
206 final_callback: final_callback as usize,
207 user_data: user_data as usize,
208 },
209 }
210 }
211
212 pub fn new_dynamic(plugin: SharedPlugin) -> Self {
217 let (min_arity, max_arity) = (plugin.min_arity(), plugin.max_arity());
218 Self {
219 name: plugin.name().to_string(),
220 arity: min_arity,
221 is_variadic: min_arity != max_arity,
222 description: plugin.description().to_string(),
223 implementation: CustomFunctionImpl::Dynamic { plugin },
224 }
225 }
226
227 pub fn is_dynamic(&self) -> bool {
229 matches!(self.implementation, CustomFunctionImpl::Dynamic { .. })
230 }
231
232 pub fn is_aggregate(&self) -> bool {
234 matches!(self.implementation, CustomFunctionImpl::Aggregate { .. })
235 }
236
237 pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
239 match &self.implementation {
240 CustomFunctionImpl::Rust(func) => func(args),
241 CustomFunctionImpl::C {
242 scalar_callback,
243 user_data: _,
244 } => {
245 let mut ctx = CFunctionContext::new();
247 ctx.argc = args.len();
248 ctx.argv = args.iter().map(core_value_to_linkrs).collect();
249 let ctx_ptr = &mut ctx as *mut CFunctionContext;
250
251 let callback: ScalarFunctionCallback =
253 unsafe { std::mem::transmute(*scalar_callback) };
254
255 let argv_ptr = if ctx.argv.is_empty() {
257 std::ptr::null_mut()
258 } else {
259 ctx.argv.as_mut_ptr()
260 };
261 callback(ctx_ptr, args.len() as i32, argv_ptr);
262
263 if let Some(error) = ctx.error {
265 return Err(ExpressionError::new(
266 ExpressionErrorType::FunctionExecutionError,
267 error,
268 ));
269 }
270
271 ctx.result.ok_or_else(|| {
273 ExpressionError::new(
274 ExpressionErrorType::FunctionExecutionError,
275 format!("The function '{}' does not set a return value", self.name),
276 )
277 })
278 }
279 CustomFunctionImpl::Aggregate { .. } => Err(ExpressionError::new(
280 ExpressionErrorType::InvalidOperation,
281 "Aggregation functions need to be executed within the aggregation context"
282 .to_string(),
283 )),
284 CustomFunctionImpl::Dynamic { plugin } => execute_isolated(plugin.as_ref(), args),
285 }
286 }
287}
288
289impl ExpressionFunction for CustomFunction {
290 fn name(&self) -> &str {
291 &self.name
292 }
293
294 fn arity(&self) -> usize {
295 self.arity
296 }
297
298 fn is_variadic(&self) -> bool {
299 self.is_variadic
300 }
301
302 fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
303 self.execute(args)
304 }
305
306 fn description(&self) -> &str {
307 &self.description
308 }
309}