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
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class BaseDimension {

public expressionPath(): string {
if (this.expression) {
return `expr:${this.expression.expressionName}`;
return `expr:${this.expressionName}`;
}
return this.query.cubeEvaluator.pathFromArray(this.path() as string[]);
}
Expand Down
103 changes: 63 additions & 40 deletions packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,27 @@ export class BaseQuery {
const multiStageMembers = R.uniq(
this.allMembersConcat(false)
// TODO boolean logic filter support
.filter(m => m.expressionPath && hasMultiStageMembers(m.expressionPath()))
.map(m => m.expressionPath())
.reduce((acc, m) => {
if (m.isMemberExpression) {
let refMemberPath;
this.evaluateSql(m.cube().name, m.definition().sql, {
sqlResolveFn: (_symbol, cube, prop) => {
const path = this.cubeEvaluator.pathFromArray([cube, prop]);
refMemberPath = path;
// We don't need real SQL here, so just returning something.
return path;
}
});

if (hasMultiStageMembers(refMemberPath)) {
acc.push(refMemberPath);
}
} else if (m.expressionPath && hasMultiStageMembers(m.expressionPath())) {
acc.push(m.expressionPath());
}

return acc;
}, [])
).map(m => this.multiStageWithQueries(
m,
{
Expand All @@ -1312,7 +1331,8 @@ export class BaseQuery {
timeDimensions: this.options.timeDimensions || [],
multiStageTimeDimensions: (this.options.timeDimensions || []).filter(td => !!td.granularity),
// TODO accessing filters directly from options might miss some processing logic
filters: this.options.filters || []
filters: this.options.filters || [],
segments: this.options.segments || [],
},
allMemberChildren,
withQueries
Expand Down Expand Up @@ -1426,46 +1446,27 @@ export class BaseQuery {
queryContext = { ...queryContext, dimensions: R.uniq(queryContext.dimensions.concat(memberDef.addGroupByReferences)) };
}
if (memberDef.timeShiftReferences?.length) {
let mapFn;

const allBackAliasMembers = this.allBackAliasTimeDimensions();
let { commonTimeShift } = queryContext;
const timeShifts = queryContext.timeShifts || {};
const memberOfCube = !this.cubeEvaluator.cubeFromPath(memberPath).isView;

if (memberDef.timeShiftReferences.length === 1 && !memberDef.timeShiftReferences[0].timeDimension) {
const timeShift = memberDef.timeShiftReferences[0];
mapFn = (td) => {
// We need to ignore aliased td, because it will match and insert shiftInterval on first
// occurrence, but later during recursion it will hit the original td but shiftInterval will be
// present and simple check for td.shiftInterval will always result in error.
if (td.shiftInterval && !td.dimension === allBackAliasMembers[timeShift.timeDimension]) {
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
};
};
} else {
mapFn = (td) => {
const timeShift = memberDef.timeShiftReferences.find(r => r.timeDimension === td.dimension || td.dimension === allBackAliasMembers[r.timeDimension]);
if (timeShift) {
// We need to ignore aliased td, because it will match and insert shiftInterval on first
// occurrence, but later during recursion it will hit the original td but shiftInterval will be
// present and simple check for td.shiftInterval will always result in error.
if (td.shiftInterval && !td.dimension === allBackAliasMembers[timeShift.timeDimension]) {
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
};
}
return td;
};
// We avoid view's timeshift evaluation as there will be another round of underlying cube's member evaluation
if (memberOfCube) {
commonTimeShift = timeShift.type === 'next' ? this.negateInterval(timeShift.interval) : timeShift.interval;
}
} else if (memberOfCube) {
// We avoid view's timeshift evaluation as there will be another round of underlying cube's member evaluation
memberDef.timeShiftReferences.forEach((r) => {
timeShifts[r.timeDimension] = r.type === 'next' ? this.negateInterval(r.interval) : r.interval;
});
}

queryContext = {
...queryContext,
timeDimensions: queryContext.timeDimensions.map(mapFn)
commonTimeShift,
timeShifts,
};
}
queryContext = {
Expand Down Expand Up @@ -1508,11 +1509,15 @@ export class BaseQuery {
...queryContext,
// TODO make it same way as keepFilters
timeDimensions: queryContext.timeDimensions.map(td => ({ ...td, dateRange: undefined })),
// TODO keep segments related to this multistage (if applicable)
segments: [],
filters: this.keepFilters(queryContext.filters, filterMember => filterMember === memberPath),
};
} else {
queryContext = {
...queryContext,
// TODO remove not related segments
// segments: queryContext.segments,
filters: this.keepFilters(queryContext.filters, filterMember => !this.memberInstanceByPath(filterMember).isMultiStage()),
};
}
Expand All @@ -1531,12 +1536,16 @@ export class BaseQuery {
return [m, measure.aliasName()];
}).concat(from.dimensions.map(m => {
const member = this.newDimension(m);
return [m, member.aliasName()];
// In case of request coming from the SQL API, member could be expression-based
const mPath = typeof m === 'string' ? m : this.cubeEvaluator.pathFromArray([m.cubeName, m.name]);
return [mPath, member.aliasName()];
})).concat(from.timeDimensions.map(m => {
const member = this.newTimeDimension(m);
return member.granularity ? [`${member.dimension}.${member.granularity}`, member.aliasName()] : [];
}))))
)
),
commonTimeShift: withQuery.commonTimeShift,
timeShifts: withQuery.timeShifts,
};

const fromSubQuery = fromMeasures && this.newSubQuery({
Expand Down Expand Up @@ -1568,6 +1577,7 @@ export class BaseQuery {
multiStageDimensions: withQuery.multiStageDimensions,
multiStageTimeDimensions: withQuery.multiStageTimeDimensions,
filters: withQuery.filters,
segments: withQuery.segments,
from: fromSql && {
sql: fromSql,
alias: `${withQuery.alias}_join`,
Expand Down Expand Up @@ -2874,10 +2884,23 @@ export class BaseQuery {
return td.dimensionSql();
} else {
let res = this.autoPrefixAndEvaluateSql(cubeName, symbol.sql, isMemberExpr);
const memPath = this.cubeEvaluator.pathFromArray([cubeName, name]);

if (symbol.shiftInterval) {
res = `(${this.addTimestampInterval(res, symbol.shiftInterval)})`;
// Skip view's member evaluation as there will be underlying cube's same member evaluation
if (symbol.type === 'time' && !this.cubeEvaluator.cubeFromPath(memPath).isView) {
if (this.safeEvaluateSymbolContext().timeShifts?.[memPath]) {
if (symbol.shiftInterval) {
throw new UserError(`Hierarchical time shift is not supported but was provided for '${memPath}'. Parent time shift is '${symbol.shiftInterval}' and current is '${this.safeEvaluateSymbolContext().timeShifts?.[memPath]}'`);
}
res = `(${this.addTimestampInterval(res, this.safeEvaluateSymbolContext().timeShifts?.[memPath])})`;
} else if (this.safeEvaluateSymbolContext().commonTimeShift) {
if (symbol.shiftInterval) {
throw new UserError(`Hierarchical time shift is not supported but was provided for '${memPath}'. Parent time shift is '${symbol.shiftInterval}' and current is '${this.safeEvaluateSymbolContext().commonTimeShift}'`);
}
res = `(${this.addTimestampInterval(res, this.safeEvaluateSymbolContext().commonTimeShift)})`;
}
}

if (this.safeEvaluateSymbolContext().convertTzForRawTimeDimension &&
!this.safeEvaluateSymbolContext().ignoreConvertTzForTimeDimension &&
!memberExpressionType &&
Expand Down
10 changes: 10 additions & 0 deletions packages/cubejs-testing-drivers/fixtures/_schemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@
"sql": "quantity",
"type": "sum"
},
{
"name": "totalQuantityPriorMonth",
"multiStage": true,
"sql": "{totalQuantity}",
"type": "number",
"timeShift": [{
"interval": "1 month",
"type": "prior"
}]
},
{
"name": "avgDiscount",
"sql": "discount",
Expand Down
14 changes: 14 additions & 0 deletions packages/cubejs-testing-drivers/src/tests/testQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2025,5 +2025,19 @@ from
`);
expect(res.rows).toMatchSnapshot('nulls_first_last_sql_push_down');
});

executePg('SQL API: Timeshift measure from cube', async (connection) => {
const res = await connection.query(`
SELECT
DATE_TRUNC('month', orderDate) AS "orderDate",
MEASURE(totalQuantity) AS "totalQuantity",
MEASURE(totalQuantityPriorMonth) AS "totalQuantityPriorMonth"
FROM "ECommerce"
WHERE orderDate >= CAST('2020-01-01' AS DATE) AND orderDate < CAST('2021-01-01' AS DATE)
GROUP BY 1
ORDER BY 1 ASC NULLS FIRST;
`);
expect(res.rows).toMatchSnapshot();
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,51 @@ Array [
]
`;

exports[`Queries with the @cubejs-backend/athena-driver SQL API: Timeshift measure from cube 1`] = `
Array [
Object {
"orderDate": 2020-02-01T00:00:00.000Z,
"totalQuantity": 2,
"totalQuantityPriorMonth": 6,
},
Object {
"orderDate": 2020-03-01T00:00:00.000Z,
"totalQuantity": 13,
"totalQuantityPriorMonth": 2,
},
Object {
"orderDate": 2020-04-01T00:00:00.000Z,
"totalQuantity": 3,
"totalQuantityPriorMonth": 13,
},
Object {
"orderDate": 2020-05-01T00:00:00.000Z,
"totalQuantity": 15,
"totalQuantityPriorMonth": 3,
},
Object {
"orderDate": 2020-06-01T00:00:00.000Z,
"totalQuantity": 18,
"totalQuantityPriorMonth": 15,
},
Object {
"orderDate": 2020-10-01T00:00:00.000Z,
"totalQuantity": 11,
"totalQuantityPriorMonth": 27,
},
Object {
"orderDate": 2020-11-01T00:00:00.000Z,
"totalQuantity": 43,
"totalQuantityPriorMonth": 11,
},
Object {
"orderDate": 2020-12-01T00:00:00.000Z,
"totalQuantity": 22,
"totalQuantityPriorMonth": 43,
},
]
`;

exports[`Queries with the @cubejs-backend/athena-driver SQL API: metabase count cast to float32 from push down: metabase_count_cast_to_float32_from_push_down 1`] = `
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3226,6 +3226,51 @@ Array [
]
`;

exports[`Queries with the @cubejs-backend/bigquery-driver SQL API: Timeshift measure from cube 1`] = `
Array [
Object {
"orderDate": 2020-02-01T00:00:00.000Z,
"totalQuantity": 2,
"totalQuantityPriorMonth": 6,
},
Object {
"orderDate": 2020-03-01T00:00:00.000Z,
"totalQuantity": 13,
"totalQuantityPriorMonth": 2,
},
Object {
"orderDate": 2020-04-01T00:00:00.000Z,
"totalQuantity": 3,
"totalQuantityPriorMonth": 13,
},
Object {
"orderDate": 2020-05-01T00:00:00.000Z,
"totalQuantity": 15,
"totalQuantityPriorMonth": 3,
},
Object {
"orderDate": 2020-06-01T00:00:00.000Z,
"totalQuantity": 18,
"totalQuantityPriorMonth": 15,
},
Object {
"orderDate": 2020-10-01T00:00:00.000Z,
"totalQuantity": 11,
"totalQuantityPriorMonth": 27,
},
Object {
"orderDate": 2020-11-01T00:00:00.000Z,
"totalQuantity": 43,
"totalQuantityPriorMonth": 11,
},
Object {
"orderDate": 2020-12-01T00:00:00.000Z,
"totalQuantity": 22,
"totalQuantityPriorMonth": 43,
},
]
`;

exports[`Queries with the @cubejs-backend/bigquery-driver SQL API: metabase count cast to float32 from push down: metabase_count_cast_to_float32_from_push_down 1`] = `
Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1620,6 +1620,51 @@ Array [
]
`;

exports[`Queries with the @cubejs-backend/clickhouse-driver export-bucket-s3 SQL API: Timeshift measure from cube 1`] = `
Array [
Object {
"orderDate": 2020-02-01T00:00:00.000Z,
"totalQuantity": 2,
"totalQuantityPriorMonth": 6,
},
Object {
"orderDate": 2020-03-01T00:00:00.000Z,
"totalQuantity": 13,
"totalQuantityPriorMonth": 2,
},
Object {
"orderDate": 2020-04-01T00:00:00.000Z,
"totalQuantity": 3,
"totalQuantityPriorMonth": 13,
},
Object {
"orderDate": 2020-05-01T00:00:00.000Z,
"totalQuantity": 15,
"totalQuantityPriorMonth": 3,
},
Object {
"orderDate": 2020-06-01T00:00:00.000Z,
"totalQuantity": 18,
"totalQuantityPriorMonth": 15,
},
Object {
"orderDate": 2020-10-01T00:00:00.000Z,
"totalQuantity": 11,
"totalQuantityPriorMonth": 27,
},
Object {
"orderDate": 2020-11-01T00:00:00.000Z,
"totalQuantity": 43,
"totalQuantityPriorMonth": 11,
},
Object {
"orderDate": 2020-12-01T00:00:00.000Z,
"totalQuantity": 22,
"totalQuantityPriorMonth": 43,
},
]
`;

exports[`Queries with the @cubejs-backend/clickhouse-driver export-bucket-s3 SQL API: metabase count cast to float32 from push down: metabase_count_cast_to_float32_from_push_down 1`] = `
Array [
Object {
Expand Down
Loading
Loading