|
| 1 | +//! Test endpoints for the Tesseract bridge layer. |
| 2 | +//! |
| 3 | +//! These functions are exported on the native module under names prefixed with |
| 4 | +//! `__testBridge` (e.g. `__testBridgeCompileMemberSql`). They drive real V8 |
| 5 | +//! through the production bridge code so |
| 6 | +//! that bridge logic can be regression-tested at the unit level rather than |
| 7 | +//! only via end-to-end JS planner tests. |
| 8 | +//! |
| 9 | +//! Stub implementations for trait dependencies (e.g. `BaseTools`) live in this |
| 10 | +//! module; they should fail loudly when an unsupported code path is exercised. |
| 11 | +
|
| 12 | +use cubenativeutils::wrappers::neon::neon_guarded_funcion_call; |
| 13 | +use cubenativeutils::wrappers::object::{NativeArray, NativeFunction, NativeStruct, NativeType}; |
| 14 | +use cubenativeutils::wrappers::serializer::NativeSerialize; |
| 15 | +use cubenativeutils::wrappers::{inner_types::InnerTypes, NativeContextHolder, NativeObjectHandle}; |
| 16 | +use cubenativeutils::CubeError; |
| 17 | +use cubesqlplanner::cube_bridge::base_tools::BaseTools; |
| 18 | +use cubesqlplanner::cube_bridge::driver_tools::DriverTools; |
| 19 | +use cubesqlplanner::cube_bridge::filter_params_callback::{ |
| 20 | + FilterParamsCallback, NativeFilterParamsCallback, |
| 21 | +}; |
| 22 | +use cubesqlplanner::cube_bridge::join_definition::JoinDefinition; |
| 23 | +use cubesqlplanner::cube_bridge::join_hints::JoinHintItem; |
| 24 | +use cubesqlplanner::cube_bridge::member_sql::{ |
| 25 | + FilterGroupItem, FilterParamsItem, MemberSql, NativeMemberSql, SqlTemplate, SqlTemplateArgs, |
| 26 | +}; |
| 27 | +use cubesqlplanner::cube_bridge::pre_aggregation_obj::PreAggregationObj; |
| 28 | +use cubesqlplanner::cube_bridge::security_context::{NativeSecurityContext, SecurityContext}; |
| 29 | +use cubesqlplanner::cube_bridge::sql_templates_render::SqlTemplatesRender; |
| 30 | +use cubesqlplanner::cube_bridge::sql_utils::SqlUtils; |
| 31 | +use neon::prelude::*; |
| 32 | +use std::any::Any; |
| 33 | +use std::rc::Rc; |
| 34 | + |
| 35 | +struct StubBaseTools; |
| 36 | + |
| 37 | +fn stub_err(method: &str) -> CubeError { |
| 38 | + CubeError::internal(format!( |
| 39 | + "StubBaseTools::{} called from bridge test harness — \ |
| 40 | + this test path requires a real BaseTools implementation", |
| 41 | + method |
| 42 | + )) |
| 43 | +} |
| 44 | + |
| 45 | +impl BaseTools for StubBaseTools { |
| 46 | + fn as_any(self: Rc<Self>) -> Rc<dyn Any> { |
| 47 | + self |
| 48 | + } |
| 49 | + fn driver_tools(&self, _external: bool) -> Result<Rc<dyn DriverTools>, CubeError> { |
| 50 | + Err(stub_err("driver_tools")) |
| 51 | + } |
| 52 | + fn sql_templates(&self) -> Result<Rc<dyn SqlTemplatesRender>, CubeError> { |
| 53 | + Err(stub_err("sql_templates")) |
| 54 | + } |
| 55 | + fn sql_utils_for_rust(&self) -> Result<Rc<dyn SqlUtils>, CubeError> { |
| 56 | + Err(stub_err("sql_utils_for_rust")) |
| 57 | + } |
| 58 | + fn generate_time_series( |
| 59 | + &self, |
| 60 | + _granularity: String, |
| 61 | + _date_range: Vec<String>, |
| 62 | + ) -> Result<Vec<Vec<String>>, CubeError> { |
| 63 | + Err(stub_err("generate_time_series")) |
| 64 | + } |
| 65 | + fn generate_custom_time_series( |
| 66 | + &self, |
| 67 | + _granularity: String, |
| 68 | + _date_range: Vec<String>, |
| 69 | + _origin: String, |
| 70 | + ) -> Result<Vec<Vec<String>>, CubeError> { |
| 71 | + Err(stub_err("generate_custom_time_series")) |
| 72 | + } |
| 73 | + fn get_allocated_params(&self) -> Result<Vec<String>, CubeError> { |
| 74 | + Err(stub_err("get_allocated_params")) |
| 75 | + } |
| 76 | + fn all_cube_members(&self, _path: String) -> Result<Vec<String>, CubeError> { |
| 77 | + Err(stub_err("all_cube_members")) |
| 78 | + } |
| 79 | + fn interval_and_minimal_time_unit(&self, _interval: String) -> Result<Vec<String>, CubeError> { |
| 80 | + Err(stub_err("interval_and_minimal_time_unit")) |
| 81 | + } |
| 82 | + fn get_pre_aggregation_by_name( |
| 83 | + &self, |
| 84 | + _cube_name: String, |
| 85 | + _name: String, |
| 86 | + ) -> Result<Rc<dyn PreAggregationObj>, CubeError> { |
| 87 | + Err(stub_err("get_pre_aggregation_by_name")) |
| 88 | + } |
| 89 | + fn pre_aggregation_table_name( |
| 90 | + &self, |
| 91 | + _cube_name: String, |
| 92 | + _name: String, |
| 93 | + ) -> Result<String, CubeError> { |
| 94 | + Err(stub_err("pre_aggregation_table_name")) |
| 95 | + } |
| 96 | + fn join_tree_for_hints( |
| 97 | + &self, |
| 98 | + _hints: Vec<JoinHintItem>, |
| 99 | + ) -> Result<Rc<dyn JoinDefinition>, CubeError> { |
| 100 | + Err(stub_err("join_tree_for_hints")) |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +fn handles_to_array<IT: InnerTypes>( |
| 105 | + items: Vec<NativeObjectHandle<IT>>, |
| 106 | + context: NativeContextHolder<IT>, |
| 107 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 108 | + let arr = context.empty_array()?; |
| 109 | + for (i, item) in items.into_iter().enumerate() { |
| 110 | + arr.set(i as u32, item)?; |
| 111 | + } |
| 112 | + Ok(NativeObjectHandle::new(arr.into_object())) |
| 113 | +} |
| 114 | + |
| 115 | +fn template_to_native<IT: InnerTypes>( |
| 116 | + template: &SqlTemplate, |
| 117 | + context: NativeContextHolder<IT>, |
| 118 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 119 | + match template { |
| 120 | + SqlTemplate::String(s) => s.to_native(context), |
| 121 | + SqlTemplate::StringVec(strings) => strings.to_native(context), |
| 122 | + } |
| 123 | +} |
| 124 | + |
| 125 | +fn filter_params_to_native<IT: InnerTypes>( |
| 126 | + items: &[FilterParamsItem], |
| 127 | + context: NativeContextHolder<IT>, |
| 128 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 129 | + let serialized = items |
| 130 | + .iter() |
| 131 | + .map(|itm| itm.to_native(context.clone())) |
| 132 | + .collect::<Result<Vec<_>, _>>()?; |
| 133 | + handles_to_array(serialized, context) |
| 134 | +} |
| 135 | + |
| 136 | +fn filter_group_to_native<IT: InnerTypes>( |
| 137 | + group: &FilterGroupItem, |
| 138 | + context: NativeContextHolder<IT>, |
| 139 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 140 | + let result = context.empty_struct()?; |
| 141 | + result.set_field( |
| 142 | + "filter_params", |
| 143 | + filter_params_to_native(&group.filter_params, context.clone())?, |
| 144 | + )?; |
| 145 | + Ok(NativeObjectHandle::new(result.into_object())) |
| 146 | +} |
| 147 | + |
| 148 | +fn args_to_native<IT: InnerTypes>( |
| 149 | + args: &SqlTemplateArgs, |
| 150 | + context: NativeContextHolder<IT>, |
| 151 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 152 | + let result = context.empty_struct()?; |
| 153 | + result.set_field( |
| 154 | + "symbol_paths", |
| 155 | + args.symbol_paths.to_native(context.clone())?, |
| 156 | + )?; |
| 157 | + result.set_field( |
| 158 | + "filter_params", |
| 159 | + filter_params_to_native(&args.filter_params, context.clone())?, |
| 160 | + )?; |
| 161 | + let groups = args |
| 162 | + .filter_groups |
| 163 | + .iter() |
| 164 | + .map(|g| filter_group_to_native(g, context.clone())) |
| 165 | + .collect::<Result<Vec<_>, _>>()?; |
| 166 | + result.set_field("filter_groups", handles_to_array(groups, context.clone())?)?; |
| 167 | + let security_context = context.empty_struct()?; |
| 168 | + security_context.set_field( |
| 169 | + "values", |
| 170 | + args.security_context.values.to_native(context.clone())?, |
| 171 | + )?; |
| 172 | + result.set_field( |
| 173 | + "security_context", |
| 174 | + NativeObjectHandle::new(security_context.into_object()), |
| 175 | + )?; |
| 176 | + Ok(NativeObjectHandle::new(result.into_object())) |
| 177 | +} |
| 178 | + |
| 179 | +fn compile_member_sql_inner<IT: InnerTypes>( |
| 180 | + context_holder: NativeContextHolder<IT>, |
| 181 | + js_fn: NativeObjectHandle<IT>, |
| 182 | + security_context_obj: NativeObjectHandle<IT>, |
| 183 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 184 | + let member_sql = NativeMemberSql::try_new(js_fn)?; |
| 185 | + let security_context: Rc<dyn SecurityContext> = |
| 186 | + Rc::new(NativeSecurityContext::try_new(security_context_obj)?); |
| 187 | + let base_tools: Rc<dyn BaseTools> = Rc::new(StubBaseTools); |
| 188 | + |
| 189 | + let (template, args) = member_sql.compile_template_sql(base_tools, security_context)?; |
| 190 | + |
| 191 | + let result = context_holder.empty_struct()?; |
| 192 | + result.set_field( |
| 193 | + "template", |
| 194 | + template_to_native(&template, context_holder.clone())?, |
| 195 | + )?; |
| 196 | + result.set_field("args", args_to_native(&args, context_holder.clone())?)?; |
| 197 | + Ok(NativeObjectHandle::new(result.into_object())) |
| 198 | +} |
| 199 | + |
| 200 | +fn compile_member_sql(cx: FunctionContext) -> JsResult<JsValue> { |
| 201 | + neon_guarded_funcion_call( |
| 202 | + cx, |
| 203 | + |context_holder: NativeContextHolder<_>, |
| 204 | + js_fn: NativeObjectHandle<_>, |
| 205 | + security_context_obj: NativeObjectHandle<_>| { |
| 206 | + compile_member_sql_inner(context_holder, js_fn, security_context_obj) |
| 207 | + }, |
| 208 | + ) |
| 209 | +} |
| 210 | + |
| 211 | +fn parse_args_names_inner<IT: InnerTypes>( |
| 212 | + context_holder: NativeContextHolder<IT>, |
| 213 | + js_fn: NativeObjectHandle<IT>, |
| 214 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 215 | + let func = js_fn.to_function()?; |
| 216 | + let names = func.args_names()?; |
| 217 | + names.to_native(context_holder) |
| 218 | +} |
| 219 | + |
| 220 | +fn parse_args_names(cx: FunctionContext) -> JsResult<JsValue> { |
| 221 | + neon_guarded_funcion_call( |
| 222 | + cx, |
| 223 | + |context_holder: NativeContextHolder<_>, js_fn: NativeObjectHandle<_>| { |
| 224 | + parse_args_names_inner(context_holder, js_fn) |
| 225 | + }, |
| 226 | + ) |
| 227 | +} |
| 228 | + |
| 229 | +fn invoke_filter_params_callback_inner<IT: InnerTypes>( |
| 230 | + context_holder: NativeContextHolder<IT>, |
| 231 | + js_fn: NativeObjectHandle<IT>, |
| 232 | + args: Vec<String>, |
| 233 | +) -> Result<NativeObjectHandle<IT>, CubeError> { |
| 234 | + let callback = NativeFilterParamsCallback::new(js_fn); |
| 235 | + let result = callback.call(&args)?; |
| 236 | + result.to_native(context_holder) |
| 237 | +} |
| 238 | + |
| 239 | +fn invoke_filter_params_callback(cx: FunctionContext) -> JsResult<JsValue> { |
| 240 | + neon_guarded_funcion_call( |
| 241 | + cx, |
| 242 | + |context_holder: NativeContextHolder<_>, |
| 243 | + js_fn: NativeObjectHandle<_>, |
| 244 | + args: Vec<String>| { |
| 245 | + invoke_filter_params_callback_inner(context_holder, js_fn, args) |
| 246 | + }, |
| 247 | + ) |
| 248 | +} |
| 249 | + |
| 250 | +pub fn register_module(cx: &mut ModuleContext) -> NeonResult<()> { |
| 251 | + cx.export_function("__testBridgeCompileMemberSql", compile_member_sql)?; |
| 252 | + cx.export_function("__testBridgeParseArgsNames", parse_args_names)?; |
| 253 | + cx.export_function( |
| 254 | + "__testBridgeInvokeFilterParamsCallback", |
| 255 | + invoke_filter_params_callback, |
| 256 | + )?; |
| 257 | + Ok(()) |
| 258 | +} |
0 commit comments