#[cfg(feature = "js")]
pub mod boa;
pub mod python;
pub mod rhai;
use crate::core::{Result, Value};
use std::collections::HashMap;
use std::sync::Arc;
use std::cell::RefCell;
thread_local! {
pub static CURRENT_SQL_RUNNER: RefCell<Option<*const dyn SqlRunner>> = RefCell::new(None);
}
pub fn with_sql_runner<F, R>(runner: Option<&dyn SqlRunner>, f: F) -> R
where
F: FnOnce() -> R,
{
let ptr = runner.map(|r| {
let r_static: &'static dyn SqlRunner = unsafe { std::mem::transmute(r) };
r_static as *const dyn SqlRunner
});
CURRENT_SQL_RUNNER.with(|r| *r.borrow_mut() = ptr);
let result = f();
CURRENT_SQL_RUNNER.with(|r| *r.borrow_mut() = None);
result
}
pub fn execute_sql_query(
sql: &str,
) -> crate::core::Result<Box<dyn crate::storage::traits::QueryResult>> {
CURRENT_SQL_RUNNER.with(|r| {
if let Some(ptr) = *r.borrow() {
let runner = unsafe { &*ptr };
runner.execute_query(sql)
} else {
Err(crate::core::Error::internal(
"Cannot execute SQL: No database context available in this procedure",
))
}
})
}
pub fn commit_transaction() -> crate::core::Result<()> {
CURRENT_SQL_RUNNER.with(|r| {
if let Some(ptr) = *r.borrow() {
let runner = unsafe { &*ptr };
runner.commit()
} else {
Err(crate::core::Error::internal(
"Cannot commit: No database context available in this procedure",
))
}
})
}
pub fn rollback_transaction() -> crate::core::Result<()> {
CURRENT_SQL_RUNNER.with(|r| {
if let Some(ptr) = *r.borrow() {
let runner = unsafe { &*ptr };
runner.rollback()
} else {
Err(crate::core::Error::internal(
"Cannot rollback: No database context available in this procedure",
))
}
})
}
pub fn begin_transaction() -> crate::core::Result<()> {
CURRENT_SQL_RUNNER.with(|r| {
if let Some(ptr) = *r.borrow() {
let runner = unsafe { &*ptr };
runner.begin()
} else {
Err(crate::core::Error::internal(
"Cannot begin: No database context available in this procedure",
))
}
})
}
pub trait SqlRunner: Send + Sync {
fn execute_query(&self, sql: &str) -> Result<Box<dyn crate::storage::traits::QueryResult>>;
fn execute_ast(
&self,
stmt: &crate::parser::ast::Statement,
) -> Result<Box<dyn crate::storage::traits::QueryResult>>;
fn commit(&self) -> Result<()>;
fn rollback(&self) -> Result<()>;
fn begin(&self) -> Result<()>;
}
pub trait ScriptingBackend {
fn name(&self) -> &'static str;
fn supported_languages(&self) -> &[&'static str];
fn execute(&self, code: &str, args: &[Value], param_names: &[&str]) -> Result<Value>;
fn execute_procedure(
&self,
code: &str,
args: &mut [Value],
param_names: &[&str],
_modes: &[&str],
_runner: Option<&dyn SqlRunner>,
) -> Result<()> {
self.execute(code, args, param_names).map(|_| ())
}
fn validate_code(&self, code: &str) -> Result<()>;
}
pub fn create_backend_registry() -> BackendRegistry {
let mut registry = BackendRegistry::new();
registry.register_backend(Arc::new(rhai::RhaiBackend::new()));
#[cfg(feature = "js")]
registry.register_backend(Arc::new(boa::BoaBackend::new()));
#[cfg(feature = "python")]
registry.register_backend(Arc::new(python::PythonBackend::new()));
registry
}
pub struct BackendRegistry {
backends: HashMap<String, Arc<dyn ScriptingBackend + Send + Sync>>,
}
impl BackendRegistry {
pub fn new() -> Self {
Self {
backends: HashMap::new(),
}
}
pub fn register_backend(&mut self, backend: Arc<dyn ScriptingBackend + Send + Sync>) {
for &lang in backend.supported_languages() {
self.backends.insert(lang.to_lowercase(), backend.clone());
}
}
pub fn get_backend(&self, language: &str) -> Option<&Arc<dyn ScriptingBackend + Send + Sync>> {
self.backends.get(&language.to_lowercase())
}
pub fn is_language_supported(&self, language: &str) -> bool {
self.backends.contains_key(&language.to_lowercase())
}
pub fn list_supported_languages(&self) -> Vec<String> {
self.backends.keys().cloned().collect()
}
}
impl Default for BackendRegistry {
fn default() -> Self {
Self::new()
}
}