diff --git a/packages/cli/src/connectors/sqlserver/connector.test.ts b/packages/cli/src/connectors/sqlserver/connector.test.ts index ef00bd3a..5c865c38 100644 --- a/packages/cli/src/connectors/sqlserver/connector.test.ts +++ b/packages/cli/src/connectors/sqlserver/connector.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it, vi } from 'vitest'; import { createSqlServerLiveDatabaseIntrospection } from '../../connectors/sqlserver/live-database-introspection.js'; -import { isKtxSqlServerConnectionConfig, KtxSqlServerScanConnector, sqlServerConnectionPoolConfigFromConfig, type KtxSqlServerPoolFactory, type KtxSqlServerQueryResult } from '../../connectors/sqlserver/connector.js'; +import { isKtxSqlServerConnectionConfig, KtxSqlServerScanConnector, sqlServerConnectionPoolConfigFromConfig, type KtxSqlServerConnectionConfig, type KtxSqlServerPoolFactory, type KtxSqlServerQueryResult } from '../../connectors/sqlserver/connector.js'; import { tableRefSet } from '../../context/scan/table-ref.js'; function recordset>( @@ -164,6 +164,45 @@ describe('KtxSqlServerScanConnector', () => { }); }); + it('defaults and validates SQL Server maxConnections', () => { + const baseConnection: KtxSqlServerConnectionConfig = { + driver: 'sqlserver', + host: 'db.example.test', + database: 'analytics', + username: 'reader', + }; + + expect( + sqlServerConnectionPoolConfigFromConfig({ + connectionId: 'warehouse', + connection: baseConnection, + }), + ).toMatchObject({ pool: { max: 10 } }); + + expect( + sqlServerConnectionPoolConfigFromConfig({ + connectionId: 'warehouse', + connection: { ...baseConnection, maxConnections: 15 }, + }), + ).toMatchObject({ pool: { max: 15 } }); + + expect( + sqlServerConnectionPoolConfigFromConfig({ + connectionId: 'warehouse', + connection: { ...baseConnection, maxConnections: '12' as never }, + }), + ).toMatchObject({ pool: { max: 12 } }); + + for (const maxConnections of [0, -1, 1.5, Number.NaN, 'abc' as never]) { + expect(() => + sqlServerConnectionPoolConfigFromConfig({ + connectionId: 'warehouse', + connection: { ...baseConnection, maxConnections }, + }), + ).toThrow('connections.warehouse.maxConnections must be a positive integer'); + } + }); + it('introspects schema, primary keys, comments, row counts, views, and foreign keys', async () => { const connector = new KtxSqlServerScanConnector({ connectionId: 'warehouse', diff --git a/packages/cli/src/connectors/sqlserver/connector.ts b/packages/cli/src/connectors/sqlserver/connector.ts index 64b8075e..c329f279 100644 --- a/packages/cli/src/connectors/sqlserver/connector.ts +++ b/packages/cli/src/connectors/sqlserver/connector.ts @@ -19,6 +19,7 @@ export interface KtxSqlServerConnectionConfig { schema?: string; schemas?: string[]; trustServerCertificate?: boolean; + maxConnections?: number; [key: string]: unknown; } @@ -197,6 +198,23 @@ function maybeNumber(value: unknown): number | undefined { return typeof value === 'number' && Number.isFinite(value) ? value : undefined; } +function positiveIntegerConfigValue(input: { + connection: KtxSqlServerConnectionConfig; + key: keyof KtxSqlServerConnectionConfig; + connectionId: string; + defaultValue: number; +}): number { + const value = input.connection[input.key]; + if (value === undefined) { + return input.defaultValue; + } + const numberValue = Number(value); + if (!Number.isInteger(numberValue) || numberValue < 1) { + throw new Error(`connections.${input.connectionId}.${String(input.key)} must be a positive integer`); + } + return numberValue; +} + function schemaNames(connection: KtxSqlServerConnectionConfig, env: NodeJS.ProcessEnv): string[] { if (Array.isArray(connection.schemas) && connection.schemas.length > 0) { return connection.schemas.filter((schema) => schema.trim().length > 0).map((schema) => resolveStringReference(schema, env)); @@ -254,6 +272,12 @@ export function sqlServerConnectionPoolConfigFromConfig(input: { const server = stringConfigValue(merged, 'host', env); const database = stringConfigValue(merged, 'database', env); const user = stringConfigValue(merged, 'username', env) ?? stringConfigValue(merged, 'user', env); + const maxConnections = positiveIntegerConfigValue({ + connection: merged, + key: 'maxConnections', + connectionId: input.connectionId, + defaultValue: 10, + }); if (!server) { throw new Error(`Native SQL Server connector requires connections.${input.connectionId}.host or url`); @@ -272,7 +296,7 @@ export function sqlServerConnectionPoolConfigFromConfig(input: { user, password: stringConfigValue(merged, 'password', env), options: { encrypt: true, trustServerCertificate: merged.trustServerCertificate ?? true }, - pool: { max: 10, min: 0, idleTimeoutMillis: 30000 }, + pool: { max: maxConnections, min: 0, idleTimeoutMillis: 30000 }, }; }