Skip to content

Commit b4ab849

Browse files
committed
feat(sea): execute statements asynchronously (submit + poll) for Thrift parity
The SEA backend's executeStatement used the blocking napi executeStatement (kernel polled to terminal internally), so SeaOperationBackend.status() synthesized a constant FINISHED and a long-running query could not be cancelled from JS until it returned. The Thrift backend always runs async (runAsync: true): it submits, gets a pending operation handle, and polls getOperationStatus to terminal during waitUntilReady. This brings the SEA path to the same model: - SeaSessionBackend.executeStatement now calls the napi submitStatement (kernel wait_timeout=0s), receiving a pending AsyncStatement handle. Metadata methods keep the blocking statement path (already terminal). - SeaOperationBackend supports both shapes: for the async path, waitUntilReady() polls status() to terminal (firing the progress callback each tick, like the Thrift getOperationStatus loop), then materialises the result stream via awaitResult(); status() reports the real Pending/Running/Succeeded/Failed state; a Failed statement surfaces the kernel's typed SQL-error envelope via awaitResult()'s rejection. - The JS-side poll loop (not a single blocking awaitResult) keeps cancel() responsive: the kernel AsyncStatement serialises its methods behind one mutex, so a single in-flight awaitResult() would queue cancel() behind it for the whole query; polling status() releases the mutex between ticks. - SeaNativeLoader gains the typed async surface (submitStatement, SeaNativeAsyncStatement, SeaNativeAsyncResultHandle, SeaNativeStatementStatus). Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent 3f1d903 commit b4ab849

7 files changed

Lines changed: 1090 additions & 129 deletions

File tree

lib/sea/SeaNativeLoader.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,49 @@ export interface SeaNativeStatement {
6464
close(): Promise<void>;
6565
}
6666

67+
/**
68+
* Server-side execution status returned by `AsyncStatement.status()`.
69+
* Mirrors the kernel `StatementStatus` enum collapsed to its variant
70+
* name. `'Unknown'` is the forward-compat arm for kernel variants the
71+
* binding doesn't recognise.
72+
*/
73+
export type SeaNativeStatementStatus =
74+
| 'Pending'
75+
| 'Running'
76+
| 'Succeeded'
77+
| 'Failed'
78+
| 'Cancelled'
79+
| 'Closed'
80+
| 'Unknown';
81+
82+
/**
83+
* Typed surface for the opaque napi `AsyncResultHandle`. Returned by
84+
* `AsyncStatement.awaitResult()`; same fetch-side surface as
85+
* `SeaNativeStatement` but without `cancel()` / `close()` (the parent
86+
* `AsyncStatement` owns server-side lifecycle).
87+
*/
88+
export interface SeaNativeAsyncResultHandle {
89+
readonly statementId: string;
90+
fetchNextBatch(): Promise<SeaArrowBatch | null>;
91+
schema(): Promise<SeaArrowSchema>;
92+
}
93+
94+
/**
95+
* Typed surface for the opaque napi `AsyncStatement`. Returned by
96+
* `Connection.submitStatement(...)`. The kernel submits with
97+
* `wait_timeout=0s`, so the server returns a `statementId` while the
98+
* query is still Pending/Running; JS drives polling via `status()`
99+
* and materialises results with `awaitResult()`. This is the
100+
* async-execution path the Thrift backend always uses (`runAsync`).
101+
*/
102+
export interface SeaNativeAsyncStatement {
103+
readonly statementId: string;
104+
status(): Promise<SeaNativeStatementStatus>;
105+
awaitResult(): Promise<SeaNativeAsyncResultHandle>;
106+
cancel(): Promise<void>;
107+
close(): Promise<void>;
108+
}
109+
67110
/**
68111
* Typed surface for the opaque napi `Connection` handle. Signatures
69112
* match `native/sea/index.d.ts` exactly as generated by napi-rs from
@@ -118,6 +161,18 @@ export interface SeaNativeConnection {
118161
*/
119162
executeStatement(sql: string, options?: SeaNativeExecuteOptions): Promise<SeaNativeStatement>;
120163

164+
/**
165+
* Submit a SQL statement asynchronously and return an
166+
* `AsyncStatement` handle without blocking until the query
167+
* finishes. The kernel sends `wait_timeout=0s`, so the server
168+
* responds as soon as it has a `statementId` (Pending/Running);
169+
* drive polling via the handle's `status()` / `awaitResult()`.
170+
* Same option semantics as `executeStatement`; only the
171+
* pending-vs-blocking return contract differs. This is the
172+
* async-execution path the Thrift backend always uses.
173+
*/
174+
submitStatement(sql: string, options?: SeaNativeExecuteOptions): Promise<SeaNativeAsyncStatement>;
175+
121176
// ── Metadata methods ──────────────────────────────────────────────────
122177
/** All catalogs visible to the session. */
123178
listCatalogs(): Promise<SeaNativeStatement>;

0 commit comments

Comments
 (0)