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
1 change: 1 addition & 0 deletions packages/cubejs-schema-compiler/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ src/parser/Python3Lexer.ts
src/parser/Python3ParserListener.ts
src/parser/Python3Parser.ts
src/parser/Python3ParserVisitor.ts
test/unit/fixtures/*
14 changes: 8 additions & 6 deletions packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,19 @@ export class CubeEvaluator extends CubeSymbols {

private prepareHierarchies(cube: any, errorReporter: ErrorReporter): void {
const uniqueHierarchyNames = new Set();
if (Array.isArray(cube.hierarchies)) {
cube.evaluatedHierarchies = cube.hierarchies.map(hierarchy => {
if (uniqueHierarchyNames.has(hierarchy.name)) {
errorReporter.error(`Duplicate hierarchy name '${hierarchy.name}' in cube '${cube.name}'`);
if (Object.keys(cube.hierarchies).length) {
cube.evaluatedHierarchies = Object.entries(cube.hierarchies).map(([name, hierarchy]) => {
if (uniqueHierarchyNames.has(name)) {
errorReporter.error(`Duplicate hierarchy name '${name}' in cube '${cube.name}'`);
}
uniqueHierarchyNames.add(hierarchy.name);
uniqueHierarchyNames.add(name);

return ({
...hierarchy,
name,
...(typeof hierarchy === 'object' ? hierarchy : {}),
levels: this.evaluateReferences(
cube.name,
// @ts-ignore
hierarchy.levels,
{ originalSorting: true }
)
Expand Down
19 changes: 5 additions & 14 deletions packages/cubejs-schema-compiler/src/compiler/CubeSymbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,12 @@ export class CubeSymbols {
let hierarchies;

const cubeObject = Object.assign({
allDefinitions(type, asArray = false) {
allDefinitions(type) {
if (cubeDefinition.extends) {
if (asArray) {
return [
...super.allDefinitions(type, asArray),
...(cubeDefinition[type] || [])
];
}

return {
...super.allDefinitions(type, asArray),
...super.allDefinitions(type),
...cubeDefinition[type]
};
} else if (asArray) {
return [...(cubeDefinition[type] || [])];
// TODO We probably do not need this shallow copy
} else {
return { ...cubeDefinition[type] };
}
Expand Down Expand Up @@ -120,7 +110,7 @@ export class CubeSymbols {

get hierarchies() {
if (!hierarchies) {
hierarchies = this.allDefinitions('hierarchies', true);
hierarchies = this.allDefinitions('hierarchies');
}
return hierarchies;
},
Expand Down Expand Up @@ -152,7 +142,8 @@ export class CubeSymbols {
R.unnest,
R.map(R.toPairs),
R.filter(v => !!v)
)([cube.measures, cube.dimensions, cube.segments, cube.preAggregations]);
)([cube.measures, cube.dimensions, cube.segments, cube.preAggregations, cube.hierarchies]);

if (duplicateNames.length > 0) {
errorReporter.error(`${duplicateNames.join(', ')} defined more than once`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,12 +758,11 @@ const baseSchema = {
const cubeSchema = inherit(baseSchema, {
sql: Joi.func(),
sqlTable: Joi.func(),
hierarchies: Joi.array().items(Joi.object().keys({
name: identifier,
hierarchies: Joi.object().pattern(identifierRegex, Joi.object().keys({
title: Joi.string(),
public: Joi.boolean().strict(),
levels: Joi.func()
})),
}))
}).xor('sql', 'sqlTable').messages({
'object.xor': 'You must use either sql or sqlTable within a model, but not both'
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class YamlCompiler {
cubeObj.segments = this.yamlArrayToObj(cubeObj.segments || [], 'segment', errorsReport);
cubeObj.preAggregations = this.yamlArrayToObj(cubeObj.preAggregations || [], 'preAggregation', errorsReport);
cubeObj.joins = this.yamlArrayToObj(cubeObj.joins || [], 'join', errorsReport);
// cubeObj.hierarchies = this.yamlArrayToObj(cubeObj.hierarchies || [], 'hierarchies', errorsReport);
cubeObj.hierarchies = this.yamlArrayToObj(cubeObj.hierarchies || [], 'hierarchies', errorsReport);

return this.transpileYaml(cubeObj, [], cubeObj.name, errorsReport);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const transpiledFieldsPatterns: Array<RegExp> = [
/^contextMembers$/,
/^includes$/,
/^excludes$/,
/^hierarchies\.[0-9]+\.levels$/,
/^hierarchies\.[_a-zA-Z][_a-zA-Z0-9]*\.levels$/,
/^cubes\.[0-9]+\.(joinPath|join_path)$/,
/^(accessPolicy|access_policy)\.[0-9]+\.(rowLevel|row_level)\.filters\.[0-9]+.*\.member$/,
/^(accessPolicy|access_policy)\.[0-9]+\.(rowLevel|row_level)\.filters\.[0-9]+.*\.values$/,
Expand Down
39 changes: 39 additions & 0 deletions packages/cubejs-schema-compiler/test/unit/fixtures/orders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cube('orders', {
sql_table: 'public.orders',

dimensions: {
id: {
sql: 'id',
type: 'number',
primary_key: true,
},

status: {
sql: 'status',
type: 'string',
},

created_at: {
sql: 'created_at',
type: 'time',
},

completed_at: {
sql: 'completed_at',
type: 'time',
},
},

measures: {
count: {
type: 'count',
},
},

hierarchies: {
hello: {
title: 'World',
levels: [status],
},
},
});
94 changes: 53 additions & 41 deletions packages/cubejs-schema-compiler/test/unit/hierarchies.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';

import { prepareYamlCompiler } from './PrepareCompiler';
import { prepareCompiler, prepareYamlCompiler } from './PrepareCompiler';

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

it(('does not accept wrong name'), async () => {
const { compiler } = prepareYamlCompiler(`cubes:
- name: orders
sql_table: orders
dimensions:
- name: id
sql: id
type: number
primary_key: true

hierarchies:
- name: hello wrong name
levels:
- id
`);

await expect(compiler.compile()).rejects.toThrow('with value "hello wrong name" fails to match the identifier pattern');
});

it(('duplicated hierarchy'), async () => {
const { compiler } = prepareYamlCompiler(`cubes:
- name: orders
sql_table: orders
dimensions:
- name: id
sql: id
type: number
primary_key: true

hierarchies:
- name: test_hierarchy
levels:
- id
- name: test_hierarchy
levels:
- id
`);

await expect(compiler.compile()).rejects.toThrow('Duplicate hierarchy name \'test_hierarchy\' in cube \'orders\'');
});
// await expect(compiler.compile()).rejects.toThrow('with value "hello wrong name" fails to match the identifier pattern');
// });

// it(('duplicated hierarchy'), async () => {
// const { compiler } = prepareYamlCompiler(`cubes:
// - name: orders
// sql_table: orders
// dimensions:
// - name: id
// sql: id
// type: number
// primary_key: true

// - name: id
// sql: id
// type: number
// primary_key: true

// hierarchies:
// - name: test_hierarchy
// levels:
// - id
// - name: test_hierarchy
// levels:
// - id
// `);

// await expect(compiler.compile()).rejects.toThrow('Duplicate hierarchy name \'test_hierarchy\' in cube \'orders\'');
// });

it(('hierarchies on extended cubes'), async () => {
const modelContent = fs.readFileSync(
Expand Down Expand Up @@ -130,4 +119,27 @@ describe('Cube hierarchies', () => {
}
]);
});

it('js model base cases', async () => {
const modelContent = fs.readFileSync(
path.join(process.cwd(), '/test/unit/fixtures/orders.js'),
'utf8'
);
const { compiler, metaTransformer } = prepareCompiler(modelContent);

await compiler.compile();

const ordersCube = metaTransformer.cubes.find(
(it) => it.config.name === 'orders'
);

expect(ordersCube.config.hierarchies).toEqual([
{
name: 'orders.hello',
title: 'World',
levels: ['orders.status'],
public: true
}
]);
});
});
Loading