diff --git a/packages/cubejs-backend-native/js/index.ts b/packages/cubejs-backend-native/js/index.ts index 8bd390d3801a4..8c2c190112765 100644 --- a/packages/cubejs-backend-native/js/index.ts +++ b/packages/cubejs-backend-native/js/index.ts @@ -450,8 +450,18 @@ export const sql4sql = async (instance: SqlInterfaceInstance, sqlQuery: string, export const buildSqlAndParams = (cubeEvaluator: any): String => { const native = loadNative(); - - return native.buildSqlAndParams(cubeEvaluator); + const safeCallFn = (fn: Function, thisArg: any, ...args: any[]) => { + try { + return { + result: fn.apply(thisArg, args), + }; + } catch (e: any) { + return { + error: e.toString(), + }; + } + }; + return native.buildSqlAndParams(cubeEvaluator, safeCallFn); }; export type ResultRow = Record; diff --git a/packages/cubejs-backend-native/src/node_export.rs b/packages/cubejs-backend-native/src/node_export.rs index 13080d1a368ef..efe7446537d56 100644 --- a/packages/cubejs-backend-native/src/node_export.rs +++ b/packages/cubejs-backend-native/src/node_export.rs @@ -585,6 +585,18 @@ fn build_sql_and_params(cx: FunctionContext) -> JsResult { .unwrap()?, )); + let safe_call_fn = neon_context_holder + .with_context(|cx| { + if let Ok(func) = cx.argument::(1) { + Some(func) + } else { + None + } + }) + .unwrap(); + + neon_context_holder.set_safe_call_fn(safe_call_fn).unwrap(); + let context_holder = NativeContextHolder::>>::new( neon_context_holder, ); diff --git a/rust/cubenativeutils/src/wrappers/neon/context.rs b/rust/cubenativeutils/src/wrappers/neon/context.rs index 10be1a53f98a2..637fd30a56564 100644 --- a/rust/cubenativeutils/src/wrappers/neon/context.rs +++ b/rust/cubenativeutils/src/wrappers/neon/context.rs @@ -29,13 +29,81 @@ impl<'cx> NoenContextLifetimeExpand<'cx> for FunctionContext<'cx> { } } +pub struct SafeCallFn<'a> { + safe_fn: &'a Option>, +} + +impl<'a> SafeCallFn<'a> { + pub fn new(safe_fn: &'a Option>) -> Self { + Self { safe_fn } + } + + pub fn safe_call, T: Value>( + &self, + cx: &mut C, + func: &Handle<'static, JsFunction>, + this: Handle<'static, T>, + mut args: Vec>, + ) -> Result, CubeError> { + if let Some(safe_fn) = self.safe_fn { + args.insert(0, this.upcast()); + + args.insert(0, func.upcast()); + + let res = safe_fn + .call(cx, this, args) + .map_err(|_| CubeError::internal(format!("Failed to call safe function")))?; + let res = res.downcast::(cx).map_err(|_| { + CubeError::internal(format!("Result of safe function call should be object")) + })?; + let result_field = res.get_value(cx, "result").map_err(|_| { + CubeError::internal(format!( + "Failed wile get `result` field of safe call function result" + )) + })?; + let err_field = res.get_value(cx, "error").map_err(|_| { + CubeError::internal(format!( + "Failed wile get `error` field of safe call function result" + )) + })?; + if !err_field.is_a::(cx) { + let error_string = err_field.downcast::(cx).map_err(|_| { + CubeError::internal(format!( + "Error in safe call function result should be string" + )) + })?; + Err(CubeError::internal(error_string.value(cx))) + } else if !result_field.is_a::(cx) { + Ok(result_field) + } else { + Err(CubeError::internal(format!( + "Safe call function should return object with result or error field" + ))) + } + } else { + let res = func + .call(cx, this, args) + .map_err(|_| CubeError::internal(format!("Failed to call function")))?; + Ok(res) + } + } +} + pub struct ContextWrapper> { cx: C, + safe_call_fn: Option>, } impl> ContextWrapper { pub fn new(cx: C) -> Rc> { - Rc::new(RefCell::new(Self { cx })) + Rc::new(RefCell::new(Self { + cx, + safe_call_fn: None, + })) + } + + pub fn set_safe_call_fn(&mut self, fn_handle: Option>) { + self.safe_call_fn = fn_handle; } pub fn with_context(&mut self, f: F) -> T @@ -45,6 +113,14 @@ impl> ContextWrapper { f(&mut self.cx) } + pub fn with_context_and_safe_fn(&mut self, f: F) -> T + where + F: FnOnce(&mut C, SafeCallFn) -> T, + { + let safe_call_fn = SafeCallFn::new(&self.safe_call_fn); + f(&mut self.cx, safe_call_fn) + } + pub fn get_context(&mut self) -> &mut C { &mut self.cx } @@ -116,6 +192,36 @@ impl> ContextHolder { )) } } + + pub fn with_context_and_safe_fn(&self, f: F) -> Result + where + F: FnOnce(&mut C, SafeCallFn) -> T, + { + if let Some(context) = self.context.upgrade() { + let mut cx = context.borrow_mut(); + let res = cx.with_context_and_safe_fn(f); + Ok(res) + } else { + Err(CubeError::internal(format!( + "Call to neon context outside of its lifetime" + ))) + } + } + + pub fn set_safe_call_fn( + &self, + f: Option>, + ) -> Result<(), CubeError> { + if let Some(context) = self.context.upgrade() { + let mut cx = context.borrow_mut(); + cx.set_safe_call_fn(f); + Ok(()) + } else { + Err(CubeError::internal(format!( + "Call to neon context outside of its lifetime" + ))) + } + } } impl + 'static> NativeContext> for ContextHolder { diff --git a/rust/cubenativeutils/src/wrappers/neon/object/mod.rs b/rust/cubenativeutils/src/wrappers/neon/object/mod.rs index 751acb6737278..4f404bec2866a 100644 --- a/rust/cubenativeutils/src/wrappers/neon/object/mod.rs +++ b/rust/cubenativeutils/src/wrappers/neon/object/mod.rs @@ -10,7 +10,10 @@ use self::{ neon_struct::NeonStruct, }; use super::inner_types::NeonInnerTypes; -use crate::wrappers::{neon::context::ContextHolder, object::NativeObject}; +use crate::wrappers::{ + neon::context::{ContextHolder, SafeCallFn}, + object::NativeObject, +}; use cubesql::CubeError; use neon::prelude::*; @@ -64,6 +67,14 @@ impl + 'static, V: Value + 'static> NeonTypeHandle { })? } + pub fn map_neon_object_with_safe_call_fn(&self, f: F) -> Result + where + F: FnOnce(&mut C, &Handle<'static, V>, SafeCallFn) -> T, + { + self.context + .with_context_and_safe_fn(|cx, safe_call_fn| f(cx, &self.object, safe_call_fn)) + } + pub fn is_a(&self) -> Result { self.context.with_context(|cx| self.object.is_a::(cx)) } diff --git a/rust/cubenativeutils/src/wrappers/neon/object/neon_function.rs b/rust/cubenativeutils/src/wrappers/neon/object/neon_function.rs index 216b6852f668e..79829631c5cab 100644 --- a/rust/cubenativeutils/src/wrappers/neon/object/neon_function.rs +++ b/rust/cubenativeutils/src/wrappers/neon/object/neon_function.rs @@ -35,12 +35,12 @@ impl + 'static> NativeFunction> for NeonFu .into_iter() .map(|arg| -> Result<_, CubeError> { Ok(arg.into_object().get_object()) }) .collect::, _>>()?; - let neon_reuslt = self.object.map_neon_object(|cx, neon_object| { - let null = cx.null(); - neon_object - .call(cx, null, neon_args) - .map_err(|_| CubeError::internal("Failed to call function ".to_string())) - })??; + let neon_reuslt = + self.object + .map_neon_object_with_safe_call_fn(|cx, neon_object, safe_call_fn| { + let null = cx.null(); + safe_call_fn.safe_call(cx, neon_object, null, neon_args) + })??; Ok(NativeObjectHandle::new(NeonObject::new( self.object.context.clone(), neon_reuslt, diff --git a/rust/cubenativeutils/src/wrappers/neon/object/neon_struct.rs b/rust/cubenativeutils/src/wrappers/neon/object/neon_struct.rs index a5446b6fa4264..7f835698642fe 100644 --- a/rust/cubenativeutils/src/wrappers/neon/object/neon_struct.rs +++ b/rust/cubenativeutils/src/wrappers/neon/object/neon_struct.rs @@ -97,19 +97,17 @@ impl + 'static> NativeStruct> for NeonStru .map(|arg| -> Result<_, CubeError> { Ok(arg.into_object().get_object()) }) .collect::, _>>()?; - let neon_reuslt = self.object.map_neon_object(|cx, neon_object| { - let neon_method = neon_object - .get::(cx, method) - .map_err(|_| CubeError::internal(format!("Method `{}` not found", method)))?; - neon_method - .call(cx, *neon_object, neon_args) - .map_err(|err| { - CubeError::internal(format!( - "Failed to call method `{} {} {:?}", - method, err, err - )) - }) - })??; + let neon_reuslt = + self.object + .map_neon_object_with_safe_call_fn(|cx, neon_object, safe_call_fn| { + let neon_method = + neon_object + .get::(cx, method) + .map_err(|_| { + CubeError::internal(format!("Method `{}` not found", method)) + })?; + safe_call_fn.safe_call(cx, &neon_method, *neon_object, neon_args) + })??; Ok(NativeObjectHandle::new(NeonObject::new( self.object.context.clone(), neon_reuslt,