diff --git a/docs-mintlify/admin/connect-to-data/data-sources/clickhouse.mdx b/docs-mintlify/admin/connect-to-data/data-sources/clickhouse.mdx index dbbd10ad9f3d7..bed5aa43771a6 100644 --- a/docs-mintlify/admin/connect-to-data/data-sources/clickhouse.mdx +++ b/docs-mintlify/admin/connect-to-data/data-sources/clickhouse.mdx @@ -38,6 +38,7 @@ CUBEJS_DB_PASS=********** | [`CUBEJS_DB_PASS`](/reference/configuration/environment-variables#cubejs_db_pass) | The password used to connect to the database | A valid database password | ✅ | | [`CUBEJS_DB_CLICKHOUSE_READONLY`](/reference/configuration/environment-variables#cubejs_db_clickhouse_readonly) | Whether the ClickHouse user has read-only access or not | `true`, `false` | ❌ | | [`CUBEJS_DB_CLICKHOUSE_COMPRESSION`](/reference/configuration/environment-variables#cubejs_db_clickhouse_compression) | Whether the ClickHouse client has compression enabled or not | `true`, `false` | ❌ | +| [`CUBEJS_DB_CLICKHOUSE_APPLICATION`](/reference/configuration/environment-variables#cubejs_db_clickhouse_application) | The application name reported to ClickHouse in the HTTP `User-Agent` header and visible in `system.query_log.http_user_agent` | A valid string | ❌ | | [`CUBEJS_DB_MAX_POOL`](/reference/configuration/environment-variables#cubejs_db_max_pool) | The maximum number of concurrent database connections to pool. Default is `20` | A valid number | ❌ | | [`CUBEJS_CONCURRENCY`](/reference/configuration/environment-variables#cubejs_concurrency) | The number of [concurrent queries][ref-data-source-concurrency] to the data source | A valid number | ❌ | diff --git a/docs-mintlify/reference/configuration/environment-variables.mdx b/docs-mintlify/reference/configuration/environment-variables.mdx index 7ef57965da466..1b89bf99d80eb 100644 --- a/docs-mintlify/reference/configuration/environment-variables.mdx +++ b/docs-mintlify/reference/configuration/environment-variables.mdx @@ -234,6 +234,16 @@ Whether the ClickHouse client has compression enabled or not. | --------------- | ---------------------- | --------------------- | | `true`, `false` | `false` | `false` | +## `CUBEJS_DB_CLICKHOUSE_APPLICATION` + +The application name reported to ClickHouse in the HTTP `User-Agent` header +and visible in `system.query_log.http_user_agent`. Useful to attribute queries +to a specific Cube deployment when several share the same ClickHouse user. + +| Possible Values | Default in Development | Default in Production | +| --------------- | ---------------------- | --------------------- | +| A valid string | N/A | N/A | + ## `CUBEJS_DB_DATABRICKS_ACCEPT_POLICY` To accept the license terms for the Databricks JDBC driver, this must be set to diff --git a/docs/content/product/configuration/data-sources/clickhouse.mdx b/docs/content/product/configuration/data-sources/clickhouse.mdx index 9a7e88dc060c9..f1c4db5fdd52c 100644 --- a/docs/content/product/configuration/data-sources/clickhouse.mdx +++ b/docs/content/product/configuration/data-sources/clickhouse.mdx @@ -35,6 +35,7 @@ CUBEJS_DB_PASS=********** | CUBEJS_DB_PASS | The password used to connect to the database | A valid database password | ✅ | | CUBEJS_DB_CLICKHOUSE_READONLY | Whether the ClickHouse user has read-only access or not | `true`, `false` | ❌ | | CUBEJS_DB_CLICKHOUSE_COMPRESSION | Whether the ClickHouse client has compression enabled or not | `true`, `false` | ❌ | +| CUBEJS_DB_CLICKHOUSE_APPLICATION | The application name reported to ClickHouse in the HTTP `User-Agent` header and visible in `system.query_log.http_user_agent` | A valid string | ❌ | | CUBEJS_DB_MAX_POOL | The maximum number of concurrent database connections to pool. Default is `20` | A valid number | ❌ | | CUBEJS_CONCURRENCY | The number of [concurrent queries][ref-data-source-concurrency] to the data source | A valid number | ❌ | diff --git a/docs/content/product/configuration/reference/environment-variables.mdx b/docs/content/product/configuration/reference/environment-variables.mdx index cab568d98042d..98e7cb040027b 100644 --- a/docs/content/product/configuration/reference/environment-variables.mdx +++ b/docs/content/product/configuration/reference/environment-variables.mdx @@ -237,6 +237,16 @@ Whether the ClickHouse client has compression enabled or not. | --------------- | ---------------------- | --------------------- | | `true`, `false` | `false` | `false` | +## `CUBEJS_DB_CLICKHOUSE_APPLICATION` + +The application name reported to ClickHouse in the HTTP `User-Agent` header +and visible in `system.query_log.http_user_agent`. Useful to attribute queries +to a specific Cube deployment when several share the same ClickHouse user. + +| Possible Values | Default in Development | Default in Production | +| --------------- | ---------------------- | --------------------- | +| A valid string | N/A | N/A | + ## `CUBEJS_DB_DATABRICKS_ACCEPT_POLICY` To accept the license terms for the Databricks JDBC driver, this must be set to diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index 1ebd6312970c3..7d810943c3b8d 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -1258,6 +1258,18 @@ const variables: Record any> = { .asBool() ), + /** + * ClickHouse application name. It is prepended to the HTTP User-Agent + * and is visible in system.query_log.http_user_agent. + */ + clickhouseApplication: ({ + dataSource, + preAggregations, + }: DataSourceOpts) => ( + get(keyByDataSource('CUBEJS_DB_CLICKHOUSE_APPLICATION', dataSource, preAggregations)) + .asString() + ), + /** **************************************************************** * ElasticSearch Driver * ***************************************************************** */ diff --git a/packages/cubejs-backend-shared/test/db_env_multi.test.ts b/packages/cubejs-backend-shared/test/db_env_multi.test.ts index c93b0fa4329fb..75de1262627a7 100644 --- a/packages/cubejs-backend-shared/test/db_env_multi.test.ts +++ b/packages/cubejs-backend-shared/test/db_env_multi.test.ts @@ -1566,6 +1566,35 @@ describe('Multiple datasources', () => { ); }); + test('getEnv("clickhouseApplication")', () => { + process.env.CUBEJS_DB_CLICKHOUSE_APPLICATION = 'default1'; + process.env.CUBEJS_DS_POSTGRES_DB_CLICKHOUSE_APPLICATION = 'postgres1'; + process.env.CUBEJS_DS_WRONG_DB_CLICKHOUSE_APPLICATION = 'wrong1'; + expect(getEnv('clickhouseApplication', { dataSource: 'default' })).toEqual('default1'); + expect(getEnv('clickhouseApplication', { dataSource: 'postgres' })).toEqual('postgres1'); + expect(() => getEnv('clickhouseApplication', { dataSource: 'wrong' })).toThrow( + 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' + ); + + process.env.CUBEJS_DB_CLICKHOUSE_APPLICATION = 'default2'; + process.env.CUBEJS_DS_POSTGRES_DB_CLICKHOUSE_APPLICATION = 'postgres2'; + process.env.CUBEJS_DS_WRONG_DB_CLICKHOUSE_APPLICATION = 'wrong2'; + expect(getEnv('clickhouseApplication', { dataSource: 'default' })).toEqual('default2'); + expect(getEnv('clickhouseApplication', { dataSource: 'postgres' })).toEqual('postgres2'); + expect(() => getEnv('clickhouseApplication', { dataSource: 'wrong' })).toThrow( + 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' + ); + + delete process.env.CUBEJS_DB_CLICKHOUSE_APPLICATION; + delete process.env.CUBEJS_DS_POSTGRES_DB_CLICKHOUSE_APPLICATION; + delete process.env.CUBEJS_DS_WRONG_DB_CLICKHOUSE_APPLICATION; + expect(getEnv('clickhouseApplication', { dataSource: 'default' })).toBeUndefined(); + expect(getEnv('clickhouseApplication', { dataSource: 'postgres' })).toBeUndefined(); + expect(() => getEnv('clickhouseApplication', { dataSource: 'wrong' })).toThrow( + 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' + ); + }); + test('getEnv("elasticApiId")', () => { process.env.CUBEJS_DB_ELASTIC_APIKEY_ID = 'default1'; process.env.CUBEJS_DS_POSTGRES_DB_ELASTIC_APIKEY_ID = 'postgres1'; diff --git a/packages/cubejs-backend-shared/test/db_env_single.test.ts b/packages/cubejs-backend-shared/test/db_env_single.test.ts index 411aa0eb79558..f14b8458f3c75 100644 --- a/packages/cubejs-backend-shared/test/db_env_single.test.ts +++ b/packages/cubejs-backend-shared/test/db_env_single.test.ts @@ -986,6 +986,23 @@ describe('Single datasources', () => { expect(getEnv('clickhouseCompression', { dataSource: 'wrong' })).toEqual(false); }); + test('getEnv("clickhouseApplication")', () => { + process.env.CUBEJS_DB_CLICKHOUSE_APPLICATION = 'default1'; + expect(getEnv('clickhouseApplication', { dataSource: 'default' })).toEqual('default1'); + expect(getEnv('clickhouseApplication', { dataSource: 'postgres' })).toEqual('default1'); + expect(getEnv('clickhouseApplication', { dataSource: 'wrong' })).toEqual('default1'); + + process.env.CUBEJS_DB_CLICKHOUSE_APPLICATION = 'default2'; + expect(getEnv('clickhouseApplication', { dataSource: 'default' })).toEqual('default2'); + expect(getEnv('clickhouseApplication', { dataSource: 'postgres' })).toEqual('default2'); + expect(getEnv('clickhouseApplication', { dataSource: 'wrong' })).toEqual('default2'); + + delete process.env.CUBEJS_DB_CLICKHOUSE_APPLICATION; + expect(getEnv('clickhouseApplication', { dataSource: 'default' })).toBeUndefined(); + expect(getEnv('clickhouseApplication', { dataSource: 'postgres' })).toBeUndefined(); + expect(getEnv('clickhouseApplication', { dataSource: 'wrong' })).toBeUndefined(); + }); + test('getEnv("elasticApiId")', () => { process.env.CUBEJS_DB_ELASTIC_APIKEY_ID = 'default1'; expect(getEnv('elasticApiId', { dataSource: 'default' })).toEqual('default1'); diff --git a/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts b/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts index 831f96a2219d4..6f3029de8ceb6 100644 --- a/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts +++ b/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts @@ -70,6 +70,11 @@ export interface ClickHouseDriverOptions { protocol?: string, database?: string, readOnly?: boolean, + /** + * The name of the application using the ClickHouse client. It is prepended + * to the HTTP User-Agent and is visible in system.query_log.http_user_agent. + */ + application?: string, /** * Timeout in milliseconds for requests to ClickHouse. * Default is 10 minutes @@ -118,6 +123,7 @@ type ClickHouseDriverConfig = { password: string, readOnly: boolean, database: string, + application?: string, requestTimeout: number, exportBucket: ClickhouseDriverExportAWS | null, compression: { response?: boolean; request?: boolean }, @@ -159,6 +165,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { const username = config.username ?? getEnv('dbUser', { dataSource, preAggregations }); const password = config.password ?? getEnv('dbPass', { dataSource, preAggregations }); const database = config.database ?? (getEnv('dbName', { dataSource, preAggregations }) as string) ?? 'default'; + const application = config.application ?? getEnv('clickhouseApplication', { dataSource, preAggregations }); // TODO this is a bit inconsistent with readOnly this.readOnlyMode = getEnv('clickhouseReadOnly', { dataSource, preAggregations }); @@ -172,6 +179,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { username, password, database, + application, exportBucket: this.getExportBucket(dataSource, preAggregations), readOnly: !!config.readOnly, requestTimeout, @@ -242,6 +250,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { username: this.config.username, password: this.config.password, database: this.config.database, + application: this.config.application, compression: this.config.compression, clickhouse_settings: this.config.clickhouseSettings, request_timeout: this.config.requestTimeout,