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 15 commits into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions docs/pages/product/configuration/data-sources/mysql.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ CUBEJS_DB_PASS=**********
| `CUBEJS_DB_SSL` | If `true`, enables SSL encryption for database connections from Cube | `true`, `false` | ❌ |
| `CUBEJS_CONCURRENCY` | The number of concurrent connections each queue has to the database. Default is `2` | A valid number | ❌ |
| `CUBEJS_DB_MAX_POOL` | The maximum number of concurrent database connections to pool. Default is `8` | A valid number | ❌ |
| `CUBEJS_DB_MYSQL_USE_NAMED_TIMEZONES` | The flag to use time zone names or numeric offsets for time zone conversion. Default is `false` | `true`, `false` | ❌ |

## Pre-Aggregation Feature Support

Expand Down
12 changes: 12 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,17 @@ 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 time zones conversion is done in the generated SQL for MySQL:
- If it is set to `true`, time zone names are used. In this case, your MySQL server needs
to be [configured][mysql-server-tz-support] properly.
- If it is set to `false`, numeric offsets are used instead.

| Possible Values | Default in Development | Default in Production |
| --------------- | ---------------------- | --------------------- |
| `true`, `false` | `false` | `false` |

## `CUBEJS_DB_SNOWFLAKE_ACCOUNT`

The Snowflake account identifier to use when connecting to the database.
Expand Down Expand Up @@ -1552,3 +1563,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