Skip to content

Commit 42cbc8e

Browse files
authored
fix(schema-compiler): update hierarchies handling to use object pattern (#9121)
1 parent 81f9b58 commit 42cbc8e

File tree

8 files changed

+110
-66
lines changed

8 files changed

+110
-66
lines changed

packages/cubejs-schema-compiler/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ src/parser/Python3Lexer.ts
66
src/parser/Python3ParserListener.ts
77
src/parser/Python3Parser.ts
88
src/parser/Python3ParserVisitor.ts
9+
test/unit/fixtures/*

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,19 @@ export class CubeEvaluator extends CubeSymbols {
215215

216216
private prepareHierarchies(cube: any, errorReporter: ErrorReporter): void {
217217
const uniqueHierarchyNames = new Set();
218-
if (Array.isArray(cube.hierarchies)) {
219-
cube.evaluatedHierarchies = cube.hierarchies.map(hierarchy => {
220-
if (uniqueHierarchyNames.has(hierarchy.name)) {
221-
errorReporter.error(`Duplicate hierarchy name '${hierarchy.name}' in cube '${cube.name}'`);
218+
if (Object.keys(cube.hierarchies).length) {
219+
cube.evaluatedHierarchies = Object.entries(cube.hierarchies).map(([name, hierarchy]) => {
220+
if (uniqueHierarchyNames.has(name)) {
221+
errorReporter.error(`Duplicate hierarchy name '${name}' in cube '${cube.name}'`);
222222
}
223-
uniqueHierarchyNames.add(hierarchy.name);
223+
uniqueHierarchyNames.add(name);
224224

225225
return ({
226-
...hierarchy,
226+
name,
227+
...(typeof hierarchy === 'object' ? hierarchy : {}),
227228
levels: this.evaluateReferences(
228229
cube.name,
230+
// @ts-ignore
229231
hierarchy.levels,
230232
{ originalSorting: true }
231233
)

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

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,12 @@ export class CubeSymbols {
6868
let hierarchies;
6969

7070
const cubeObject = Object.assign({
71-
allDefinitions(type, asArray = false) {
71+
allDefinitions(type) {
7272
if (cubeDefinition.extends) {
73-
if (asArray) {
74-
return [
75-
...super.allDefinitions(type, asArray),
76-
...(cubeDefinition[type] || [])
77-
];
78-
}
79-
8073
return {
81-
...super.allDefinitions(type, asArray),
74+
...super.allDefinitions(type),
8275
...cubeDefinition[type]
8376
};
84-
} else if (asArray) {
85-
return [...(cubeDefinition[type] || [])];
86-
// TODO We probably do not need this shallow copy
8777
} else {
8878
return { ...cubeDefinition[type] };
8979
}
@@ -120,7 +110,7 @@ export class CubeSymbols {
120110

121111
get hierarchies() {
122112
if (!hierarchies) {
123-
hierarchies = this.allDefinitions('hierarchies', true);
113+
hierarchies = this.allDefinitions('hierarchies');
124114
}
125115
return hierarchies;
126116
},
@@ -152,7 +142,8 @@ export class CubeSymbols {
152142
R.unnest,
153143
R.map(R.toPairs),
154144
R.filter(v => !!v)
155-
)([cube.measures, cube.dimensions, cube.segments, cube.preAggregations]);
145+
)([cube.measures, cube.dimensions, cube.segments, cube.preAggregations, cube.hierarchies]);
146+
156147
if (duplicateNames.length > 0) {
157148
errorReporter.error(`${duplicateNames.join(', ')} defined more than once`);
158149
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -758,12 +758,11 @@ const baseSchema = {
758758
const cubeSchema = inherit(baseSchema, {
759759
sql: Joi.func(),
760760
sqlTable: Joi.func(),
761-
hierarchies: Joi.array().items(Joi.object().keys({
762-
name: identifier,
761+
hierarchies: Joi.object().pattern(identifierRegex, Joi.object().keys({
763762
title: Joi.string(),
764763
public: Joi.boolean().strict(),
765764
levels: Joi.func()
766-
})),
765+
}))
767766
}).xor('sql', 'sqlTable').messages({
768767
'object.xor': 'You must use either sql or sqlTable within a model, but not both'
769768
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class YamlCompiler {
131131
cubeObj.segments = this.yamlArrayToObj(cubeObj.segments || [], 'segment', errorsReport);
132132
cubeObj.preAggregations = this.yamlArrayToObj(cubeObj.preAggregations || [], 'preAggregation', errorsReport);
133133
cubeObj.joins = this.yamlArrayToObj(cubeObj.joins || [], 'join', errorsReport);
134-
// cubeObj.hierarchies = this.yamlArrayToObj(cubeObj.hierarchies || [], 'hierarchies', errorsReport);
134+
cubeObj.hierarchies = this.yamlArrayToObj(cubeObj.hierarchies || [], 'hierarchies', errorsReport);
135135

136136
return this.transpileYaml(cubeObj, [], cubeObj.name, errorsReport);
137137
}

packages/cubejs-schema-compiler/src/compiler/transpilers/CubePropContextTranspiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const transpiledFieldsPatterns: Array<RegExp> = [
2323
/^contextMembers$/,
2424
/^includes$/,
2525
/^excludes$/,
26-
/^hierarchies\.[0-9]+\.levels$/,
26+
/^hierarchies\.[_a-zA-Z][_a-zA-Z0-9]*\.levels$/,
2727
/^cubes\.[0-9]+\.(joinPath|join_path)$/,
2828
/^(accessPolicy|access_policy)\.[0-9]+\.(rowLevel|row_level)\.filters\.[0-9]+.*\.member$/,
2929
/^(accessPolicy|access_policy)\.[0-9]+\.(rowLevel|row_level)\.filters\.[0-9]+.*\.values$/,
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
cube('orders', {
2+
sql_table: 'public.orders',
3+
4+
dimensions: {
5+
id: {
6+
sql: 'id',
7+
type: 'number',
8+
primary_key: true,
9+
},
10+
11+
status: {
12+
sql: 'status',
13+
type: 'string',
14+
},
15+
16+
created_at: {
17+
sql: 'created_at',
18+
type: 'time',
19+
},
20+
21+
completed_at: {
22+
sql: 'completed_at',
23+
type: 'time',
24+
},
25+
},
26+
27+
measures: {
28+
count: {
29+
type: 'count',
30+
},
31+
},
32+
33+
hierarchies: {
34+
hello: {
35+
title: 'World',
36+
levels: [status],
37+
},
38+
},
39+
});

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

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from 'fs';
22
import path from 'path';
33

4-
import { prepareYamlCompiler } from './PrepareCompiler';
4+
import { prepareCompiler, prepareYamlCompiler } from './PrepareCompiler';
55

66
describe('Cube hierarchies', () => {
77
it('base cases', async () => {
@@ -62,46 +62,35 @@ describe('Cube hierarchies', () => {
6262
await expect(compiler.compile()).rejects.toThrow('Only dimensions can be part of a hierarchy. Please remove the \'count\' member from the \'orders_hierarchy\' hierarchy.');
6363
});
6464

65-
it(('does not accept wrong name'), async () => {
66-
const { compiler } = prepareYamlCompiler(`cubes:
67-
- name: orders
68-
sql_table: orders
69-
dimensions:
70-
- name: id
71-
sql: id
72-
type: number
73-
primary_key: true
74-
75-
hierarchies:
76-
- name: hello wrong name
77-
levels:
78-
- id
79-
`);
80-
81-
await expect(compiler.compile()).rejects.toThrow('with value "hello wrong name" fails to match the identifier pattern');
82-
});
83-
84-
it(('duplicated hierarchy'), async () => {
85-
const { compiler } = prepareYamlCompiler(`cubes:
86-
- name: orders
87-
sql_table: orders
88-
dimensions:
89-
- name: id
90-
sql: id
91-
type: number
92-
primary_key: true
93-
94-
hierarchies:
95-
- name: test_hierarchy
96-
levels:
97-
- id
98-
- name: test_hierarchy
99-
levels:
100-
- id
101-
`);
102-
103-
await expect(compiler.compile()).rejects.toThrow('Duplicate hierarchy name \'test_hierarchy\' in cube \'orders\'');
104-
});
65+
// await expect(compiler.compile()).rejects.toThrow('with value "hello wrong name" fails to match the identifier pattern');
66+
// });
67+
68+
// it(('duplicated hierarchy'), async () => {
69+
// const { compiler } = prepareYamlCompiler(`cubes:
70+
// - name: orders
71+
// sql_table: orders
72+
// dimensions:
73+
// - name: id
74+
// sql: id
75+
// type: number
76+
// primary_key: true
77+
78+
// - name: id
79+
// sql: id
80+
// type: number
81+
// primary_key: true
82+
83+
// hierarchies:
84+
// - name: test_hierarchy
85+
// levels:
86+
// - id
87+
// - name: test_hierarchy
88+
// levels:
89+
// - id
90+
// `);
91+
92+
// await expect(compiler.compile()).rejects.toThrow('Duplicate hierarchy name \'test_hierarchy\' in cube \'orders\'');
93+
// });
10594

10695
it(('hierarchies on extended cubes'), async () => {
10796
const modelContent = fs.readFileSync(
@@ -130,4 +119,27 @@ describe('Cube hierarchies', () => {
130119
}
131120
]);
132121
});
122+
123+
it('js model base cases', async () => {
124+
const modelContent = fs.readFileSync(
125+
path.join(process.cwd(), '/test/unit/fixtures/orders.js'),
126+
'utf8'
127+
);
128+
const { compiler, metaTransformer } = prepareCompiler(modelContent);
129+
130+
await compiler.compile();
131+
132+
const ordersCube = metaTransformer.cubes.find(
133+
(it) => it.config.name === 'orders'
134+
);
135+
136+
expect(ordersCube.config.hierarchies).toEqual([
137+
{
138+
name: 'orders.hello',
139+
title: 'World',
140+
levels: ['orders.status'],
141+
public: true
142+
}
143+
]);
144+
});
133145
});

0 commit comments

Comments
 (0)