From 53b43d826530e3bceae8bb798a03de0d1372429b Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 11:35:24 +0200 Subject: [PATCH 1/7] refactor(backend-shared): fix getEnv return type inference Remove explicit `Record any>` annotation from `variables` and use `satisfies` to preserve inferred types. Change `getEnv` to use rest parameters so `ReturnType` resolves to actual types (boolean, string, number, etc.) instead of `any`. --- packages/cubejs-backend-shared/src/env.ts | 12 +++---- .../test/db_env_multi.test.ts | 36 +++++++++---------- .../test/db_env_single.test.ts | 30 ++++++++-------- .../src/FireboltDriver.ts | 2 +- .../cubejs-mssql-driver/src/MSSqlDriver.ts | 2 +- .../src/orchestrator/PreAggregations.ts | 2 +- .../src/orchestrator/QueryQueue.ts | 2 +- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index cb9072b5af01b..8292d8b758384 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -159,7 +159,7 @@ function asBoolOrTime(input: string, envName: string): number | boolean { ); } -const variables: Record any> = { +const variables = { devMode: () => get('CUBEJS_DEV_MODE') .default('false') .asBoolStrict(), @@ -707,8 +707,8 @@ const variables: Record any> = { dbQueryTimeout: ({ dataSource, }: { - dataSource?: string, - } = {}) => { + dataSource: string, + }) => { const key = keyByDataSource('CUBEJS_DB_QUERY_TIMEOUT', dataSource); const value = process.env[key] || '10m'; return convertTimeStrToSeconds(value, key); @@ -2329,13 +2329,13 @@ const variables: Record any> = { .asString(), accessPolicyMaskNumber: () => get('CUBEJS_ACCESS_POLICY_MASK_NUMBER') .asString(), -}; +} satisfies Record unknown>; type Vars = typeof variables; -export function getEnv(key: T, opts?: Parameters): ReturnType { +export function getEnv(key: T, ...args: Parameters): ReturnType { if (key in variables) { - return variables[key](opts); + return (variables[key] as any)(...args); } throw new Error( 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..15333b6db4b1b 100644 --- a/packages/cubejs-backend-shared/test/db_env_multi.test.ts +++ b/packages/cubejs-backend-shared/test/db_env_multi.test.ts @@ -702,20 +702,20 @@ describe('Multiple datasources', () => { process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE = 'default1'; process.env.CUBEJS_DS_POSTGRES_DB_EXPORT_BUCKET_TYPE = 'postgres1'; process.env.CUBEJS_DS_WRONG_DB_EXPORT_BUCKET_TYPE = 'wrong1'; - expect(getEnv('dbExportBucketType', { dataSource: 'default' })).toEqual('default1'); - expect(getEnv('dbExportBucketType', { dataSource: 'postgres' })).toEqual('postgres1'); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: ['default1'] as any })).toEqual('default1'); + expect(getEnv('dbExportBucketType', { dataSource: 'postgres', supported: ['postgres1'] as any })).toEqual('postgres1'); + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); expect(getEnv('dbExportBucketType', { dataSource: 'default', - supported: ['default1'], + supported: ['default1'] as any, })).toEqual('default1'); expect(getEnv('dbExportBucketType', { dataSource: 'postgres', - supported: ['postgres1'], + supported: ['postgres1'] as any, })).toEqual('postgres1'); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); expect(() => getEnv('dbExportBucketType', { @@ -726,27 +726,27 @@ describe('Multiple datasources', () => { dataSource: 'postgres', supported: [], })).toThrow('The CUBEJS_DS_POSTGRES_DB_EXPORT_BUCKET_TYPE must be one of the [].'); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE = 'default2'; process.env.CUBEJS_DS_POSTGRES_DB_EXPORT_BUCKET_TYPE = 'postgres2'; process.env.CUBEJS_DS_WRONG_DB_EXPORT_BUCKET_TYPE = 'wrong2'; - expect(getEnv('dbExportBucketType', { dataSource: 'default' })).toEqual('default2'); - expect(getEnv('dbExportBucketType', { dataSource: 'postgres' })).toEqual('postgres2'); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: ['default2'] as any })).toEqual('default2'); + expect(getEnv('dbExportBucketType', { dataSource: 'postgres', supported: ['postgres2'] as any })).toEqual('postgres2'); + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); expect(getEnv('dbExportBucketType', { dataSource: 'default', - supported: ['default2'], + supported: ['default2'] as any, })).toEqual('default2'); expect(getEnv('dbExportBucketType', { dataSource: 'postgres', - supported: ['postgres2'], + supported: ['postgres2'] as any, })).toEqual('postgres2'); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); expect(() => getEnv('dbExportBucketType', { @@ -757,16 +757,16 @@ describe('Multiple datasources', () => { dataSource: 'postgres', supported: [], })).toThrow('The CUBEJS_DS_POSTGRES_DB_EXPORT_BUCKET_TYPE must be one of the [].'); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); delete process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE; delete process.env.CUBEJS_DS_POSTGRES_DB_EXPORT_BUCKET_TYPE; delete process.env.CUBEJS_DS_WRONG_DB_EXPORT_BUCKET_TYPE; - expect(getEnv('dbExportBucketType', { dataSource: 'default' })).toBeUndefined(); - expect(getEnv('dbExportBucketType', { dataSource: 'postgres' })).toBeUndefined(); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: [] })).toBeUndefined(); + expect(getEnv('dbExportBucketType', { dataSource: 'postgres', supported: [] })).toBeUndefined(); + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); expect(getEnv('dbExportBucketType', { @@ -777,7 +777,7 @@ describe('Multiple datasources', () => { dataSource: 'postgres', supported: [], })).toBeUndefined(); - expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong' })).toThrow( + expect(() => getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toThrow( 'The wrong data source is missing in the declared CUBEJS_DATASOURCES.' ); }); 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..7bd5d6a4d6417 100644 --- a/packages/cubejs-backend-shared/test/db_env_single.test.ts +++ b/packages/cubejs-backend-shared/test/db_env_single.test.ts @@ -441,20 +441,20 @@ describe('Single datasources', () => { test('getEnv("dbExportBucketType")', () => { process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE = 'default1'; - expect(getEnv('dbExportBucketType', { dataSource: 'default' })).toEqual('default1'); - expect(getEnv('dbExportBucketType', { dataSource: 'postgres' })).toEqual('default1'); - expect(getEnv('dbExportBucketType', { dataSource: 'wrong' })).toEqual('default1'); + expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: ['default1'] as any })).toEqual('default1'); + expect(getEnv('dbExportBucketType', { dataSource: 'postgres', supported: ['default1'] as any })).toEqual('default1'); + expect(getEnv('dbExportBucketType', { dataSource: 'wrong', supported: ['default1'] as any })).toEqual('default1'); expect(getEnv('dbExportBucketType', { dataSource: 'default', - supported: ['default1'], + supported: ['default1'] as any, })).toEqual('default1'); expect(getEnv('dbExportBucketType', { dataSource: 'postgres', - supported: ['default1'], + supported: ['default1'] as any, })).toEqual('default1'); expect(getEnv('dbExportBucketType', { dataSource: 'wrong', - supported: ['default1'], + supported: ['default1'] as any, })).toEqual('default1'); expect(() => getEnv('dbExportBucketType', { dataSource: 'default', @@ -470,20 +470,20 @@ describe('Single datasources', () => { })).toThrow('The CUBEJS_DB_EXPORT_BUCKET_TYPE must be one of the [].'); process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE = 'default2'; - expect(getEnv('dbExportBucketType', { dataSource: 'default' })).toEqual('default2'); - expect(getEnv('dbExportBucketType', { dataSource: 'postgres' })).toEqual('default2'); - expect(getEnv('dbExportBucketType', { dataSource: 'wrong' })).toEqual('default2'); + expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: ['default2'] as any })).toEqual('default2'); + expect(getEnv('dbExportBucketType', { dataSource: 'postgres', supported: ['default2'] as any })).toEqual('default2'); + expect(getEnv('dbExportBucketType', { dataSource: 'wrong', supported: ['default2'] as any })).toEqual('default2'); expect(getEnv('dbExportBucketType', { dataSource: 'default', - supported: ['default2'], + supported: ['default2'] as any, })).toEqual('default2'); expect(getEnv('dbExportBucketType', { dataSource: 'postgres', - supported: ['default2'], + supported: ['default2'] as any, })).toEqual('default2'); expect(getEnv('dbExportBucketType', { dataSource: 'wrong', - supported: ['default2'], + supported: ['default2'] as any, })).toEqual('default2'); expect(() => getEnv('dbExportBucketType', { dataSource: 'default', @@ -499,9 +499,9 @@ describe('Single datasources', () => { })).toThrow('The CUBEJS_DB_EXPORT_BUCKET_TYPE must be one of the [].'); delete process.env.CUBEJS_DB_EXPORT_BUCKET_TYPE; - expect(getEnv('dbExportBucketType', { dataSource: 'default' })).toBeUndefined(); - expect(getEnv('dbExportBucketType', { dataSource: 'postgres' })).toBeUndefined(); - expect(getEnv('dbExportBucketType', { dataSource: 'wrong' })).toBeUndefined(); + expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: [] })).toBeUndefined(); + expect(getEnv('dbExportBucketType', { dataSource: 'postgres', supported: [] })).toBeUndefined(); + expect(getEnv('dbExportBucketType', { dataSource: 'wrong', supported: [] })).toBeUndefined(); expect(getEnv('dbExportBucketType', { dataSource: 'default', supported: [], diff --git a/packages/cubejs-firebolt-driver/src/FireboltDriver.ts b/packages/cubejs-firebolt-driver/src/FireboltDriver.ts index fb29bb51c82ce..207f146c32ff3 100644 --- a/packages/cubejs-firebolt-driver/src/FireboltDriver.ts +++ b/packages/cubejs-firebolt-driver/src/FireboltDriver.ts @@ -93,7 +93,7 @@ export class FireboltDriver extends BaseDriver implements DriverInterface { this.config = { readOnly: true, - requestTimeout: getEnv('dbQueryTimeout') * 1000, + requestTimeout: getEnv('dbQueryTimeout', { dataSource }) * 1000, apiEndpoint: getEnv('fireboltApiEndpoint', { dataSource }) || 'api.app.firebolt.io', ...config, diff --git a/packages/cubejs-mssql-driver/src/MSSqlDriver.ts b/packages/cubejs-mssql-driver/src/MSSqlDriver.ts index f9b750ab50283..2dba961aa7350 100644 --- a/packages/cubejs-mssql-driver/src/MSSqlDriver.ts +++ b/packages/cubejs-mssql-driver/src/MSSqlDriver.ts @@ -118,7 +118,7 @@ export class MSSqlDriver extends BaseDriver implements DriverInterface { user: getEnv('dbUser', { dataSource }), password: getEnv('dbPass', { dataSource }), domain: getEnv('dbDomain', { dataSource }), - requestTimeout: getEnv('dbQueryTimeout') * 1000, + requestTimeout: getEnv('dbQueryTimeout', { dataSource }) * 1000, options: { encrypt: getEnv('dbSsl', { dataSource }), useUTC: true diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts index 71eea71b64cf1..2c60beff74501 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregations.ts @@ -287,7 +287,7 @@ export class PreAggregations { this.touchTablePersistTime = options.touchTablePersistTime || getEnv('touchPreAggregationTimeout'); this.preAggBackoffMaxTime = options.preAggBackoffMaxTime || getEnv('preAggBackoffMaxTime'); this.dropPreAggregationsWithoutTouch = options.dropPreAggregationsWithoutTouch || getEnv('dropPreAggregationsWithoutTouch'); - this.usedTablePersistTime = options.usedTablePersistTime || getEnv('dbQueryTimeout'); + this.usedTablePersistTime = options.usedTablePersistTime || getEnv('dbQueryTimeout', { dataSource: 'default' }); this.externalRefresh = options.externalRefresh; /** diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/QueryQueue.ts b/packages/cubejs-query-orchestrator/src/orchestrator/QueryQueue.ts index 873747bcd332b..7a3bd1d630dbe 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/QueryQueue.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/QueryQueue.ts @@ -112,7 +112,7 @@ export class QueryQueue { ) { this.concurrency = options.concurrency || 2; this.continueWaitTimeout = options.continueWaitTimeout || 5; - this.executionTimeout = options.executionTimeout || getEnv('dbQueryTimeout'); + this.executionTimeout = options.executionTimeout || getEnv('dbQueryTimeout', { dataSource: 'default' }); this.orphanedTimeout = options.orphanedTimeout || 120; this.heartBeatInterval = options.heartBeatInterval || 30; From e1ed03e89b33d73a7230b9df85c47b55c751ac9e Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 17:28:24 +0200 Subject: [PATCH 2/7] chore: fix --- packages/cubejs-api-gateway/src/sql-server.ts | 4 +- .../cubejs-api-gateway/src/types/strings.ts | 11 +---- .../cubejs-athena-driver/src/AthenaDriver.ts | 2 +- packages/cubejs-backend-shared/src/env.ts | 48 ++++++++++++++++--- packages/cubejs-backend-shared/src/index.ts | 1 + .../cubejs-backend-shared/src/shared-types.ts | 13 +++++ .../src/BigQueryDriver.ts | 6 ++- .../src/ClickHouseDriver.ts | 29 +++++------ .../src/DatabricksDriver.ts | 16 ++++--- .../cubejs-duckdb-driver/src/DuckDBDriver.ts | 10 ++-- .../src/FireboltDriver.ts | 15 ++++-- .../test/autostart.test.ts | 20 ++++++-- packages/cubejs-ksql-driver/src/KsqlDriver.ts | 15 ++++-- .../cubejs-pinot-driver/src/PinotDriver.ts | 4 +- .../cubejs-postgres-driver/src/PgClient.ts | 4 +- .../src/PrestoDriver.ts | 14 +++--- .../src/orchestrator/QueryOrchestrator.ts | 2 +- .../src/RedshiftDriver.ts | 32 ++++++++----- .../src/SnowflakeDriver.ts | 28 +++++------ 19 files changed, 180 insertions(+), 94 deletions(-) diff --git a/packages/cubejs-api-gateway/src/sql-server.ts b/packages/cubejs-api-gateway/src/sql-server.ts index b121530dc22b7..9a6810678db54 100644 --- a/packages/cubejs-api-gateway/src/sql-server.ts +++ b/packages/cubejs-api-gateway/src/sql-server.ts @@ -361,8 +361,8 @@ export class SQLServer { } protected createDefaultCheckSqlAuthFn(options: SQLServerOptions): CheckSQLAuthFn { - let allowedUser: string | null = options.sqlUser || getEnv('sqlUser'); - let allowedPassword: string | null = options.sqlPassword || getEnv('sqlPassword'); + let allowedUser: string | null = options.sqlUser || getEnv('sqlUser') || null; + let allowedPassword: string | null = options.sqlPassword || getEnv('sqlPassword') || null; if (!getEnv('devMode')) { if (!allowedUser) { diff --git a/packages/cubejs-api-gateway/src/types/strings.ts b/packages/cubejs-api-gateway/src/types/strings.ts index b8c24670d8dfb..8c34e1ddbe2de 100644 --- a/packages/cubejs-api-gateway/src/types/strings.ts +++ b/packages/cubejs-api-gateway/src/types/strings.ts @@ -100,15 +100,7 @@ type QueryOrderType = 'asc' | 'desc'; -/** - * ApiScopes data type. - */ -type ApiScopes = - 'graphql' | - 'meta' | - 'data' | - 'sql' | - 'jobs'; +export type { ApiScopes } from '@cubejs-backend/shared'; export { RequestType, @@ -121,5 +113,4 @@ export { FilterOperator, QueryTimeDimensionGranularity, QueryOrderType, - ApiScopes, }; diff --git a/packages/cubejs-athena-driver/src/AthenaDriver.ts b/packages/cubejs-athena-driver/src/AthenaDriver.ts index fd8d5f76da755..9e4f31b89faa3 100644 --- a/packages/cubejs-athena-driver/src/AthenaDriver.ts +++ b/packages/cubejs-athena-driver/src/AthenaDriver.ts @@ -87,7 +87,7 @@ export class AthenaDriver extends BaseDriver implements DriverInterface { private athena: Athena; - private schema: string; + private schema: string | undefined; /** * Class constructor. diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index 8292d8b758384..6a0aa46ea483e 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -2,6 +2,8 @@ import { get } from 'env-var'; import { displayCLIWarning } from './cli'; import { isNativeSupported } from './platform'; +import type { LogLevel } from './logger'; +import type { ApiScopes, ExportBucketType } from './shared-types'; export class InvalidConfiguration extends Error { public constructor(key: string, value: any, description: string) { @@ -163,7 +165,24 @@ const variables = { devMode: () => get('CUBEJS_DEV_MODE') .default('false') .asBoolStrict(), - logLevel: () => get('CUBEJS_LOG_LEVEL').asString(), + logLevel: (): LogLevel | undefined => { + const value = get('CUBEJS_LOG_LEVEL').asString(); + if (value) { + switch (value.toLowerCase()) { + case 'trace': + case 'info': + case 'warn': + case 'error': + break; + // not used, but let's allow + case 'debug': + break; + default: + throw new InvalidConfiguration('CUBEJS_LOG_LEVEL', value, 'Must be one of: trace, debug, info, warn, error'); + } + } + return value as LogLevel | undefined; + }, port: () => asPortOrSocket(process.env.PORT || '4000', 'PORT'), tls: () => get('CUBEJS_ENABLE_TLS') .default('false') @@ -847,20 +866,20 @@ const variables = { /** * Export bucket storage type. */ - dbExportBucketType: ({ + dbExportBucketType: ({ supported, dataSource, }: { - supported: ('s3' | 'gcp' | 'azure')[], + supported: readonly T[], dataSource: string, - }) => { + }): T | undefined => { const val = process.env[ keyByDataSource('CUBEJS_DB_EXPORT_BUCKET_TYPE', dataSource) - ]; + ] as T | undefined; if ( val && supported && - supported.indexOf(<'s3' | 'gcp' | 'azure'>val) === -1 + supported.indexOf(val) === -1 ) { throw new TypeError( `The ${ @@ -868,6 +887,7 @@ const variables = { } must be one of the [${supported.join(', ')}].` ); } + return val; }, @@ -2186,7 +2206,7 @@ const variables = { cacheAndQueueDriver: () => get('CUBEJS_CACHE_AND_QUEUE_DRIVER') .asString(), defaultApiScope: () => get('CUBEJS_DEFAULT_API_SCOPES') - .asArray(','), + .asArray(',') as ApiScopes[] | undefined, jwkUrl: () => get('CUBEJS_JWK_URL') .asString(), jwtKey: () => get('CUBEJS_JWT_KEY') @@ -2333,6 +2353,20 @@ const variables = { type Vars = typeof variables; +export function getEnvFn(key: T): Vars[T] { + if (key in variables) { + return variables[key] as Vars[T]; + } + + throw new Error( + `Unsupported env variable: "${key}"`, + ); +} + +/** + * @deprecated Use getEnvFn instead. TypeScript cannot infer return types correctly + * for generic env functions through this wrapper, as ReturnType<> erases generics. + */ export function getEnv(key: T, ...args: Parameters): ReturnType { if (key in variables) { return (variables[key] as any)(...args); diff --git a/packages/cubejs-backend-shared/src/index.ts b/packages/cubejs-backend-shared/src/index.ts index 77ec6de45053e..a23236316c731 100644 --- a/packages/cubejs-backend-shared/src/index.ts +++ b/packages/cubejs-backend-shared/src/index.ts @@ -1,5 +1,6 @@ export { getEnv, + getEnvFn, assertDataSource, keyByDataSource, isDockerImage, diff --git a/packages/cubejs-backend-shared/src/shared-types.ts b/packages/cubejs-backend-shared/src/shared-types.ts index 52eb4fc09db74..9f6ba9bfb0d51 100644 --- a/packages/cubejs-backend-shared/src/shared-types.ts +++ b/packages/cubejs-backend-shared/src/shared-types.ts @@ -20,3 +20,16 @@ no-cache — AKA “forceRefresh” Returns fresh data from the database, even if it takes minutes and many “Continue wait” intervals */ export type CacheMode = 'stale-if-slow' | 'stale-while-revalidate' | 'must-revalidate' | 'no-cache'; + +export type ApiScopes = + 'graphql' | + 'meta' | + 'data' | + 'sql' | + 'jobs'; + +export type ExportBucketType = + 's3' | + 'gcp' | + 'gcs' | + 'azure'; diff --git a/packages/cubejs-bigquery-driver/src/BigQueryDriver.ts b/packages/cubejs-bigquery-driver/src/BigQueryDriver.ts index 69b8aca39761b..4a6bd26a112bd 100644 --- a/packages/cubejs-bigquery-driver/src/BigQueryDriver.ts +++ b/packages/cubejs-bigquery-driver/src/BigQueryDriver.ts @@ -111,6 +111,8 @@ export class BigQueryDriver extends BaseDriver implements DriverInterface { config.dataSource || assertDataSource('default'); + const bigqueryCredentials = getEnv('bigqueryCredentials', { dataSource }); + this.options = { scopes: [ 'https://www.googleapis.com/auth/bigquery', @@ -118,10 +120,10 @@ export class BigQueryDriver extends BaseDriver implements DriverInterface { ], projectId: getEnv('bigqueryProjectId', { dataSource }), keyFilename: getEnv('bigqueryKeyFile', { dataSource }), - credentials: getEnv('bigqueryCredentials', { dataSource }) + credentials: bigqueryCredentials ? JSON.parse( Buffer.from( - getEnv('bigqueryCredentials', { dataSource }), + bigqueryCredentials, 'base64', ).toString('utf8') ) diff --git a/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts b/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts index 0e9ac262185a4..ae09f0a229e65 100644 --- a/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts +++ b/packages/cubejs-clickhouse-driver/src/ClickHouseDriver.ts @@ -7,6 +7,7 @@ import { getEnv, assertDataSource, + ExportBucketType, } from '@cubejs-backend/shared'; import { BaseDriver, @@ -35,7 +36,7 @@ import sqlstring from 'sqlstring'; import { transformRow, transformStreamRow } from './HydrationStream'; -const SUPPORTED_BUCKET_TYPES = ['s3']; +const SUPPORTED_BUCKET_TYPES: ExportBucketType[] = ['s3']; const ClickhouseTypeToGeneric: Record = { enum: 'text', @@ -94,14 +95,14 @@ export interface ClickHouseDriverOptions { } interface ClickhouseDriverExportRequiredAWS { - bucketType: 's3', + bucketType: ExportBucketType, bucketName: string, region: string, } interface ClickhouseDriverExportKeySecretAWS extends ClickhouseDriverExportRequiredAWS { - keyId: string, - secretKey: string, + keyId?: string, + secretKey?: string, } interface ClickhouseDriverExportAWS extends ClickhouseDriverExportKeySecretAWS { @@ -109,8 +110,8 @@ interface ClickhouseDriverExportAWS extends ClickhouseDriverExportKeySecretAWS { type ClickHouseDriverConfig = { url: string, - username: string, - password: string, + username?: string, + password?: string, readOnly: boolean, database: string, requestTimeout: number, @@ -505,7 +506,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { protected getExportBucket( dataSource: string, ): ClickhouseDriverExportAWS | null { - const requiredExportBucket: ClickhouseDriverExportRequiredAWS = { + const requiredExportBucket: Partial = { bucketType: getEnv('dbExportBucketType', { supported: SUPPORTED_BUCKET_TYPES, dataSource, @@ -514,7 +515,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { region: getEnv('dbExportBucketAwsRegion', { dataSource }), }; - const exportBucket: ClickhouseDriverExportAWS = { + const exportBucket: Partial = { ...requiredExportBucket, keyId: getEnv('dbExportBucketAwsKey', { dataSource }), secretKey: getEnv('dbExportBucketAwsSecret', { dataSource }), @@ -536,7 +537,7 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { ); } - return exportBucket; + return exportBucket as ClickhouseDriverExportAWS; } return null; @@ -620,13 +621,13 @@ export class ClickHouseDriver extends BaseDriver implements DriverInterface { await this.command(formattedQuery); + const { keyId, secretKey, region } = this.config.exportBucket; const csvFile = await this.extractUnloadedFilesFromS3( { - credentials: { - accessKeyId: this.config.exportBucket.keyId, - secretAccessKey: this.config.exportBucket.secretKey, - }, - region: this.config.exportBucket.region, + credentials: keyId && secretKey + ? { accessKeyId: keyId, secretAccessKey: secretKey } + : undefined, + region, }, bucketName, exportPrefix, diff --git a/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts b/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts index 0ff044f80724a..e19d4bce23e45 100644 --- a/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts +++ b/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts @@ -5,7 +5,7 @@ */ import fetch from 'node-fetch'; -import { assertDataSource, getEnv, LoggerFn } from '@cubejs-backend/shared'; +import { assertDataSource, getEnv, getEnvFn, LoggerFn, ExportBucketType } from '@cubejs-backend/shared'; import { DatabaseStructure, DriverCapabilities, @@ -25,7 +25,7 @@ import { resolveJDBCDriver } from './helpers'; -const SUPPORTED_BUCKET_TYPES = ['s3', 'gcs', 'azure']; +const SUPPORTED_BUCKET_TYPES = ['s3', 'gcs', 'azure'] as const; export type DatabricksDriverConfiguration = JDBCDriverConfiguration & { @@ -37,7 +37,7 @@ export type DatabricksDriverConfiguration = JDBCDriverConfiguration & /** * Export bucket type. */ - bucketType?: string, + bucketType?: 's3' | 'gcs' | 'azure', /** * Export bucket path. @@ -204,10 +204,14 @@ export class DatabricksDriver extends JDBCDriver { assertDataSource('default'); let showSparkProtocolWarn = false; - let url: string = + let url = conf?.url || getEnv('databricksUrl', { dataSource }) || getEnv('jdbcUrl', { dataSource }); + if (!url) { + throw new Error('url is required for Databricks'); + } + if (url.indexOf('jdbc:spark://') !== -1) { showSparkProtocolWarn = true; url = url.replace('jdbc:spark://', 'jdbc:databricks://'); @@ -265,7 +269,7 @@ export class DatabricksDriver extends JDBCDriver { // common export bucket config bucketType: conf?.bucketType || - getEnv('dbExportBucketType', { supported: SUPPORTED_BUCKET_TYPES, dataSource }), + getEnvFn('dbExportBucketType')({ supported: SUPPORTED_BUCKET_TYPES, dataSource }), exportBucket: conf?.exportBucket || getEnv('dbExportBucket', { dataSource }), @@ -759,7 +763,7 @@ export class DatabricksDriver extends JDBCDriver { * export bucket data. */ public async unload(tableName: string, options: UnloadOptions) { - if (!SUPPORTED_BUCKET_TYPES.includes(this.config.bucketType as string)) { + if (!this.config.bucketType || !SUPPORTED_BUCKET_TYPES.includes(this.config.bucketType)) { throw new Error(`Unsupported export bucket type: ${ this.config.bucketType }`); diff --git a/packages/cubejs-duckdb-driver/src/DuckDBDriver.ts b/packages/cubejs-duckdb-driver/src/DuckDBDriver.ts index 451ccf3d75811..267ed69e7a303 100644 --- a/packages/cubejs-duckdb-driver/src/DuckDBDriver.ts +++ b/packages/cubejs-duckdb-driver/src/DuckDBDriver.ts @@ -8,7 +8,7 @@ import { TableStructure, TableColumnQueryResult, } from '@cubejs-backend/base-driver'; -import { getEnv } from '@cubejs-backend/shared'; +import { assertDataSource, getEnv } from '@cubejs-backend/shared'; import { promisify } from 'util'; import * as stream from 'stream'; import { Connection, Database } from 'duckdb'; @@ -40,15 +40,19 @@ const DuckDBToGenericType: Record = { }; export class DuckDBDriver extends BaseDriver implements DriverInterface { + protected readonly config: DuckDBDriverConfiguration & { dataSource: string }; + protected initPromise: Promise | null = null; - private readonly schema: string; + private readonly schema: string | undefined; public constructor( - protected readonly config: DuckDBDriverConfiguration = {}, + config: DuckDBDriverConfiguration = {}, ) { super(); + const dataSource = config.dataSource || assertDataSource('default'); + this.config = { ...config, dataSource }; this.schema = this.config.schema || getEnv('duckdbSchema', this.config); } diff --git a/packages/cubejs-firebolt-driver/src/FireboltDriver.ts b/packages/cubejs-firebolt-driver/src/FireboltDriver.ts index 207f146c32ff3..25779f5a14bfb 100644 --- a/packages/cubejs-firebolt-driver/src/FireboltDriver.ts +++ b/packages/cubejs-firebolt-driver/src/FireboltDriver.ts @@ -87,9 +87,18 @@ export class FireboltDriver extends BaseDriver implements DriverInterface { assertDataSource('default'); const username = getEnv('dbUser', { dataSource }); - const auth = username.includes('@') - ? { username, password: getEnv('dbPass', { dataSource }) } - : { client_id: username, client_secret: getEnv('dbPass', { dataSource }) }; + if (!username) { + throw new Error('username is required for Firebolt'); + } + + const password = getEnv('dbPass', { dataSource }); + if (!password) { + throw new Error('password is required for Firebolt'); + } + + const auth: ConnectionOptions['auth'] = username.includes('@') + ? { username, password } + : { client_id: username, client_secret: password }; this.config = { readOnly: true, diff --git a/packages/cubejs-firebolt-driver/test/autostart.test.ts b/packages/cubejs-firebolt-driver/test/autostart.test.ts index a746a589fd77c..6f833c66cfb01 100644 --- a/packages/cubejs-firebolt-driver/test/autostart.test.ts +++ b/packages/cubejs-firebolt-driver/test/autostart.test.ts @@ -1,7 +1,7 @@ import { assertDataSource, getEnv } from '@cubejs-backend/shared'; import { DriverTests } from '@cubejs-backend/testing-shared'; -import { Firebolt } from 'firebolt-sdk'; +import { Firebolt, ConnectionOptions } from 'firebolt-sdk'; import { version } from 'firebolt-sdk/package.json'; import { FireboltDriver } from '../src'; @@ -37,10 +37,22 @@ describe('FireboltDriver autostart', () => { const dataSource = assertDataSource('default'); const username = getEnv('dbUser', { dataSource }); - const auth = username.includes('@') - ? { username, password: getEnv('dbPass', { dataSource }) } - : { client_id: username, client_secret: getEnv('dbPass', { dataSource }) }; + if (!username) { + throw new Error('username is required for Firebolt'); + } + + const password = getEnv('dbPass', { dataSource }); + if (!password) { + throw new Error('password is required for Firebolt'); + } + + const auth: ConnectionOptions['auth'] = username.includes('@') + ? { username, password } + : { client_id: username, client_secret: password }; const engineName = getEnv('fireboltEngineName', { dataSource }); + if (!engineName) { + throw new Error('engineName is required for Firebolt'); + } const firebolt = Firebolt({ apiEndpoint: getEnv('fireboltApiEndpoint', { dataSource }) || 'api.app.firebolt.io', }); diff --git a/packages/cubejs-ksql-driver/src/KsqlDriver.ts b/packages/cubejs-ksql-driver/src/KsqlDriver.ts index 0af5c0f0d4089..11e57f6770466 100644 --- a/packages/cubejs-ksql-driver/src/KsqlDriver.ts +++ b/packages/cubejs-ksql-driver/src/KsqlDriver.ts @@ -20,8 +20,8 @@ import { KsqlQuery } from './KsqlQuery'; type KsqlDriverOptions = { url: string, - username: string, - password: string, + username?: string, + password?: string, kafkaHost?: string, kafkaUser?: string, kafkaPassword?: string, @@ -117,8 +117,13 @@ export class KsqlDriver extends BaseDriver implements DriverInterface { config.dataSource || assertDataSource('default'); + const url = getEnv('dbUrl', { dataSource }); + if (!url) { + throw new Error('CUBEJS_DB_URL is required for ksqlDB'); + } + this.config = { - url: getEnv('dbUrl', { dataSource }), + url, username: getEnv('dbUser', { dataSource }), password: getEnv('dbPass', { dataSource }), kafkaHost: getEnv('dbKafkaHost', { dataSource }), @@ -150,10 +155,10 @@ export class KsqlDriver extends BaseDriver implements DriverInterface { const url = `${this.config.url}${path}`; try { return await axios.post(url, body, { - auth: { + auth: this.config.username && this.config.password ? { username: this.config.username, password: this.config.password, - }, + } : undefined, }); } catch (e) { throw new Error( diff --git a/packages/cubejs-pinot-driver/src/PinotDriver.ts b/packages/cubejs-pinot-driver/src/PinotDriver.ts index fa7ed2e3d7c9b..9444f009928e7 100644 --- a/packages/cubejs-pinot-driver/src/PinotDriver.ts +++ b/packages/cubejs-pinot-driver/src/PinotDriver.ts @@ -25,10 +25,10 @@ import { PinotQuery } from './PinotQuery'; export type PinotDriverConfiguration = { host?: string; - port?: string; + port?: string | number; user?: string; database?: string; - basicAuth?: { user: string, password: string }; + basicAuth?: { user?: string, password?: string }; authToken?: string; ssl?: string | TLSConnectionOptions; dataSource?: string; diff --git a/packages/cubejs-postgres-driver/src/PgClient.ts b/packages/cubejs-postgres-driver/src/PgClient.ts index 841f3d2c97d03..1191a1622abf5 100644 --- a/packages/cubejs-postgres-driver/src/PgClient.ts +++ b/packages/cubejs-postgres-driver/src/PgClient.ts @@ -14,5 +14,7 @@ export class PgClient extends Client { } } -export type PgClientConfig = ClientConfig; +export type PgClientConfig = ClientConfig & { + password?: string; +}; export type PgQueryResult = QueryResult; diff --git a/packages/cubejs-prestodb-driver/src/PrestoDriver.ts b/packages/cubejs-prestodb-driver/src/PrestoDriver.ts index 925e0b7e015e8..5d38491338ba5 100644 --- a/packages/cubejs-prestodb-driver/src/PrestoDriver.ts +++ b/packages/cubejs-prestodb-driver/src/PrestoDriver.ts @@ -15,7 +15,8 @@ import { } from '@cubejs-backend/base-driver'; import { getEnv, - assertDataSource, + getEnvFn, + assertDataSource, ExportBucketType, } from '@cubejs-backend/shared'; import { Transform, TransformCallback } from 'stream'; @@ -41,7 +42,7 @@ export type PrestoDriverExportBucket = { export type PrestoDriverConfiguration = PrestoDriverExportBucket & { host?: string; - port?: string; + port?: string | number; catalog?: string; schema?: string; user?: string; @@ -54,7 +55,8 @@ export type PrestoDriverConfiguration = PrestoDriverExportBucket & { queryTimeout?: number; }; -const SUPPORTED_BUCKET_TYPES = ['gcs', 's3']; +const SUPPORTED_BUCKET_TYPES = ['gcs', 's3'] as const; + /** * Presto driver class. */ @@ -105,9 +107,9 @@ export class PrestoDriver extends BaseDriver implements DriverInterface { getEnv('dbSchema', { dataSource }), user: dbUser, ...(authToken ? { custom_auth: `Bearer ${authToken}` } : {}), - ...(dbPassword ? { basic_auth: { user: dbUser, password: dbPassword } } : {}), + ...(dbUser && dbPassword ? { basic_auth: { user: dbUser, password: dbPassword } } : {}), ssl: this.getSslOptions(dataSource), - bucketType: getEnv('dbExportBucketType', { supported: SUPPORTED_BUCKET_TYPES, dataSource }), + bucketType: getEnvFn('dbExportBucketType')({ supported: SUPPORTED_BUCKET_TYPES, dataSource }), exportBucket: getEnv('dbExportBucket', { dataSource }), accessKeyId: getEnv('dbExportBucketAwsKey', { dataSource }), secretAccessKey: getEnv('dbExportBucketAwsSecret', { dataSource }), @@ -309,7 +311,7 @@ export class PrestoDriver extends BaseDriver implements DriverInterface { throw new Error('Export bucket is not configured.'); } - if (!SUPPORTED_BUCKET_TYPES.includes(this.config.bucketType as string)) { + if (!this.config.bucketType || !SUPPORTED_BUCKET_TYPES.includes(this.config.bucketType)) { throw new Error(`Unsupported export bucket type: ${ this.config.bucketType }`); diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts b/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts index e257579fa5397..13ed8401cb264 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts @@ -43,7 +43,7 @@ function detectQueueAndCacheDriver(options: QueryOrchestratorOptions): CacheAndQ return options.cacheAndQueueDriver; } - const cacheAndQueueDriver = getEnv('cacheAndQueueDriver'); + const cacheAndQueueDriver = getEnv('cacheAndQueueDriver') as CacheAndQueryDriverType | undefined; if (cacheAndQueueDriver) { return cacheAndQueueDriver; } diff --git a/packages/cubejs-redshift-driver/src/RedshiftDriver.ts b/packages/cubejs-redshift-driver/src/RedshiftDriver.ts index 46aee81aa3973..9c31f4df6952f 100644 --- a/packages/cubejs-redshift-driver/src/RedshiftDriver.ts +++ b/packages/cubejs-redshift-driver/src/RedshiftDriver.ts @@ -4,7 +4,7 @@ * @fileoverview The `RedshiftDriver` and related types declaration. */ -import { assertDataSource, getEnv } from '@cubejs-backend/shared'; +import { assertDataSource, getEnv, getEnvFn, ExportBucketType } from '@cubejs-backend/shared'; import { PostgresDriver, PostgresDriverConfiguration, type PgQueryResult, PgClient, PgClientConfig } from '@cubejs-backend/postgres-driver'; import { DatabaseStructure, @@ -93,7 +93,11 @@ export class RedshiftDriver extends PostgresDriver const clusterIdentifier = getEnv('redshiftClusterIdentifier', { dataSource }); const dbPass = getEnv('dbPass', { dataSource }); const dbUser = getEnv('dbUser', { dataSource }); + const dbName = getEnv('dbName', { dataSource }); + if (!dbName) { + throw new Error('CUBEJS_DB_NAME is required for Redshift'); + } let credentialsProvider: RedshiftCredentialsProvider; @@ -106,9 +110,19 @@ export class RedshiftDriver extends PostgresDriver dbName, }); } else { + const user = config.user || dbUser; + if (!user) { + throw new Error('CUBEJS_DB_USER is required for Redshift'); + } + + const password = config.password || dbPass; + if (!password) { + throw new Error('CUBEJS_DB_PASS is required for Redshift'); + } + credentialsProvider = new RedshiftPlainCredentialsProvider( - config.user || dbUser, - config.password || dbPass, + user, + password, dbName ); } @@ -331,11 +345,9 @@ export class RedshiftDriver extends PostgresDriver protected getExportBucket( dataSource: string, ): RedshiftDriverExportAWS | undefined { - const supportedBucketTypes = ['s3']; - const requiredExportBucket: Partial = { - bucketType: getEnv('dbExportBucketType', { - supported: supportedBucketTypes, + bucketType: getEnvFn('dbExportBucketType')({ + supported: ['s3'], dataSource, }), bucketName: getEnv('dbExportBucket', { dataSource }), @@ -350,12 +362,6 @@ export class RedshiftDriver extends PostgresDriver }; if (exportBucket.bucketType) { - if (!supportedBucketTypes.includes(exportBucket.bucketType)) { - throw new Error( - `Unsupported EXPORT_BUCKET_TYPE, supported: ${supportedBucketTypes.join(',')}` - ); - } - // Make sure the required keys are set const emptyRequiredKeys = Object.keys(requiredExportBucket) .filter((key: string) => requiredExportBucket[key] === undefined); diff --git a/packages/cubejs-snowflake-driver/src/SnowflakeDriver.ts b/packages/cubejs-snowflake-driver/src/SnowflakeDriver.ts index beb8e758d0069..c2f9f2db87c5f 100644 --- a/packages/cubejs-snowflake-driver/src/SnowflakeDriver.ts +++ b/packages/cubejs-snowflake-driver/src/SnowflakeDriver.ts @@ -4,7 +4,7 @@ * @fileoverview The `SnowflakeDriver` and related types declaration. */ -import { assertDataSource, getEnv, } from '@cubejs-backend/shared'; +import { assertDataSource, getEnv, ExportBucketType } from '@cubejs-backend/shared'; import snowflake, { Column, Connection, RowStatement } from 'snowflake-sdk'; import { BaseDriver, @@ -26,7 +26,7 @@ import crypto from 'crypto'; import { S3ClientConfig } from '@aws-sdk/client-s3'; import { HydrationMap, HydrationStream } from './HydrationStream'; -const SUPPORTED_BUCKET_TYPES = ['s3', 'gcs', 'azure']; +const SUPPORTED_BUCKET_TYPES: ExportBucketType[] = ['s3', 'gcs', 'azure']; type HydrationConfiguration = { types: string[], toValue: (column: Column) => ((value: any) => any) | null @@ -109,23 +109,23 @@ const SnowflakeToGenericType: Record = { // User can create own stage to pass permission restrictions. interface SnowflakeDriverExportAWS { bucketType: 's3', - bucketName: string, + bucketName?: string, keyId?: string, secretKey?: string, - region: string, + region?: string, integrationName?: string, } interface SnowflakeDriverExportGCS { bucketType: 'gcs', - integrationName: string, - bucketName: string, - credentials: any, + integrationName?: string, + bucketName?: string, + credentials?: any, } interface SnowflakeDriverExportAzure { bucketType: 'azure', - bucketName: string, + bucketName?: string, azureKey?: string, sasToken?: string, integrationName?: string, @@ -148,9 +148,9 @@ export type SnowflakeDriverExportBucket = SnowflakeDriverExportAWS | SnowflakeDr interface SnowflakeDriverOptions { host?: string, - account: string, - username: string, - password: string, + account?: string, + username?: string, + password?: string, region?: string, warehouse?: string, role?: string, @@ -275,7 +275,7 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface { privateKey = privateKeyObject.export({ format: 'pem', type: 'pkcs8' - }); + }).toString(); } } @@ -572,7 +572,7 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface { if (!this.config.exportBucket) { throw new Error('Export bucket is not configured.'); } - if (!SUPPORTED_BUCKET_TYPES.includes(this.config.exportBucket.bucketType as string)) { + if (!SUPPORTED_BUCKET_TYPES.includes(this.config.exportBucket.bucketType)) { throw new Error(`Unsupported export bucket type: ${ this.config.exportBucket.bucketType }`); @@ -735,7 +735,7 @@ export class SnowflakeDriver extends BaseDriver implements DriverInterface { private exportOptionsClause(options: UnloadOptions): string { const { bucketType } = this.config.exportBucket; - const optionsToExport: Record = { + const optionsToExport: Record = { HEADER: 'false', INCLUDE_QUERY_ID: 'true', MAX_FILE_SIZE: (options.maxFileSize * 1024 * 1024).toFixed(), From a16eec9a5ca72a3cb1c75c5a5c33c0f4a8250c76 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 17:41:10 +0200 Subject: [PATCH 3/7] chore: simplify --- packages/cubejs-backend-shared/src/env.ts | 29 ++++++++++++++++++- .../cubejs-pinot-driver/src/PinotDriver.ts | 9 ++---- .../src/PrestoDriver.ts | 9 +++--- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index 6a0aa46ea483e..54ca3d37bb463 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -1,4 +1,4 @@ -/* eslint-disable no-restricted-syntax */ +/* eslint-disable no-restricted-syntax,no-use-before-define */ import { get } from 'env-var'; import { displayCLIWarning } from './cli'; import { isNativeSupported } from './platform'; @@ -551,6 +551,33 @@ const variables = { process.env[keyByDataSource('CUBEJS_DB_PASS', dataSource)] ), + /** + * Small helper to simplify getting basicAuth across drivers + */ + dbBasicAuth: ({ + dataSource, + }: { + dataSource: string, + }): { user: string; password: string } | undefined => { + const user = getEnvFn('dbUser')({ + dataSource, + }); + const password = getEnvFn('dbPass')({ + dataSource, + }); + if (user && password) { + return { user, password }; + } + + if (user || password) { + throw new Error( + `Both ${keyByDataSource('CUBEJS_DB_USER', dataSource)} and ${keyByDataSource('CUBEJS_DB_PASS', dataSource)} must be set for basic auth` + ); + } + + return undefined; + }, + /** * Database name. */ diff --git a/packages/cubejs-pinot-driver/src/PinotDriver.ts b/packages/cubejs-pinot-driver/src/PinotDriver.ts index 9444f009928e7..cce9cdb8855a7 100644 --- a/packages/cubejs-pinot-driver/src/PinotDriver.ts +++ b/packages/cubejs-pinot-driver/src/PinotDriver.ts @@ -28,7 +28,7 @@ export type PinotDriverConfiguration = { port?: string | number; user?: string; database?: string; - basicAuth?: { user?: string, password?: string }; + basicAuth?: { user: string, password: string }; authToken?: string; ssl?: string | TLSConnectionOptions; dataSource?: string; @@ -101,12 +101,7 @@ export class PinotDriver extends BaseDriver implements DriverInterface { port: getEnv('dbPort', { dataSource }), user: getEnv('dbUser', { dataSource }), database: getEnv('dbName', { dataSource }), - basicAuth: getEnv('dbPass', { dataSource }) - ? { - user: getEnv('dbUser', { dataSource }), - password: getEnv('dbPass', { dataSource }), - } - : undefined, + basicAuth: getEnv('dbBasicAuth', { dataSource }), authToken: getEnv('pinotAuthToken', { dataSource }), ssl: this.getSslOptions(dataSource), nullHandling: getEnv('pinotNullHandling', { dataSource }), diff --git a/packages/cubejs-prestodb-driver/src/PrestoDriver.ts b/packages/cubejs-prestodb-driver/src/PrestoDriver.ts index 5d38491338ba5..b9b65a7591cc6 100644 --- a/packages/cubejs-prestodb-driver/src/PrestoDriver.ts +++ b/packages/cubejs-prestodb-driver/src/PrestoDriver.ts @@ -86,11 +86,10 @@ export class PrestoDriver extends BaseDriver implements DriverInterface { config.dataSource || assertDataSource('default'); - const dbUser = getEnv('dbUser', { dataSource }); - const dbPassword = getEnv('dbPass', { dataSource }); + const basicAuth = getEnv('dbBasicAuth', { dataSource }); const authToken = getEnv('prestoAuthToken', { dataSource }); - if (authToken && dbPassword) { + if (authToken && basicAuth) { throw new Error('Both user/password and auth token are set. Please remove password or token.'); } @@ -105,9 +104,9 @@ export class PrestoDriver extends BaseDriver implements DriverInterface { schema: getEnv('dbName', { dataSource }) || getEnv('dbSchema', { dataSource }), - user: dbUser, + user: getEnv('dbUser', { dataSource }), ...(authToken ? { custom_auth: `Bearer ${authToken}` } : {}), - ...(dbUser && dbPassword ? { basic_auth: { user: dbUser, password: dbPassword } } : {}), + ...(basicAuth ? { basic_auth: basicAuth } : {}), ssl: this.getSslOptions(dataSource), bucketType: getEnvFn('dbExportBucketType')({ supported: SUPPORTED_BUCKET_TYPES, dataSource }), exportBucket: getEnv('dbExportBucket', { dataSource }), From 57490a3d90bd9706db67a35f70500870442ec1fe Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 17:52:05 +0200 Subject: [PATCH 4/7] chore: fixes --- .../cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts | 2 +- packages/cubejs-jdbc-driver/src/types.ts | 2 +- packages/cubejs-server/src/server.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts b/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts index e19d4bce23e45..88ceef1ca6fb3 100644 --- a/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts +++ b/packages/cubejs-databricks-jdbc-driver/src/DatabricksDriver.ts @@ -5,7 +5,7 @@ */ import fetch from 'node-fetch'; -import { assertDataSource, getEnv, getEnvFn, LoggerFn, ExportBucketType } from '@cubejs-backend/shared'; +import { assertDataSource, getEnv, getEnvFn, LoggerFn } from '@cubejs-backend/shared'; import { DatabaseStructure, DriverCapabilities, diff --git a/packages/cubejs-jdbc-driver/src/types.ts b/packages/cubejs-jdbc-driver/src/types.ts index 16aabec1c3cb4..20bab2dd4ad80 100644 --- a/packages/cubejs-jdbc-driver/src/types.ts +++ b/packages/cubejs-jdbc-driver/src/types.ts @@ -1,7 +1,7 @@ import { PoolOptions } from '@cubejs-backend/shared'; export type JDBCDriverConfiguration = { - database: string, + database?: string, dbType: string, url: string, drivername: string, diff --git a/packages/cubejs-server/src/server.ts b/packages/cubejs-server/src/server.ts index 45b7da627b252..9dd88ab1253e4 100644 --- a/packages/cubejs-server/src/server.ts +++ b/packages/cubejs-server/src/server.ts @@ -49,7 +49,7 @@ type RequireOne = { export class CubejsServer { protected readonly core: CubeCore; - protected readonly config: RequireOne; + protected readonly config: RequireOne; protected server: GracefulHttpServer | null = null; @@ -64,7 +64,7 @@ export class CubejsServer { ...config, webSockets: config.webSockets || getEnv('webSockets'), sqlPort: config.sqlPort || getEnv('sqlPort'), - pgSqlPort: config.pgSqlPort || getEnv('pgSqlPort'), + pgSqlPort: config.pgSqlPort || getEnv('pgSqlPort') || undefined, gatewayPort: config.gatewayPort || getEnv('nativeApiGatewayPort'), serverHeadersTimeout: config.serverHeadersTimeout ?? getEnv('serverHeadersTimeout'), serverKeepAliveTimeout: config.serverKeepAliveTimeout ?? getEnv('serverKeepAliveTimeout'), @@ -85,7 +85,7 @@ export class CubejsServer { return new CubeCore(config, systemOptions); } - public async listen(options: http.ServerOptions = {}): Promise<{app: Express, port: number, server: GracefulHttpServer, version: any }> { + public async listen(options: http.ServerOptions = {}): Promise<{app: Express, port: number | string, server: GracefulHttpServer, version: any }> { try { if (this.server) { throw new Error('CubeServer is already listening'); From 7918180760b09806fcd5a6b4d84497fbc48be71e Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 18:29:52 +0200 Subject: [PATCH 5/7] chore: fix --- packages/cubejs-ksql-driver/src/KsqlDriver.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/cubejs-ksql-driver/src/KsqlDriver.ts b/packages/cubejs-ksql-driver/src/KsqlDriver.ts index 11e57f6770466..d97cbdab90165 100644 --- a/packages/cubejs-ksql-driver/src/KsqlDriver.ts +++ b/packages/cubejs-ksql-driver/src/KsqlDriver.ts @@ -19,7 +19,7 @@ import { Mutex } from 'async-mutex'; import { KsqlQuery } from './KsqlQuery'; type KsqlDriverOptions = { - url: string, + url?: string, username?: string, password?: string, kafkaHost?: string, @@ -117,13 +117,8 @@ export class KsqlDriver extends BaseDriver implements DriverInterface { config.dataSource || assertDataSource('default'); - const url = getEnv('dbUrl', { dataSource }); - if (!url) { - throw new Error('CUBEJS_DB_URL is required for ksqlDB'); - } - this.config = { - url, + url: getEnv('dbUrl', { dataSource }), username: getEnv('dbUser', { dataSource }), password: getEnv('dbPass', { dataSource }), kafkaHost: getEnv('dbKafkaHost', { dataSource }), @@ -178,6 +173,7 @@ export class KsqlDriver extends BaseDriver implements DriverInterface { if (query.toLowerCase().startsWith('select')) { throw new Error('Select queries for ksql allowed only from Cube Store. In order to query ksql create pre-aggregation first.'); } + const { data } = await this.apiQuery('/ksql', { ksql: `${formatSql(query, values)};`, ...(options.streamOffset ? { From bfd60864f75b3c8e5cb2555e1be7367dbb8bf785 Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 19:10:22 +0200 Subject: [PATCH 6/7] chore: fix --- packages/cubejs-backend-shared/src/env.ts | 14 +++++++------- packages/cubejs-pinot-driver/src/PinotDriver.ts | 2 +- .../cubejs-prestodb-driver/src/PrestoDriver.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index 54ca3d37bb463..d3f1d0e447c59 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -558,23 +558,23 @@ const variables = { dataSource, }: { dataSource: string, - }): { user: string; password: string } | undefined => { + }): { user: string; password: string | undefined } | undefined => { const user = getEnvFn('dbUser')({ dataSource, }); const password = getEnvFn('dbPass')({ dataSource, }); - if (user && password) { - return { user, password }; - } - - if (user || password) { + if (password && !user) { throw new Error( - `Both ${keyByDataSource('CUBEJS_DB_USER', dataSource)} and ${keyByDataSource('CUBEJS_DB_PASS', dataSource)} must be set for basic auth` + `${keyByDataSource('CUBEJS_DB_USER', dataSource)} must be set when ${keyByDataSource('CUBEJS_DB_PASS', dataSource)} is provided` ); } + if (user) { + return { user, password }; + } + return undefined; }, diff --git a/packages/cubejs-pinot-driver/src/PinotDriver.ts b/packages/cubejs-pinot-driver/src/PinotDriver.ts index cce9cdb8855a7..450ffe63d1982 100644 --- a/packages/cubejs-pinot-driver/src/PinotDriver.ts +++ b/packages/cubejs-pinot-driver/src/PinotDriver.ts @@ -28,7 +28,7 @@ export type PinotDriverConfiguration = { port?: string | number; user?: string; database?: string; - basicAuth?: { user: string, password: string }; + basicAuth?: { user: string, password?: string }; authToken?: string; ssl?: string | TLSConnectionOptions; dataSource?: string; diff --git a/packages/cubejs-prestodb-driver/src/PrestoDriver.ts b/packages/cubejs-prestodb-driver/src/PrestoDriver.ts index b9b65a7591cc6..eee1731a8598e 100644 --- a/packages/cubejs-prestodb-driver/src/PrestoDriver.ts +++ b/packages/cubejs-prestodb-driver/src/PrestoDriver.ts @@ -49,7 +49,7 @@ export type PrestoDriverConfiguration = PrestoDriverExportBucket & { // eslint-disable-next-line camelcase custom_auth?: string; // eslint-disable-next-line camelcase - basic_auth?: { user: string, password: string }; + basic_auth?: { user: string, password?: string }; ssl?: string | TLSConnectionOptions; dataSource?: string; queryTimeout?: number; From ab6822051ce0e6122ffc5791d274d819ccd2e0dd Mon Sep 17 00:00:00 2001 From: Dmitry Patsura Date: Wed, 1 Apr 2026 19:33:16 +0200 Subject: [PATCH 7/7] chore: fix --- packages/cubejs-backend-shared/src/env.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-backend-shared/src/env.ts b/packages/cubejs-backend-shared/src/env.ts index d3f1d0e447c59..4516c0495e92e 100644 --- a/packages/cubejs-backend-shared/src/env.ts +++ b/packages/cubejs-backend-shared/src/env.ts @@ -558,7 +558,7 @@ const variables = { dataSource, }: { dataSource: string, - }): { user: string; password: string | undefined } | undefined => { + }): { user: string; password?: string } | undefined => { const user = getEnvFn('dbUser')({ dataSource, }); @@ -571,10 +571,14 @@ const variables = { ); } - if (user) { + if (user && password) { return { user, password }; } + if (user) { + return { user }; + } + return undefined; },