Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(schema-compiler): Add flag for using named timezones in MySQL Query class #9111

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
11 changes: 11 additions & 0 deletions docs/pages/reference/configuration/environment-variables.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,16 @@ The cluster name to use when connecting to [Materialize](/product/configuration/
| --------------------------------------------------------- | ---------------------- | --------------------- |
| A valid Materialize cluster name | N/A | N/A |

## `CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES`

This flag controls how timezones are passed to CONVERT_TZ. If it is set to TRUE - timezone names will be used.
IF it is set to FALSE - numeric offsets will be passed instead. To use named timezones, MySQL Server needs
to be [configured][mysql-server-tz-support] properly.

| Possible Values | Default in Development | Default in Production |
| ------------------------------------- | ---------------------- | --------------------- |
| Whether to use named timezones or not | `false` | `false` |

## `CUBEJS_DB_SNOWFLAKE_ACCOUNT`

The Snowflake account identifier to use when connecting to the database.
Expand Down Expand Up @@ -1552,3 +1562,4 @@ The port for a Cube deployment to listen to API connections on.
[ref-sql-api]: /product/apis-integrations/sql-api
[ref-sql-api-streaming]: /product/apis-integrations/sql-api#streaming
[ref-row-limit]: /product/apis-integrations/queries#row-limit
[mysql-server-tz-support]: https://dev.mysql.com/doc/refman/8.4/en/time-zone-support.html
2 changes: 1 addition & 1 deletion packages/cubejs-api-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"jsonwebtoken": "^9.0.2",
"jwk-to-pem": "^2.0.4",
"moment": "^2.24.0",
"moment-timezone": "^0.5.27",
"moment-timezone": "^0.5.46",
"nexus": "^1.1.0",
"node-fetch": "^2.6.1",
"ramda": "^0.27.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-backend-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"fs-extra": "^9.1.0",
"http-proxy-agent": "^4.0.1",
"moment-range": "^4.0.1",
"moment-timezone": "^0.5.33",
"moment-timezone": "^0.5.46",
"node-fetch": "^2.6.1",
"shelljs": "^0.8.5",
"throttle-debounce": "^3.0.1",
Expand Down
38 changes: 38 additions & 0 deletions packages/cubejs-backend-shared/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,44 @@ const variables: Record<string, (...args: any) => any> = {
return undefined;
},

/** ****************************************************************
* MySQL Driver *
***************************************************************** */

/**
* Use timezone names for date/time conversions.
* Defaults to FALSE, meaning that numeric offsets for timezone will be used.
* @see https://dev.mysql.com/doc/refman/8.4/en/date-and-time-functions.html#function_convert-tz
* @see https://dev.mysql.com/doc/refman/8.4/en/time-zone-support.html
*/
mysqlUseNamedTimezones: ({ dataSource }: { dataSource: string }) => {
const val = process.env[
keyByDataSource(
'CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES',
dataSource,
)
];

if (val) {
if (val.toLocaleLowerCase() === 'true') {
return true;
} else if (val.toLowerCase() === 'false') {
return false;
} else {
throw new TypeError(
`The ${
keyByDataSource(
'CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES',
dataSource,
)
} must be either 'true' or 'false'.`
);
}
} else {
return false;
}
},

/** ****************************************************************
* Databricks Driver *
***************************************************************** */
Expand Down
1 change: 0 additions & 1 deletion packages/cubejs-cubestore-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"flatbuffers": "23.3.3",
"fs-extra": "^9.1.0",
"generic-pool": "^3.6.0",
"moment-timezone": "^0.5.31",
"node-fetch": "^2.6.1",
"sqlstring": "^2.3.3",
"tempy": "^1.0.1",
Expand Down
13 changes: 9 additions & 4 deletions packages/cubejs-dremio-driver/driver/DremioQuery.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const moment = require('moment-timezone');
const { BaseFilter, BaseQuery } = require('@cubejs-backend/schema-compiler');

const GRANULARITY_TO_INTERVAL = {
Expand Down Expand Up @@ -36,17 +35,23 @@ class DremioQuery extends BaseQuery {
return new DremioFilter(this, filter);
}

/**
* CONVERT_TIMEZONE([sourceTimezone string], destinationTimezone string,
* timestamp date, timestamp, or string in ISO 8601 format) → timestamp
* sourceTimezone (optional): The time zone of the timestamp. If you omit this parameter,
* Dremio assumes that the source time zone is UTC.
* @see https://docs.dremio.com/cloud/reference/sql/sql-functions/functions/CONVERT_TIMEZONE/
*/
convertTz(field) {
const targetTZ = moment().tz(this.timezone).format('Z');
return `CONVERT_TIMEZONE('${targetTZ}', ${field})`;
return `CONVERT_TIMEZONE('${this.timezone}', ${field})`;
}

timeStampCast(value) {
return `TO_TIMESTAMP(${value}, 'YYYY-MM-DD"T"HH24:MI:SS.FFF')`;
}

timestampFormat() {
return moment.HTML5_FMT.DATETIME_LOCAL_MS;
return 'YYYY-MM-DDTHH:mm:ss.SSS';
}

dateTimeCast(value) {
Expand Down
1 change: 0 additions & 1 deletion packages/cubejs-dremio-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
"@cubejs-backend/schema-compiler": "1.1.15",
"@cubejs-backend/shared": "1.1.12",
"axios": "^0.21.1",
"moment-timezone": "^0.5.31",
"sqlstring": "^2.3.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-dremio-driver/test/DremioQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ cube(\`sales\`, {
const queryAndParams = query.buildSqlAndParams();

expect(queryAndParams[0]).toContain(
'DATE_TRUNC(\'day\', CONVERT_TIMEZONE(\'-08:00\', "sales".sales_datetime))'
'DATE_TRUNC(\'day\', CONVERT_TIMEZONE(\'America/Los_Angeles\', "sales".sales_datetime))'
);
}));

Expand Down
3 changes: 1 addition & 2 deletions packages/cubejs-druid-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
"@cubejs-backend/base-driver": "1.1.12",
"@cubejs-backend/schema-compiler": "1.1.15",
"@cubejs-backend/shared": "1.1.12",
"axios": "^0.21.1",
"moment-timezone": "^0.5.31"
"axios": "^0.21.1"
},
"devDependencies": {
"@cubejs-backend/linter": "^1.0.0",
Expand Down
1 change: 0 additions & 1 deletion packages/cubejs-druid-driver/src/DruidQuery.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import moment from 'moment-timezone';
import { BaseFilter, BaseQuery } from '@cubejs-backend/schema-compiler';

const GRANULARITY_TO_INTERVAL: Record<string, (date: string) => string> = {
Expand Down
1 change: 0 additions & 1 deletion packages/cubejs-query-orchestrator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
"es5-ext": "0.10.53",
"generic-pool": "^3.7.1",
"lru-cache": "^6.0.0",
"moment-timezone": "^0.5.33",
"ramda": "^0.27.2"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/cubejs-schema-compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"joi": "^17.8.3",
"js-yaml": "^4.1.0",
"lru-cache": "^5.1.1",
"moment-timezone": "^0.5.33",
"moment-timezone": "^0.5.46",
"node-dijkstra": "^2.5.0",
"ramda": "^0.27.2",
"syntax-error": "^1.3.0",
Expand Down
4 changes: 0 additions & 4 deletions packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -2818,10 +2818,6 @@ export class BaseQuery {
return this.join && this.join.multiplicationFactor[cubeName];
}

inIntegrationTimeZone(date) {
return moment.tz(date, this.timezone);
}

inDbTimeZone(date) {
return inDbTimeZone(this.timezone, this.timestampFormat(), date);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class CubeStoreQuery extends BaseQuery {
}

public timestampFormat() {
return moment.HTML5_FMT.DATETIME_LOCAL_MS;
return 'YYYY-MM-DDTHH:mm:ss.SSS';
}

public dateTimeCast(value) {
Expand Down
31 changes: 20 additions & 11 deletions packages/cubejs-schema-compiler/src/adapter/MysqlQuery.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import moment from 'moment-timezone';

import { parseSqlInterval } from '@cubejs-backend/shared';

import { getEnv, parseSqlInterval } from '@cubejs-backend/shared';
import { BaseQuery } from './BaseQuery';
import { BaseFilter } from './BaseFilter';
import { UserError } from '../compiler/UserError';
Expand All @@ -26,39 +24,50 @@ class MysqlFilter extends BaseFilter {
}

export class MysqlQuery extends BaseQuery {
private readonly useNamedTimezones: boolean;

public constructor(compilers: any, options: any) {
super(compilers, options);

this.useNamedTimezones = getEnv('mysqlUseNamedTimezones', { dataSource: this.dataSource });
}

public newFilter(filter) {
return new MysqlFilter(this, filter);
}

public castToString(sql) {
public castToString(sql: string) {
return `CAST(${sql} as CHAR)`;
}

public convertTz(field) {
public convertTz(field: string) {
if (this.useNamedTimezones) {
return `CONVERT_TZ(${field}, @@session.time_zone, '${this.timezone}')`;
}
return `CONVERT_TZ(${field}, @@session.time_zone, '${moment().tz(this.timezone).format('Z')}')`;
}

public timeStampCast(value) {
public timeStampCast(value: string) {
return `TIMESTAMP(convert_tz(${value}, '+00:00', @@session.time_zone))`;
}

public timestampFormat() {
return moment.HTML5_FMT.DATETIME_LOCAL_MS;
return 'YYYY-MM-DDTHH:mm:ss.SSS';
}

public dateTimeCast(value) {
public dateTimeCast(value: string) {
return `TIMESTAMP(${value})`;
}

public subtractInterval(date, interval) {
public subtractInterval(date: string, interval: string) {
return `DATE_SUB(${date}, INTERVAL ${this.formatInterval(interval)})`;
}

public addInterval(date, interval) {
public addInterval(date: string, interval: string) {
return `DATE_ADD(${date}, INTERVAL ${this.formatInterval(interval)})`;
}

public timeGroupedColumn(granularity, dimension) {
public timeGroupedColumn(granularity: string, dimension) {
return `CAST(${GRANULARITY_TO_INTERVAL[granularity](dimension)} AS DATETIME)`;
}

Expand Down
1 change: 0 additions & 1 deletion packages/cubejs-vertica-driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"@cubejs-backend/base-driver": "1.1.12",
"@cubejs-backend/query-orchestrator": "1.1.12",
"@cubejs-backend/schema-compiler": "1.1.15",
"moment-timezone": "^0.5.45",
"vertica-nodejs": "^1.0.3"
},
"license": "Apache-2.0",
Expand Down
3 changes: 1 addition & 2 deletions packages/cubejs-vertica-driver/src/VerticaQuery.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const moment = require('moment-timezone');
const { BaseFilter, BaseQuery } = require('@cubejs-backend/schema-compiler');

const GRANULARITY_TO_INTERVAL = {
Expand Down Expand Up @@ -38,7 +37,7 @@ class VerticaQuery extends BaseQuery {
}

timestampFormat() {
return moment.HTML5_FMT.DATETIME_LOCAL_MS;
return 'YYYY-MM-DDTHH:mm:ss.SSS';
}

dateTimeCast(value) {
Expand Down
6 changes: 3 additions & 3 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -21864,16 +21864,16 @@ moment-range@*, moment-range@^4.0.1:
dependencies:
es6-symbol "^3.1.0"

moment-timezone@^0.5.15, moment-timezone@^0.5.27, moment-timezone@^0.5.31, moment-timezone@^0.5.33:
moment-timezone@^0.5.15, moment-timezone@^0.5.33:
version "0.5.45"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.45.tgz#cb685acd56bac10e69d93c536366eb65aa6bcf5c"
integrity sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==
dependencies:
moment "^2.29.4"

moment-timezone@^0.5.45:
moment-timezone@^0.5.46:
version "0.5.46"
resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.46.tgz#a21aa6392b3c6b3ed916cd5e95858a28d893704a"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.46.tgz#a21aa6392b3c6b3ed916cd5e95858a28d893704a"
integrity sha512-ZXm9b36esbe7OmdABqIWJuBBiLLwAjrN7CE+7sYdCCx82Nabt1wHDj8TVseS59QIlfFPbOoiBPm6ca9BioG4hw==
dependencies:
moment "^2.29.4"
Expand Down
Loading