From aa47ded2ba90a78d80f694978e516b63f81d5c15 Mon Sep 17 00:00:00 2001 From: xrip Date: Mon, 10 Jul 2023 16:38:27 +0300 Subject: [PATCH 1/3] Add support for URL connection string in ClickHouse client Extended the ClickHouse client configuration to accept a URL connection string which can contain user info, hosts and ports, database name and query parameters. This change provides flexible and standard connections setup. Updated related unit tests to reflect this change. --- __tests__/unit/client.test.ts | 13 +++++++------ src/client.ts | 30 ++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/__tests__/unit/client.test.ts b/__tests__/unit/client.test.ts index 00c6d314..75ec5791 100644 --- a/__tests__/unit/client.test.ts +++ b/__tests__/unit/client.test.ts @@ -2,27 +2,28 @@ import type { ClickHouseClientConfigOptions } from '../../src' import { createClient } from '../../src' describe('createClient', () => { - it('throws on incorrect "host" config value', () => { + it('throws on incorrect "host" or "url" config value', () => { expect(() => createClient({ host: 'foo' })).toThrowError( - 'Configuration parameter "host" contains malformed url.' + 'Configuration parameter "host" or "url" contains malformed url.' ) }) - it('should not mutate provided configuration', async () => { + it('should accept url connection string and not mutate provided configuration', async () => { const config: ClickHouseClientConfigOptions = { - host: 'http://localhost', + url: 'clickhouse://default:password@localhost/default', + password: 'foobar', } createClient(config) // none of the initial configuration settings are overridden // by the defaults we assign when we normalize the specified config object expect(config).toEqual({ - host: 'http://localhost', + url: 'clickhouse://default:password@localhost/default', request_timeout: undefined, max_open_connections: undefined, tls: undefined, compression: undefined, username: undefined, - password: undefined, + password: 'foobar', application: undefined, database: undefined, clickhouse_settings: undefined, diff --git a/src/client.ts b/src/client.ts index 3a805afa..893a22b5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -14,6 +14,10 @@ import type { ClickHouseSettings } from './settings' import type { InputJSON, InputJSONObjectEachRow } from './clickhouse_types' export interface ClickHouseClientConfigOptions { + /** ClickHouse connection string url + * ```clickhouse://[user_info@][hosts_and_ports][/dbname][?query_parameters]``` + */ + url?: string /** A ClickHouse instance URL. *
Default value: `http://localhost:8123`. */ host?: string @@ -135,11 +139,13 @@ function validateConfig({ url }: NormalizedConfig): void { // TODO add SSL validation } -function createUrl(host: string): URL { +function createUrl(url: string): URL { try { - return new URL(host) + return new URL(url) } catch (err) { - throw new Error('Configuration parameter "host" contains malformed url.') + throw new Error( + 'Configuration parameter "host" or "url" contains malformed url.' + ) } } @@ -158,9 +164,14 @@ function normalizeConfig(config: ClickHouseClientConfigOptions) { } } } + + const url = config.url && createUrl(config.url) + return { application_id: config.application, - url: createUrl(config.host ?? 'http://localhost:8123'), + url: createUrl( + config.host ?? (url && `http://${url.host}`) ?? 'http://localhost:8123' + ), request_timeout: config.request_timeout ?? 300_000, max_open_connections: config.max_open_connections ?? Infinity, tls, @@ -168,10 +179,13 @@ function normalizeConfig(config: ClickHouseClientConfigOptions) { decompress_response: config.compression?.response ?? true, compress_request: config.compression?.request ?? false, }, - username: config.username ?? 'default', - password: config.password ?? '', - database: config.database ?? 'default', - clickhouse_settings: config.clickhouse_settings ?? {}, + username: config.username ?? (url && url.username) ?? 'default', + password: config.password ?? (url && url.password) ?? '', + database: config.database ?? (url && url.pathname) ?? 'default', + clickhouse_settings: + config.clickhouse_settings ?? + (url && Object.fromEntries(url.searchParams)) ?? + {}, log: { LoggerClass: config.log?.LoggerClass ?? DefaultLogger, }, From 4205ad7f78231a1897216ff8da1fd5982eda7557 Mon Sep 17 00:00:00 2001 From: xrip Date: Mon, 10 Jul 2023 18:46:32 +0300 Subject: [PATCH 2/3] https://github.com/ClickHouse/clickhouse-js/pull/174#discussion_r1258351521 --- __tests__/unit/client.test.ts | 10 ++++++++-- src/client.ts | 11 +++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/__tests__/unit/client.test.ts b/__tests__/unit/client.test.ts index 75ec5791..7628dcb4 100644 --- a/__tests__/unit/client.test.ts +++ b/__tests__/unit/client.test.ts @@ -2,9 +2,15 @@ import type { ClickHouseClientConfigOptions } from '../../src' import { createClient } from '../../src' describe('createClient', () => { - it('throws on incorrect "host" or "url" config value', () => { + it('throws on incorrect "host" config value', () => { expect(() => createClient({ host: 'foo' })).toThrowError( - 'Configuration parameter "host" or "url" contains malformed url.' + 'Configuration parameter "host" contains malformed url.' + ) + }) + + it('throws on incorrect "url" config value', () => { + expect(() => createClient({ url: 'bar' })).toThrowError( + 'Configuration parameter "url" contains malformed url.' ) }) diff --git a/src/client.ts b/src/client.ts index 893a22b5..04a63002 100644 --- a/src/client.ts +++ b/src/client.ts @@ -139,13 +139,11 @@ function validateConfig({ url }: NormalizedConfig): void { // TODO add SSL validation } -function createUrl(url: string): URL { +function createUrl(url: string, type: 'host' | 'url'): URL { try { return new URL(url) } catch (err) { - throw new Error( - 'Configuration parameter "host" or "url" contains malformed url.' - ) + throw new Error(`Configuration parameter "${type}" contains malformed url.`) } } @@ -165,12 +163,13 @@ function normalizeConfig(config: ClickHouseClientConfigOptions) { } } - const url = config.url && createUrl(config.url) + const url = config.url && createUrl(config.url, 'url') return { application_id: config.application, url: createUrl( - config.host ?? (url && `http://${url.host}`) ?? 'http://localhost:8123' + config.host ?? (url && `http://${url.host}`) ?? 'http://localhost:8123', + 'host' ), request_timeout: config.request_timeout ?? 300_000, max_open_connections: config.max_open_connections ?? Infinity, From 6b6545a2758917021226df724e34263f8b0f5d92 Mon Sep 17 00:00:00 2001 From: xrip Date: Mon, 10 Jul 2023 19:38:32 +0300 Subject: [PATCH 3/3] url.pathname as database name trailing slash remove --- src/client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 04a63002..d326fca6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -180,7 +180,7 @@ function normalizeConfig(config: ClickHouseClientConfigOptions) { }, username: config.username ?? (url && url.username) ?? 'default', password: config.password ?? (url && url.password) ?? '', - database: config.database ?? (url && url.pathname) ?? 'default', + database: config.database ?? (url && url.pathname.slice(1)) ?? 'default', clickhouse_settings: config.clickhouse_settings ?? (url && Object.fromEntries(url.searchParams)) ??