Skip to content

Commit 2fc53f2

Browse files
authored
feat(schema-compiler): Support custom granularities with Year-Month intervals for AWS Redshift dialect (#9489)
1 parent 1701f21 commit 2fc53f2

File tree

3 files changed

+631
-8
lines changed

3 files changed

+631
-8
lines changed

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

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

34
export class RedshiftQuery extends PostgresQuery {
@@ -12,6 +13,74 @@ export class RedshiftQuery extends PostgresQuery {
1213
return 'GETDATE()';
1314
}
1415

16+
/**
17+
* Redshift doesn't support Interval values with month or year parts (as Postgres does)
18+
* so we need to make date math on our own.
19+
*/
20+
public override subtractInterval(date: string, interval: string): string {
21+
const intervalParsed = parseSqlInterval(interval);
22+
let result = date;
23+
24+
for (const [datePart, intervalValue] of Object.entries(intervalParsed)) {
25+
result = `DATEADD(${datePart}, -${intervalValue}, ${result})`;
26+
}
27+
28+
return result;
29+
}
30+
31+
/**
32+
* Redshift doesn't support Interval values with month or year parts (as Postgres does)
33+
* so we need to make date math on our own.
34+
*/
35+
public override addInterval(date: string, interval: string): string {
36+
const intervalParsed = parseSqlInterval(interval);
37+
let result = date;
38+
39+
for (const [datePart, intervalValue] of Object.entries(intervalParsed)) {
40+
result = `DATEADD(${datePart}, ${intervalValue}, ${result})`;
41+
}
42+
43+
return result;
44+
}
45+
46+
/**
47+
* Redshift doesn't support Interval values with month or year parts (as Postgres does)
48+
* so we need to make date math on our own.
49+
*/
50+
public override dateBin(interval: string, source: string, origin: string): string {
51+
const intervalParsed = parseSqlInterval(interval);
52+
53+
if ((intervalParsed.year || intervalParsed.month || intervalParsed.quarter) &&
54+
(intervalParsed.week || intervalParsed.day || intervalParsed.hour || intervalParsed.minute || intervalParsed.second)) {
55+
throw new Error(`Complex intervals like "${interval}" are not supported. Please use Year to Month or Day to second intervals`);
56+
}
57+
58+
if (intervalParsed.year || intervalParsed.month || intervalParsed.quarter) {
59+
let totalMonths = 0;
60+
61+
if (intervalParsed.year) {
62+
totalMonths += intervalParsed.year * 12;
63+
}
64+
65+
if (intervalParsed.quarter) {
66+
totalMonths += intervalParsed.quarter * 3;
67+
}
68+
69+
if (intervalParsed.month) {
70+
totalMonths += intervalParsed.month;
71+
}
72+
73+
return `DATEADD(
74+
month,
75+
(FLOOR(DATEDIFF(month, ${this.dateTimeCast(`'${origin}'`)}, ${source}) / ${totalMonths}) * ${totalMonths})::int,
76+
${this.dateTimeCast(`'${origin}'`)}
77+
)`;
78+
}
79+
80+
// For days and lower intervals - we can reuse Postgres version
81+
return super.dateBin(interval, source, origin);
82+
}
83+
1584
public sqlTemplates() {
1685
const templates = super.sqlTemplates();
1786
templates.functions.DLOG10 = 'LOG(10, {{ args_concat }})';

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -155,17 +155,10 @@
155155
},
156156
"skip": [
157157
"---------------------------------------",
158-
"Error: Interval values with month or year parts are not supported",
158+
"Error: Complex intervals like \"3 months 2 weeks 3 days\" are not supported. @see dateBin impl in ReshiftQuery",
159159
"---------------------------------------",
160-
"querying BigECommerce: rolling window by 2 month",
161-
"querying custom granularities ECommerce: count by half_year + no dimension",
162-
"querying custom granularities ECommerce: count by half_year_by_1st_april + no dimension",
163160
"querying custom granularities ECommerce: count by three_months_by_march + no dimension",
164-
"querying custom granularities ECommerce: count by half_year + dimension",
165-
"querying custom granularities ECommerce: count by half_year_by_1st_april + dimension",
166161
"querying custom granularities ECommerce: count by three_months_by_march + dimension",
167-
"querying custom granularities ECommerce: count by two_mo_by_feb + no dimension + rollingCountByTrailing",
168-
"querying custom granularities ECommerce: count by two_mo_by_feb + no dimension + rollingCountByLeading",
169162

170163
"---------------------------------------",
171164
"SKIPPED FOR ALL ",

0 commit comments

Comments
 (0)