@@ -345,47 +345,23 @@ export class ClickhouseClient {
345
345
}
346
346
347
347
// https://github.com/ClickHouse/clickhouse-js/blob/1ebdd39203730bb99fad4c88eac35d9a5e96b34a/packages/client-web/src/connection/web_connection.ts#L151
348
- async query < T extends DataFormat > ( {
348
+ async query < Format extends DataFormat > ( {
349
349
query,
350
- format = 'JSON' ,
350
+ format = 'JSON' as Format ,
351
351
query_params = { } ,
352
352
abort_signal,
353
353
clickhouse_settings,
354
354
connectionId,
355
355
queryId,
356
356
} : {
357
357
query : string ;
358
- format ?: string ;
358
+ format ?: Format ;
359
359
abort_signal ?: AbortSignal ;
360
360
query_params ?: Record < string , any > ;
361
361
clickhouse_settings ?: Record < string , any > ;
362
362
connectionId ?: string ;
363
363
queryId ?: string ;
364
- } ) : Promise < BaseResultSet < ReadableStream , T > > {
365
- const isLocalMode = this . username != null && this . password != null ;
366
- const includeCredentials = ! isLocalMode ;
367
- const includeCorsHeader = isLocalMode ;
368
-
369
- const searchParams = new URLSearchParams ( [
370
- ...( includeCorsHeader ? [ [ 'add_http_cors_header' , '1' ] ] : [ ] ) ,
371
- [ 'query' , query ] ,
372
- [ 'default_format' , format ] ,
373
- [ 'date_time_output_format' , 'iso' ] ,
374
- [ 'wait_end_of_query' , '0' ] ,
375
- [ 'cancel_http_readonly_queries_on_client_close' , '1' ] ,
376
- ...( this . username ? [ [ 'user' , this . username ] ] : [ ] ) ,
377
- ...( this . password ? [ [ 'password' , this . password ] ] : [ ] ) ,
378
- ...( queryId ? [ [ 'query_id' , queryId ] ] : [ ] ) ,
379
- ...Object . entries ( query_params ) . map ( ( [ key , value ] ) => [
380
- `param_${ key } ` ,
381
- value ,
382
- ] ) ,
383
- ...Object . entries ( clickhouse_settings ?? { } ) . map ( ( [ key , value ] ) => [
384
- key ,
385
- value ,
386
- ] ) ,
387
- ] ) ;
388
-
364
+ } ) : Promise < BaseResultSet < ReadableStream , Format > > {
389
365
let debugSql = '' ;
390
366
try {
391
367
debugSql = parameterizedQueryToSql ( { sql : query , params : query_params } ) ;
@@ -402,38 +378,96 @@ export class ClickhouseClient {
402
378
403
379
if ( isBrowser ) {
404
380
// TODO: check if we can use the client-web directly
405
- const { ResultSet } = await import ( '@clickhouse/client-web' ) ;
406
-
407
- const headers = { } ;
408
- if ( ! isLocalMode && connectionId ) {
409
- headers [ 'x-hyperdx-connection-id' ] = connectionId ;
410
- }
411
- // https://github.com/ClickHouse/clickhouse-js/blob/1ebdd39203730bb99fad4c88eac35d9a5e96b34a/packages/client-web/src/connection/web_connection.ts#L200C7-L200C23
412
- const response = await fetch ( `${ this . host } /?${ searchParams . toString ( ) } ` , {
413
- ...( includeCredentials ? { credentials : 'include' } : { } ) ,
414
- signal : abort_signal ,
415
- method : 'GET' ,
416
- headers,
417
- } ) ;
381
+ const { createClient, ResultSet } = await import (
382
+ '@clickhouse/client-web'
383
+ ) ;
418
384
419
- // TODO: Send command to CH to cancel query on abort_signal
420
- if ( ! response . ok ) {
421
- if ( ! isSuccessfulResponse ( response . status ) ) {
422
- const text = await response . text ( ) ;
423
- throw new ClickHouseQueryError ( `${ text } ` , debugSql ) ;
385
+ const isLocalMode = this . username != null && this . password != null ;
386
+ if ( isLocalMode ) {
387
+ // LocalMode may potentially interact directly with a db, so it needs to
388
+ // send a get request. @clickhouse /client-web does not currently support
389
+ // querying via GET
390
+ const includeCredentials = ! isLocalMode ;
391
+ const includeCorsHeader = isLocalMode ;
392
+
393
+ const searchParams = new URLSearchParams ( [
394
+ ...( includeCorsHeader ? [ [ 'add_http_cors_header' , '1' ] ] : [ ] ) ,
395
+ [ 'query' , query ] ,
396
+ [ 'default_format' , format ] ,
397
+ [ 'date_time_output_format' , 'iso' ] ,
398
+ [ 'wait_end_of_query' , '0' ] ,
399
+ [ 'cancel_http_readonly_queries_on_client_close' , '1' ] ,
400
+ ...( this . username ? [ [ 'user' , this . username ] ] : [ ] ) ,
401
+ ...( this . password ? [ [ 'password' , this . password ] ] : [ ] ) ,
402
+ ...( queryId ? [ [ 'query_id' , queryId ] ] : [ ] ) ,
403
+ ...Object . entries ( query_params ) . map ( ( [ key , value ] ) => [
404
+ `param_${ key } ` ,
405
+ value ,
406
+ ] ) ,
407
+ ...Object . entries ( clickhouse_settings ?? { } ) . map ( ( [ key , value ] ) => [
408
+ key ,
409
+ value ,
410
+ ] ) ,
411
+ ] ) ;
412
+ const headers = { } ;
413
+ if ( ! isLocalMode && connectionId ) {
414
+ headers [ 'x-hyperdx-connection-id' ] = connectionId ;
415
+ }
416
+ // https://github.com/ClickHouse/clickhouse-js/blob/1ebdd39203730bb99fad4c88eac35d9a5e96b34a/packages/client-web/src/connection/web_connection.ts#L200C7-L200C23
417
+ const response = await fetch (
418
+ `${ this . host } /?${ searchParams . toString ( ) } ` ,
419
+ {
420
+ ...( includeCredentials ? { credentials : 'include' } : { } ) ,
421
+ signal : abort_signal ,
422
+ method : 'GET' ,
423
+ headers,
424
+ } ,
425
+ ) ;
426
+
427
+ // TODO: Send command to CH to cancel query on abort_signal
428
+ if ( ! response . ok ) {
429
+ if ( ! isSuccessfulResponse ( response . status ) ) {
430
+ const text = await response . text ( ) ;
431
+ throw new ClickHouseQueryError ( `${ text } ` , debugSql ) ;
432
+ }
424
433
}
425
- }
426
434
427
- if ( response . body == null ) {
428
- // TODO: Handle empty responses better?
429
- throw new Error ( 'Unexpected empty response from ClickHouse' ) ;
435
+ if ( response . body == null ) {
436
+ // TODO: Handle empty responses better?
437
+ throw new Error ( 'Unexpected empty response from ClickHouse' ) ;
438
+ }
439
+ return new ResultSet < Format > (
440
+ response . body ,
441
+ format ,
442
+ queryId ?? '' ,
443
+ getResponseHeaders ( response ) ,
444
+ ) ;
445
+ } else {
446
+ if ( connectionId === undefined ) {
447
+ throw new Error ( 'ConnectionId must be defined' ) ;
448
+ }
449
+ const clickhouseClient = createClient ( {
450
+ url : window . origin ,
451
+ pathname : this . host ,
452
+ http_headers : { 'x-hyperdx-connection-id' : connectionId } ,
453
+ clickhouse_settings : {
454
+ date_time_output_format : 'iso' ,
455
+ wait_end_of_query : 0 ,
456
+ cancel_http_readonly_queries_on_client_close : 1 ,
457
+ } ,
458
+ compression : {
459
+ response : true ,
460
+ } ,
461
+ } ) ;
462
+ return clickhouseClient . query < Format > ( {
463
+ query,
464
+ query_params,
465
+ format,
466
+ abort_signal,
467
+ clickhouse_settings,
468
+ query_id : queryId ,
469
+ } ) as Promise < BaseResultSet < ReadableStream , Format > > ;
430
470
}
431
- return new ResultSet < T > (
432
- response . body ,
433
- format as T ,
434
- queryId ?? '' ,
435
- getResponseHeaders ( response ) ,
436
- ) ;
437
471
} else if ( isNode ) {
438
472
const { createClient } = await import ( '@clickhouse/client' ) ;
439
473
const _client = createClient ( {
@@ -448,14 +482,14 @@ export class ClickhouseClient {
448
482
} ) ;
449
483
450
484
// TODO: Custom error handling
451
- return _client . query ( {
485
+ return _client . query < Format > ( {
452
486
query,
453
487
query_params,
454
- format : format as T ,
488
+ format,
455
489
abort_signal,
456
490
clickhouse_settings,
457
491
query_id : queryId ,
458
- } ) as unknown as BaseResultSet < any , T > ;
492
+ } ) as unknown as Promise < BaseResultSet < ReadableStream , Format > > ;
459
493
} else {
460
494
throw new Error (
461
495
'ClickhouseClient is only supported in the browser or node environment' ,
0 commit comments