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' ;
43import { GenericContainer } from 'testcontainers' ;
54import type { StartedTestContainer } from 'testcontainers' ;
65import { format as formatSql } from 'sqlstring' ;
76import { v4 as uuidv4 } from 'uuid' ;
87import { ClickHouseQuery } from '../../../src/adapter/ClickHouseQuery' ;
98import { BaseDbRunner } from "../utils/BaseDbRunner" ;
109
11- // Just a placeholder for now
12- type ClickHouseClient = any ;
13-
1410process . env . TZ = 'GMT' ;
1511
1612export class ClickHouseDbRunner extends BaseDbRunner {
@@ -35,27 +31,32 @@ export class ClickHouseDbRunner extends BaseDbRunner {
3531 // let engine = 'MergeTree PARTITION BY id ORDER BY (id) SETTINGS index_granularity = 8192'
3632 const engine = 'Memory' ;
3733
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+ ` } ) ;
4138
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+ ` } ) ;
4543
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+ ` } ) ;
4948
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+ ` } ) ;
5353
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+ ` } ) ;
5758
58- await clickHouse . querying ( `
59+ await clickHouse . command ( { query : `
5960 INSERT INTO
6061 visitors
6162 (id, amount, created_at, updated_at, status, source, latitude, longitude) VALUES
@@ -65,9 +66,9 @@ export class ClickHouseDbRunner extends BaseDbRunner {
6566 (4, 400, '2017-01-06 16:00:00', '2017-01-24 16:00:00', 2, null, 120.120, 10.60),
6667 (5, 500, '2017-01-06 16:00:00', '2017-01-24 16:00:00', 2, null, 120.120, 58.10),
6768 (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+ ` } ) ;
6970
70- await clickHouse . querying ( `
71+ await clickHouse . command ( { query : `
7172 INSERT INTO
7273 visitor_checkins
7374 (id, visitor_id, created_at, source) VALUES
@@ -77,28 +78,28 @@ export class ClickHouseDbRunner extends BaseDbRunner {
7778 (4, 2, '2017-01-04 16:00:00', null),
7879 (5, 2, '2017-01-04 16:00:00', null),
7980 (6, 3, '2017-01-05 16:00:00', null)
80- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
81+ ` } ) ;
8182
82- await clickHouse . querying ( `
83+ await clickHouse . command ( { query : `
8384 INSERT INTO
8485 cards
8586 (id, visitor_id, visitor_checkin_id) VALUES
8687 (1, 1, 1),
8788 (2, 1, 2),
8889 (3, 3, 6)
89- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
90+ ` } ) ;
9091
91- await clickHouse . querying ( `
92+ await clickHouse . command ( { query : `
9293 INSERT INTO
9394 events
9495 (id, type, name, started_at, ended_at) VALUES
9596 (1, 'moon_missions', 'Apollo 10', '1969-05-18 16:49:00', '1969-05-26 16:52:23'),
9697 (2, 'moon_missions', 'Apollo 11', '1969-07-16 13:32:00', '1969-07-24 16:50:35'),
9798 (3, 'moon_missions', 'Artemis I', '2021-11-16 06:32:00', '2021-12-11 18:50:00'),
9899 (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+ ` } ) ;
100101
101- await clickHouse . querying ( `
102+ await clickHouse . command ( { query : `
102103 INSERT INTO
103104 numbers
104105 (num) VALUES
@@ -108,7 +109,7 @@ export class ClickHouseDbRunner extends BaseDbRunner {
108109 (30), (31), (32), (33), (34), (35), (36), (37), (38), (39),
109110 (40), (41), (42), (43), (44), (45), (46), (47), (48), (49),
110111 (50), (51), (52), (53), (54), (55), (56), (57), (58), (59)
111- ` , { queryOptions : { session_id : clickHouse . sessionId , join_use_nulls : '1' } } ) ;
112+ ` } ) ;
112113 }
113114
114115 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 {
127128 port = this . container . getMappedPort ( 8123 ) ;
128129 }
129130
130- const clickHouse = new ClickHouse ( {
131- host,
132- port,
133- } ) ;
131+ const clickHouse = createClient ( {
132+ url : `http://${ host } :${ port } ` ,
134133
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+ } ) ;
136138
137139 prepareDataSet = prepareDataSet || this . gutterDataSet ;
138140 await prepareDataSet ( clickHouse ) ;
139141
140- const requests : Array < unknown > = [ ] ;
141-
142142 // Controls whether functions return results with extended date and time ranges.
143143 //
144144 // 0 — Functions return Date or DateTime for all arguments (default).
@@ -150,19 +150,23 @@ export class ClickHouseDbRunner extends BaseDbRunner {
150150 //
151151 // https://clickhouse.com/docs/en/operations/settings/settings#enable-extended-results-for-datetime-functions
152152 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+ } ) ;
166170
167171 const results = await Promise . all ( requests ) ;
168172
@@ -189,26 +193,37 @@ export class ClickHouseDbRunner extends BaseDbRunner {
189193 //
190194 // https://github.com/statsbotco/cube.js/pull/98#discussion_r279698399
191195 //
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 } ` ) ;
207216 }
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 } ` ;
208223 }
209224 }
210- } ) ;
211- }
212- return res . data ;
225+ }
226+ } ) ;
227+ return data ;
213228 }
214229}
0 commit comments