Skip to content
Closed
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
84 changes: 78 additions & 6 deletions packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ export class CubeSymbols {
}
}

const includeMembers = this.generateIncludeMembers(cubeIncludes, type);
const includeMembers = this.generateIncludeMembers(cubeIncludes, type, cube);
this.applyIncludeMembers(includeMembers, cube, type, errorReporter);

const existing = cube.includedMembers ?? [];
Expand Down Expand Up @@ -756,7 +756,7 @@ export class CubeSymbols {
splitViewDef = splitViews[viewName];
}

const includeMembers = this.generateIncludeMembers(finalIncludes, type);
const includeMembers = this.generateIncludeMembers(finalIncludes, type, splitViewDef);
this.applyIncludeMembers(includeMembers, splitViewDef, type, errorReporter);
} else {
for (const member of finalIncludes) {
Expand Down Expand Up @@ -786,13 +786,84 @@ export class CubeSymbols {
return this.symbols[cubeName]?.cubeObj()?.[type]?.[memberName];
}

protected generateIncludeMembers(members: any[], type: string) {
protected createViewAwareDrillMemberFunction(
originalFunction: Function,
sourceCubeName: string,
targetCubeName: string,
originalDrillMembers: string[]
) {
const cubeEvaluator = this;

return function drillMemberFilter(..._args: any[]) {
// Transform source cube references to target cube references
// e.g., "Orders.id" -> "OrdersSimpleView.id"
const transformedDrillMembers = originalDrillMembers.map(member => {
const memberParts = member.split('.');
if (memberParts[0] === sourceCubeName) {
return `${targetCubeName}.${memberParts[1]}`;
}
return member; // Keep as-is if not from source cube
});

// Get the target cube to check which members actually exist
const targetCubeSymbol = cubeEvaluator.symbols[targetCubeName];
if (!targetCubeSymbol) {
return [];
}

const targetCube = targetCubeSymbol.cubeObj();
if (!targetCube) {
return [];
}

// Build set of available members in the target cube
const availableMembers = new Set<string>();
['measures', 'dimensions', 'segments'].forEach(memberType => {
if (targetCube[memberType]) {
Object.keys(targetCube[memberType]).forEach(memberName => {
availableMembers.add(`${targetCubeName}.${memberName}`);
});
}
});

// Filter drill members to only include available ones
return transformedDrillMembers.filter(member => availableMembers.has(member));
};
}

protected generateIncludeMembers(members: any[], type: string, targetCube?: any) {
return members.map(memberRef => {
const path = memberRef.member.split('.');
const resolvedMember = this.getResolvedMember(type, path[path.length - 2], path[path.length - 1]);
if (!resolvedMember) {
throw new Error(`Can't resolve '${memberRef.member}' while generating include members`);
}

// Store drill member processing info for later use in the member definition
let processedDrillMembers = resolvedMember.drillMembers;

if (type === 'measures' && resolvedMember.drillMembers && targetCube?.isView) {
const sourceCubeName = path[path.length - 2]; // e.g., "Orders"

const evaluatedDrillMembers = this.evaluateReferences(
sourceCubeName,
resolvedMember.drillMembers,
{ originalSorting: true }
);

// Ensure we have an array
const drillMembersArray = Array.isArray(evaluatedDrillMembers)
? evaluatedDrillMembers
: [evaluatedDrillMembers];

// Create a new filtered function for this view
processedDrillMembers = this.createViewAwareDrillMemberFunction(
resolvedMember.drillMembers,
sourceCubeName,
targetCube.name,
drillMembersArray
);
}

// eslint-disable-next-line no-new-func
const sql = new Function(path[0], `return \`\${${memberRef.member}}\`;`);
Expand All @@ -809,6 +880,8 @@ export class CubeSymbols {
...(resolvedMember.multiStage && { multiStage: resolvedMember.multiStage }),
...(resolvedMember.timeShift && { timeShift: resolvedMember.timeShift }),
...(resolvedMember.orderBy && { orderBy: resolvedMember.orderBy }),
...(processedDrillMembers && { drillMembers: processedDrillMembers }),
...(resolvedMember.drillMembersGrouped && { drillMembersGrouped: resolvedMember.drillMembersGrouped }),
};
} else if (type === 'dimensions') {
memberDefinition = {
Expand Down Expand Up @@ -891,8 +964,7 @@ export class CubeSymbols {
name
);
// eslint-disable-next-line no-underscore-dangle
// if (resolvedSymbol && resolvedSymbol._objectWithResolvedProperties) {
if (resolvedSymbol._objectWithResolvedProperties) {
if (resolvedSymbol?._objectWithResolvedProperties) {
return resolvedSymbol;
}
return cubeEvaluator.pathFromArray(fullPath(cubeEvaluator.joinHints(), [referencedCube, name]));
Expand Down Expand Up @@ -1002,7 +1074,7 @@ export class CubeSymbols {
cubeName,
name
);
if (resolvedSymbol._objectWithResolvedProperties) {
if (resolvedSymbol?._objectWithResolvedProperties) {
return resolvedSymbol;
}
return '';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class ErrorReporter {
if (this.rootReporter().errors.length) {
throw new CompileError(
this.rootReporter().errors.map((e) => e.message).join('\n'),
this.rootReporter().errors.map((e) => e.plainMessage).join('\n')
this.rootReporter().errors.map((e) => e.plainMessage || e.message || '').join('\n')
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ cube(\`Orders\`, {
measures: {
count: {
type: \`count\`,
//drillMembers: [id, createdAt]
drillMembers: [id, createdAt]
},

runningTotal: {
Expand Down Expand Up @@ -255,6 +255,13 @@ view(\`OrdersView3\`, {
split: true
}]
});

view(\`OrdersSimpleView\`, {
cubes: [{
join_path: Orders,
includes: ['createdAt', 'count']
}]
});
Comment on lines +258 to +264
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The important part is that id is a drill member for count, but it is not included.

`);

async function runQueryTest(q: any, expectedResult: any, additionalTest?: (query: BaseQuery) => any) {
Expand Down Expand Up @@ -429,4 +436,71 @@ view(\`OrdersView3\`, {
orders_view3__count: '2',
orders_view3__product_categories__name: 'Groceries',
}]));

it('check drillMembers are inherited in views', async () => {
await compiler.compile();
const cube = metaTransformer.cubes.find(c => c.config.name === 'OrdersView');
const countMeasure = cube.config.measures.find((m) => m.name === 'OrdersView.count');
expect(countMeasure.drillMembers).toEqual(['OrdersView.id', 'OrdersView.createdAt']);
expect(countMeasure.drillMembersGrouped).toEqual({
measures: [],
dimensions: ['OrdersView.id', 'OrdersView.createdAt']
});
});

it('verify drill member inheritance functionality', async () => {
await compiler.compile();

// Check that the source Orders cube has drill members
const sourceOrdersCube = metaTransformer.cubes.find(c => c.config.name === 'Orders');
const sourceCountMeasure = sourceOrdersCube.config.measures.find((m) => m.name === 'Orders.count');
expect(sourceCountMeasure.drillMembers).toEqual(['Orders.id', 'Orders.createdAt']);

// Check that the OrdersView cube inherits these drill members with correct naming
const viewCube = metaTransformer.cubes.find(c => c.config.name === 'OrdersView');
const viewCountMeasure = viewCube.config.measures.find((m) => m.name === 'OrdersView.count');

// Before our fix, this would have been undefined or empty
// After our fix, drill members are properly inherited and renamed to use the view naming
expect(viewCountMeasure.drillMembers).toBeDefined();
expect(Array.isArray(viewCountMeasure.drillMembers)).toBe(true);
expect(viewCountMeasure.drillMembers.length).toBeGreaterThan(0);
expect(viewCountMeasure.drillMembers).toContain('OrdersView.id');
expect(viewCountMeasure.drillMembersGrouped).toBeDefined();
});

it('check drill member inheritance with limited includes in OrdersSimpleView', async () => {
await compiler.compile();
const cube = metaTransformer.cubes.find(c => c.config.name === 'OrdersSimpleView');

if (!cube) {
throw new Error('OrdersSimpleView not found in compiled cubes');
}

const countMeasure = cube.config.measures.find((m) => m.name === 'OrdersSimpleView.count');

if (!countMeasure) {
throw new Error('OrdersSimpleView.count measure not found');
}

// Check what dimensions are actually available in this limited view
const availableDimensions = cube.config.dimensions?.map(d => d.name) || [];

// This view only includes ['id', 'createdAt', 'count'] - should have both id and createdAt
expect(availableDimensions).not.toContain('OrdersSimpleView.id');
expect(availableDimensions).toContain('OrdersSimpleView.createdAt');

// The source measure has drillMembers: ['Orders.id', 'Orders.createdAt']
// Both should be available in this view since we explicitly included them
expect(countMeasure.drillMembers).toBeDefined();
expect(Array.isArray(countMeasure.drillMembers)).toBe(true);
expect(countMeasure.drillMembers.length).toBeGreaterThan(0);

// Verify drill members are inherited and correctly transformed to use View naming
expect(countMeasure.drillMembers).toEqual(['OrdersSimpleView.createdAt']);
expect(countMeasure.drillMembersGrouped).toEqual({
measures: [],
dimensions: ['OrdersSimpleView.createdAt']
});
});
Comment on lines +500 to +505
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we test that id is not in the drill members.

});
Loading