1
- /* eslint-disable */
2
- // @ts -ignore
3
- import ClickHouse from '@cubejs-backend/apla-clickhouse' ;
1
+ import { createClient } from '@clickhouse/client' ;
2
+ import type { ClickHouseClient , ResponseJSON } from '@clickhouse/client' ;
4
3
import { GenericContainer } from 'testcontainers' ;
5
4
import type { StartedTestContainer } from 'testcontainers' ;
6
5
import { format as formatSql } from 'sqlstring' ;
7
6
import { v4 as uuidv4 } from 'uuid' ;
8
7
import { ClickHouseQuery } from '../../../src/adapter/ClickHouseQuery' ;
9
8
import { BaseDbRunner } from "../utils/BaseDbRunner" ;
10
9
11
- // Just a placeholder for now
12
- type ClickHouseClient = any ;
13
-
14
10
process . env . TZ = 'GMT' ;
15
11
16
12
export class ClickHouseDbRunner extends BaseDbRunner {
@@ -35,27 +31,32 @@ export class ClickHouseDbRunner extends BaseDbRunner {
35
31
// let engine = 'MergeTree PARTITION BY id ORDER BY (id) SETTINGS index_granularity = 8192'
36
32
const engine = 'Memory' ;
37
33
38
- await clickHouse . querying ( `
39
- CREATE TEMPORARY TABLE visitors (id UInt64, amount UInt64, created_at DateTime, updated_at DateTime, status UInt64, source Nullable(String), latitude Float64, longitude Float64)
40
- ENGINE = ${ engine } ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
34
+ await clickHouse . command ( { query : `
35
+ CREATE TEMPORARY TABLE visitors (id UInt64, amount UInt64, created_at DateTime, updated_at DateTime, status UInt64, source Nullable(String), latitude Float64, longitude Float64)
36
+ ENGINE = ${ engine }
37
+ ` } ) ;
41
38
42
- await clickHouse . querying ( `
43
- CREATE TEMPORARY TABLE visitor_checkins (id UInt64, visitor_id UInt64, created_at DateTime, source Nullable(String))
44
- ENGINE = ${ engine } ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
39
+ await clickHouse . command ( { query : `
40
+ CREATE TEMPORARY TABLE visitor_checkins (id UInt64, visitor_id UInt64, created_at DateTime, source Nullable(String))
41
+ ENGINE = ${ engine }
42
+ ` } ) ;
45
43
46
- await clickHouse . querying ( `
47
- CREATE TEMPORARY TABLE cards (id UInt64, visitor_id UInt64, visitor_checkin_id UInt64)
48
- ENGINE = ${ engine } ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
44
+ await clickHouse . command ( { query : `
45
+ CREATE TEMPORARY TABLE cards (id UInt64, visitor_id UInt64, visitor_checkin_id UInt64)
46
+ ENGINE = ${ engine }
47
+ ` } ) ;
49
48
50
- await clickHouse . querying ( `
51
- CREATE TEMPORARY TABLE events (id UInt64, type String, name String, started_at DateTime64, ended_at Nullable(DateTime64))
52
- ENGINE = ${ engine } ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
49
+ await clickHouse . command ( { query : `
50
+ CREATE TEMPORARY TABLE events (id UInt64, type String, name String, started_at DateTime64, ended_at Nullable(DateTime64))
51
+ ENGINE = ${ engine }
52
+ ` } ) ;
53
53
54
- await clickHouse . querying ( `
55
- CREATE TEMPORARY TABLE numbers (num Int)
56
- ENGINE = ${ engine } ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
54
+ await clickHouse . command ( { query : `
55
+ CREATE TEMPORARY TABLE numbers (num Int)
56
+ ENGINE = ${ engine }
57
+ ` } ) ;
57
58
58
- await clickHouse . querying ( `
59
+ await clickHouse . command ( { query : `
59
60
INSERT INTO
60
61
visitors
61
62
(id, amount, created_at, updated_at, status, source, latitude, longitude) VALUES
@@ -65,9 +66,9 @@ export class ClickHouseDbRunner extends BaseDbRunner {
65
66
(4, 400, '2017-01-06 16:00:00', '2017-01-24 16:00:00', 2, null, 120.120, 10.60),
66
67
(5, 500, '2017-01-06 16:00:00', '2017-01-24 16:00:00', 2, null, 120.120, 58.10),
67
68
(6, 500, '2016-09-06 16:00:00', '2016-09-06 16:00:00', 2, null, 120.120, 58.10)
68
- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
69
+ ` } ) ;
69
70
70
- await clickHouse . querying ( `
71
+ await clickHouse . command ( { query : `
71
72
INSERT INTO
72
73
visitor_checkins
73
74
(id, visitor_id, created_at, source) VALUES
@@ -77,28 +78,28 @@ export class ClickHouseDbRunner extends BaseDbRunner {
77
78
(4, 2, '2017-01-04 16:00:00', null),
78
79
(5, 2, '2017-01-04 16:00:00', null),
79
80
(6, 3, '2017-01-05 16:00:00', null)
80
- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
81
+ ` } ) ;
81
82
82
- await clickHouse . querying ( `
83
+ await clickHouse . command ( { query : `
83
84
INSERT INTO
84
85
cards
85
86
(id, visitor_id, visitor_checkin_id) VALUES
86
87
(1, 1, 1),
87
88
(2, 1, 2),
88
89
(3, 3, 6)
89
- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
90
+ ` } ) ;
90
91
91
- await clickHouse . querying ( `
92
+ await clickHouse . command ( { query : `
92
93
INSERT INTO
93
94
events
94
95
(id, type, name, started_at, ended_at) VALUES
95
96
(1, 'moon_missions', 'Apollo 10', '1969-05-18 16:49:00', '1969-05-26 16:52:23'),
96
97
(2, 'moon_missions', 'Apollo 11', '1969-07-16 13:32:00', '1969-07-24 16:50:35'),
97
98
(3, 'moon_missions', 'Artemis I', '2021-11-16 06:32:00', '2021-12-11 18:50:00'),
98
99
(4, 'private_missions', 'Axiom Mission 1', '2022-04-08 15:17:12', '2022-04-25 17:06:00')
99
- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
100
+ ` } ) ;
100
101
101
- await clickHouse . querying ( `
102
+ await clickHouse . command ( { query : `
102
103
INSERT INTO
103
104
numbers
104
105
(num) VALUES
@@ -108,7 +109,7 @@ export class ClickHouseDbRunner extends BaseDbRunner {
108
109
(30), (31), (32), (33), (34), (35), (36), (37), (38), (39),
109
110
(40), (41), (42), (43), (44), (45), (46), (47), (48), (49),
110
111
(50), (51), (52), (53), (54), (55), (56), (57), (58), (59)
111
- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
112
+ ` } ) ;
112
113
}
113
114
114
115
public override async testQueries ( queries : Array < [ string , Array < unknown > ] > , prepareDataSet ?: ( ( client : ClickHouseClient ) => Promise < void > ) | null ) : Promise < Array < Array < Record < string , unknown > > > > {
@@ -127,18 +128,17 @@ export class ClickHouseDbRunner extends BaseDbRunner {
127
128
port = this . container . getMappedPort ( 8123 ) ;
128
129
}
129
130
130
- const clickHouse = new ClickHouse ( {
131
- host,
132
- port,
133
- } ) ;
131
+ const clickHouse = createClient ( {
132
+ url : `http://${ host } :${ port } ` ,
134
133
135
- clickHouse . sessionId = uuidv4 ( ) ; // needed for tests to use temporary tables
134
+ // needed for tests to use temporary tables
135
+ session_id : uuidv4 ( ) ,
136
+ max_open_connections : 1 ,
137
+ } ) ;
136
138
137
139
prepareDataSet = prepareDataSet || this . gutterDataSet ;
138
140
await prepareDataSet ( clickHouse ) ;
139
141
140
- const requests : Array < unknown > = [ ] ;
141
-
142
142
// Controls whether functions return results with extended date and time ranges.
143
143
//
144
144
// 0 — Functions return Date or DateTime for all arguments (default).
@@ -150,19 +150,23 @@ export class ClickHouseDbRunner extends BaseDbRunner {
150
150
//
151
151
// https://clickhouse.com/docs/en/operations/settings/settings#enable-extended-results-for-datetime-functions
152
152
const extendedDateTimeResultsOptions = this . supportsExtendedDateTimeResults ? {
153
- enable_extended_results_for_datetime_functions : '1'
154
- } : { } ;
155
-
156
- for ( const [ query , params ] of queries ) {
157
- requests . push ( clickHouse . querying ( formatSql ( query , params ) , {
158
- dataObjects : true ,
159
- queryOptions : {
160
- session_id : clickHouse . sessionId ,
161
- join_use_nulls : '1' ,
162
- ...extendedDateTimeResultsOptions
163
- }
164
- } ) ) ;
165
- }
153
+ enable_extended_results_for_datetime_functions : 1
154
+ } as const : { } ;
155
+
156
+ const requests = queries
157
+ . map ( async ( [ query , params ] ) => {
158
+ const resultSet = await clickHouse . query ( {
159
+ query : formatSql ( query , params ) ,
160
+ format : 'JSON' ,
161
+ clickhouse_settings : {
162
+ join_use_nulls : 1 ,
163
+ ...extendedDateTimeResultsOptions
164
+ }
165
+ } ) ;
166
+ // Because we used JSON format we expect each row in result set to be a record of column name => value
167
+ const result = await resultSet . json < Record < string , unknown > > ( ) ;
168
+ return result ;
169
+ } ) ;
166
170
167
171
const results = await Promise . all ( requests ) ;
168
172
@@ -189,26 +193,37 @@ export class ClickHouseDbRunner extends BaseDbRunner {
189
193
//
190
194
// https://github.com/statsbotco/cube.js/pull/98#discussion_r279698399
191
195
//
192
- protected static _normaliseResponse ( res : any ) : Array < Record < string , unknown > > {
193
- if ( process . env . DEBUG_LOG === 'true' ) console . log ( res ) ;
194
- if ( res . data ) {
195
- res . data . forEach ( row => {
196
- for ( const field in row ) {
197
- const value = row [ field ] ;
198
- if ( value !== null ) {
199
- const meta = res . meta . find ( m => m . name == field ) ;
200
- if ( meta . type . includes ( 'DateTime' ) ) {
201
- row [ field ] = `${ value . substring ( 0 , 10 ) } T${ value . substring ( 11 , 22 ) } .000` ;
202
- } else if ( meta . type . includes ( 'Date' ) ) {
203
- row [ field ] = `${ value } T00:00:00.000` ;
204
- } else if ( meta . type . includes ( 'Int' ) || meta . type . includes ( 'Float' ) ) {
205
- // convert all numbers into strings
206
- row [ field ] = `${ value } ` ;
196
+ protected static _normaliseResponse ( res : ResponseJSON < Record < string , unknown > > ) : Array < Record < string , unknown > > {
197
+ if ( process . env . DEBUG_LOG === 'true' ) {
198
+ console . log ( res ) ;
199
+ }
200
+
201
+ const { meta, data } = res ;
202
+ if ( meta === undefined ) {
203
+ throw new Error ( 'Unexpected missing meta' ) ;
204
+ }
205
+
206
+ data . forEach ( row => {
207
+ for ( const [ field , value ] of Object . entries ( row ) ) {
208
+ if ( value !== null ) {
209
+ const fieldMeta = meta . find ( m => m . name == field ) ;
210
+ if ( fieldMeta === undefined ) {
211
+ throw new Error ( `Missing meta for field ${ field } ` ) ;
212
+ }
213
+ if ( fieldMeta . type . includes ( 'DateTime' ) ) {
214
+ if ( typeof value !== 'string' ) {
215
+ throw new Error ( `Unexpected value for ${ field } ` ) ;
207
216
}
217
+ row [ field ] = `${ value . substring ( 0 , 10 ) } T${ value . substring ( 11 , 22 ) } .000` ;
218
+ } else if ( fieldMeta . type . includes ( 'Date' ) ) {
219
+ row [ field ] = `${ value } T00:00:00.000` ;
220
+ } else if ( fieldMeta . type . includes ( 'Int' ) || fieldMeta . type . includes ( 'Float' ) ) {
221
+ // convert all numbers into strings
222
+ row [ field ] = `${ value } ` ;
208
223
}
209
224
}
210
- } ) ;
211
- }
212
- return res . data ;
225
+ }
226
+ } ) ;
227
+ return data ;
213
228
}
214
229
}
0 commit comments