Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 89 additions & 1 deletion packages/cubejs-backend-native/src/bridge_test_exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@

use cubenativeutils::wrappers::bridge_meta::{BridgeFieldKind, BridgeFieldMeta};
use cubenativeutils::wrappers::neon::neon_guarded_funcion_call;
use cubenativeutils::wrappers::object::{NativeArray, NativeFunction, NativeStruct, NativeType};
use cubenativeutils::wrappers::object::{
NativeArray, NativeFunction, NativeRustBox, NativeStruct, NativeType,
};
use cubenativeutils::wrappers::rust_handle::NativeRustHandle;
use cubenativeutils::wrappers::serializer::NativeSerialize;
use cubenativeutils::wrappers::{inner_types::InnerTypes, NativeContextHolder, NativeObjectHandle};
use cubenativeutils::CubeError;
Expand Down Expand Up @@ -906,6 +909,88 @@ fn invoke_cube_evaluator<IT: InnerTypes>(b: &NativeCubeEvaluator<IT>) -> InvokeR
r
}

#[derive(Debug)]
struct RustBoxProbe {
value: f64,
label: String,
}

/// Distinct probe used to exercise the type-mismatch branch of
/// `NativeRustHandle::downcast`: callers create an `Alt` box and try to
/// unwrap it as `RustBoxProbe`, which must fail with a message that names
/// both the source and target types.
#[derive(Debug)]
struct RustBoxProbeAlt {
#[allow(dead_code)]
note: String,
}

#[derive(serde::Serialize)]
struct RustBoxProbeView {
value: f64,
label: String,
type_name: String,
}

fn rust_box_create_inner<IT: InnerTypes>(
context: NativeContextHolder<IT>,
value: f64,
label: String,
) -> Result<NativeObjectHandle<IT>, CubeError> {
let handle = NativeRustHandle::new(RustBoxProbe { value, label });
let rust_box = context.rust_box(handle)?;
Ok(NativeObjectHandle::new(rust_box.into_object()))
}

fn rust_box_create(cx: FunctionContext) -> JsResult<JsValue> {
neon_guarded_funcion_call(
cx,
|context_holder: NativeContextHolder<_>, value: f64, label: String| {
rust_box_create_inner(context_holder, value, label)
},
)
}

fn rust_box_create_alt_inner<IT: InnerTypes>(
context: NativeContextHolder<IT>,
note: String,
) -> Result<NativeObjectHandle<IT>, CubeError> {
let handle = NativeRustHandle::new(RustBoxProbeAlt { note });
let rust_box = context.rust_box(handle)?;
Ok(NativeObjectHandle::new(rust_box.into_object()))
}

fn rust_box_create_alt(cx: FunctionContext) -> JsResult<JsValue> {
neon_guarded_funcion_call(
cx,
|context_holder: NativeContextHolder<_>, note: String| {
rust_box_create_alt_inner(context_holder, note)
},
)
}

fn rust_box_unwrap_inner<IT: InnerTypes>(
_context: NativeContextHolder<IT>,
obj: NativeObjectHandle<IT>,
) -> Result<RustBoxProbeView, CubeError> {
let rust_box = obj.into_rust_box()?;
let probe = rust_box.handle().downcast::<RustBoxProbe>()?;
Ok(RustBoxProbeView {
value: probe.value,
label: probe.label.clone(),
type_name: rust_box.handle().type_name().to_string(),
})
}

fn rust_box_unwrap(cx: FunctionContext) -> JsResult<JsValue> {
neon_guarded_funcion_call(
cx,
|context_holder: NativeContextHolder<_>, obj: NativeObjectHandle<_>| {
rust_box_unwrap_inner(context_holder, obj)
},
)
}

pub fn register_module(cx: &mut ModuleContext) -> NeonResult<()> {
cx.export_function("__testBridgeCompileMemberSql", compile_member_sql)?;
cx.export_function("__testBridgeParseArgsNames", parse_args_names)?;
Expand All @@ -917,5 +1002,8 @@ pub fn register_module(cx: &mut ModuleContext) -> NeonResult<()> {
cx.export_function("__testBridgeParse", parse_bridge)?;
cx.export_function("__testBridgeInvoke", invoke_bridge)?;
cx.export_function("__testBridgeListBridgeNames", list_bridge_names)?;
cx.export_function("__testBridgeRustBoxCreate", rust_box_create)?;
cx.export_function("__testBridgeRustBoxCreateAlt", rust_box_create_alt)?;
cx.export_function("__testBridgeRustBoxUnwrap", rust_box_unwrap)?;
Ok(())
}
18 changes: 18 additions & 0 deletions packages/cubejs-backend-native/test/bridge/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,21 @@ export function expectAllInvocationsOk(result: InvokeResult): void {
);
}
}

export interface RustBoxProbeView {
value: number;
label: string;
type_name: string;
}

export function createRustBoxProbe(value: number, label: string): unknown {
return native.__testBridgeRustBoxCreate(value, label);
}

export function createRustBoxProbeAlt(note: string): unknown {
return native.__testBridgeRustBoxCreateAlt(note);
}

export function unwrapRustBoxProbe(handle: unknown): RustBoxProbeView {
return native.__testBridgeRustBoxUnwrap(handle);
}
79 changes: 79 additions & 0 deletions packages/cubejs-backend-native/test/bridge/rust-box.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
bridgeHarnessAvailable,
createRustBoxProbe,
createRustBoxProbeAlt,
unwrapRustBoxProbe,
} from './helpers';

const describeBridge = bridgeHarnessAvailable ? describe : describe.skip;

describeBridge('bridge: NativeRustHandle / JsBox roundtrip', () => {
describe('happy path', () => {
it('returns the same value and label after a roundtrip through JS', () => {
const handle = createRustBoxProbe(42.5, 'hello');
const view = unwrapRustBoxProbe(handle);
expect(view.value).toBe(42.5);
expect(view.label).toBe('hello');
expect(view.type_name).toContain('RustBoxProbe');
});

it('keeps independent state across multiple boxes', () => {
const a = createRustBoxProbe(1, 'a');
const b = createRustBoxProbe(2, 'b');
expect(unwrapRustBoxProbe(a).value).toBe(1);
expect(unwrapRustBoxProbe(b).value).toBe(2);
expect(unwrapRustBoxProbe(a).label).toBe('a');
expect(unwrapRustBoxProbe(b).label).toBe('b');
});

it('survives many unwrap calls on the same handle', () => {
const handle = createRustBoxProbe(7, 'persist');
for (let i = 0; i < 100; i += 1) {
const view = unwrapRustBoxProbe(handle);
expect(view.value).toBe(7);
expect(view.label).toBe('persist');
}
});
});

describe('non-RustBox arguments', () => {
// RootHolder dispatch path: `is_a::<JsBox<NativeRustHandle>>` is false,
// value gets classified as some other JS type (or null/undefined),
// `.into_rust_box()` then surfaces the "Object is not a Rust box" error.
const message = /Object is not a Rust box/;

it('rejects a plain object', () => {
expect(() => unwrapRustBoxProbe({})).toThrow(message);
});

it('rejects a string', () => {
expect(() => unwrapRustBoxProbe('not a box')).toThrow(message);
});

it('rejects a number', () => {
expect(() => unwrapRustBoxProbe(123)).toThrow(message);
});

it('rejects null', () => {
expect(() => unwrapRustBoxProbe(null)).toThrow(message);
});

it('rejects undefined', () => {
expect(() => unwrapRustBoxProbe(undefined)).toThrow(message);
});
});

describe('type-mismatch on downcast', () => {
// RootHolder accepts the value (it really is JsBox<NativeRustHandle>),
// but the inner type tag does not match: NativeRustHandle::downcast
// surfaces a message naming both the stored and the requested type.
// The format is the public diagnostic contract for cache misses and
// similar lookups, so we pin it down with a regex.
it('reports source and target type names in the error', () => {
const altHandle = createRustBoxProbeAlt('payload');
expect(() => unwrapRustBoxProbe(altHandle)).toThrow(
/cannot downcast.*RustBoxProbeAlt.*RustBoxProbe(?!Alt)/
);
});
});
});
6 changes: 5 additions & 1 deletion rust/cube/cubenativeutils/src/wrappers/context.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::any::Any;

use super::FunctionArgsDef;
use super::{inner_types::InnerTypes, object_handle::NativeObjectHandle};
use super::{inner_types::InnerTypes, object_handle::NativeObjectHandle, NativeRustHandle};
use crate::wrappers::serializer::NativeSerialize;
use crate::CubeError;

Expand All @@ -13,6 +13,7 @@ pub trait NativeContext<IT: InnerTypes>: Clone {
fn null(&self) -> Result<NativeObjectHandle<IT>, CubeError>;
fn empty_array(&self) -> Result<IT::Array, CubeError>;
fn empty_struct(&self) -> Result<IT::Struct, CubeError>;
fn rust_box(&self, handle: NativeRustHandle) -> Result<IT::RustBox, CubeError>;
fn to_string_fn(&self, result: String) -> Result<IT::Function, CubeError>;
fn global(&self, name: &str) -> Result<NativeObjectHandle<IT>, CubeError>;
fn make_function<In, Rt, F: FunctionArgsDef<IT::FunctionIT, In, Rt> + 'static>(
Expand Down Expand Up @@ -85,6 +86,9 @@ impl<IT: InnerTypes> NativeContextHolder<IT> {
pub fn empty_struct(&self) -> Result<IT::Struct, CubeError> {
self.context.empty_struct()
}
pub fn rust_box(&self, handle: NativeRustHandle) -> Result<IT::RustBox, CubeError> {
self.context.rust_box(handle)
}
#[allow(dead_code)]
pub fn to_string_fn(&self, result: String) -> Result<IT::Function, CubeError> {
self.context.to_string_fn(result)
Expand Down
5 changes: 3 additions & 2 deletions rust/cube/cubenativeutils/src/wrappers/inner_types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::{
context::NativeContext,
object::{
NativeArray, NativeBoolean, NativeFunction, NativeNumber, NativeObject, NativeString,
NativeStruct,
NativeArray, NativeBoolean, NativeFunction, NativeNumber, NativeObject, NativeRustBox,
NativeString, NativeStruct,
},
};
pub trait InnerTypes: Clone + 'static {
Expand All @@ -13,6 +13,7 @@ pub trait InnerTypes: Clone + 'static {
type Boolean: NativeBoolean<Self>;
type Function: NativeFunction<Self>;
type Number: NativeNumber<Self>;
type RustBox: NativeRustBox<Self>;
type Context: NativeContext<Self>;
type FunctionIT: InnerTypes;
}
2 changes: 2 additions & 0 deletions rust/cube/cubenativeutils/src/wrappers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ pub mod neon;
pub mod object;
pub mod object_handle;
mod proxy;
pub mod rust_handle;
pub mod serializer;

pub use context::*;
pub use functions_args_def::*;
pub use object::*;
pub use object_handle::NativeObjectHandle;
pub use proxy::*;
pub use rust_handle::NativeRustHandle;
14 changes: 12 additions & 2 deletions rust/cube/cubenativeutils/src/wrappers/neon/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ use super::*;
use super::{
inner_types::NeonInnerTypes,
object::{
base_types::*, neon_array::NeonArray, neon_function::NeonFunction, neon_struct::NeonStruct,
NeonObject,
base_types::*, neon_array::NeonArray, neon_function::NeonFunction,
neon_rust_box::NeonRustBox, neon_struct::NeonStruct,
object_root_holder::ObjectNeonTypeHolder, NeonObject,
},
ContextWrapper,
};
use crate::wrappers::neon::object::IntoNeonObject;
use crate::wrappers::object::*;
use crate::wrappers::rust_handle::NativeRustHandle;
use crate::wrappers::serializer::NativeSerialize;
use crate::wrappers::{
context::NativeContext, functions_args_def::FunctionArgsDef, object::NativeObject,
Expand Down Expand Up @@ -145,6 +147,14 @@ impl<C: Context<'static> + 'static> NativeContext<NeonInnerTypes<C>> for Context
let obj = NeonObject::new(self.clone(), self.with_context(|cx| cx.empty_object())?)?;
obj.into_struct()
}
fn rust_box(&self, handle: NativeRustHandle) -> Result<NeonRustBox<C>, CubeError> {
let cached = handle.clone();
let obj_holder = self.with_context(|cx| {
let js_box = cx.boxed(handle);
ObjectNeonTypeHolder::new(self.clone(), js_box, cx)
})?;
Ok(NeonRustBox::new(obj_holder, cached))
}
fn to_string_fn(&self, result: String) -> Result<NeonFunction<C>, CubeError> {
let obj = self
.with_context(|cx| -> Result<_, CubeError> {
Expand Down
5 changes: 3 additions & 2 deletions rust/cube/cubenativeutils/src/wrappers/neon/inner_types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::{
context::ContextHolder,
object::{
base_types::*, neon_array::NeonArray, neon_function::NeonFunction, neon_struct::NeonStruct,
NeonObject,
base_types::*, neon_array::NeonArray, neon_function::NeonFunction,
neon_rust_box::NeonRustBox, neon_struct::NeonStruct, NeonObject,
},
};
use crate::wrappers::inner_types::InnerTypes;
Expand Down Expand Up @@ -30,5 +30,6 @@ impl<C: Context<'static> + 'static> InnerTypes for NeonInnerTypes<C> {
type Boolean = NeonBoolean<C>;
type Function = NeonFunction<C>;
type Number = NeonNumber<C>;
type RustBox = NeonRustBox<C>;
type FunctionIT = NeonInnerTypes<FunctionContext<'static>>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ impl<C: Context<'static>> NeonString<C> {
impl<C: Context<'static> + 'static> NativeType<NeonInnerTypes<C>> for NeonString<C> {
fn into_object(self) -> NeonObject<C> {
let root_holder = RootHolder::from_typed(self.holder);
NeonObject::form_root(root_holder)
NeonObject::from_root(root_holder)
}
}

Expand All @@ -43,7 +43,7 @@ impl<C: Context<'static>> NeonNumber<C> {
impl<C: Context<'static> + 'static> NativeType<NeonInnerTypes<C>> for NeonNumber<C> {
fn into_object(self) -> NeonObject<C> {
let root_holder = RootHolder::from_typed(self.holder);
NeonObject::form_root(root_holder)
NeonObject::from_root(root_holder)
}
}

Expand All @@ -67,7 +67,7 @@ impl<C: Context<'static>> NeonBoolean<C> {
impl<C: Context<'static> + 'static> NativeType<NeonInnerTypes<C>> for NeonBoolean<C> {
fn into_object(self) -> NeonObject<C> {
let root_holder = RootHolder::from_typed(self.holder);
NeonObject::form_root(root_holder)
NeonObject::from_root(root_holder)
}
}

Expand Down
2 changes: 2 additions & 0 deletions rust/cube/cubenativeutils/src/wrappers/neon/object/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ pub mod base_types;
pub mod neon_array;
pub mod neon_function;
pub mod neon_object;
pub mod neon_rust_box;
pub mod neon_struct;
pub mod object_root_holder;
pub mod primitive_root_holder;
pub mod root_holder;

pub use neon_object::*;
pub use neon_rust_box::NeonRustBox;
use object_root_holder::*;
use primitive_root_holder::*;
use root_holder::*;
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl<C: Context<'static>> Clone for NeonArray<C> {
impl<C: Context<'static> + 'static> NativeType<NeonInnerTypes<C>> for NeonArray<C> {
fn into_object(self) -> NeonObject<C> {
let root_holder = RootHolder::from_typed(self.object);
NeonObject::form_root(root_holder)
NeonObject::from_root(root_holder)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<C: Context<'static>> Clone for NeonFunction<C> {
impl<C: Context<'static> + 'static> NativeType<NeonInnerTypes<C>> for NeonFunction<C> {
fn into_object(self) -> NeonObject<C> {
let root_holder = RootHolder::from_typed(self.object);
NeonObject::form_root(root_holder)
NeonObject::from_root(root_holder)
}
}

Expand Down
Loading
Loading