use super::ScriptingBackend;
use crate::core::{Error, Result, Value};
use rhai::{Engine, Scope};
pub struct RhaiBackend {
engine: Engine,
}
impl RhaiBackend {
pub fn new() -> Self {
let mut engine = Engine::new();
engine.register_fn("to_int", |v: i64| v);
engine.register_fn("to_float", |v: f64| v);
engine.register_fn("to_string", |v: String| v);
let mut oxibase_module = rhai::Module::new();
oxibase_module.set_native_fn(
"execute",
|sql: rhai::ImmutableString| -> std::result::Result<i64, Box<rhai::EvalAltResult>> {
match crate::functions::backends::execute_sql_query(&sql) {
Ok(res) => Ok(res.rows_affected()),
Err(e) => Err(e.to_string().into()),
}
},
);
engine.register_static_module("oxibase", rhai::Shared::new(oxibase_module));
Self { engine }
}
}
impl Default for RhaiBackend {
fn default() -> Self {
Self::new()
}
}
impl ScriptingBackend for RhaiBackend {
fn name(&self) -> &'static str {
"rhai"
}
fn supported_languages(&self) -> &[&'static str] {
&["rhai"]
}
fn execute(&self, code: &str, args: &[Value], param_names: &[&str]) -> Result<Value> {
let mut scope = Scope::new();
let mut args_array = rhai::Array::new();
for arg in args {
match arg {
Value::Integer(i) => args_array.push(rhai::Dynamic::from(*i)),
Value::Float(f) => args_array.push(rhai::Dynamic::from(*f)),
Value::Text(s) => args_array.push(rhai::Dynamic::from(s.as_ref().to_string())),
Value::Boolean(b) => args_array.push(rhai::Dynamic::from(*b)),
_ => return Err(Error::internal("Unsupported argument type for Rhai")),
};
}
scope.push("arguments", args_array);
for (i, arg) in args.iter().enumerate() {
let var_name = param_names[i];
match arg {
Value::Integer(i) => scope.push(var_name, *i),
Value::Float(f) => scope.push(var_name, *f),
Value::Text(s) => scope.push(var_name, s.as_ref().to_string()),
Value::Boolean(b) => scope.push(var_name, *b),
_ => return Err(Error::internal("Unsupported argument type for Rhai")),
};
}
match self
.engine
.eval_with_scope::<rhai::Dynamic>(&mut scope, code)
{
Ok(result) => {
if result.is::<i64>() {
Ok(Value::Integer(result.cast::<i64>()))
} else if result.is::<f64>() {
Ok(Value::Float(result.cast::<f64>()))
} else if result.is::<String>() {
Ok(Value::Text(result.cast::<String>().into()))
} else if result.is::<bool>() {
Ok(Value::Boolean(result.cast::<bool>()))
} else if result.is::<()>() {
Ok(Value::null_unknown())
} else {
Err(Error::internal("Unsupported return type from Rhai script"))
}
}
Err(e) => Err(Error::internal(format!("Rhai execution error: {}", e))),
}
}
fn validate_code(&self, code: &str) -> Result<()> {
match self.engine.compile(code) {
Ok(_) => Ok(()),
Err(e) => Err(Error::internal(format!("Rhai syntax error: {}", e))),
}
}
fn execute_procedure(
&self,
code: &str,
args: &mut [Value],
param_names: &[&str],
_modes: &[&str],
_runner: Option<&dyn crate::functions::backends::SqlRunner>,
) -> Result<()> {
let mut scope = Scope::new();
for (i, arg) in args.iter().enumerate() {
let var_name = param_names[i];
match arg {
Value::Integer(i) => scope.push(var_name, *i),
Value::Float(f) => scope.push(var_name, *f),
Value::Text(s) => scope.push(var_name, s.as_ref().to_string()),
Value::Boolean(b) => scope.push(var_name, *b),
Value::Null(_) => scope.push(var_name, ()),
_ => return Err(Error::internal("Unsupported argument type for Rhai")),
};
}
match self
.engine
.eval_with_scope::<rhai::Dynamic>(&mut scope, code)
{
Ok(_) => {
for (i, arg) in args.iter_mut().enumerate() {
let var_name = param_names[i];
if let Some(val) = scope.get_value::<rhai::Dynamic>(var_name) {
if val.is::<i64>() {
*arg = Value::Integer(val.cast::<i64>());
} else if val.is::<f64>() {
*arg = Value::Float(val.cast::<f64>());
} else if val.is::<String>() {
*arg = Value::Text(val.cast::<String>().into());
} else if val.is::<bool>() {
*arg = Value::Boolean(val.cast::<bool>());
} else if val.is::<()>() {
*arg = Value::null_unknown();
}
}
}
Ok(())
}
Err(e) => Err(Error::internal(format!("Rhai execution error: {}", e))),
}
}
}