Skip to content

Commit bb749d6

Browse files
committed
[SEA-NodeJS] Sync execute via directResults (executeStatementDirect)
The default sync path (`runAsync: false`) now calls the kernel's additive `Connection.executeStatementDirect` instead of `executeStatementCancellable`. The kernel runs ExecuteStatement with a bounded server inline wait and returns WITHOUT polling past it; the session feature-detects the arm via `awaitResult`: a fast query comes back as a terminal `Statement` (result inline) → wrapped with the operation backend's `statement` arm; a slow one as an `AsyncStatement` → the `asyncStatement` arm. Because the returned handle always corresponds to a server-owned statement: - fire-and-forget CREATE/INSERT commits (server runs it inline in the POST); - `close()` is a clean release, never a drive-to-terminal (no close-drives); - a long query stays cancellable via `op.cancel()` (~150-300ms), Thrift parity; - errors surface at `executeStatement`, matching Thrift / Python use_kernel. Requires the kernel's additive directResults execute (databricks/databricks-sql-kernel#140). `execute()` on the kernel is unchanged, so Python `use_kernel` needs no change. Regenerated napi types add `executeStatementDirect`. Validated e2e (pecotesting): CREATE fire-and-forget commits, 100k read, error at execute, mid-run cancel, close() cheap (~120ms) on an abandoned long query. Unit: SEA suite 260 passing; eslint clean. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent f4d1675 commit bb749d6

3 files changed

Lines changed: 57 additions & 11 deletions

File tree

lib/sea/SeaSessionBackend.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import InfoValue from '../dto/InfoValue';
3333
import HiveDriverError from '../errors/HiveDriverError';
3434
import ParameterError from '../errors/ParameterError';
3535
import { LogLevel } from '../contracts/IDBSQLLogger';
36-
import { SeaConnection, SeaNativeExecuteOptions, SeaStatement } from './SeaNativeLoader';
36+
import { SeaConnection, SeaNativeExecuteOptions, SeaStatement, SeaNativeAsyncStatement } from './SeaNativeLoader';
3737
import { decodeNapiKernelError } from './SeaErrorMapping';
3838
import { numberToInt64 } from '../thrift-backend/ThriftSessionBackend';
3939
import SeaOperationBackend from './SeaOperationBackend';
@@ -183,24 +183,34 @@ export default class SeaSessionBackend implements ISessionBackend {
183183
options.queryTimeout !== undefined ? numberToInt64(options.queryTimeout).toNumber() : undefined;
184184

185185
if (!runAsync) {
186-
// Sync path: forward `queryTimeoutSecs` to the napi options — the kernel
187-
// `execute()` honours it (server statement timeout).
186+
// DEFAULT — directResults (the Thrift/JDBC model). The kernel's
187+
// `executeStatementDirect` runs ExecuteStatement with a bounded server
188+
// inline wait and returns WITHOUT polling past it:
189+
// - a terminal `Statement` (result inline) for a fast query, or
190+
// - an `AsyncStatement` (a poll/cancel handle) for a slow one.
191+
// Either way the handle is tied to a server-owned statement, so a long
192+
// query stays cancellable via `op.cancel()` and `close()` is a clean
193+
// release (no client-side drive-to-terminal). Fire-and-forget DDL/DML
194+
// commits because the server runs it inline during the POST.
188195
const execOptions = this.buildExecuteOptions(options, queryTimeoutSecs);
189-
let cancellableExecution;
196+
let direct;
190197
try {
191-
cancellableExecution =
198+
direct =
192199
execOptions === undefined
193-
? await this.connection.executeStatementCancellable(statement)
194-
: await this.connection.executeStatementCancellable(statement, execOptions);
200+
? await this.connection.executeStatementDirect(statement)
201+
: await this.connection.executeStatementDirect(statement, execOptions);
195202
} catch (err) {
196203
throw this.logAndMapError('executeStatement', err);
197204
}
205+
// Feature-detect the arm: only `AsyncStatement` (the Running arm) exposes
206+
// `awaitResult`; the terminal `Statement` (Completed arm) does not.
207+
const asAsync = direct as unknown as SeaNativeAsyncStatement;
208+
if (direct !== null && typeof asAsync.awaitResult === 'function') {
209+
return new SeaOperationBackend({ asyncStatement: asAsync, context: this.context, queryTimeoutSecs });
210+
}
198211
return new SeaOperationBackend({
199-
cancellableExecution: cancellableExecution!,
212+
statement: direct as unknown as SeaStatement,
200213
context: this.context,
201-
// The kernel honours `queryTimeoutSecs` on the sync `execute` path, so
202-
// it is forwarded via the napi options (see `buildExecuteOptions`); the
203-
// backend also keeps it as a deadline guard for parity with async.
204214
queryTimeoutSecs,
205215
});
206216
}

native/sea/index.d.ts

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/unit/sea/execution.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,26 @@ class FakeNativeConnection implements SeaConnection {
234234
return this.lastCancellableExecution;
235235
}
236236

237+
// directResults (`runAsync: false`, the DEFAULT) query path: records sql +
238+
// options and returns either a terminal `Statement` (Completed arm) or — when
239+
// `directReturnsRunning` is set — a pending `AsyncStatement` (Running arm),
240+
// the two arms `SeaSessionBackend.executeStatement` feature-detects via
241+
// `awaitResult`.
242+
public directReturnsRunning = false;
243+
244+
public async executeStatementDirect(sql: string, options?: unknown): Promise<any> {
245+
if (this.throwOnExecute) {
246+
throw this.throwOnExecute;
247+
}
248+
this.lastSql = sql;
249+
this.lastOptions = options;
250+
if (this.directReturnsRunning) {
251+
this.lastAsyncStatement = new FakeAsyncStatement(this.submitStatusValue);
252+
return this.lastAsyncStatement;
253+
}
254+
return this.statementToReturn;
255+
}
256+
237257
// Async-submit path: records sql + per-statement options (for forwarding
238258
// assertions) and returns a pending AsyncStatement.
239259
public async submitStatement(sql: string, options?: unknown): Promise<any> {

0 commit comments

Comments
 (0)