Skip to content

Commit f2733fe

Browse files
igorlukaninKSDaemon
authored andcommitted
feat(schema-compiler): Custom granularities support for Amazon Athena/Presto dialect (cube-js#9472)
* Add docs * code hygiene * feat(schema-compiler): Custom granularities support for AWS Athena/Presto dialect --------- Co-authored-by: Konstantin Burkalev <[email protected]>
1 parent 9e266bf commit f2733fe

File tree

7 files changed

+577
-14
lines changed

7 files changed

+577
-14
lines changed

docs/pages/reference/data-model/dimensions.mdx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,14 @@ examples.
618618

619619
</ReferenceBox>
620620

621+
<WarningBox>
622+
623+
Custom granularities are supported for the following [data sources][ref-data-sources]:
624+
Amazon Athena, Amazon Redshift, DuckDB, Databricks, Google BigQuery, ClickHouse, Microsoft SQL Server, MySQL, Postgres, and Snowflake.
625+
Please [file an issue](https://github.com/cube-js/cube/issues) if you need support for another data source.
626+
627+
</WarningBox>
628+
621629
For each custom granularity, the `interval` parameter is required. It specifies
622630
the duration of the time interval and has the following format:
623631
`quantity unit [quantity unit...]`, e.g., `5 days` or `1 year 6 months`.
@@ -713,4 +721,5 @@ cube(`orders`, {
713721
[ref-time-dimensions]: /reference/data-model/types-and-formats#time-1
714722
[link-date-time-string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
715723
[ref-custom-granularity-recipe]: /guides/recipes/data-modeling/custom-granularity
716-
[ref-ref-hierarchies]: /reference/data-model/hierarchies
724+
[ref-ref-hierarchies]: /reference/data-model/hierarchies
725+
[ref-data-sources]: /product/configuration/data-sources

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3144,7 +3144,7 @@ export class BaseQuery {
31443144
*/
31453145
// eslint-disable-next-line @typescript-eslint/no-unused-vars
31463146
dateBin(interval, source, origin) {
3147-
throw new Error('Date bin function is not implemented');
3147+
throw new Error('Date bin function, required for custom time dimension granularities, is not implemented for this data source');
31483148
// Different syntax possible in different DBs
31493149
}
31503150

packages/cubejs-schema-compiler/src/adapter/PreAggregations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export class PreAggregations {
157157
const tableName = this.preAggregationTableName(cube, preAggregationName, preAggregation);
158158
const invalidateKeyQueries = this.query.preAggregationInvalidateKeyQueries(cube, preAggregation, preAggregationName);
159159
const queryForSqlEvaluation = this.query.preAggregationQueryForSqlEvaluation(cube, preAggregation);
160-
const partitionInvalidateKeyQueries = queryForSqlEvaluation.partitionInvalidateKeyQueries && queryForSqlEvaluation.partitionInvalidateKeyQueries(cube, preAggregation);
160+
const partitionInvalidateKeyQueries = queryForSqlEvaluation.partitionInvalidateKeyQueries?.(cube, preAggregation);
161161

162162
const allBackAliasMembers = this.query.allBackAliasMembers();
163163

packages/cubejs-schema-compiler/src/adapter/PrestodbQuery.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { parseSqlInterval } from '@cubejs-backend/shared';
12
import { BaseQuery } from './BaseQuery';
23
import { BaseFilter } from './BaseFilter';
34

@@ -55,6 +56,30 @@ export class PrestodbQuery extends BaseQuery {
5556
field;
5657
}
5758

59+
/**
60+
* Returns sql for source expression floored to timestamps aligned with
61+
* intervals relative to origin timestamp point.
62+
* Athena doesn't support INTERVALs directly — using date_diff/date_add
63+
*/
64+
public dateBin(interval: string, source: string, origin: string): string {
65+
const intervalParsed = parseSqlInterval(interval);
66+
const intervalParts = Object.entries(intervalParsed);
67+
68+
if (intervalParts.length > 1) {
69+
throw new Error('Athena/Presto supports only simple intervals with one date part');
70+
}
71+
72+
const [unit, count] = intervalParts[0];
73+
const originExpr = this.timeStampCast(`'${origin}'`);
74+
75+
return `date_add('${unit}',
76+
floor(
77+
date_diff('${unit}', ${originExpr}, ${source}) / ${count}
78+
) * ${count},
79+
${originExpr}
80+
)`;
81+
}
82+
5883
public timeGroupedColumn(granularity, dimension) {
5984
return `date_trunc('${GRANULARITY_TO_INTERVAL[granularity]}', ${dimension})`;
6085
}

packages/cubejs-server-core/src/core/DriverResolvers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,13 @@ export const lookupDriverClass = (dbType): Constructor<BaseDriver> & {
4949
*/
5050
export const isDriver = (val: any): boolean => {
5151
let isDriverInstance = val instanceof BaseDriver;
52-
if (!isDriverInstance && val && val.constructor) {
52+
if (!isDriverInstance && val?.constructor) {
5353
let end = false;
5454
let obj = val.constructor;
5555
while (!isDriverInstance && !end) {
5656
obj = Object.getPrototypeOf(obj);
5757
end = !obj;
58-
isDriverInstance = obj && obj.name ? obj.name === 'BaseDriver' : false;
58+
isDriverInstance = obj?.name ? obj.name === 'BaseDriver' : false;
5959
}
6060
}
6161
return isDriverInstance;
@@ -82,11 +82,11 @@ export const getDriverMaxPool = async (
8282
const queryQueueOptions = await options
8383
.queryCacheOptions
8484
.queueOptions(context.dataSource);
85-
85+
8686
const preAggregationsQueueOptions = await options
8787
.preAggregationsOptions
8888
.queueOptions(context.dataSource);
89-
89+
9090
return 2 * (
9191
queryQueueOptions.concurrency +
9292
preAggregationsQueueOptions.concurrency

packages/cubejs-testing-drivers/fixtures/athena.json

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,15 +151,8 @@
151151
"---------------------------------------",
152152
"Custom Granularities ",
153153
"---------------------------------------",
154-
"querying custom granularities ECommerce: count by half_year + no dimension",
155-
"querying custom granularities ECommerce: count by half_year_by_1st_april + no dimension",
156154
"querying custom granularities ECommerce: count by three_months_by_march + no dimension",
157-
"querying custom granularities ECommerce: count by half_year + dimension",
158-
"querying custom granularities ECommerce: count by half_year_by_1st_april + dimension",
159155
"querying custom granularities ECommerce: count by three_months_by_march + dimension",
160-
"querying custom granularities ECommerce: count by two_mo_by_feb + no dimension + rollingCountByUnbounded",
161-
"querying custom granularities ECommerce: count by two_mo_by_feb + no dimension + rollingCountByTrailing",
162-
"querying custom granularities ECommerce: count by two_mo_by_feb + no dimension + rollingCountByLeading",
163156

164157
"SKIPPED SQL API (Need work)",
165158
"---------------------------------------",

0 commit comments

Comments
 (0)