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
650 changes: 637 additions & 13 deletions packages/cubejs-backend-native/src/bridge_test_exports.rs

Large diffs are not rendered by default.

31 changes: 7 additions & 24 deletions packages/cubejs-backend-native/test/bridge/args-names.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,51 +29,34 @@ describeBridge('bridge: args_names parser', () => {
});
});

// The skipped tests below assert how a correct parser should behave.
// They are buggy today because both this bridge and the JS-side
// schema-compiler use the same regex; a fix must touch both.
describe('known bugs (skip = expected behavior after fix)', () => {
// The named-function branch of the regex captures [A-Za-z0-9_,]* —
// no whitespace allowed. V8 renders `function f(x, y)` with a space
// after the comma, so capture stops after the first arg.
it.skip('parses named function declaration with multiple args', () => {
describe('edge cases', () => {
it('parses named function declaration with multiple args', () => {
function named(x: any, y: any) {
return [x, y];
}
expect(parseArgsNames(named)).toEqual(['x', 'y']);
});

it.skip('parses async named function declaration with multiple args', () => {
it('parses async named function declaration with multiple args', () => {
async function named(x: any, y: any) {
return [x, y];
}
expect(parseArgsNames(named)).toEqual(['x', 'y']);
});

// Default args land inside the (...) capture as a single token "x = 1"
// and survive the comma split unchanged. Special names like
// SECURITY_CONTEXT in this position fail to dispatch.
it.skip('parses default args, returning just the identifier', () => {
it('parses default args, returning just the identifier', () => {
expect(parseArgsNames((x: any = 1) => x)).toEqual(['x']);
});

// Rest args keep their leading dots after capture+split, yielding
// "...args" instead of "args".
it.skip('parses rest args, dropping the spread dots', () => {
it('parses rest args, dropping the spread dots', () => {
expect(parseArgsNames((...args: any[]) => args)).toEqual(['args']);
});

// Destructuring patterns get split by the comma inside the braces,
// breaking the pattern into half-tokens.
it.skip('parses destructuring args, returning the destructured identifiers', () => {
it('parses destructuring args, returning the destructured identifiers', () => {
expect(parseArgsNames(({ a, b }: any) => [a, b])).toEqual(['a', 'b']);
});

// Anonymous function expressions (`function (x){}`) match no branch
// of the regex at all. Today Rust silently returns []; the JS side
// throws `Can't match args for: ...`. Neither is the desired
// behavior — the parser should just return the args.
it.skip('parses anonymous function expressions', () => {
it('parses anonymous function expressions', () => {
// eslint-disable-next-line func-names, prefer-arrow-callback
const fn = function (x: any) { return x; };
expect(parseArgsNames(fn)).toEqual(['x']);
Expand Down
273 changes: 273 additions & 0 deletions packages/cubejs-backend-native/test/bridge/bridge-fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// Reference JS-side shapes for every macro-generated bridge.
//
// Each factory returns a fresh value that must satisfy:
// 1. `NativeX::try_new` — required trait fields and required serde-static
// fields must be present with the right types.
// 2. Per-bridge invoke dispatcher in bridge_test_exports.rs — every
// `field` getter must deserialize successfully, and every `call` method
// stub must return a value that marshals back into the declared Rust
// return type.
//
// Treat these factories as the executable contract documenting what
// schema-compiler and friends are expected to hand to Tesseract for each
// bridge. If a Rust trait gains a new method, the bridge_registry guard
// will fire from the Rust side; if a JS shape stops matching, the invoke
// check fires here.
//
// Keys are JS-side identifiers (post-`#[serde(rename)]`, camelCase for trait
// methods that the macro auto-converts).

/* eslint-disable @typescript-eslint/no-empty-function */

export const memberSqlFn = (): unknown => () => 'sql';

export const filterGroupFixture = (): unknown => ({});
export const filterParamsFixture = (): unknown => ({});
export const securityContextFixture = (): unknown => ({});
export const sqlUtilsFixture = (): unknown => ({});
export const preAggregationObjFixture = (): unknown => ({});

export const geoItemFixture = (): unknown => ({
sql: memberSqlFn(),
});

export const structWithSqlMemberFixture = (): unknown => ({
sql: memberSqlFn(),
});

// CaseElseItem.label -> StringOrSql; deserializer tries String first.
export const caseElseItemFixture = (): unknown => ({
label: 'else',
});

export const caseItemFixture = (): unknown => ({
sql: memberSqlFn(),
label: 'when_label',
});

export const caseDefinitionFixture = (): unknown => ({
when: [caseItemFixture()],
// CaseDefinition.else_label is renamed to "else" via #[nbridge(rename = "else")].
else: caseElseItemFixture(),
});

export const caseSwitchElseItemFixture = (): unknown => ({
sql: memberSqlFn(),
});

export const caseSwitchItemFixture = (): unknown => ({
value: 'v',
sql: memberSqlFn(),
});

export const caseSwitchDefinitionFixture = (): unknown => ({
switch: memberSqlFn(),
when: [caseSwitchItemFixture()],
// CaseSwitchDefinition.else_sql is renamed to "else" via #[nbridge(rename = "else")].
else: caseSwitchElseItemFixture(),
});

export const memberOrderByFixture = (): unknown => ({
sql: memberSqlFn(),
dir: 'asc',
});

export const memberDefinitionFixture = (): unknown => ({
type: 'dimension',
// sql is optional
});

export const segmentDefinitionFixture = (): unknown => ({
sql: memberSqlFn(),
// segment_type, owned_by_cube optional
});

export const joinItemDefinitionFixture = (): unknown => ({
relationship: 'many_to_one',
sql: memberSqlFn(),
});

export const joinItemFixture = (): unknown => ({
from: 'orders',
to: 'users',
originalFrom: 'orders',
originalTo: 'users',
join: joinItemDefinitionFixture(),
});

export const joinDefinitionFixture = (): unknown => ({
root: 'orders',
multiplicationFactor: {},
joins: [joinItemFixture()],
});

export const joinGraphFixture = (): unknown => ({
buildJoin: () => joinDefinitionFixture(),
});

export const granularityDefinitionFixture = (): unknown => ({
interval: '1 day',
// origin, offset optional
sql: memberSqlFn(),
});

export const timeShiftDefinitionFixture = (): unknown => ({
// all static optional, sql optional
sql: memberSqlFn(),
interval: '1 day',
type: 'prior',
name: 'last_day',
});

export const preAggregationTimeDimensionFixture = (): unknown => ({
granularity: 'day',
dimension: memberSqlFn(),
});

export const preAggregationDescriptionFixture = (): unknown => ({
name: 'main',
type: 'rollup',
// granularity, sqlAlias, external, allowNonStrictDateRangeMatch optional
// measure_references, dimension_references, etc — all optional getters
});

export const cubeDefinitionFixture = (): unknown => ({
name: 'Orders',
// sqlAlias, isView, isCalendar, joinMap optional
// sql_table, sql optional getters
});

export const dimensionDefinitionFixture = (): unknown => ({
type: 'string',
// owned_by_cube, multi_stage, etc. — all optional
// sql/case/latitude/longitude/time_shift/mask_sql — all optional getters
});

export const measureDefinitionFixture = (): unknown => ({
type: 'count',
// owned_by_cube, multi_stage, reduce_by_references, etc. — all optional
// sql/case/filters/drill_filters/order_by/mask_sql — all optional getters
});

export const expressionStructFixture = (): unknown => ({
type: 'PatchMeasure',
// sourceMeasure, replaceAggregationType, addFilters — all optional
});

export const memberExpressionDefinitionFixture = (): unknown => ({
// expressionName, name, cubeName, definition — all optional
// expression — required, MemberExpressionExpressionDef tries MemberSql first
expression: memberSqlFn(),
});

// CubeEvaluator: every method is a `call`. Each stub must return a value
// that marshals into the declared Rust return type. The cascade is real —
// measureByPath has to hand back something that NativeMeasureDefinition
// can wrap, and so on.
export const cubeEvaluatorFixture = (): unknown => ({
primaryKeys: {},
parsePath: () => [],
measureByPath: () => measureDefinitionFixture(),
dimensionByPath: () => dimensionDefinitionFixture(),
segmentByPath: () => segmentDefinitionFixture(),
cubeFromPath: () => cubeDefinitionFixture(),
isMeasure: () => false,
isDimension: () => false,
isSegment: () => false,
cubeExists: () => false,
resolveGranularity: () => granularityDefinitionFixture(),
preAggregationsForCubeAsArray: () => [preAggregationDescriptionFixture()],
preAggregationDescriptionByName: () => preAggregationDescriptionFixture(),
// evaluate_rollup_references is invoke-skipped on the Rust side because
// its `Rc<dyn MemberSql>` argument has no auto-default, but the JS object
// still needs the key for try_new's has_field check.
evaluateRollupReferences: () => [],
});

export const driverToolsFixture = (): unknown => ({
convertTz: () => 'tz',
timeGroupedColumn: () => 'col',
sqlTemplates: () => ({}),
timestampPrecision: () => 6,
timeStampCast: () => 'ts',
dateTimeCast: () => 'dt',
inDbTimeZone: () => 'tz',
getAllocatedParams: () => [],
subtractInterval: () => 'd',
addInterval: () => 'd',
intervalString: () => 's',
addTimestampInterval: () => 'd',
intervalAndMinimalTimeUnit: () => ['1', 'day'],
hllInit: () => 'h',
hllMerge: () => 'h',
hllCardinalityMerge: () => 'h',
countDistinctApprox: () => 'c',
supportGeneratedSeriesForCustomTd: () => false,
dateBin: () => 'b',
});

export const baseToolsFixture = (): unknown => ({
driverTools: () => driverToolsFixture(),
sqlTemplates: () => ({}),
sqlUtilsForRust: () => sqlUtilsFixture(),
generateTimeSeries: () => [],
generateCustomTimeSeries: () => [],
getAllocatedParams: () => [],
allCubeMembers: () => [],
intervalAndMinimalTimeUnit: () => ['1', 'day'],
getPreAggregationByName: () => preAggregationObjFixture(),
preAggregationTableName: () => 'pre_aggr_table',
joinTreeForHints: () => joinDefinitionFixture(),
});

export const baseQueryOptionsFixture = (): unknown => ({
// Static fields
exportAnnotatedSql: false,
disableExternalPreAggregations: false,
// Optional static — omitted intentionally; serde fills None.
//
// Trait fields (all `field, optional, vec` except the four required ones)
cubeEvaluator: cubeEvaluatorFixture(),
baseTools: baseToolsFixture(),
joinGraph: joinGraphFixture(),
securityContext: securityContextFixture(),
// Optional vec fields can be omitted
});

export type BridgeFixtureFactory = () => unknown;

export const FIXTURES: Record<string, BridgeFixtureFactory> = {
baseQueryOptions: baseQueryOptionsFixture,
baseTools: baseToolsFixture,
caseDefinition: caseDefinitionFixture,
caseElseItem: caseElseItemFixture,
caseItem: caseItemFixture,
caseSwitchDefinition: caseSwitchDefinitionFixture,
caseSwitchElseItem: caseSwitchElseItemFixture,
caseSwitchItem: caseSwitchItemFixture,
cubeDefinition: cubeDefinitionFixture,
cubeEvaluator: cubeEvaluatorFixture,
dimensionDefinition: dimensionDefinitionFixture,
driverTools: driverToolsFixture,
expressionStruct: expressionStructFixture,
filterGroup: filterGroupFixture,
filterParams: filterParamsFixture,
geoItem: geoItemFixture,
granularityDefinition: granularityDefinitionFixture,
joinDefinition: joinDefinitionFixture,
joinGraph: joinGraphFixture,
joinItem: joinItemFixture,
joinItemDefinition: joinItemDefinitionFixture,
measureDefinition: measureDefinitionFixture,
memberDefinition: memberDefinitionFixture,
memberExpressionDefinition: memberExpressionDefinitionFixture,
memberOrderBy: memberOrderByFixture,
preAggregationDescription: preAggregationDescriptionFixture,
preAggregationObj: preAggregationObjFixture,
preAggregationTimeDimension: preAggregationTimeDimensionFixture,
securityContext: securityContextFixture,
segmentDefinition: segmentDefinitionFixture,
sqlUtils: sqlUtilsFixture,
structWithSqlMember: structWithSqlMemberFixture,
timeShiftDefinition: timeShiftDefinitionFixture,
};
Loading
Loading