use crate::executor::expression::{ExpressionError, ExpressionErrorType};
use linkrs_core::value_conversion::core_value_to_linkrs;
use linkrs_core::Value;
use std::ffi::c_void;
use super::udf::{execute_isolated, SharedPlugin};
use super::ExpressionFunction;
pub struct CFunctionContext {
pub result: Option<Value>,
pub error: Option<String>,
pub aggregate_state: Option<Box<dyn std::any::Any + Send>>,
pub user_data: usize,
pub argc: usize,
pub argv: Vec<linkrs_core::types::c_api::linkrs_value_t>,
}
impl Default for CFunctionContext {
fn default() -> Self {
Self::new()
}
}
impl CFunctionContext {
pub fn new() -> Self {
Self {
result: None,
error: None,
aggregate_state: None,
user_data: 0,
argc: 0,
argv: Vec::new(),
}
}
pub fn with_user_data(user_data: usize) -> Self {
Self {
result: None,
error: None,
aggregate_state: None,
user_data,
argc: 0,
argv: Vec::new(),
}
}
pub fn set_result(&mut self, value: Value) {
self.result = Some(value);
}
pub fn set_error(&mut self, error: String) {
self.error = Some(error);
}
pub fn set_aggregate_state<T: std::any::Any + Send + 'static>(&mut self, state: T) {
self.aggregate_state = Some(Box::new(state));
}
pub fn get_aggregate_state<T: std::any::Any + Send + 'static>(&self) -> Option<&T> {
self.aggregate_state.as_ref()?.downcast_ref::<T>()
}
pub fn get_aggregate_state_mut<T: std::any::Any + Send + 'static>(&mut self) -> Option<&mut T> {
self.aggregate_state.as_mut()?.downcast_mut::<T>()
}
}
pub type ScalarFunctionCallback =
extern "C" fn(*mut CFunctionContext, i32, *mut linkrs_core::types::c_api::linkrs_value_t);
pub type AggregateStepCallback =
extern "C" fn(*mut CFunctionContext, i32, *mut linkrs_core::types::c_api::linkrs_value_t);
pub type AggregateFinalCallback = extern "C" fn(*mut CFunctionContext);
#[derive(Clone)]
pub enum CustomFunctionImpl {
Rust(fn(&[Value]) -> Result<Value, ExpressionError>),
C {
scalar_callback: usize,
user_data: usize,
},
Aggregate {
step_callback: usize,
final_callback: usize,
user_data: usize,
},
Dynamic {
plugin: SharedPlugin,
},
}
impl std::fmt::Debug for CustomFunctionImpl {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CustomFunctionImpl::Rust(_) => write!(f, "Rust closure"),
CustomFunctionImpl::C { .. } => write!(f, "C scalar callback"),
CustomFunctionImpl::Aggregate { .. } => write!(f, "C aggregate callback"),
CustomFunctionImpl::Dynamic { plugin } => {
write!(f, "Dynamic UDF '{}'", plugin.name())
}
}
}
}
#[derive(Debug, Clone)]
pub struct CustomFunction {
pub name: String,
pub arity: usize,
pub is_variadic: bool,
pub description: String,
pub implementation: CustomFunctionImpl,
}
impl CustomFunction {
pub fn new_rust(
name: impl Into<String>,
arity: usize,
is_variadic: bool,
description: impl Into<String>,
implementation: fn(&[Value]) -> Result<Value, ExpressionError>,
) -> Self {
Self {
name: name.into(),
arity,
is_variadic,
description: description.into(),
implementation: CustomFunctionImpl::Rust(implementation),
}
}
pub fn new_c(
name: impl Into<String>,
arity: usize,
is_variadic: bool,
description: impl Into<String>,
scalar_callback: ScalarFunctionCallback,
user_data: *mut c_void,
) -> Self {
Self {
name: name.into(),
arity,
is_variadic,
description: description.into(),
implementation: CustomFunctionImpl::C {
scalar_callback: scalar_callback as usize,
user_data: user_data as usize,
},
}
}
pub fn new_c_aggregate(
name: impl Into<String>,
arity: usize,
is_variadic: bool,
description: impl Into<String>,
step_callback: AggregateStepCallback,
final_callback: AggregateFinalCallback,
user_data: *mut c_void,
) -> Self {
Self {
name: name.into(),
arity,
is_variadic,
description: description.into(),
implementation: CustomFunctionImpl::Aggregate {
step_callback: step_callback as usize,
final_callback: final_callback as usize,
user_data: user_data as usize,
},
}
}
pub fn new_dynamic(plugin: SharedPlugin) -> Self {
let (min_arity, max_arity) = (plugin.min_arity(), plugin.max_arity());
Self {
name: plugin.name().to_string(),
arity: min_arity,
is_variadic: min_arity != max_arity,
description: plugin.description().to_string(),
implementation: CustomFunctionImpl::Dynamic { plugin },
}
}
pub fn is_dynamic(&self) -> bool {
matches!(self.implementation, CustomFunctionImpl::Dynamic { .. })
}
pub fn is_aggregate(&self) -> bool {
matches!(self.implementation, CustomFunctionImpl::Aggregate { .. })
}
pub fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
match &self.implementation {
CustomFunctionImpl::Rust(func) => func(args),
CustomFunctionImpl::C {
scalar_callback,
user_data: _,
} => {
let mut ctx = CFunctionContext::new();
ctx.argc = args.len();
ctx.argv = args.iter().map(core_value_to_linkrs).collect();
let ctx_ptr = &mut ctx as *mut CFunctionContext;
let callback: ScalarFunctionCallback =
unsafe { std::mem::transmute(*scalar_callback) };
let argv_ptr = if ctx.argv.is_empty() {
std::ptr::null_mut()
} else {
ctx.argv.as_mut_ptr()
};
callback(ctx_ptr, args.len() as i32, argv_ptr);
if let Some(error) = ctx.error {
return Err(ExpressionError::new(
ExpressionErrorType::FunctionExecutionError,
error,
));
}
ctx.result.ok_or_else(|| {
ExpressionError::new(
ExpressionErrorType::FunctionExecutionError,
format!("The function '{}' does not set a return value", self.name),
)
})
}
CustomFunctionImpl::Aggregate { .. } => Err(ExpressionError::new(
ExpressionErrorType::InvalidOperation,
"Aggregation functions need to be executed within the aggregation context"
.to_string(),
)),
CustomFunctionImpl::Dynamic { plugin } => execute_isolated(plugin.as_ref(), args),
}
}
}
impl ExpressionFunction for CustomFunction {
fn name(&self) -> &str {
&self.name
}
fn arity(&self) -> usize {
self.arity
}
fn is_variadic(&self) -> bool {
self.is_variadic
}
fn execute(&self, args: &[Value]) -> Result<Value, ExpressionError> {
self.execute(args)
}
fn description(&self) -> &str {
&self.description
}
}