Skip to content

Commit 1a00e4a

Browse files
committed
fix: MS SQL has unusual CTAS syntax
Fixes #185
1 parent d76e192 commit 1a00e4a

File tree

7 files changed

+690
-40
lines changed

7 files changed

+690
-40
lines changed

.circleci/config.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@ jobs:
66
environment:
77
TEST_PG_USER: root
88
TEST_CLICKHOUSE_HOST: localhost
9+
TEST_LOCAL: true
10+
TEST_DB_PASSWORD: Test1test
911
- image: circleci/redis:5.0.5
1012
- image: circleci/postgres:9.6.8
1113
environment:
1214
POSTGRES_USER: root
1315
POSTGRES_DB: model_test
14-
- image: yandex/clickhouse-server
16+
- image: yandex/clickhouse-server:2017-latest
17+
environment:
18+
ACCEPT_EULA: Y
19+
SA_PASSWORD: Test1test
20+
1521

1622
working_directory: ~/repo
1723

packages/cubejs-schema-compiler/adapter/MssqlQuery.js

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
const moment = require('moment-timezone');
22
const R = require('ramda');
33

4-
var abbrs = {
5-
EST : 'Eastern Standard Time',
6-
EDT : 'Eastern Standard Time',
7-
CST : 'Central Standard Time',
8-
CDT : 'Central Standard Time',
9-
MST : 'Mountain Standard Time',
10-
MDT : 'Mountain Standard Time',
11-
PST : 'Pacific Standard Time',
12-
PDT : 'Pacific Standard Time',
4+
const abbrs = {
5+
EST: 'Eastern Standard Time',
6+
EDT: 'Eastern Standard Time',
7+
CST: 'Central Standard Time',
8+
CDT: 'Central Standard Time',
9+
MST: 'Mountain Standard Time',
10+
MDT: 'Mountain Standard Time',
11+
PST: 'Pacific Standard Time',
12+
PDT: 'Pacific Standard Time',
1313
};
1414

1515
moment.fn.zoneName = function () {
16-
var abbr = this.zoneAbbr();
16+
const abbr = this.zoneAbbr();
1717
return abbrs[abbr] || abbr;
1818
};
1919

@@ -39,9 +39,7 @@ const GRANULARITY_TO_INTERVAL = {
3939
class MssqlFilter extends BaseFilter {
4040
// noinspection JSMethodCanBeStatic
4141
escapeWildcardChars(param) {
42-
return typeof param === 'string'
43-
? param.replace(/([_%])/gi, '[$1]')
44-
: param;
42+
return typeof param === 'string' ? param.replace(/([_%])/gi, '[$1]') : param;
4543
}
4644

4745
likeIgnoreCase(column, not) {
@@ -84,9 +82,19 @@ class MssqlQuery extends BaseQuery {
8482

8583
groupByClause() {
8684
const dimensionsForSelect = this.dimensionsForSelect();
87-
const dimensionColumns = R.flatten(dimensionsForSelect.map(s => s.selectColumns() && s.dimensionSql())).filter(s => !!s);
85+
const dimensionColumns = R.flatten(dimensionsForSelect.map(s => s.selectColumns() && s.dimensionSql()))
86+
.filter(s => !!s);
8887
return dimensionColumns.length ? ` GROUP BY ${dimensionColumns.join(', ')}` : '';
8988
}
89+
90+
nowTimestampSql() {
91+
return `CURRENT_TIMESTAMP`;
92+
}
93+
94+
preAggregationLoadSql(cube, preAggregation, tableName) {
95+
const sqlAndParams = this.preAggregationSql(cube, preAggregation);
96+
return [`SELECT * INTO ${tableName} FROM (${sqlAndParams[0]}) AS PreAggregation`, sqlAndParams[1]];
97+
}
9098
}
9199

92100
module.exports = MssqlQuery;

packages/cubejs-schema-compiler/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"eslint-plugin-import": "^2.16.0",
3131
"eslint-plugin-node": "^5.2.1",
3232
"mocha": "^3.4.2",
33+
"mssql": "^5.1.0",
3334
"pg-promise": "^7.3.2",
3435
"request": "^2.88.0",
3536
"request-promise": "^4.2.4",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
class BaseDbRunner {
2+
testQuery(query, fixture) {
3+
return this.testQueries([query], fixture);
4+
}
5+
6+
async testQueries(queries, fixture) {
7+
if (!this.container && !process.env.TEST_LOCAL) {
8+
console.log(`Starting container`);
9+
this.container = await this.containerLazyInit();
10+
}
11+
if (!this.connection) {
12+
console.log(`Initializing connection`);
13+
const port = this.container ? this.container.getMappedPort(this.port()) : this.port();
14+
this.connection = await this.connectionLazyInit(port);
15+
}
16+
return this.connection.testQueries(queries, fixture);
17+
}
18+
19+
async tearDown() {
20+
if (this.container) {
21+
await this.container.stop();
22+
this.connection = null;
23+
this.container = null;
24+
}
25+
}
26+
27+
// eslint-disable-next-line no-unused-vars
28+
async connectionLazyInit(port) {
29+
throw new Error('Not implemented');
30+
}
31+
32+
async containerLazyInit() {
33+
throw new Error('Not implemented');
34+
}
35+
36+
port() {
37+
throw new Error('Not implemented');
38+
}
39+
}
40+
41+
module.exports = BaseDbRunner;
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const { GenericContainer, Wait } = require("testcontainers");
2+
const sql = require('mssql');
3+
const BaseDbRunner = require('./BaseDbRunner');
4+
5+
class MSSqlDbRunner extends BaseDbRunner {
6+
async connectionLazyInit(port) {
7+
return {
8+
testQueries: async (queries, fixture) => {
9+
const pool = new sql.ConnectionPool({
10+
server: 'localhost',
11+
port,
12+
user: 'sa',
13+
password: this.password()
14+
});
15+
16+
await pool.connect();
17+
18+
try {
19+
const tx = new sql.Transaction(pool);
20+
await tx.begin();
21+
try {
22+
await this.prepareFixture(tx, fixture);
23+
const result = await queries.map(query => async () => {
24+
const request = new sql.Request(tx);
25+
(query[1] || []).forEach((v, i) => request.input(`_${i + 1}`, v));
26+
return (await request.query(query[0])).recordset;
27+
}).reduce((a, b) => a.then(b), Promise.resolve());
28+
await tx.commit();
29+
return result;
30+
} catch (e) {
31+
// console.log(e.stack);
32+
await tx.rollback();
33+
throw e;
34+
}
35+
} finally {
36+
await pool.close();
37+
}
38+
}
39+
};
40+
}
41+
42+
async prepareFixture(tx) {
43+
const query = async (q) => {
44+
const request = new sql.Request(tx);
45+
await request.query(q);
46+
};
47+
await query('CREATE TABLE ##visitors (id INT, amount INT, created_at datetime, updated_at datetime, status INT, source VARCHAR(MAX), latitude DECIMAL, longitude DECIMAL)');
48+
await query('CREATE TABLE ##visitor_checkins (id INT, visitor_id INT, created_at datetime, source VARCHAR(MAX))');
49+
await query('CREATE TABLE ##cards (id INT, visitor_id INT, visitor_checkin_id INT)');
50+
await query(`
51+
INSERT INTO
52+
##visitors
53+
(id, amount, created_at, updated_at, status, source, latitude, longitude) VALUES
54+
(1, 100, '2017-01-03', '2017-01-30', 1, 'some', 120.120, 40.60),
55+
(2, 200, '2017-01-05', '2017-01-15', 1, 'some', 120.120, 58.60),
56+
(3, 300, '2017-01-06', '2017-01-20', 2, 'google', 120.120, 70.60),
57+
(4, 400, '2017-01-07', '2017-01-25', 2, NULL, 120.120, 10.60),
58+
(5, 500, '2017-01-07', '2017-01-25', 2, NULL, 120.120, 58.10),
59+
(6, 500, '2016-09-07', '2016-09-07', 2, NULL, 120.120, 58.10)
60+
`);
61+
await query(`
62+
INSERT INTO
63+
##visitor_checkins
64+
(id, visitor_id, created_at, source) VALUES
65+
(1, 1, '2017-01-03', NULL),
66+
(2, 1, '2017-01-04', NULL),
67+
(3, 1, '2017-01-05', 'google'),
68+
(4, 2, '2017-01-05', NULL),
69+
(5, 2, '2017-01-05', NULL),
70+
(6, 3, '2017-01-06', NULL)
71+
`);
72+
await query(`
73+
INSERT INTO
74+
##cards
75+
(id, visitor_id, visitor_checkin_id) VALUES
76+
(1, 1, 1),
77+
(2, 1, 2),
78+
(3, 3, 6)
79+
`);
80+
}
81+
82+
password() {
83+
return process.env.TEST_DB_PASSWORD || 'Test1test';
84+
}
85+
86+
async containerLazyInit() {
87+
return new GenericContainer("mcr.microsoft.com/mssql/server", '2017-latest')
88+
.withEnv("ACCEPT_EULA", "Y")
89+
.withEnv("SA_PASSWORD", this.password())
90+
.withExposedPorts(this.port())
91+
.withWaitStrategy(Wait.forLogMessage("Server is listening on"))
92+
.start();
93+
}
94+
95+
port() {
96+
return 1433;
97+
}
98+
}
99+
100+
module.exports = new MSSqlDbRunner();

0 commit comments

Comments
 (0)