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
27 changes: 22 additions & 5 deletions packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -1287,10 +1287,22 @@
if (memberDef.addGroupByReferences) {
queryContext = { ...queryContext, dimensions: R.uniq(queryContext.dimensions.concat(memberDef.addGroupByReferences)) };
}
if (memberDef.timeShiftReferences) {
queryContext = {
...queryContext,
timeDimensions: queryContext.timeDimensions.map(td => {
if (memberDef.timeShiftReferences?.length) {
let mapFn;

if (memberDef.timeShiftReferences.length === 1 && !memberDef.timeShiftReferences[0].timeDimension) {
const timeShift = memberDef.timeShiftReferences[0];
mapFn = (td) => {
if (td.shiftInterval) {
throw new UserError(`Hierarchical time shift is not supported but was provided for '${td.dimension}'. Parent time shift is '${td.shiftInterval}' and current is '${timeShift.interval}'`);
}
return {
...td,
shiftInterval: timeShift.type === 'next' ? this.negateInterval(timeShift.interval) : timeShift.interval

Check warning on line 1301 in packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js#L1301

Added line #L1301 was not covered by tests
};
};
} else {

Check warning on line 1304 in packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js#L1304

Added line #L1304 was not covered by tests
mapFn = (td) => {
const timeShift = memberDef.timeShiftReferences.find(r => r.timeDimension === td.dimension);
if (timeShift) {
if (td.shiftInterval) {
Expand All @@ -1302,7 +1314,12 @@
};
}
return td;
})
};

Check warning on line 1317 in packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js#L1317

Added line #L1317 was not covered by tests
}

queryContext = {

Check warning on line 1320 in packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js#L1319-L1320

Added lines #L1319 - L1320 were not covered by tests
...queryContext,
timeDimensions: queryContext.timeDimensions.map(mapFn)

Check warning on line 1322 in packages/cubejs-schema-compiler/src/adapter/BaseQuery.js

View check run for this annotation

Codecov / codecov/patch

packages/cubejs-schema-compiler/src/adapter/BaseQuery.js#L1322

Added line #L1322 was not covered by tests
};
}
queryContext = {
Expand Down
13 changes: 9 additions & 4 deletions packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export type DimensionDefinition = {
};

export type TimeShiftDefinition = {
timeDimension: (...args: Array<unknown>) => ToString,
timeDimension?: (...args: Array<unknown>) => ToString,
interval: string,
type: 'next' | 'prior',
};

export type TimeShiftDefinitionReference = {
timeDimension: string,
timeDimension?: string,
interval: string,
type: 'next' | 'prior',
};
Expand Down Expand Up @@ -353,8 +353,13 @@ export class CubeEvaluator extends CubeSymbols {
member.addGroupByReferences = this.evaluateReferences(cubeName, member.addGroupBy);
}
if (member.timeShift) {
member.timeShiftReferences = member.timeShift
.map(s => ({ ...s, timeDimension: this.evaluateReferences(cubeName, s.timeDimension) }));
member.timeShiftReferences = member.timeShift.map((s): TimeShiftDefinitionReference => ({
interval: s.interval,
type: s.type,
...(typeof s.timeDimension === 'function'
? { timeDimension: this.evaluateReferences(cubeName, s.timeDimension) }
: {}),
}));
}
}
}
Expand Down
21 changes: 16 additions & 5 deletions packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,18 @@ const multiStageMeasureType = Joi.string().valid(
'rank'
);

const timeShiftItemRequired = Joi.object({
timeDimension: Joi.func().required(),
interval: regexTimeInterval.required(),
type: Joi.string().valid('next', 'prior').required(),
});

const timeShiftItemOptional = Joi.object({
timeDimension: Joi.func(), // не required
interval: regexTimeInterval.required(),
type: Joi.string().valid('next', 'prior').required(),
});

const MeasuresSchema = Joi.object().pattern(identifierRegex, Joi.alternatives().conditional(Joi.ref('.multiStage'), [
{
is: true,
Expand All @@ -574,11 +586,10 @@ const MeasuresSchema = Joi.object().pattern(identifierRegex, Joi.alternatives().
groupBy: Joi.func(),
reduceBy: Joi.func(),
addGroupBy: Joi.func(),
timeShift: Joi.array().items(Joi.object().keys({
timeDimension: Joi.func().required(),
interval: regexTimeInterval.required(),
type: Joi.string().valid('next', 'prior').required(),
})),
timeShift: Joi.alternatives().conditional(Joi.array().length(1), {
then: Joi.array().items(timeShiftItemOptional),
otherwise: Joi.array().items(timeShiftItemRequired)
}),
// TODO validate for order window functions
orderBy: Joi.array().items(Joi.object().keys({
sql: Joi.func().required(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ describe('SQL Generation', () => {
type: 'prior',
}]
},
revenue_day_ago_no_td: {
multi_stage: true,
type: 'sum',
sql: \`\${revenue}\`,
time_shift: [{
interval: '1 day',
type: 'prior',
}]
},
cagr_day: {
multi_stage: true,
sql: \`ROUND(100 * \${revenue} / NULLIF(\${revenue_day_ago}, 0))\`,
Expand Down Expand Up @@ -1347,6 +1356,26 @@ SELECT 1 AS revenue, cast('2024-01-01' AS timestamp) as time UNION ALL
{ visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__cagr_day: '300', visitors__revenue: '900', visitors__revenue_day_ago: '300' }
]));

it('CAGR (no td in time_shift)', async () => runQueryTest({
measures: [
'visitors.revenue',
'visitors.revenue_day_ago_no_td',
'visitors.cagr_day'
],
timeDimensions: [{
dimension: 'visitors.created_at',
granularity: 'day',
dateRange: ['2016-12-01', '2017-01-31']
}],
order: [{
id: 'visitors.created_at'
}],
timezone: 'America/Los_Angeles'
}, [
{ visitors__created_at_day: '2017-01-05T00:00:00.000Z', visitors__cagr_day: '150', visitors__revenue: '300', visitors__revenue_day_ago_no_td: '200' },
{ visitors__created_at_day: '2017-01-06T00:00:00.000Z', visitors__cagr_day: '300', visitors__revenue: '900', visitors__revenue_day_ago_no_td: '300' }
]));

it('sql utils', async () => runQueryTest({
measures: [
'visitors.visitor_count'
Expand Down
145 changes: 127 additions & 18 deletions packages/cubejs-schema-compiler/test/unit/cube-validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,27 +251,136 @@ describe('Cube Validation', () => {
expect(validationResult.error).toBeTruthy();
});

it('measures alternatives', async () => {
const cubeValidator = new CubeValidator(new CubeSymbols());
const cube = {
name: 'name',
sql: () => '',
fileName: 'fileName',
measures: {
number: {
type: 'suma'
describe('Measures', () => {
it('measures alternatives', async () => {
const cubeValidator = new CubeValidator(new CubeSymbols());
const cube = {
name: 'name',
sql: () => '',
fileName: 'fileName',
measures: {
number: {
type: 'suma'
}
}
}
};
};

const validationResult = cubeValidator.validate(cube, {
error: (message: any, _e: any) => {
console.log(message);
expect(message).toContain('must be one of [count, number,');
}
} as any);
const validationResult = cubeValidator.validate(cube, {
error: (message: any, _e: any) => {
console.log(message);
expect(message).toContain('must be one of [count, number,');
}
} as any);

expect(validationResult.error).toBeTruthy();
expect(validationResult.error).toBeTruthy();
});

it('2 timeShifts, 1 without timeDimension', async () => {
const cubeValidator = new CubeValidator(new CubeSymbols());
const cube = {
name: 'name',
sql: () => '',
fileName: 'fileName',
measures: {
measure_with_time_shift: {
multiStage: true,
type: 'sum',
sql: () => '',
timeShift: [
{
timeDimension: () => '',
interval: '1 day',
type: 'prior',
},
{
interval: '1 day',
type: 'prior',
}
]
}
}
};

const validationResult = cubeValidator.validate(cube, {
error: (message: any, _e: any) => {
console.log(message);
expect(message).toContain('(measures.measure_with_time_shift.timeShift[1].timeDimension) is required');
}
} as any);

expect(validationResult.error).toBeTruthy();
});

it('3 timeShifts', async () => {
const cubeValidator = new CubeValidator(new CubeSymbols());
const cube = {
name: 'name',
sql: () => '',
fileName: 'fileName',
measures: {
measure_with_time_shift: {
multiStage: true,
type: 'sum',
sql: () => '',
timeShift: [
{
timeDimension: () => '',
interval: '1 day',
type: 'prior',
},
{
timeDimension: () => '',
interval: '1 year',
type: 'next',
},
{
timeDimension: () => '',
interval: '1 week',
type: 'prior',
}
]
}
}
};

const validationResult = cubeValidator.validate(cube, {
error: (message: any, _e: any) => {
console.log(message);
}
} as any);

expect(validationResult.error).toBeFalsy();
});

it('1 timeShift without timeDimension', async () => {
const cubeValidator = new CubeValidator(new CubeSymbols());
const cube = {
name: 'name',
sql: () => '',
fileName: 'fileName',
measures: {
measure_with_time_shift: {
multiStage: true,
type: 'sum',
sql: () => '',
timeShift: [
{
interval: '1 day',
type: 'prior',
}
]
}
}
};

const validationResult = cubeValidator.validate(cube, {
error: (message: any, _e: any) => {
console.log(message);
}
} as any);

expect(validationResult.error).toBeFalsy();
});
});

it('OriginalSqlSchema', async () => {
Expand Down
Loading