Skip to content
Closed
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
0085928
sea-abstraction: introduce IBackend / ISessionBackend / IOperationBac…
msrathore-db May 15, 2026
2be1a63
sea-abstraction: cleanup — restore JSDoc, dedupe test pre-seed, fix i…
msrathore-db May 15, 2026
8a22d54
sea-abstraction: address full-review findings (F1-F17 except F5)
msrathore-db May 24, 2026
e78ed27
sea-napi-binding: scaffold native/sea/ crate with version() smoke test
msrathore-db May 15, 2026
c8211be
sea-napi-binding: Database/Connection/Statement/ResultStream methods …
msrathore-db May 15, 2026
04728b7
sea-napi-binding: cleanup — drop unused tracing deps; address bloat f…
msrathore-db May 15, 2026
ee7e82e
sea-napi-binding: relocate Rust source to kernel workspace
msrathore-db May 15, 2026
01f31cd
sea-napi-binding: build:native uses --platform so index.js router is …
msrathore-db May 15, 2026
548a14b
sea-napi-binding: review round 2 — lint, publish, lazy-load, tests
msrathore-db May 24, 2026
3216019
sea-napi-binding: address review — sql-kernel rename, loader class + …
msrathore-db May 30, 2026
83015ac
sea-errors-logging: kernel ErrorCode → JS error class mapping
msrathore-db May 15, 2026
bfaa2cc
sea-auth: PAT auth flow through SeaBackend → napi binding
msrathore-db May 15, 2026
50f436e
sea-auth: address PR #379 review (F1–F10)
msrathore-db May 31, 2026
3a315a9
sea-execution: executeStatement + openSession via SeaSessionBackend
msrathore-db May 15, 2026
d57093d
integration: post-merge fix — update execution.test.ts assertions
msrathore-db May 15, 2026
df98b15
sea-execution: refresh test fakes for the merged-kernel binding surface
msrathore-db May 31, 2026
ff8c349
sea-results: SeaOperationBackend wires kernel result-stream → JS rows
msrathore-db May 15, 2026
42de02f
sea-results: refresh test fakes for the merged-kernel binding surface
msrathore-db May 31, 2026
1630fa7
sea-operation: cancel/close/finished lifecycle for SEA operations
msrathore-db May 16, 2026
1f77d96
sea-integration: INTERVAL YEAR-MONTH + DAY-TIME parity with thrift
msrathore-db May 15, 2026
7b457dc
sea-operation: refresh test fakes for the merged-kernel binding surface
msrathore-db May 31, 2026
a3a4e2d
sea-auth-u2m: OAuth M2M + U2M through SeaBackend → napi binding → kernel
msrathore-db May 15, 2026
cf6e64f
sea-auth-u2m: address round-1 M2M review parity — shared fakeBinding …
msrathore-db May 15, 2026
17a05bd
sea-auth-u2m: address round-1 review (HIGH error-mapping wiring + 7 m…
msrathore-db May 15, 2026
8692a3b
sea-auth-u2m: round-2 fixup — wrap close() in decodeNapiKernelError, …
msrathore-db May 15, 2026
dc314e3
sea-auth-u2m: round-3 fixup — namespace kernel metadata, dedupe predi…
msrathore-db May 15, 2026
a15ed50
sea-auth-u2m: rewire M2M e2e to AAD SP on pecotesting HTTP_PATH2
msrathore-db May 16, 2026
81a8403
sea-auth-u2m: round-4 fixup — restore M2M-with-bad-secret class, stri…
msrathore-db May 16, 2026
37b9021
sea-auth-u2m: round-5 fixup — JSDoc selector contract, defense-in-dep…
msrathore-db May 16, 2026
fda43a8
sea-auth-u2m: dry-run rebase reconciliation — API-shear fix for post-…
msrathore-db May 16, 2026
bed0d03
refactor(sea)!: move catalog/schema/sessionConf from per-statement fo…
msrathore-db May 24, 2026
cea1925
sea-auth-u2m: refresh test fakes for the merged-kernel binding surface
msrathore-db May 31, 2026
327f1a2
sea: complete neutral-type conformance so the SEA driver builds + runs
msrathore-db May 31, 2026
b6c06f8
fix(sea): cascade operation review fixes
msrathore-db May 31, 2026
78c5658
merge: origin/main into sea-auth-u2m (consolidate foundation onto main)
msrathore-db Jun 1, 2026
5ca531b
fix(sea): conform SEA backends to main's neutral abstraction + clear …
msrathore-db Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ Dockerfile* text
#
.gitattributes export-ignore
.gitignore export-ignore

# napi-rs auto-generates these files from the kernel's `napi-binding/napi/`
# crate; regenerated by `npm run build:native`. Tell git/GitHub they're
# machine-generated so they collapse in diffs and are excluded from
# blame and language stats.
native/sea/index.d.ts linguist-generated=true
native/sea/index.js linguist-generated=true
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,12 @@ coverage_unit
dist
*.DS_Store
lib/version.ts

# SEA native binding — copied/generated from kernel workspace by `npm run build:native`.
# The committed contract is `native/sea/index.d.ts` (TypeScript declarations) and
# `native/sea/index.js` (the napi-rs platform router — small, stable, and required in
# the publish tarball so a missing build step can't ship a tarball that can't load).
# The `.node` binaries are large per-platform artifacts and must NOT be committed;
# in production they arrive via the `@databricks/sql-kernel-<triple>` optional deps.
native/sea/index.node
native/sea/index.*.node
7 changes: 7 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
!dist/**/*
!thrift/**/*

# SEA napi-rs router shim + TypeScript declarations. The router (index.js)
# selects the per-platform `.node` artifact from `@databricks/sql-kernel-*`
# optionalDependencies (populated when the kernel CI publishes them);
# the .d.ts is the consumer-facing type contract.
!native/sea/index.js
!native/sea/index.d.ts

!LICENSE
!NOTICE
!package.json
Expand Down
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ coverage
dist
thrift
package-lock.json

# Generated by napi-rs from the kernel's `napi-binding/napi/` crate;
# regenerated by `npm run build:native`. Format follows napi-rs's
# defaults (no semicolons), not this repo's prettier config.
native/sea/index.d.ts
native/sea/index.js
149 changes: 65 additions & 84 deletions lib/DBSQLClient.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import thrift from 'thrift';
import Int64 from 'node-int64';

import { EventEmitter } from 'events';
import TCLIService from '../thrift/TCLIService';
import { TProtocolVersion } from '../thrift/TCLIService_types';
import IDBSQLClient, { ClientOptions, ConnectionOptions, OpenSessionRequest } from './contracts/IDBSQLClient';
import IDriver from './contracts/IDriver';
import IClientContext, { ClientConfig } from './contracts/IClientContext';
Expand All @@ -14,9 +12,12 @@ import IDBSQLSession from './contracts/IDBSQLSession';
import IAuthentication from './connection/contracts/IAuthentication';
import HttpConnection from './connection/connections/HttpConnection';
import IConnectionOptions from './connection/contracts/IConnectionOptions';
import Status from './dto/Status';
import HiveDriverError from './errors/HiveDriverError';
import { buildUserAgentString, definedOrError, serializeQueryTags } from './utils';
import { buildUserAgentString } from './utils';
import IBackend from './contracts/IBackend';
import { InternalConnectionOptions } from './contracts/InternalConnectionOptions';
import ThriftBackend from './thrift-backend/ThriftBackend';
import SeaBackend from './sea/SeaBackend';
import PlainHttpAuthentication from './connection/auth/PlainHttpAuthentication';
import DatabricksOAuth, { OAuthFlow } from './connection/auth/DatabricksOAuth';
import {
Expand All @@ -31,26 +32,7 @@ import IDBSQLLogger, { LogLevel } from './contracts/IDBSQLLogger';
import DBSQLLogger from './DBSQLLogger';
import CloseableCollection from './utils/CloseableCollection';
import IConnectionProvider from './connection/contracts/IConnectionProvider';

function prependSlash(str: string): string {
if (str.length > 0 && str.charAt(0) !== '/') {
return `/${str}`;
}
return str;
}

function getInitialNamespaceOptions(catalogName?: string, schemaName?: string) {
if (!catalogName && !schemaName) {
return {};
}

return {
initialNamespace: {
catalogName,
schemaName,
},
};
}
import prependSlash from './utils/prependSlash';

export type ThriftLibrary = Pick<typeof thrift, 'createClient'>;

Expand All @@ -75,6 +57,8 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I

private readonly sessions = new CloseableCollection<DBSQLSession>();

private backend?: IBackend;

private static getDefaultLogger(): IDBSQLLogger {
if (!this.defaultLogger) {
this.defaultLogger = new DBSQLLogger();
Expand Down Expand Up @@ -244,40 +228,61 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I
this.config.userAgentEntry = options.userAgentEntry;
}

this.authProvider = this.createAuthProvider(options, authProvider);
// M0: `useSEA` is consumed via a non-exported internal-options cast so it
// doesn't ship in the public `.d.ts`. Mirrors Python's `kwargs.get("use_sea")`
// pattern (see databricks-sql-python/src/databricks/sql/session.py).
const internalOptions = options as ConnectionOptions & InternalConnectionOptions;
if (internalOptions.useSEA) {
// The SEA backend authenticates inside the native binding; the
// Thrift auth/connection providers are never read on this path, so
// we don't build them (avoids validating the PAT twice and
// constructing a throwaway OAuth provider for an OAuth+useSEA call).
// The backend reads logger/config off the IClientContext it's given.
this.logger.log(LogLevel.info, 'Connecting via the SEA (native) backend');
this.backend = new SeaBackend({ context: this });
} else {
this.authProvider = this.createAuthProvider(options, authProvider);
this.connectionProvider = this.createConnectionProvider(options);
this.backend = new ThriftBackend({
context: this,
onConnectionEvent: (event, payload) => this.forwardConnectionEvent(event, payload),
});
}

this.connectionProvider = this.createConnectionProvider(options);
await this.backend.connect(options);

const thriftConnection = await this.connectionProvider.getThriftConnection();
return this;
}

thriftConnection.on('error', (error: Error) => {
// Error.stack already contains error type and message, so log stack if available,
// otherwise fall back to just error type + message
this.logger.log(LogLevel.error, error.stack || `${error.name}: ${error.message}`);
try {
this.emit('error', error);
} catch (e) {
// EventEmitter will throw unhandled error when emitting 'error' event.
// Since we already logged it few lines above, just suppress this behaviour
private forwardConnectionEvent(event: 'error' | 'reconnecting' | 'close' | 'timeout', payload?: unknown): void {
switch (event) {
case 'error': {
const error = payload as Error;
this.logger.log(LogLevel.error, error.stack || `${error.name}: ${error.message}`);
try {
this.emit('error', error);
} catch (e) {
// EventEmitter throws when 'error' has no listeners; we've already logged it.
}
return;
}
});

thriftConnection.on('reconnecting', (params: { delay: number; attempt: number }) => {
this.logger.log(LogLevel.debug, `Reconnecting, params: ${JSON.stringify(params)}`);
this.emit('reconnecting', params);
});

thriftConnection.on('close', () => {
this.logger.log(LogLevel.debug, 'Closing connection.');
this.emit('close');
});

thriftConnection.on('timeout', () => {
this.logger.log(LogLevel.debug, 'Connection timed out.');
this.emit('timeout');
});

return this;
case 'reconnecting':
this.logger.log(LogLevel.debug, `Reconnecting, params: ${JSON.stringify(payload)}`);
this.emit('reconnecting', payload);
return;
case 'close':
this.logger.log(LogLevel.debug, 'Closing connection.');
this.emit('close');
return;
case 'timeout':
this.logger.log(LogLevel.debug, 'Connection timed out.');
this.emit('timeout');
// Explicit return mirrors the other cases and protects against
// fall-through if a new event is added below.
// eslint-disable-next-line no-useless-return
return;
// no default
}
}

/**
Expand All @@ -290,44 +295,20 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I
* const session = await client.openSession();
*/
public async openSession(request: OpenSessionRequest = {}): Promise<IDBSQLSession> {
// Prepare session configuration
const configuration = request.configuration ? { ...request.configuration } : {};

// Add metric view metadata config if enabled
if (this.config.enableMetricViewMetadata) {
configuration['spark.sql.thriftserver.metadata.metricview.enabled'] = 'true';
}

// Serialize queryTags dict and set in configuration; takes precedence over configuration.QUERY_TAGS
if (request.queryTags !== undefined) {
const serialized = serializeQueryTags(request.queryTags);
if (serialized) {
configuration.QUERY_TAGS = serialized;
} else {
delete configuration.QUERY_TAGS;
}
if (!this.backend) {
throw new HiveDriverError('DBSQLClient: not connected');
}

const response = await this.driver.openSession({
client_protocol_i64: new Int64(TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V8),
...getInitialNamespaceOptions(request.initialCatalog, request.initialSchema),
configuration,
canUseMultipleCatalogs: true,
});

Status.assert(response.status);
const session = new DBSQLSession({
handle: definedOrError(response.sessionHandle),
context: this,
serverProtocolVersion: response.serverProtocolVersion,
});
const sessionBackend = await this.backend.openSession(request);
const session = new DBSQLSession({ backend: sessionBackend, context: this });
this.sessions.add(session);
return session;
}

public async close(): Promise<void> {
await this.sessions.closeAll();
await this.backend?.close();

this.backend = undefined;
this.client = undefined;
this.connectionProvider = undefined;
this.authProvider = undefined;
Expand Down
Loading
Loading