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,