diff --git a/CHANGELOG.md b/CHANGELOG.md index e47b3e25..08db0da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,7 +43,7 @@ See also: [readonly documentation](https://clickhouse.com/docs/en/operations/set - Added `url` configuration parameter. It is intended to replace the deprecated `host`, which was already supposed to be passed a valid URL. - It is now possible to configure most of the client instance parameters with a URL. The URL format is `http[s]://[username:password@]hostname:port[/database][?param1=value1¶m2=value2]`. The name of a particular parameter is supposed to reflect its path in the config options interface. The following parameters are supported: - - `readonly` - boolean. + - `readonly` - boolean. See below [1]. - `application_id` - non-empty string. - `session_id` - non-empty string. - `request_timeout` - non-negative number. @@ -52,10 +52,33 @@ See also: [readonly documentation](https://clickhouse.com/docs/en/operations/set - `compression_response` - boolean. - `log_level` - allowed values: `OFF`, `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`. - `keep_alive_enabled` - boolean. + - `clickhouse_settings_*` - see below [2]. + - `additional_headers_*` - see below [3]. - (Node.js only) `keep_alive_socket_ttl` - non-negative number. - (Node.js only) `keep_alive_retry_on_expired_socket` - boolean. -For booleans, valid values will be `true`/`1` and `false`/`0`. +[1] For booleans, valid values will be `true`/`1` and `false`/`0`. + +[2] Any parameter prefixed with `clickhouse_settings_` will have this prefix removed and the rest added to client's `clickhouse_settings`. For example, `?clickhouse_settings_async_insert=1&clickhouse_settings_wait_for_async_insert=1` will be the same as: + +```ts +createClient({ + clickhouse_settings: { + async_insert: 1, + wait_for_async_insert: 1, + }, +}) +``` + +[3] Similar to [2], but for `additional_headers` configuration. For example, `?additional_headers_x-clickhouse-auth=foobar` will be an equivalent of: + +```ts +createClient({ + additional_headers: { + 'x-clickhouse-auth': 'foobar', + }, +}) +``` URL will _always_ overwrite the hardcoded values and a warning will be logged in this case. diff --git a/packages/client-common/src/config.ts b/packages/client-common/src/config.ts index 2026336e..f382c5ba 100644 --- a/packages/client-common/src/config.ts +++ b/packages/client-common/src/config.ts @@ -282,21 +282,23 @@ export function loadConfigOptionsFromURL( config.readonly = booleanConfigURLValue({ key, value }) break case 'application': - config.application = stringConfigURLValue({ key, value }) + config.application = value break case 'session_id': - config.session_id = stringConfigURLValue({ key, value }) + config.session_id = value break case 'request_timeout': - config.request_timeout = numberConfigURLValue({ min: 0 })({ + config.request_timeout = numberConfigURLValue({ key, value, + min: 0, }) break case 'max_open_connections': - config.max_open_connections = numberConfigURLValue({ min: 1 })({ + config.max_open_connections = numberConfigURLValue({ key, value, + min: 1, }) break case 'compression_request': @@ -318,9 +320,10 @@ export function loadConfigOptionsFromURL( if (config.log === undefined) { config.log = {} } - config.log.level = enumConfigURLValue(ClickHouseLogLevel)({ + config.log.level = enumConfigURLValue({ key, value, + enumObject: ClickHouseLogLevel, }) break case 'keep_alive_enabled': @@ -350,10 +353,13 @@ export function loadConfigOptionsFromURL( return [clickHouseURL, config] } -type KV = { key: string; value: string } -type ConfigURLValue = (kv: KV) => T - -export function booleanConfigURLValue({ key, value }: KV): boolean { +export function booleanConfigURLValue({ + key, + value, +}: { + key: string + value: string +}): boolean { const trimmed = value.trim() if (trimmed === 'true' || trimmed === '1') return true if (trimmed === 'false' || trimmed === '0') return false @@ -361,44 +367,44 @@ export function booleanConfigURLValue({ key, value }: KV): boolean { } export function numberConfigURLValue({ + key, + value, min, max, }: { + key: string + value: string min?: number max?: number -}): ConfigURLValue { - return ({ key, value }: KV): number => { - const trimmed = value.trim() - const number = Number(trimmed) - if (isNaN(number)) - throw new Error(`${key} has invalid number value: ${trimmed}`) - if (min !== undefined && number < min) { - throw new Error(`${key} value is less than minimum: ${trimmed}`) - } - if (max !== undefined && number > max) { - throw new Error(`${key} value is greater than maximum: ${trimmed}`) - } - return number +}): number { + const trimmed = value.trim() + const number = Number(trimmed) + if (isNaN(number)) + throw new Error(`${key} has invalid number value: ${trimmed}`) + if (min !== undefined && number < min) { + throw new Error(`${key} value is less than minimum: ${trimmed}`) } -} - -export function enumConfigURLValue(enumObject: { - [key in Key]: Enum -}): ConfigURLValue { - const values = Object.keys(enumObject).filter((item) => isNaN(Number(item))) - return ({ key, value }: KV): Enum => { - const trimmed = value.trim() - if (!values.includes(trimmed)) { - throw new Error(`${key} has invalid value: ${trimmed}`) - } - return enumObject[trimmed as Key] + if (max !== undefined && number > max) { + throw new Error(`${key} value is greater than maximum: ${trimmed}`) } + return number } -export function stringConfigURLValue({ key, value }: KV): string { +export function enumConfigURLValue({ + key, + value, + enumObject, +}: { + key: string + value: string + enumObject: { + [key in Key]: Enum + } +}): Enum { + const values = Object.keys(enumObject).filter((item) => isNaN(Number(item))) const trimmed = value.trim() - if (trimmed === '') { - throw new Error(`${key} has empty value.`) + if (!values.includes(trimmed)) { + throw new Error(`${key} has invalid value: ${trimmed}`) } - return trimmed + return enumObject[trimmed as Key] } diff --git a/packages/client-common/src/index.ts b/packages/client-common/src/index.ts index 75091f69..23142202 100644 --- a/packages/client-common/src/index.ts +++ b/packages/client-common/src/index.ts @@ -53,7 +53,6 @@ export { booleanConfigURLValue, enumConfigURLValue, numberConfigURLValue, - stringConfigURLValue, } from './config' export { withCompressionHeaders, diff --git a/packages/client-node/src/client.ts b/packages/client-node/src/client.ts index 3e0a04cd..a844b7e8 100644 --- a/packages/client-node/src/client.ts +++ b/packages/client-node/src/client.ts @@ -112,8 +112,10 @@ export function createClient( nodeConfig.keep_alive = {} } nodeConfig.keep_alive.socket_ttl = numberConfigURLValue({ + key, + value, min: 0, - })({ key, value }) + }) handledParams.add(key) break default: diff --git a/packages/client-web/__tests__/unit/web_client.test.ts b/packages/client-web/__tests__/unit/web_client.test.ts index 3910799d..9d9151f1 100644 --- a/packages/client-web/__tests__/unit/web_client.test.ts +++ b/packages/client-web/__tests__/unit/web_client.test.ts @@ -3,20 +3,20 @@ import { createClient } from '../../src' describe('[Web] createClient', () => { it('throws on incorrect "host" config value', () => { - expect(() => createClient({ host: 'foo' })).toThrowError( + expect(() => createClient({ url: 'foo' })).toThrowError( 'Configuration parameter "host" contains malformed url.' ) }) it('should not mutate provided configuration', async () => { - const config: BaseClickHouseClientConfigOptions = { - host: 'http://localhost', + const config: BaseClickHouseClientConfigOptions = { + url: 'http://localhost', } createClient(config) // initial configuration is not overridden by the defaults we assign // when we transform the specified config object to the connection params expect(config).toEqual({ - host: 'http://localhost', + url: 'http://localhost', }) }) })