Skip to content

Commit b4095a6

Browse files
committed
feat(sea): expose maxConnections through ConnectionOptions to the napi binding
Surfaces the kernel's `HttpConfig::pool_max_idle_per_host` knob through `ConnectionOptions.maxConnections` on the public client options, forwards it through `buildSeaConnectionOptions` into the napi `openSession` call. When omitted, the kernel's default (100) applies. Mirrors the Python connector's `max_connections` kwarg on the SEA backend. The NodeJS Thrift backend does NOT currently expose this knob — it remains a no-op on that path, documented inline. No break to Thrift parity (Thrift never had it). Plumbing: - `IDBSQLClient.ConnectionOptions` gains `maxConnections?: number` with a doc note that the Thrift backend ignores it today. - `SeaSessionDefaults` (already the shared base across auth-mode variants in `SeaNativeConnectionOptions`) gains the field so all three auth modes (PAT, OAuth M2M, OAuth U2M) round-trip it. - `buildSeaConnectionOptions` validates positive-integer-ness at the JS boundary (rejects 0, negative, non-integer, NaN) so a misuse fails here with a clear `HiveDriverError` instead of inside the kernel with a cryptic napi error. - `native/sea/index.d.ts` (the committed copy of the napi-rs generated d.ts) gets the `maxConnections?: number` field on `ConnectionOptions`, in step with the kernel branch `msrathore/krn-max-connections` (commit `0c5ca3f`). Tests: - `tests/unit/sea/auth-max-connections.test.ts` — 9 cases covering omission, valid values, boundary (1), and rejection of 0, negative, fractional, NaN; plus the OAuth M2M / U2M arms. Cross-PR dependency: stacks on `msrathore/sea-auth-u2m` (current NodeJS tip) and requires kernel branch `msrathore/krn-max-connections` (napi binding side of the same change). Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent 0eb08b8 commit b4095a6

4 files changed

Lines changed: 172 additions & 1 deletion

File tree

lib/contracts/IDBSQLClient.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,14 @@ export type ConnectionOptions = {
5454
socketTimeout?: number;
5555
proxy?: ProxyOptions;
5656
enableMetricViewMetadata?: boolean;
57+
/**
58+
* Maximum number of pooled HTTP connections per host. SEA backend
59+
* only — Thrift backend currently ignores this. Mirrors the Python
60+
* connector's `max_connections` kwarg (default 100 in the kernel's
61+
* connection pool). Tune up when many statements run concurrently
62+
* against the same warehouse to reduce reconnect overhead.
63+
*/
64+
maxConnections?: number;
5765
} & AuthOptions;
5866

5967
export interface OpenSessionRequest {

lib/sea/SeaAuth.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ export interface SeaSessionDefaults {
6666
catalog?: string;
6767
schema?: string;
6868
sessionConf?: Record<string, string>;
69+
/**
70+
* Maximum pooled HTTP connections per host. Forwarded to the
71+
* napi `ConnectionOptions.maxConnections` field, which the kernel
72+
* threads into `HttpConfig::pool_max_idle_per_host`. Connection-
73+
* level (not session-level), but grouped with the session defaults
74+
* here because both flow through the same napi `openSession` call.
75+
* Omitted → kernel default (100) applies.
76+
*/
77+
maxConnections?: number;
6978
}
7079

7180
export type SeaNativeConnectionOptions = SeaSessionDefaults &
@@ -158,10 +167,22 @@ export function isBlankOrReserved(s: string): boolean {
158167
export function buildSeaConnectionOptions(options: ConnectionOptions): SeaNativeConnectionOptions {
159168
const { authType } = options as { authType?: string };
160169

161-
const base = {
170+
const base: { hostName: string; httpPath: string; maxConnections?: number } = {
162171
hostName: options.host,
163172
httpPath: prependSlash(options.path),
164173
};
174+
// Connection-level pool size — forwarded to the napi binding,
175+
// ignored when undefined so the kernel default (100) applies.
176+
// Validate at the JS layer (positive integer) so a misuse fails
177+
// here instead of inside the kernel with a cryptic message.
178+
if (options.maxConnections !== undefined) {
179+
if (!Number.isInteger(options.maxConnections) || options.maxConnections < 1) {
180+
throw new HiveDriverError(
181+
`SEA backend: \`maxConnections\` must be a positive integer; got ${options.maxConnections}.`,
182+
);
183+
}
184+
base.maxConnections = options.maxConnections;
185+
}
165186

166187
const oauth = options as {
167188
oauthClientId?: string;

native/sea/index.d.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,19 @@ export interface ConnectionOptions {
4949
* `session_confs`. Unknown keys are rejected server-side.
5050
*/
5151
sessionConf?: Record<string, string>
52+
/**
53+
* Maximum number of pooled HTTP connections per host. Routes
54+
* through the kernel's `HttpConfig::pool_max_idle_per_host`.
55+
* Tunes the underlying `reqwest` connection pool — higher values
56+
* reduce reconnect overhead when many statements run
57+
* concurrently against the same warehouse. `None` falls back to
58+
* the kernel default (100). Mirrors the Python connector's
59+
* `max_connections` kwarg on the SEA backend.
60+
*
61+
* Napi-rs serialises `u32` as JS `number`; values up to
62+
* `2^32 - 1` round-trip safely (any reasonable pool size fits).
63+
*/
64+
maxConnections?: number
5265
}
5366
/**
5467
* Open a Databricks SQL session over PAT auth and return an opaque
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright (c) 2026 Databricks, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { expect } from 'chai';
16+
import { buildSeaConnectionOptions } from '../../../lib/sea/SeaAuth';
17+
import { ConnectionOptions } from '../../../lib/contracts/IDBSQLClient';
18+
import HiveDriverError from '../../../lib/errors/HiveDriverError';
19+
20+
describe('SeaAuth — maxConnections plumbing', () => {
21+
describe('buildSeaConnectionOptions forwards maxConnections', () => {
22+
it('omits maxConnections when not supplied (kernel default applies)', () => {
23+
const opts: ConnectionOptions = {
24+
host: 'example.cloud.databricks.com',
25+
path: '/sql/1.0/warehouses/abc',
26+
token: 'dapi-fake-pat',
27+
};
28+
29+
const native = buildSeaConnectionOptions(opts);
30+
expect(native).to.not.have.property('maxConnections');
31+
});
32+
33+
it('forwards maxConnections as a positive integer', () => {
34+
const opts: ConnectionOptions = {
35+
host: 'example.cloud.databricks.com',
36+
path: '/sql/1.0/warehouses/abc',
37+
token: 'dapi-fake-pat',
38+
maxConnections: 200,
39+
};
40+
41+
const native = buildSeaConnectionOptions(opts);
42+
expect(native.maxConnections).to.equal(200);
43+
});
44+
45+
it('accepts the minimum value (1)', () => {
46+
const opts: ConnectionOptions = {
47+
host: 'example.cloud.databricks.com',
48+
path: '/sql/1.0/warehouses/abc',
49+
token: 'dapi-fake-pat',
50+
maxConnections: 1,
51+
};
52+
53+
const native = buildSeaConnectionOptions(opts);
54+
expect(native.maxConnections).to.equal(1);
55+
});
56+
57+
it('rejects zero', () => {
58+
const opts: ConnectionOptions = {
59+
host: 'example.cloud.databricks.com',
60+
path: '/sql/1.0/warehouses/abc',
61+
token: 'dapi-fake-pat',
62+
maxConnections: 0,
63+
};
64+
65+
expect(() => buildSeaConnectionOptions(opts)).to.throw(HiveDriverError, /maxConnections.*positive integer/);
66+
});
67+
68+
it('rejects negative values', () => {
69+
const opts: ConnectionOptions = {
70+
host: 'example.cloud.databricks.com',
71+
path: '/sql/1.0/warehouses/abc',
72+
token: 'dapi-fake-pat',
73+
maxConnections: -5,
74+
};
75+
76+
expect(() => buildSeaConnectionOptions(opts)).to.throw(HiveDriverError, /maxConnections.*positive integer/);
77+
});
78+
79+
it('rejects non-integer values', () => {
80+
const opts: ConnectionOptions = {
81+
host: 'example.cloud.databricks.com',
82+
path: '/sql/1.0/warehouses/abc',
83+
token: 'dapi-fake-pat',
84+
maxConnections: 1.5,
85+
};
86+
87+
expect(() => buildSeaConnectionOptions(opts)).to.throw(HiveDriverError, /maxConnections.*positive integer/);
88+
});
89+
90+
it('rejects NaN', () => {
91+
const opts: ConnectionOptions = {
92+
host: 'example.cloud.databricks.com',
93+
path: '/sql/1.0/warehouses/abc',
94+
token: 'dapi-fake-pat',
95+
maxConnections: NaN,
96+
};
97+
98+
expect(() => buildSeaConnectionOptions(opts)).to.throw(HiveDriverError, /maxConnections.*positive integer/);
99+
});
100+
101+
it('forwards maxConnections through the OAuth M2M arm', () => {
102+
const opts: ConnectionOptions = {
103+
host: 'example.cloud.databricks.com',
104+
path: '/sql/1.0/warehouses/abc',
105+
authType: 'databricks-oauth',
106+
oauthClientId: 'client-id',
107+
oauthClientSecret: 'client-secret',
108+
maxConnections: 50,
109+
};
110+
111+
const native = buildSeaConnectionOptions(opts);
112+
expect(native.authMode).to.equal('OAuthM2m');
113+
expect(native.maxConnections).to.equal(50);
114+
});
115+
116+
it('forwards maxConnections through the OAuth U2M arm', () => {
117+
const opts: ConnectionOptions = {
118+
host: 'example.cloud.databricks.com',
119+
path: '/sql/1.0/warehouses/abc',
120+
authType: 'databricks-oauth',
121+
maxConnections: 25,
122+
};
123+
124+
const native = buildSeaConnectionOptions(opts);
125+
expect(native.authMode).to.equal('OAuthU2m');
126+
expect(native.maxConnections).to.equal(25);
127+
});
128+
});
129+
});

0 commit comments

Comments
 (0)