Skip to content

Commit c8f24f7

Browse files
authored
fix(schema-compiler): hierarchies on extended cubes (#9100)
1 parent 2f11d20 commit c8f24f7

File tree

6 files changed

+115
-12
lines changed

6 files changed

+115
-12
lines changed

packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts

+6-7
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,10 @@ export class CubeEvaluator extends CubeSymbols {
213213
return [];
214214
}
215215

216-
private prepareHierarchies(cube: any, errorReporter: ErrorReporter) {
216+
private prepareHierarchies(cube: any, errorReporter: ErrorReporter): void {
217217
const uniqueHierarchyNames = new Set();
218218
if (Array.isArray(cube.hierarchies)) {
219-
cube.hierarchies = cube.hierarchies.map(hierarchy => {
219+
cube.evaluatedHierarchies = cube.hierarchies.map(hierarchy => {
220220
if (uniqueHierarchyNames.has(hierarchy.name)) {
221221
errorReporter.error(`Duplicate hierarchy name '${hierarchy.name}' in cube '${cube.name}'`);
222222
}
@@ -239,7 +239,8 @@ export class CubeEvaluator extends CubeSymbols {
239239
const includedHierarchyNames = cube.includedMembers.filter(it => it.type === 'hierarchies').map(it => it.memberPath.split('.')[1]);
240240

241241
for (const cubeName of includedCubeNames) {
242-
const { hierarchies } = this.evaluatedCubes[cubeName] || {};
242+
// As views come after cubes in the list, we can safely assume that cube is already evaluated
243+
const { evaluatedHierarchies: hierarchies } = this.evaluatedCubes[cubeName] || {};
243244

244245
if (Array.isArray(hierarchies) && hierarchies.length) {
245246
const filteredHierarchies = hierarchies
@@ -264,11 +265,11 @@ export class CubeEvaluator extends CubeSymbols {
264265
})
265266
.filter(it => it.levels.length);
266267

267-
cube.hierarchies = [...(cube.hierarchies || []), ...filteredHierarchies];
268+
cube.evaluatedHierarchies = [...(cube.evaluatedHierarchies || []), ...filteredHierarchies];
268269
}
269270
}
270271

271-
cube.hierarchies = (cube.hierarchies || []).map((hierarchy) => ({
272+
cube.evaluatedHierarchies = (cube.evaluatedHierarchies || []).map((hierarchy) => ({
272273
...hierarchy,
273274
levels: hierarchy.levels.map((level) => {
274275
const member = cube.includedMembers.find(m => m.memberPath === level);
@@ -281,8 +282,6 @@ export class CubeEvaluator extends CubeSymbols {
281282
}).filter(Boolean)
282283
}));
283284
}
284-
285-
return [];
286285
}
287286

288287
private evaluateMultiStageReferences(cubeName: string, obj: { [key: string]: MeasureDefinition }) {

packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js

+25-4
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,26 @@ export class CubeSymbols {
6565
let measures;
6666
let dimensions;
6767
let segments;
68+
let hierarchies;
6869

6970
const cubeObject = Object.assign({
70-
allDefinitions(type) {
71+
allDefinitions(type, asArray = false) {
7172
if (cubeDefinition.extends) {
73+
if (asArray) {
74+
return [
75+
...super.allDefinitions(type, asArray),
76+
...(cubeDefinition[type] || [])
77+
];
78+
}
79+
7280
return {
73-
...super.allDefinitions(type),
81+
...super.allDefinitions(type, asArray),
7482
...cubeDefinition[type]
7583
};
76-
} else {
84+
} else if (asArray) {
85+
return [...(cubeDefinition[type] || [])];
7786
// TODO We probably do not need this shallow copy
87+
} else {
7888
return { ...cubeDefinition[type] };
7989
}
8090
},
@@ -107,7 +117,18 @@ export class CubeSymbols {
107117
set segments(v) {
108118
// Dont allow to modify
109119
},
110-
}, cubeDefinition);
120+
121+
get hierarchies() {
122+
if (!hierarchies) {
123+
hierarchies = this.allDefinitions('hierarchies', true);
124+
}
125+
return hierarchies;
126+
},
127+
set hierarchies(v) {
128+
//
129+
}
130+
},
131+
cubeDefinition);
111132

112133
if (cubeDefinition.extends) {
113134
const superCube = this.resolveSymbolsCall(cubeDefinition.extends, (name) => this.cubeReferenceProxy(name));

packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class CubeToMetaTransformer {
106106
})),
107107
R.toPairs
108108
)(cube.segments || {}),
109-
hierarchies: (cube.hierarchies || []).map((it) => ({
109+
hierarchies: (cube.evaluatedHierarchies || []).map((it) => ({
110110
...it,
111111
public: it.public ?? true,
112112
name: `${cube.name}.${it.name}`,

packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts

+1
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ const viewSchema = inherit(baseSchema, {
794794
})
795795
),
796796
accessPolicy: Joi.array().items(RolePolicySchema.required()),
797+
hierarchies: Joi.any()
797798
});
798799

799800
function formatErrorMessageFromDetails(explain, d) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
cubes:
2+
- name: base_orders
3+
sql_table: orders
4+
measures:
5+
- name: count
6+
sql: id
7+
type: count
8+
dimensions:
9+
- name: id
10+
sql: id
11+
type: number
12+
primary_key: true
13+
14+
- name: number
15+
sql: number
16+
type: number
17+
18+
- name: status
19+
sql: status
20+
type: string
21+
22+
- name: city
23+
sql: city
24+
type: string
25+
hierarchies:
26+
- name: base_orders_hierarchy
27+
title: Hello Hierarchy
28+
levels:
29+
- "{CUBE}.status"
30+
- number
31+
#
32+
- name: orders
33+
extends: base_orders
34+
hierarchies:
35+
- name: orders_hierarchy
36+
levels:
37+
- state
38+
- city
39+
dimensions:
40+
- name: state
41+
sql: state
42+
type: string
43+
- name: city
44+
sql: city
45+
type: string
46+
47+
views:
48+
- name: test_view
49+
cubes:
50+
- join_path: orders
51+
includes: "*"
52+
53+
54+

packages/cubejs-schema-compiler/test/unit/hierarchies.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,32 @@ describe('Cube hierarchies', () => {
102102

103103
await expect(compiler.compile()).rejects.toThrow('Duplicate hierarchy name \'test_hierarchy\' in cube \'orders\'');
104104
});
105+
106+
it(('hierarchies on extended cubes'), async () => {
107+
const modelContent = fs.readFileSync(
108+
path.join(process.cwd(), '/test/unit/fixtures/hierarchies-extended-cubes.yml'),
109+
'utf8'
110+
);
111+
const { compiler, metaTransformer } = prepareYamlCompiler(modelContent);
112+
113+
await compiler.compile();
114+
115+
const testView = metaTransformer.cubes.find(
116+
(it) => it.config.name === 'test_view'
117+
);
118+
119+
expect(testView?.config.hierarchies).toEqual([
120+
{
121+
name: 'test_view.base_orders_hierarchy',
122+
title: 'Hello Hierarchy',
123+
levels: ['test_view.status', 'test_view.number'],
124+
public: true
125+
},
126+
{
127+
name: 'test_view.orders_hierarchy',
128+
levels: ['test_view.state', 'test_view.city'],
129+
public: true
130+
}
131+
]);
132+
});
105133
});

0 commit comments

Comments
 (0)