Skip to content

Commit 0ec696f

Browse files
authored
fix(mssql-driver): Support MS SQL dialect for Tesseract (cube-js#10343)
1 parent e379947 commit 0ec696f

File tree

10 files changed

+505
-73
lines changed

10 files changed

+505
-73
lines changed

.github/workflows/push.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,9 @@ jobs:
432432
- db: postgres
433433
node-version: 22.x
434434
use_tesseract_sql_planner: true
435+
- db: mssql
436+
node-version: 22.x
437+
use_tesseract_sql_planner: true
435438
fail-fast: false
436439

437440
steps:

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ yarn build # Build for production
7575
3. **API Gateway**: Provides REST, GraphQL, and SQL APIs
7676
4. **CubeSQL**: Postgres-compatible SQL interface (Rust)
7777
5. **CubeStore**: Distributed OLAP storage engine (Rust)
78+
6. **Tesseract**: Native SQL planner (Rust) located in `/rust/cubesqlplanner` - enabled via `CUBESQL_SQL_PUSH_DOWN=true` environment variable
7879

7980
### Package Management
8081
- Uses Yarn workspaces with Lerna for package management

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,36 @@ export class MssqlQuery extends BaseQuery {
270270
delete templates.types.interval;
271271
templates.types.binary = 'VARBINARY';
272272
templates.params.param = '@_{{ param_index + 1 }}';
273+
// MSSQL does not support ordinal GROUP BY (GROUP BY 1, 2), must use expressions
274+
templates.statements.group_by_exprs = '{{ group_by | map(attribute=\'expr\') | join(\', \') }}';
275+
// MSSQL does not support ordinal ORDER BY (ORDER BY 1, 2), must use expressions
276+
templates.expressions.order_by = '{{ expr }} {% if asc %}ASC{% else %}DESC{% endif %}';
277+
// MSSQL uses CAST(...AS DATETIME2) instead of ::timestamp
278+
templates.statements.time_series_select = 'SELECT CAST(date_from AS DATETIME2) AS "date_from",\n' +
279+
'CAST(date_to AS DATETIME2) AS "date_to" \n' +
280+
'FROM(\n' +
281+
' VALUES ' +
282+
'{% for time_item in seria %}' +
283+
'(\'{{ time_item[0] }}\', \'{{ time_item[1] }}\')' +
284+
'{% if not loop.last %}, {% endif %}' +
285+
'{% endfor %}' +
286+
') AS dates (date_from, date_to)';
287+
// MSSQL uses OFFSET/FETCH instead of LIMIT/OFFSET
288+
templates.statements.select = '{% if ctes %} WITH \n' +
289+
'{{ ctes | join(\',\n\') }}\n' +
290+
'{% endif %}' +
291+
'SELECT {% if distinct %}DISTINCT {% endif %}' +
292+
'{{ select_concat | map(attribute=\'aliased\') | join(\', \') }} {% if from %}\n' +
293+
'FROM (\n' +
294+
'{{ from | indent(2, true) }}\n' +
295+
') AS {{ from_alias }}{% elif from_prepared %}\n' +
296+
'FROM {{ from_prepared }}' +
297+
'{% endif %}' +
298+
'{% if filter %}\nWHERE {{ filter }}{% endif %}' +
299+
'{% if group_by %}\nGROUP BY {{ group_by }}{% endif %}' +
300+
'{% if having %}\nHAVING {{ having }}{% endif %}' +
301+
'{% if order_by %}\nORDER BY {{ order_by | map(attribute=\'expr\') | join(\', \') }}\nOFFSET {% if offset is not none %}{{ offset }}{% else %}0{% endif %} ROWS{% endif %}' +
302+
'{% if limit is not none %}\nFETCH NEXT {{ limit }} ROWS ONLY{% endif %}';
273303
return templates;
274304
}
275305
}

packages/cubejs-schema-compiler/test/integration/mssql/MSSqlDbRunner.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,10 @@ export class MSSqlDbRunner extends BaseDbRunner {
143143
return new MssqlQuery(compilers, query);
144144
}
145145
}
146+
147+
export const dbRunner = new MSSqlDbRunner();
148+
149+
// eslint-disable-next-line no-undef
150+
afterAll(async () => {
151+
await dbRunner.tearDown();
152+
});

packages/cubejs-schema-compiler/test/integration/mssql/custom-granularities.test.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { prepareYamlCompiler } from '../../unit/PrepareCompiler';
2-
import { MSSqlDbRunner } from './MSSqlDbRunner';
2+
import { dbRunner } from './MSSqlDbRunner';
33

44
describe('Custom Granularities', () => {
55
jest.setTimeout(200000);
66

7-
const dbRunner = new MSSqlDbRunner();
8-
97
const { compiler, joinGraph, cubeEvaluator } = prepareYamlCompiler(`
108
cubes:
119
- name: orders

packages/cubejs-schema-compiler/test/integration/mssql/mssql-cumulative-measures.test.ts

Lines changed: 179 additions & 55 deletions
Large diffs are not rendered by default.

packages/cubejs-schema-compiler/test/integration/mssql/mssql-pre-aggregations.test.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
import R from 'ramda';
22
import { MssqlQuery } from '../../../src/adapter/MssqlQuery';
33
import { prepareJsCompiler } from '../../unit/PrepareCompiler';
4-
import { MSSqlDbRunner } from './MSSqlDbRunner';
4+
import { dbRunner } from './MSSqlDbRunner';
55
import { createJoinedCubesSchema } from '../../unit/utils';
66

77
describe('MSSqlPreAggregations', () => {
88
jest.setTimeout(200000);
99

10-
const dbRunner = new MSSqlDbRunner();
11-
12-
afterAll(async () => {
13-
await dbRunner.tearDown();
14-
});
15-
1610
const { compiler, joinGraph, cubeEvaluator } = prepareJsCompiler(`
1711
cube(\`visitors\`, {
1812
sql: \`

packages/cubejs-schema-compiler/test/integration/mssql/mssql-ungrouped.test.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
11
import { MssqlQuery } from '../../../src/adapter/MssqlQuery';
22
import { prepareJsCompiler } from '../../unit/PrepareCompiler';
3-
import { MSSqlDbRunner } from './MSSqlDbRunner';
3+
import { dbRunner } from './MSSqlDbRunner';
44

55
describe('MSSqlUngrouped', () => {
66
jest.setTimeout(200000);
77

8-
const dbRunner = new MSSqlDbRunner();
9-
10-
afterAll(async () => {
11-
await dbRunner.tearDown();
12-
});
13-
148
const { compiler, joinGraph, cubeEvaluator } = prepareJsCompiler(`
159
const perVisitorRevenueMeasure = {
1610
type: 'number',

0 commit comments

Comments
 (0)