Skip to content

Commit 42639d7

Browse files
committed
[WIP] feat(clickhouse-driver): Switch from apla-clickhouse to @clickhouse/client
1 parent c7a3016 commit 42639d7

File tree

3 files changed

+109
-45
lines changed

3 files changed

+109
-45
lines changed

packages/cubejs-clickhouse-driver/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"integration:clickhouse": "jest dist/test"
2828
},
2929
"dependencies": {
30-
"@cubejs-backend/apla-clickhouse": "^1.7",
30+
"@clickhouse/client": "^1.7.0",
3131
"@cubejs-backend/base-driver": "1.1.2",
3232
"@cubejs-backend/shared": "1.1.2",
3333
"generic-pool": "^3.6.0",

packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ import {
2121
TableStructure,
2222
UnloadOptions,
2323
} from '@cubejs-backend/base-driver';
24+
25+
import { ClickHouseClient, createClient } from '@clickhouse/client';
2426
import genericPool, { Pool } from 'generic-pool';
2527
import { v4 as uuidv4 } from 'uuid';
2628
import sqlstring from 'sqlstring';
2729

2830
import { HydrationStream, transformRow } from './HydrationStream';
2931

30-
const ClickHouse = require('@cubejs-backend/apla-clickhouse');
31-
3232
const ClickhouseTypeToGeneric: Record<string, string> = {
3333
enum: 'text',
3434
string: 'text',
@@ -86,7 +86,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface {
8686
return 5;
8787
}
8888

89-
protected readonly pool: Pool<any>;
89+
protected readonly pool: Pool<ClickHouseClient>;
9090

9191
protected readonly readOnlyMode: boolean;
9292

@@ -122,19 +122,33 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface {
122122
config.dataSource ||
123123
assertDataSource('default');
124124

125+
// TODO recheck everything in config for new driver
126+
const host = getEnv('dbHost', { dataSource });
127+
const port = getEnv('dbPort', { dataSource });
128+
const protocol = getEnv('dbSsl', { dataSource }) ? 'https:' : 'http:';
129+
// TODO proper value here, with proper back compat, and treating protocol
130+
const url = `${protocol}//${host}:${port}`;
131+
// TODO drop this
132+
console.log('ClickHouseDriver will use url', url);
133+
134+
const username = getEnv('dbUser', { dataSource });
135+
const password = getEnv('dbPass', { dataSource });
125136
this.config = {
126-
host: getEnv('dbHost', { dataSource }),
127-
port: getEnv('dbPort', { dataSource }),
128-
auth:
129-
getEnv('dbUser', { dataSource }) ||
130-
getEnv('dbPass', { dataSource })
131-
? `${
132-
getEnv('dbUser', { dataSource })
133-
}:${
134-
getEnv('dbPass', { dataSource })
135-
}`
136-
: '',
137-
protocol: getEnv('dbSsl', { dataSource }) ? 'https:' : 'http:',
137+
// host: getEnv('dbHost', { dataSource }),
138+
// port: getEnv('dbPort', { dataSource }),
139+
url,
140+
// auth:
141+
// getEnv('dbUser', { dataSource }) ||
142+
// getEnv('dbPass', { dataSource })
143+
// ? `${
144+
// getEnv('dbUser', { dataSource })
145+
// }:${
146+
// getEnv('dbPass', { dataSource })
147+
// }`
148+
// : '',
149+
username,
150+
password,
151+
// protocol: getEnv('dbSsl', { dataSource }) ? 'https:' : 'http:',
138152
queryOptions: {
139153
database:
140154
getEnv('dbName', { dataSource }) ||
@@ -148,10 +162,17 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface {
148162
this.readOnlyMode =
149163
getEnv('clickhouseReadOnly', { dataSource }) === 'true';
150164

165+
// TODO @clickhouse/client have internal pool, that does NOT guarantee same connection, and can break with temp tables. Disable it?
151166
this.pool = genericPool.createPool({
152-
create: async () => new ClickHouse({
167+
create: async () => createClient({
153168
...this.config,
154-
queryOptions: {
169+
170+
username: getEnv('dbUser', { dataSource }),
171+
password: getEnv('dbPass', { dataSource }),
172+
173+
database: this.config.queryOptions.database,
174+
session_id: uuidv4(),
175+
clickhouse_settings: {
155176
//
156177
//
157178
// If ClickHouse user's permissions are restricted with "readonly = 1",
@@ -160,9 +181,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface {
160181
//
161182
//
162183
...(this.readOnlyMode ? {} : { join_use_nulls: 1 }),
163-
session_id: uuidv4(),
164-
...this.config.queryOptions,
165-
}
184+
},
166185
}),
167186
destroy: () => Promise.resolve()
168187
}, {
@@ -176,36 +195,43 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface {
176195
idleTimeoutMillis: 30000,
177196
acquireTimeoutMillis: 20000
178197
});
198+
199+
// https://github.com/coopernurse/node-pool/blob/ee5db9ddb54ce3a142fde3500116b393d4f2f755/README.md#L220-L226
200+
this.pool.on('factoryCreateError', (err) => {
201+
this.databasePoolError(err);
202+
});
203+
this.pool.on('factoryDestroyError', (err) => {
204+
this.databasePoolError(err);
205+
});
179206
}
180207

181-
protected withConnection(fn: (con: any, queryId: string) => Promise<any>) {
208+
protected withConnection(fn: (con: ClickHouseClient, queryId: string) => Promise<any>) {
209+
console.log("withConnection call");
182210
const self = this;
183211
const connectionPromise = this.pool.acquire();
184212
const queryId = uuidv4();
185213

186214
let cancelled = false;
187215
const cancelObj: any = {};
188216

189-
const promise: any = connectionPromise.then((connection: any) => {
217+
const promise: any = connectionPromise.then((connection: ClickHouseClient) => {
218+
console.log("withConnection got connection");
190219
cancelObj.cancel = async () => {
191220
cancelled = true;
192221
await self.withConnection(async conn => {
193-
await conn.querying(`KILL QUERY WHERE query_id = '${queryId}'`);
222+
await conn.command({
223+
query: `KILL QUERY WHERE query_id = '${queryId}'`,
224+
});
194225
});
195226
};
196227
return fn(connection, queryId)
197-
.then(res => this.pool.release(connection).then(() => {
228+
.finally(() => this.pool.release(connection))
229+
.then((res) => {
198230
if (cancelled) {
199231
throw new Error('Query cancelled');
200232
}
201233
return res;
202-
}))
203-
.catch((err) => this.pool.release(connection).then(() => {
204-
if (cancelled) {
205-
throw new Error('Query cancelled');
206-
}
207-
throw err;
208-
}));
234+
});
209235
});
210236
promise.cancel = () => cancelObj.cancel();
211237

@@ -227,22 +253,48 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface {
227253
}
228254

229255
protected queryResponse(query: string, values: unknown[]) {
256+
console.log("queryResponse call", query);
257+
230258
const formattedQuery = sqlstring.format(query, values);
231259

232-
return this.withConnection((connection, queryId) => connection.querying(formattedQuery, {
233-
dataObjects: true,
234-
queryOptions: {
260+
return this.withConnection(async (connection, queryId) => {
261+
// if (formattedQuery.startsWith("CREATE TABLE")) {
262+
//
263+
// }
264+
265+
const resultSet = await connection.query({
266+
query: formattedQuery,
235267
query_id: queryId,
236-
//
237-
//
238-
// If ClickHouse user's permissions are restricted with "readonly = 1",
239-
// change settings queries are not allowed. Thus, "join_use_nulls" setting
240-
// can not be changed
241-
//
242-
//
243-
...(this.readOnlyMode ? {} : { join_use_nulls: 1 }),
268+
clickhouse_settings: {
269+
//
270+
//
271+
// If ClickHouse user's permissions are restricted with "readonly = 1",
272+
// change settings queries are not allowed. Thus, "join_use_nulls" setting
273+
// can not be changed
274+
//
275+
//
276+
...(this.readOnlyMode ? {} : { join_use_nulls: 1 }),
277+
},
278+
});
279+
console.log("queryResponse resultSet", query, resultSet.query_id, resultSet.response_headers);
280+
281+
if (resultSet.response_headers['x-clickhouse-format'] !== 'JSON') {
282+
const results = await resultSet.text();
283+
console.log("queryResponse text results", query, results);
284+
console.log("queryResponse text results JSON", JSON.stringify(results));
285+
return [];
286+
} else {
287+
const results = await resultSet.json();
288+
console.log("queryResponse json results", query, results);
289+
console.log("queryResponse json results JSON", JSON.stringify(results));
290+
return results;
244291
}
245-
}));
292+
293+
// 'content-type': 'text/plain; charset=UTF-8',
294+
// vs
295+
// 'content-type': 'application/json; charset=UTF-8',
296+
// 'x-clickhouse-format': 'JSON',
297+
});
246298
}
247299

248300
protected normaliseResponse(res: any) {

yarn.lock

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4222,6 +4222,18 @@
42224222
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
42234223
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
42244224

4225+
"@clickhouse/[email protected]":
4226+
version "1.7.0"
4227+
resolved "https://registry.yarnpkg.com/@clickhouse/client-common/-/client-common-1.7.0.tgz#4d0315158d275ea8d55ed8e04d69871832f4d8ba"
4228+
integrity sha512-RkHYf23/wyv/6C0KcVD4nRX4JAn/Y+9AZBQPlrSId2JwXsmAnjDkkKpuPLwZPNVH6J3BkW+y8bQCEk3VHQzArw==
4229+
4230+
"@clickhouse/client@^1.7.0":
4231+
version "1.7.0"
4232+
resolved "https://registry.yarnpkg.com/@clickhouse/client/-/client-1.7.0.tgz#a6b7b72db825162b1f54c2fe383f349dbf437fbd"
4233+
integrity sha512-2aESIFRbSPWEZIU++sXt1RYWgEKZH75C3jyXLcRBeafMDjq7bKV2AX1X9n9xscN+Y4VvnkBzkjFxcbuqFSBk6w==
4234+
dependencies:
4235+
"@clickhouse/client-common" "1.7.0"
4236+
42254237
"@codemirror/highlight@^0.19.0":
42264238
version "0.19.6"
42274239
resolved "https://registry.yarnpkg.com/@codemirror/highlight/-/highlight-0.19.6.tgz#7f2e066f83f5649e8e0748a3abe0aaeaf64b8ac2"
@@ -4353,7 +4365,7 @@
43534365
tiny-invariant "^1.3.3"
43544366
valid-url "^1.0.9"
43554367

4356-
"@cubejs-backend/apla-clickhouse@^1.7", "@cubejs-backend/apla-clickhouse@^1.7.0":
4368+
"@cubejs-backend/apla-clickhouse@^1.7.0":
43574369
version "1.7.0"
43584370
resolved "https://registry.yarnpkg.com/@cubejs-backend/apla-clickhouse/-/apla-clickhouse-1.7.0.tgz#6359f46c56492d1704d18be0210c7546fdac5f5e"
43594371
integrity sha512-qwXapTC/qosA6RprElRjnl8gmlDQaxtJPtbgcdjyNvkmiyao1HI+w5QkjHWCiVm6aTzE0gjFr6/2y87TZ9fojg==

0 commit comments

Comments
 (0)