From cda34abfbbaceacb94d10f86594eef04b3cc202b Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 22:58:05 +0000 Subject: [PATCH 01/30] feat(kernel): complete SQL volume file transfers Signed-off-by: Aakash Saravanan --- CHANGELOG.md | 3 + CONNECTION_PARAMETERS.md | 2 +- KERNEL_REV | 2 +- lib/kernel/KernelErrorMapping.ts | 105 +++++++++++++----------- lib/kernel/KernelSessionBackend.ts | 23 ++---- native/kernel/index.d.ts | 4 +- tests/e2e/kernel/volume-e2e.test.ts | 94 +++++++++++++++++++++ tests/unit/kernel/error-mapping.test.ts | 22 +++++ tests/unit/kernel/execution.test.ts | 18 +++- 9 files changed, 204 insertions(+), 69 deletions(-) create mode 100644 tests/e2e/kernel/volume-e2e.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 77d44b38..48ac822b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Release History +## Unreleased +- Honor `stagingAllowedLocalPath` for SQL PUT, GET, and REMOVE through the native backend and report transfer errors as `StagingError`. Requires matching native packages. + ## Unreleased - CloudFetch retry-exhaustion errors now identify the download and the last failure: the `RetryError` message reads `CloudFetch result download failed: (last error: )`. `` is `HTTP : ` (or `HTTP ` when the status text is empty) for HTTP failures, and `network error `, `network error ()` (e.g. `aborted`, when there is no `code`) or a bare `network error` for network failures (the raw network-error message, which can contain the presigned URL, is not copied). If no cause can be determined, the ` (last error: …)` suffix is omitted. The `RetryError` instance, `errorCode` and `payload` (including the node-fetch `Request` / `Response`) are unchanged. (#471) diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index af46726c..ed466ce4 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -163,7 +163,7 @@ telemetry implementation, so only the options explicitly forwarded by | `maxRows` | `number \| bigint \| Int64 \| null` | ✅ | ❌ | `100000` / — | Thrift uses it for the initial Direct Results row count; `null` disables Direct Results. Kernel does not retain or forward the supplied value. | | `useCloudFetch` | `boolean` | ✅ | ❌ | `true` / — | Kernel logs and ignores this per-statement hint; Kernel result fetching owns its CloudFetch behavior. | | `useLZ4Compression` | `boolean` | ✅ | ❌ | `true` / — | Thrift uses it when supported and when the result is not CloudFetch. Kernel owns and auto-detects result compression. | -| `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ❌ | — | Local allowlist for Thrift volume/staging operations. Kernel volume operations are unsupported. | +| `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ✅ | — | Local allowlist for SQL PUT/GET. Native transfers require the kernel revision in `KERNEL_REV` and matching native packages; leaving the option unset returns transfer instructions. | | `namedParameters` | `Record` | ✅ | ✅ | — | Named SQL parameters. Cannot be combined with non-empty `ordinalParameters`. Thrift requires a protocol that supports parameterized queries. | | `ordinalParameters` | `Array` | ✅ | ✅ | — | Positional SQL parameters. Cannot be combined with non-empty `namedParameters`. | | `queryTags` | `Record` | ✅ | ✅ | — | Serialized into the per-statement `query_tags` conf overlay on both. | diff --git a/KERNEL_REV b/KERNEL_REV index d025be44..31390e27 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -6f4b6d607a61bdde61dcda2cf82bc8e80fd32cb9 +bc39c7921acecd95e4eea0740becb883867a834b diff --git a/lib/kernel/KernelErrorMapping.ts b/lib/kernel/KernelErrorMapping.ts index 2d151a8c..e8cce5a6 100644 --- a/lib/kernel/KernelErrorMapping.ts +++ b/lib/kernel/KernelErrorMapping.ts @@ -2,6 +2,7 @@ import HiveDriverError from '../errors/HiveDriverError'; import AuthenticationError from '../errors/AuthenticationError'; import OperationStateError, { OperationStateErrorCode } from '../errors/OperationStateError'; import ParameterError from '../errors/ParameterError'; +import StagingError from '../errors/StagingError'; /** * Sentinel prefix the napi binding's `napi_err_from_kernel` puts on @@ -27,6 +28,7 @@ export interface KernelErrorShape { message: string; /** Optional SQLSTATE — five-char alphanumeric, when the kernel was able to surface it. */ sqlstate?: string; + errorCode?: string; } /** @@ -137,59 +139,61 @@ export function mapKernelErrorToJsError(kErr: KernelErrorShape): ErrorWithSqlSta let error: ErrorWithSqlState; - switch (code as KernelErrorCode) { - case 'Unauthenticated': - case 'PermissionDenied': - error = new AuthenticationError(message); - break; + if (kErr.errorCode === 'FILE_TRANSFER_ERROR') { + error = new StagingError(message); + } else + switch (code as KernelErrorCode) { + case 'Unauthenticated': + case 'PermissionDenied': + error = new AuthenticationError(message); + break; - case 'Cancelled': - // OperationStateError with the Canceled code carries the kernel message - // through the response.displayMessage fallback path. - error = new OperationStateError(OperationStateErrorCode.Canceled); - error.message = message; - break; + case 'Cancelled': + // OperationStateError with the Canceled code carries the kernel message + // through the response.displayMessage fallback path. + error = new OperationStateError(OperationStateErrorCode.Canceled); + error.message = message; + break; - case 'Timeout': - error = new OperationStateError(OperationStateErrorCode.Timeout); - error.message = message; - break; + case 'Timeout': + error = new OperationStateError(OperationStateErrorCode.Timeout); + error.message = message; + break; - case 'InvalidArgument': - error = new ParameterError(message); - break; + case 'InvalidArgument': + error = new ParameterError(message); + break; - case 'SqlError': { - // A server-reported SQL execution failure (kernel `SqlError`, e.g. a - // bad query, missing table, divide-by-zero, invalid cast). The Thrift - // backend surfaces the same situation as `OperationStateError(Error)` - // when the operation reaches ERROR_STATE (see DBSQLOperation), so map - // SqlError to the same class for backend parity. OperationStateError - // extends HiveDriverError, so existing `instanceof HiveDriverError` - // catches are unaffected. - const stateError = new OperationStateError(OperationStateErrorCode.Error); - stateError.message = message; - error = stateError; - break; - } + case 'SqlError': { + // A server-reported SQL execution failure (kernel `SqlError`, e.g. a + // bad query, missing table, divide-by-zero, invalid cast). The Thrift + // backend surfaces the same situation as `OperationStateError(Error)` + // when the operation reaches ERROR_STATE (see DBSQLOperation), so map + // SqlError to the same class for backend parity. OperationStateError + // extends HiveDriverError, so existing `instanceof HiveDriverError` + // catches are unaffected. + const stateError = new OperationStateError(OperationStateErrorCode.Error); + stateError.message = message; + error = stateError; + break; + } - // All remaining kernel ErrorCode variants map to the base driver error class. - // M0 intentionally does not introduce new error classes; M1 may add nuance. - case 'NotFound': - case 'ResourceExhausted': - case 'Unavailable': - case 'DataLoss': - case 'Internal': - case 'InvalidStatementHandle': - case 'NetworkError': - error = new HiveDriverError(message); - break; + // All remaining kernel ErrorCode variants map to the base driver error class. + case 'NotFound': + case 'ResourceExhausted': + case 'Unavailable': + case 'DataLoss': + case 'Internal': + case 'InvalidStatementHandle': + case 'NetworkError': + error = new HiveDriverError(message); + break; - default: - // Unknown/future kernel variant — never drop the error, surface as base class. - error = new HiveDriverError(message); - break; - } + default: + // Unknown/future kernel variant — never drop the error, surface as base class. + error = new HiveDriverError(message); + break; + } if (sqlstate !== undefined) { defineErrorMetadata(error, 'sqlState', sqlstate); @@ -305,7 +309,12 @@ export function decodeNapiKernelError(err: unknown): Error { const msg = envelope.message as string; const sqlState = typeof envelope.sqlState === 'string' ? envelope.sqlState : undefined; - const jsErr = mapKernelErrorToJsError({ code, message: msg, sqlstate: sqlState }); + const jsErr = mapKernelErrorToJsError({ + code, + message: msg, + sqlstate: sqlState, + errorCode: typeof envelope.errorCode === 'string' ? envelope.errorCode : undefined, + }); const meta = buildKernelMetadata(envelope); // Skip the namespace attachment entirely when no fields validated diff --git a/lib/kernel/KernelSessionBackend.ts b/lib/kernel/KernelSessionBackend.ts index 73fd5cb2..391297c8 100644 --- a/lib/kernel/KernelSessionBackend.ts +++ b/lib/kernel/KernelSessionBackend.ts @@ -148,10 +148,8 @@ export default class KernelSessionBackend implements ISessionBackend { * `query_tags` key (the same wire shape Thrift's `serializeQueryTags` * produces), merged with any explicit `statementConf`. * - * Still rejected (genuinely unsupported on kernel, rather than silently - * dropped): `useCloudFetch` (governed by the kernel `ResultConfig`, not a - * per-statement knob), `useLZ4Compression` (kernel owns result compression), - * and `stagingAllowedLocalPath` (volume operations). `maxRows` is applied by + * `stagingAllowedLocalPath` configures kernel file transfers. `useCloudFetch` + * and `useLZ4Compression` remain kernel-owned settings. `maxRows` is applied by * the facade at fetch time, so it is intentionally not handled here. */ public async executeStatement(statement: string, options: ExecuteStatementOptions): Promise { @@ -160,8 +158,7 @@ export default class KernelSessionBackend implements ISessionBackend { // Per-statement options the kernel backend doesn't honour are treated as // NO-OPs (logged at warn), not errors — so call sites written for the Thrift // backend are drop-in on the kernel path. These are perf/format hints the - // kernel governs internally (useCloudFetch / useLZ4Compression) or a feature - // it doesn't expose yet (staging); ignoring them cannot change query results. + // kernel governs internally (useCloudFetch / useLZ4Compression) and do not change query results. // NB: parameter binding (compound/BINARY) is deliberately NOT no-op'd — a // dropped param would silently change results, so it still throws. if (options.useCloudFetch !== undefined) { @@ -180,15 +177,6 @@ export default class KernelSessionBackend implements ISessionBackend { 'kernel executeStatement: ignoring per-statement `useLZ4Compression` — result compression is governed by the kernel (no-op on kernel).', ); } - if (options.stagingAllowedLocalPath !== undefined) { - this.context - .getLogger() - .log( - LogLevel.warn, - 'kernel executeStatement: ignoring `stagingAllowedLocalPath` — volume/staging operations are not yet supported on the kernel backend (no-op).', - ); - } - // `runAsync` selects the kernel execution path. NOTE: this is a kernel- // specific use of the option — the Thrift backend hardcodes `runAsync: true` // on the wire and never reads `options.runAsync`, so the field is a no-op @@ -299,6 +287,11 @@ export default class KernelSessionBackend implements ISessionBackend { } const execOptions: KernelNativeExecuteOptions = {}; + if (options.stagingAllowedLocalPath !== undefined) { + execOptions.stagingAllowedLocalPaths = Array.isArray(options.stagingAllowedLocalPath) + ? options.stagingAllowedLocalPath + : [options.stagingAllowedLocalPath]; + } // Raw binding preserves qualified SQL types such as INTERVAL MONTH. const rawParams = positionalParams ?? namedParams; if (rawParams !== undefined) { diff --git a/native/kernel/index.d.ts b/native/kernel/index.d.ts index 1f1d3581..c4702bd6 100644 --- a/native/kernel/index.d.ts +++ b/native/kernel/index.d.ts @@ -767,7 +767,7 @@ export interface ConnectionOptions { * representation. `true` (the default) requests Arrow * `struct` values; `false` requests WKT / EWKT * in Arrow UTF-8 values. The kernel normalizes both standard-warehouse - * text and Reyden native Arrow results locally and never sends this choice + * text and native Arrow results locally and never sends this choice * to SEA. */ enableGeoSpatialSupport?: boolean @@ -1126,6 +1126,8 @@ export interface ExecuteOptions { * `StatementSpec.row_limit`. Omitted ⇒ no driver-imposed cap. */ rowLimit?: number + /** Omit to handle instructions in the caller; an empty list denies local file access. */ + stagingAllowedLocalPaths?: Array /** * Positional parameters, in 1-based wire order. Index `i` in this * Vec corresponds to the `i+1`-th `?` placeholder in the SQL. diff --git a/tests/e2e/kernel/volume-e2e.test.ts b/tests/e2e/kernel/volume-e2e.test.ts new file mode 100644 index 00000000..7f72160f --- /dev/null +++ b/tests/e2e/kernel/volume-e2e.test.ts @@ -0,0 +1,94 @@ +import { expect } from 'chai'; +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import { v4 as uuid } from 'uuid'; +import { DBSQLClient } from '../../../lib'; +import StagingError from '../../../lib/errors/StagingError'; +import { ConnectionOptions } from '../../../lib/contracts/IDBSQLClient'; +import { InternalConnectionOptions } from '../../../lib/contracts/InternalConnectionOptions'; + +describe('kernel volume file lifecycle', function volumeSuite() { + this.timeout(120_000); + const host = process.env.DATABRICKS_PECOTESTING_SERVER_HOSTNAME; + const httpPath = process.env.DATABRICKS_PECOTESTING_HTTP_PATH; + const token = process.env.DATABRICKS_PECOTESTING_TOKEN_PERSONAL; + + before(function gate() { + if (!host || !httpPath || !token) this.skip(); + }); + + [false, true].forEach((runAsync) => { + it(`transfers bytes and reports failures with runAsync=${runAsync}`, async () => { + const local = fs.mkdtempSync(path.join(os.tmpdir(), 'volume-')); + const allowed = path.join(local, 'allowed'); + fs.mkdirSync(allowed); + const source = path.join(allowed, 'input.bin'); + const destination = path.join(allowed, 'output.bin'); + const denied = path.join(local, 'denied.bin'); + const payload = Buffer.alloc(1024 * 1024); + payload.forEach((_, i) => { + payload[i] = i % 256; + }); + fs.writeFileSync(source, payload); + fs.writeFileSync(denied, payload); + const namespace = `volume_${uuid().replace(/-/g, '')}`; + const remote = `/Volumes/main/${namespace}/files/input.bin`; + const client = new DBSQLClient(); + try { + await client.connect({ + host: host!, + path: httpPath!, + token: token!, + useKernel: true, + } as ConnectionOptions & InternalConnectionOptions); + const session = await client.openSession(); + try { + const execute = async (query: string) => { + const operation = await session.executeStatement(query, { runAsync, stagingAllowedLocalPath: [allowed] }); + try { + await operation.finished(); + return await operation.fetchAll(); + } finally { + await operation.close(); + } + }; + await execute(`CREATE SCHEMA main.${namespace}`); + try { + await execute(`CREATE VOLUME main.${namespace}.files`); + expect(await execute(`PUT '${source}' INTO '${remote}' OVERWRITE`)).to.deep.equal([]); + expect(await execute(`GET '${remote}' TO '${destination}'`)).to.deep.equal([]); + expect(fs.readFileSync(destination).equals(payload)).to.equal(true); + const expectTransferFailure = async (query: string) => { + let failure: unknown; + try { + await execute(query); + } catch (error) { + failure = error; + } + expect(failure).to.be.instanceOf(StagingError); + }; + await expectTransferFailure(`PUT '${denied}' INTO '${remote}' OVERWRITE`); + await expectTransferFailure(`PUT '${path.join(allowed, 'missing.bin')}' INTO '${remote}' OVERWRITE`); + await execute(`REMOVE '${remote}'`); + await execute(`REMOVE '${remote}'`); + let deleted: unknown; + try { + await execute(`GET '${remote}' TO '${path.join(allowed, 'deleted.bin')}'`); + } catch (error) { + deleted = error; + } + expect(deleted).to.be.instanceOf(StagingError); + } finally { + await execute(`DROP SCHEMA main.${namespace} CASCADE`); + } + } finally { + await session.close(); + } + } finally { + await client.close(); + fs.rmSync(local, { recursive: true, force: true }); + } + }); + }); +}); diff --git a/tests/unit/kernel/error-mapping.test.ts b/tests/unit/kernel/error-mapping.test.ts index 0dad3335..9c826694 100644 --- a/tests/unit/kernel/error-mapping.test.ts +++ b/tests/unit/kernel/error-mapping.test.ts @@ -10,6 +10,7 @@ import HiveDriverError from '../../../lib/errors/HiveDriverError'; import AuthenticationError from '../../../lib/errors/AuthenticationError'; import OperationStateError, { OperationStateErrorCode } from '../../../lib/errors/OperationStateError'; import ParameterError from '../../../lib/errors/ParameterError'; +import StagingError from '../../../lib/errors/StagingError'; describe('KernelErrorMapping.mapKernelErrorToJsError', () => { // The 13 kernel ErrorCode variants — kept in sync with src/kernel_error.rs:66-134. @@ -312,3 +313,24 @@ describe('KernelErrorMapping.decodeNapiKernelError — diagnostics enrichment (T expect(err.kernelMetadata?.diagnosticInfo).to.equal('ArithmeticException'); // Thrift: response.errorMessage }); }); + +describe('file transfer errors', () => { + it('preserves StagingError and structured diagnostics', () => { + const error = decodeNapiKernelError( + new Error( + '__databricks_error__:' + + JSON.stringify({ + code: 'NotFound', + message: 'File transfer failed with HTTP 404', + errorCode: 'FILE_TRANSFER_ERROR', + sqlState: 'HY000', + queryId: 'statement', + }), + ), + ); + const mapped = error as ErrorWithSqlState; + expect(error).to.be.instanceOf(StagingError); + expect(mapped.kernelMetadata?.queryId).to.equal('statement'); + expect(mapped.sqlState).to.equal('HY000'); + }); +}); diff --git a/tests/unit/kernel/execution.test.ts b/tests/unit/kernel/execution.test.ts index 73e37822..7804750d 100644 --- a/tests/unit/kernel/execution.test.ts +++ b/tests/unit/kernel/execution.test.ts @@ -1041,13 +1041,11 @@ describe('KernelSessionBackend', () => { // Unsupported-on-kernel per-statement HINTS are NO-OPs (not errors) so Thrift- // targeted call sites are drop-in on the kernel path. They are kernel-governed - // perf/format hints (useCloudFetch / useLZ4Compression) or an unexposed feature - // (staging); ignoring them can't change results. (Parameter binding is NOT here + // perf/format hints (useCloudFetch / useLZ4Compression). (Parameter binding is NOT here // — a dropped param would change results, so it still throws; asserted above.) for (const { name, options } of [ { name: 'useCloudFetch', options: { useCloudFetch: true } }, { name: 'useLZ4Compression', options: { useLZ4Compression: true } }, - { name: 'stagingAllowedLocalPath', options: { stagingAllowedLocalPath: '/tmp' } }, ] as const) { it(`executeStatement ignores unsupported ${name} (no-op, does not throw)`, async () => { const connection = new FakeNativeConnection(); @@ -1058,6 +1056,20 @@ describe('KernelSessionBackend', () => { }); } + for (const runAsync of [false, true]) { + for (const allowed of [undefined, '/tmp/files', ['/tmp/a', '/tmp/b'], []]) { + it(`forwards allowed local paths with runAsync=${runAsync} and paths=${JSON.stringify(allowed)}`, async () => { + const connection = new FakeNativeConnection(); + const session = makeSession(connection); + await session.executeStatement('SELECT 1', { runAsync, stagingAllowedLocalPath: allowed }); + const expected = typeof allowed === 'string' ? [allowed] : allowed; + expect( + (connection.lastOptions as { stagingAllowedLocalPaths?: string[] })?.stagingAllowedLocalPaths, + ).to.deep.equal(expected); + }); + } + } + // Metadata calls forward to the kernel's metadata surface and wrap the // returned napi `Statement` as a `KernelOperationBackend`. Each case asserts // the request → napi-argument mapping (the only logic the driver owns). From 489d6c57347bb8dd6a59199027583400ddd9f763 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 23:03:57 +0000 Subject: [PATCH 02/30] style: format volume transfer documentation Signed-off-by: Aakash Saravanan --- CHANGELOG.md | 1 + CONNECTION_PARAMETERS.md | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48ac822b..4b9e906a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Release History ## Unreleased + - Honor `stagingAllowedLocalPath` for SQL PUT, GET, and REMOVE through the native backend and report transfer errors as `StagingError`. Requires matching native packages. ## Unreleased diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index ed466ce4..f137de91 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -156,19 +156,19 @@ telemetry implementation, so only the options explicitly forwarded by ## Per-statement options (`session.executeStatement(sql, options)`) -| Option | Type | Thrift | Kernel | Default Value | Note | -| ------------------------- | ------------------------------------------------------- | :----: | :----: | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `queryTimeout` | `number \| bigint \| Int64` | ✅ | ❌ | — | Thrift sends this server-side timeout for Compute clusters. Kernel ignores it; SQL Warehouses use `STATEMENT_TIMEOUT`. | -| `runAsync` | `boolean` | ❌ | ✅ | — / `false` | Thrift always sends asynchronously and ignores the option. Kernel `false`/omitted uses direct execution; `true` submits and polls. This execute option is not deprecated. | -| `maxRows` | `number \| bigint \| Int64 \| null` | ✅ | ❌ | `100000` / — | Thrift uses it for the initial Direct Results row count; `null` disables Direct Results. Kernel does not retain or forward the supplied value. | -| `useCloudFetch` | `boolean` | ✅ | ❌ | `true` / — | Kernel logs and ignores this per-statement hint; Kernel result fetching owns its CloudFetch behavior. | -| `useLZ4Compression` | `boolean` | ✅ | ❌ | `true` / — | Thrift uses it when supported and when the result is not CloudFetch. Kernel owns and auto-detects result compression. | -| `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ✅ | — | Local allowlist for SQL PUT/GET. Native transfers require the kernel revision in `KERNEL_REV` and matching native packages; leaving the option unset returns transfer instructions. | -| `namedParameters` | `Record` | ✅ | ✅ | — | Named SQL parameters. Cannot be combined with non-empty `ordinalParameters`. Thrift requires a protocol that supports parameterized queries. | -| `ordinalParameters` | `Array` | ✅ | ✅ | — | Positional SQL parameters. Cannot be combined with non-empty `namedParameters`. | -| `queryTags` | `Record` | ✅ | ✅ | — | Serialized into the per-statement `query_tags` conf overlay on both. | -| `rowLimit` | `number` | ❌ | ✅ | — | Kernel-only server-side row cap. Thrift logs and ignores it. | -| `statementConf` | `Record` | ❌ | ✅ | — | Kernel-only per-statement Spark conf overlay. Structured `queryTags` overwrite its `query_tags` key when non-empty. | +| Option | Type | Thrift | Kernel | Default Value | Note | +| ------------------------- | ------------------------------------------------------- | :----: | :----: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `queryTimeout` | `number \| bigint \| Int64` | ✅ | ❌ | — | Thrift sends this server-side timeout for Compute clusters. Kernel ignores it; SQL Warehouses use `STATEMENT_TIMEOUT`. | +| `runAsync` | `boolean` | ❌ | ✅ | — / `false` | Thrift always sends asynchronously and ignores the option. Kernel `false`/omitted uses direct execution; `true` submits and polls. This execute option is not deprecated. | +| `maxRows` | `number \| bigint \| Int64 \| null` | ✅ | ❌ | `100000` / — | Thrift uses it for the initial Direct Results row count; `null` disables Direct Results. Kernel does not retain or forward the supplied value. | +| `useCloudFetch` | `boolean` | ✅ | ❌ | `true` / — | Kernel logs and ignores this per-statement hint; Kernel result fetching owns its CloudFetch behavior. | +| `useLZ4Compression` | `boolean` | ✅ | ❌ | `true` / — | Thrift uses it when supported and when the result is not CloudFetch. Kernel owns and auto-detects result compression. | +| `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ✅ | — | Local allowlist for SQL PUT/GET. Native transfers require the kernel revision in `KERNEL_REV` and matching native packages; leaving the option unset returns transfer instructions. | +| `namedParameters` | `Record` | ✅ | ✅ | — | Named SQL parameters. Cannot be combined with non-empty `ordinalParameters`. Thrift requires a protocol that supports parameterized queries. | +| `ordinalParameters` | `Array` | ✅ | ✅ | — | Positional SQL parameters. Cannot be combined with non-empty `namedParameters`. | +| `queryTags` | `Record` | ✅ | ✅ | — | Serialized into the per-statement `query_tags` conf overlay on both. | +| `rowLimit` | `number` | ❌ | ✅ | — | Kernel-only server-side row cap. Thrift logs and ignores it. | +| `statementConf` | `Record` | ❌ | ✅ | — | Kernel-only per-statement Spark conf overlay. Structured `queryTags` overwrite its `query_tags` key when non-empty. | ## Metadata-operation request parameters From 8ad809ed62ebc0b396b54b53f40a405d8aa780b9 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 23:13:02 +0000 Subject: [PATCH 03/30] fix(volume): normalize changelog and pin validated native source Signed-off-by: Aakash Saravanan --- CHANGELOG.md | 2 -- KERNEL_REV | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b9e906a..0c393cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,6 @@ - Honor `stagingAllowedLocalPath` for SQL PUT, GET, and REMOVE through the native backend and report transfer errors as `StagingError`. Requires matching native packages. -## Unreleased - - CloudFetch retry-exhaustion errors now identify the download and the last failure: the `RetryError` message reads `CloudFetch result download failed: (last error: )`. `` is `HTTP : ` (or `HTTP ` when the status text is empty) for HTTP failures, and `network error `, `network error ()` (e.g. `aborted`, when there is no `code`) or a bare `network error` for network failures (the raw network-error message, which can contain the presigned URL, is not copied). If no cause can be determined, the ` (last error: …)` suffix is omitted. The `RetryError` instance, `errorCode` and `payload` (including the node-fetch `Request` / `Response`) are unchanged. (#471) - Report untyped NULL columns as STRING in kernel result metadata, matching Thrift. (#470) - Reject non-scalar query parameter values at bind time with a `ParameterError` on both the Thrift and kernel backends, instead of stringifying them (which could leave a Thrift operation stuck before FINISHED). Newly rejected on the Thrift backend: arrays, plain objects, boxed primitives (e.g. `new String('x')`, `new Number(1)`), functions, symbols, and a `DBSQLParameter` that wraps another `DBSQLParameter`. Primitives, `Date`, `Int64`, and `DBSQLParameter` are still accepted. diff --git a/KERNEL_REV b/KERNEL_REV index 31390e27..a3d86de0 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -bc39c7921acecd95e4eea0740becb883867a834b +ebb8204b36951b751dd837b2c41ced8c821b2d94 From 2148a8904533db5d7121f90a422b7db7a4e47c5a Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 23:24:21 +0000 Subject: [PATCH 04/30] chore(volume): pin native file-mode fix Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index a3d86de0..7b10abd3 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -ebb8204b36951b751dd837b2c41ced8c821b2d94 +02283500d50e1bcd61007e0cf04a7e1e3d16ea86 From e75455b37c340fb62c30b354266fe9c5c236ab38 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 23:37:09 +0000 Subject: [PATCH 05/30] fix(kernel): align native pin and brace transfer error branch Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- lib/kernel/KernelErrorMapping.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/KERNEL_REV b/KERNEL_REV index 7b10abd3..ef0cbe60 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -02283500d50e1bcd61007e0cf04a7e1e3d16ea86 +2f317d97212f00d73b89d947f5261a2145ea7a10 diff --git a/lib/kernel/KernelErrorMapping.ts b/lib/kernel/KernelErrorMapping.ts index e8cce5a6..51bd9651 100644 --- a/lib/kernel/KernelErrorMapping.ts +++ b/lib/kernel/KernelErrorMapping.ts @@ -141,7 +141,7 @@ export function mapKernelErrorToJsError(kErr: KernelErrorShape): ErrorWithSqlSta if (kErr.errorCode === 'FILE_TRANSFER_ERROR') { error = new StagingError(message); - } else + } else { switch (code as KernelErrorCode) { case 'Unauthenticated': case 'PermissionDenied': @@ -194,6 +194,7 @@ export function mapKernelErrorToJsError(kErr: KernelErrorShape): ErrorWithSqlSta error = new HiveDriverError(message); break; } + } if (sqlstate !== undefined) { defineErrorMetadata(error, 'sqlState', sqlstate); From 9c1b37b282d0adebc5ba23ad09787c732c4e7c59 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 23:46:45 +0000 Subject: [PATCH 06/30] build(kernel): pin reviewed Volume transfer implementation Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index ef0cbe60..913fb888 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -2f317d97212f00d73b89d947f5261a2145ea7a10 +95f294c8c2c825440191af9f58a9b6071aa68408 From 417496a0547c02356734a84e443672629740eaa7 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Wed, 11 Nov 2026 23:59:27 +0000 Subject: [PATCH 07/30] docs(kernel): clarify file transfer options and source dependency Signed-off-by: Aakash Saravanan --- CHANGELOG.md | 1 - CONNECTION_PARAMETERS.md | 2 +- KERNEL_REV | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c393cc9..7151b60a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,6 @@ ## Unreleased - Honor `stagingAllowedLocalPath` for SQL PUT, GET, and REMOVE through the native backend and report transfer errors as `StagingError`. Requires matching native packages. - - CloudFetch retry-exhaustion errors now identify the download and the last failure: the `RetryError` message reads `CloudFetch result download failed: (last error: )`. `` is `HTTP : ` (or `HTTP ` when the status text is empty) for HTTP failures, and `network error `, `network error ()` (e.g. `aborted`, when there is no `code`) or a bare `network error` for network failures (the raw network-error message, which can contain the presigned URL, is not copied). If no cause can be determined, the ` (last error: …)` suffix is omitted. The `RetryError` instance, `errorCode` and `payload` (including the node-fetch `Request` / `Response`) are unchanged. (#471) - Report untyped NULL columns as STRING in kernel result metadata, matching Thrift. (#470) - Reject non-scalar query parameter values at bind time with a `ParameterError` on both the Thrift and kernel backends, instead of stringifying them (which could leave a Thrift operation stuck before FINISHED). Newly rejected on the Thrift backend: arrays, plain objects, boxed primitives (e.g. `new String('x')`, `new Number(1)`), functions, symbols, and a `DBSQLParameter` that wraps another `DBSQLParameter`. Primitives, `Date`, `Int64`, and `DBSQLParameter` are still accepted. diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index f137de91..d75a8ead 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -163,7 +163,7 @@ telemetry implementation, so only the options explicitly forwarded by | `maxRows` | `number \| bigint \| Int64 \| null` | ✅ | ❌ | `100000` / — | Thrift uses it for the initial Direct Results row count; `null` disables Direct Results. Kernel does not retain or forward the supplied value. | | `useCloudFetch` | `boolean` | ✅ | ❌ | `true` / — | Kernel logs and ignores this per-statement hint; Kernel result fetching owns its CloudFetch behavior. | | `useLZ4Compression` | `boolean` | ✅ | ❌ | `true` / — | Thrift uses it when supported and when the result is not CloudFetch. Kernel owns and auto-detects result compression. | -| `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ✅ | — | Local allowlist for SQL PUT/GET. Native transfers require the kernel revision in `KERNEL_REV` and matching native packages; leaving the option unset returns transfer instructions. | +| `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ✅ | — | Local allowlist for SQL PUT/GET; also enables SQL REMOVE without a local path. Native transfers require the kernel revision in `KERNEL_REV` and matching native packages; leaving the option unset returns transfer instructions. | | `namedParameters` | `Record` | ✅ | ✅ | — | Named SQL parameters. Cannot be combined with non-empty `ordinalParameters`. Thrift requires a protocol that supports parameterized queries. | | `ordinalParameters` | `Array` | ✅ | ✅ | — | Positional SQL parameters. Cannot be combined with non-empty `namedParameters`. | | `queryTags` | `Record` | ✅ | ✅ | — | Serialized into the per-statement `query_tags` conf overlay on both. | diff --git a/KERNEL_REV b/KERNEL_REV index 913fb888..f164365a 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -95f294c8c2c825440191af9f58a9b6071aa68408 +f68e4e5d1600e987187cf84f58ddf6a61e6a5ba6 From 99885c10eca48decd7ea6bf86359d63a8a21c47c Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 00:10:13 +0000 Subject: [PATCH 08/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- CONNECTION_PARAMETERS.md | 24 ++++++++++++------------ KERNEL_REV | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/CONNECTION_PARAMETERS.md b/CONNECTION_PARAMETERS.md index d75a8ead..cb6ec727 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -156,19 +156,19 @@ telemetry implementation, so only the options explicitly forwarded by ## Per-statement options (`session.executeStatement(sql, options)`) -| Option | Type | Thrift | Kernel | Default Value | Note | -| ------------------------- | ------------------------------------------------------- | :----: | :----: | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `queryTimeout` | `number \| bigint \| Int64` | ✅ | ❌ | — | Thrift sends this server-side timeout for Compute clusters. Kernel ignores it; SQL Warehouses use `STATEMENT_TIMEOUT`. | -| `runAsync` | `boolean` | ❌ | ✅ | — / `false` | Thrift always sends asynchronously and ignores the option. Kernel `false`/omitted uses direct execution; `true` submits and polls. This execute option is not deprecated. | -| `maxRows` | `number \| bigint \| Int64 \| null` | ✅ | ❌ | `100000` / — | Thrift uses it for the initial Direct Results row count; `null` disables Direct Results. Kernel does not retain or forward the supplied value. | -| `useCloudFetch` | `boolean` | ✅ | ❌ | `true` / — | Kernel logs and ignores this per-statement hint; Kernel result fetching owns its CloudFetch behavior. | -| `useLZ4Compression` | `boolean` | ✅ | ❌ | `true` / — | Thrift uses it when supported and when the result is not CloudFetch. Kernel owns and auto-detects result compression. | +| Option | Type | Thrift | Kernel | Default Value | Note | +| ------------------------- | ------------------------------------------------------- | :----: | :----: | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `queryTimeout` | `number \| bigint \| Int64` | ✅ | ❌ | — | Thrift sends this server-side timeout for Compute clusters. Kernel ignores it; SQL Warehouses use `STATEMENT_TIMEOUT`. | +| `runAsync` | `boolean` | ❌ | ✅ | — / `false` | Thrift always sends asynchronously and ignores the option. Kernel `false`/omitted uses direct execution; `true` submits and polls. This execute option is not deprecated. | +| `maxRows` | `number \| bigint \| Int64 \| null` | ✅ | ❌ | `100000` / — | Thrift uses it for the initial Direct Results row count; `null` disables Direct Results. Kernel does not retain or forward the supplied value. | +| `useCloudFetch` | `boolean` | ✅ | ❌ | `true` / — | Kernel logs and ignores this per-statement hint; Kernel result fetching owns its CloudFetch behavior. | +| `useLZ4Compression` | `boolean` | ✅ | ❌ | `true` / — | Thrift uses it when supported and when the result is not CloudFetch. Kernel owns and auto-detects result compression. | | `stagingAllowedLocalPath` | `string \| string[]` | ✅ | ✅ | — | Local allowlist for SQL PUT/GET; also enables SQL REMOVE without a local path. Native transfers require the kernel revision in `KERNEL_REV` and matching native packages; leaving the option unset returns transfer instructions. | -| `namedParameters` | `Record` | ✅ | ✅ | — | Named SQL parameters. Cannot be combined with non-empty `ordinalParameters`. Thrift requires a protocol that supports parameterized queries. | -| `ordinalParameters` | `Array` | ✅ | ✅ | — | Positional SQL parameters. Cannot be combined with non-empty `namedParameters`. | -| `queryTags` | `Record` | ✅ | ✅ | — | Serialized into the per-statement `query_tags` conf overlay on both. | -| `rowLimit` | `number` | ❌ | ✅ | — | Kernel-only server-side row cap. Thrift logs and ignores it. | -| `statementConf` | `Record` | ❌ | ✅ | — | Kernel-only per-statement Spark conf overlay. Structured `queryTags` overwrite its `query_tags` key when non-empty. | +| `namedParameters` | `Record` | ✅ | ✅ | — | Named SQL parameters. Cannot be combined with non-empty `ordinalParameters`. Thrift requires a protocol that supports parameterized queries. | +| `ordinalParameters` | `Array` | ✅ | ✅ | — | Positional SQL parameters. Cannot be combined with non-empty `namedParameters`. | +| `queryTags` | `Record` | ✅ | ✅ | — | Serialized into the per-statement `query_tags` conf overlay on both. | +| `rowLimit` | `number` | ❌ | ✅ | — | Kernel-only server-side row cap. Thrift logs and ignores it. | +| `statementConf` | `Record` | ❌ | ✅ | — | Kernel-only per-statement Spark conf overlay. Structured `queryTags` overwrite its `query_tags` key when non-empty. | ## Metadata-operation request parameters diff --git a/KERNEL_REV b/KERNEL_REV index f164365a..098e9943 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -f68e4e5d1600e987187cf84f58ddf6a61e6a5ba6 +c3e7cf1ba339155c7932c77e39e2821fd918cddf From c2d8000faf06b53e808daf31ae9dc4267d712bc9 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 00:22:23 +0000 Subject: [PATCH 09/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 098e9943..fc75c3b8 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -c3e7cf1ba339155c7932c77e39e2821fd918cddf +7a938104d5e9870f995926f135aa41925c044cf6 From 37de7fd868853b9bb0364e4675d04fb3e1e9ea51 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 00:35:43 +0000 Subject: [PATCH 10/30] fix(volume): preserve staging errors for local permissions Signed-off-by: Aakash Saravanan --- CHANGELOG.md | 2 +- lib/kernel/KernelErrorMapping.ts | 2 +- tests/unit/kernel/error-mapping.test.ts | 36 +++++++++++++------------ 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7151b60a..1cc48565 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- Honor `stagingAllowedLocalPath` for SQL PUT, GET, and REMOVE through the native backend and report transfer errors as `StagingError`. Requires matching native packages. +- Honor `stagingAllowedLocalPath` for SQL PUT, GET, and REMOVE through the native backend and report transfer errors as `StagingError`. Requires matching native packages. (#582) - CloudFetch retry-exhaustion errors now identify the download and the last failure: the `RetryError` message reads `CloudFetch result download failed: (last error: )`. `` is `HTTP : ` (or `HTTP ` when the status text is empty) for HTTP failures, and `network error `, `network error ()` (e.g. `aborted`, when there is no `code`) or a bare `network error` for network failures (the raw network-error message, which can contain the presigned URL, is not copied). If no cause can be determined, the ` (last error: …)` suffix is omitted. The `RetryError` instance, `errorCode` and `payload` (including the node-fetch `Request` / `Response`) are unchanged. (#471) - Report untyped NULL columns as STRING in kernel result metadata, matching Thrift. (#470) - Reject non-scalar query parameter values at bind time with a `ParameterError` on both the Thrift and kernel backends, instead of stringifying them (which could leave a Thrift operation stuck before FINISHED). Newly rejected on the Thrift backend: arrays, plain objects, boxed primitives (e.g. `new String('x')`, `new Number(1)`), functions, symbols, and a `DBSQLParameter` that wraps another `DBSQLParameter`. Primitives, `Date`, `Int64`, and `DBSQLParameter` are still accepted. diff --git a/lib/kernel/KernelErrorMapping.ts b/lib/kernel/KernelErrorMapping.ts index 51bd9651..55dd0cfc 100644 --- a/lib/kernel/KernelErrorMapping.ts +++ b/lib/kernel/KernelErrorMapping.ts @@ -139,7 +139,7 @@ export function mapKernelErrorToJsError(kErr: KernelErrorShape): ErrorWithSqlSta let error: ErrorWithSqlState; - if (kErr.errorCode === 'FILE_TRANSFER_ERROR') { + if (kErr.errorCode === 'FILE_TRANSFER_ERROR' || kErr.errorCode === 'FILE_TRANSFER_PERMISSION_ERROR') { error = new StagingError(message); } else { switch (code as KernelErrorCode) { diff --git a/tests/unit/kernel/error-mapping.test.ts b/tests/unit/kernel/error-mapping.test.ts index 9c826694..2fbc9084 100644 --- a/tests/unit/kernel/error-mapping.test.ts +++ b/tests/unit/kernel/error-mapping.test.ts @@ -315,22 +315,24 @@ describe('KernelErrorMapping.decodeNapiKernelError — diagnostics enrichment (T }); describe('file transfer errors', () => { - it('preserves StagingError and structured diagnostics', () => { - const error = decodeNapiKernelError( - new Error( - '__databricks_error__:' + - JSON.stringify({ - code: 'NotFound', - message: 'File transfer failed with HTTP 404', - errorCode: 'FILE_TRANSFER_ERROR', - sqlState: 'HY000', - queryId: 'statement', - }), - ), - ); - const mapped = error as ErrorWithSqlState; - expect(error).to.be.instanceOf(StagingError); - expect(mapped.kernelMetadata?.queryId).to.equal('statement'); - expect(mapped.sqlState).to.equal('HY000'); + ['FILE_TRANSFER_ERROR', 'FILE_TRANSFER_PERMISSION_ERROR'].forEach((errorCode) => { + it(`preserves StagingError and structured diagnostics for ${errorCode}`, () => { + const error = decodeNapiKernelError( + new Error( + '__databricks_error__:' + + JSON.stringify({ + code: 'NotFound', + message: 'File transfer failed with HTTP 404', + errorCode, + sqlState: 'HY000', + queryId: 'statement', + }), + ), + ); + const mapped = error as ErrorWithSqlState; + expect(error).to.be.instanceOf(StagingError); + expect(mapped.kernelMetadata?.queryId).to.equal('statement'); + expect(mapped.sqlState).to.equal('HY000'); + }); }); }); From b021a9d495195c460883fd3cb58e53bad605ea74 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 00:38:15 +0000 Subject: [PATCH 11/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index fc75c3b8..152d3f41 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -7a938104d5e9870f995926f135aa41925c044cf6 +ab10f222493fe45c645f746cdd2d762173a70fc5 From 76e1c432135aca1759c3502f9d9047123e5ea4c2 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 00:49:47 +0000 Subject: [PATCH 12/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 152d3f41..731c1165 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -ab10f222493fe45c645f746cdd2d762173a70fc5 +6dbc9a7c721e55e535b469df446251f1fc0ccaaa From 141209e309618d30a992435db553456d2280208e Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 01:00:06 +0000 Subject: [PATCH 13/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 731c1165..bbb42949 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -6dbc9a7c721e55e535b469df446251f1fc0ccaaa +bd8adef4f3a38cc8807e4a22b614865246ee7cc2 From caa35d26813746c278a6c57fc6e95dacdc6913c9 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 01:10:24 +0000 Subject: [PATCH 14/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index bbb42949..533b2c97 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -bd8adef4f3a38cc8807e4a22b614865246ee7cc2 +c3300aaa17d0d1109e2579c40e6f35b3c3aeaf54 From c02439813c754ff2000558675effe7cc6e4fe53e Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 01:30:23 +0000 Subject: [PATCH 15/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 533b2c97..4d7e0fd5 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -c3300aaa17d0d1109e2579c40e6f35b3c3aeaf54 +ed829e8ac8b27bdb503beaf6576dc41876df9465 From 47fc11dff465da31e12ff83bf9893cac992b35d0 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 01:41:25 +0000 Subject: [PATCH 16/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 4d7e0fd5..f67811cf 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -ed829e8ac8b27bdb503beaf6576dc41876df9465 +2006c55d1b67903ca7b6090d0b84a2be73fcb82f From 6b8a321fef796854eaf29ee2727b91b52cbaf45b Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 01:50:16 +0000 Subject: [PATCH 17/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- native/kernel/index.d.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/KERNEL_REV b/KERNEL_REV index f67811cf..d0a7d067 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -2006c55d1b67903ca7b6090d0b84a2be73fcb82f +892ee5698302b4e34db5ea926baa0354e9c229da diff --git a/native/kernel/index.d.ts b/native/kernel/index.d.ts index c4702bd6..4d7680a4 100644 --- a/native/kernel/index.d.ts +++ b/native/kernel/index.d.ts @@ -1126,7 +1126,10 @@ export interface ExecuteOptions { * `StatementSpec.row_limit`. Omitted ⇒ no driver-imposed cap. */ rowLimit?: number - /** Omit to handle instructions in the caller; an empty list denies local file access. */ + /** + * Configured transfers consume instruction rows. + * Omit to handle instructions in the caller; an empty list denies local file access. + */ stagingAllowedLocalPaths?: Array /** * Positional parameters, in 1-based wire order. Index `i` in this From 07ac457de2828d966b3d149711b6afee226fbcc2 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 02:02:05 +0000 Subject: [PATCH 18/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index d0a7d067..e4f03240 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -892ee5698302b4e34db5ea926baa0354e9c229da +00e1701a9b14082dba9e4d4d7d8df378971e1818 From 9ab554945f822cd9a687106d00742166a27391c2 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 02:12:06 +0000 Subject: [PATCH 19/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index e4f03240..a3bdb6dd 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -00e1701a9b14082dba9e4d4d7d8df378971e1818 +e78a4b1e58562e03a06c9166cc76bde5842f1de0 From 89abedee1e71947061ed4ad5f2fb794200112fb4 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 02:41:55 +0000 Subject: [PATCH 20/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index a3bdb6dd..3f36a79f 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -e78a4b1e58562e03a06c9166cc76bde5842f1de0 +f139344c9c8cf9f3224f6d3068adba08632a0c32 From be17f4fa4b09ac8882e4ae5d04884c9a68f3672b Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 03:08:04 +0000 Subject: [PATCH 21/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 3f36a79f..0c799948 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -f139344c9c8cf9f3224f6d3068adba08632a0c32 +9f7bf932aacbd76ff67c52cc1cd84082af2e5ab0 From c870f3e66f2942b770dd9463b1fb8f14a6fce208 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 03:18:16 +0000 Subject: [PATCH 22/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 0c799948..679cb150 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -9f7bf932aacbd76ff67c52cc1cd84082af2e5ab0 +be474276cbffa2aa89eeee593c957cacc7b43c81 From 8bf93ffd3e6a630a8fe0eb0d23c47e36536acc64 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 03:46:21 +0000 Subject: [PATCH 23/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 679cb150..029e66f9 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -be474276cbffa2aa89eeee593c957cacc7b43c81 +23cd28e82876a75d0ecbaa487018906bc0dca924 From 2ca406219e728b31907de2b1310fcb324afbd93b Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 04:03:35 +0000 Subject: [PATCH 24/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 029e66f9..b9db4871 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -23cd28e82876a75d0ecbaa487018906bc0dca924 +4f12d122d435c03ca489f77138deebd2ebe17081 From c72ec9061dbbbb5aa6d8c51085cc91d61f355e65 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 04:16:40 +0000 Subject: [PATCH 25/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index b9db4871..0d8e1b74 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -4f12d122d435c03ca489f77138deebd2ebe17081 +b92a86716b49f48f577e2299989f235c20b50ee7 From 5a608f89c14d4e5e1cfd51e70f620b336b023d5c Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 04:30:08 +0000 Subject: [PATCH 26/30] fix(kernel): preserve file transfer operation states Signed-off-by: Aakash Saravanan --- lib/kernel/KernelErrorMapping.ts | 9 ++++++++- tests/unit/kernel/error-mapping.test.ts | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/kernel/KernelErrorMapping.ts b/lib/kernel/KernelErrorMapping.ts index 55dd0cfc..9182118b 100644 --- a/lib/kernel/KernelErrorMapping.ts +++ b/lib/kernel/KernelErrorMapping.ts @@ -127,6 +127,9 @@ function defineErrorMetadata(error: Error, key: K, value: V * DataLoss, Internal, * InvalidStatementHandle, SqlError → HiveDriverError * + * File-transfer diagnostics map to StagingError except Cancelled and Timeout, + * which retain their operation-state errors for lifecycle handling. + * * Unknown `code` values (e.g. if the kernel adds a new variant) fall through * to HiveDriverError so the driver never silently drops an error. The kernel's * `ErrorCode` is `#[non_exhaustive]` so this can legitimately happen. @@ -139,7 +142,11 @@ export function mapKernelErrorToJsError(kErr: KernelErrorShape): ErrorWithSqlSta let error: ErrorWithSqlState; - if (kErr.errorCode === 'FILE_TRANSFER_ERROR' || kErr.errorCode === 'FILE_TRANSFER_PERMISSION_ERROR') { + if ( + code !== 'Cancelled' && + code !== 'Timeout' && + (kErr.errorCode === 'FILE_TRANSFER_ERROR' || kErr.errorCode === 'FILE_TRANSFER_PERMISSION_ERROR') + ) { error = new StagingError(message); } else { switch (code as KernelErrorCode) { diff --git a/tests/unit/kernel/error-mapping.test.ts b/tests/unit/kernel/error-mapping.test.ts index 2fbc9084..1a3c3397 100644 --- a/tests/unit/kernel/error-mapping.test.ts +++ b/tests/unit/kernel/error-mapping.test.ts @@ -315,6 +315,22 @@ describe('KernelErrorMapping.decodeNapiKernelError — diagnostics enrichment (T }); describe('file transfer errors', () => { + [ + ['Cancelled', OperationStateErrorCode.Canceled], + ['Timeout', OperationStateErrorCode.Timeout], + ].forEach(([code, expected]) => { + it(`preserves operation state for a ${code} file transfer`, () => { + const error = decodeNapiKernelError( + new Error( + '__databricks_error__:' + + JSON.stringify({ code, message: 'Transfer stopped', errorCode: 'FILE_TRANSFER_ERROR' }), + ), + ) as ErrorWithSqlState; + expect(error).to.be.instanceOf(OperationStateError); + expect((error as OperationStateError).errorCode).to.equal(expected); + expect(error.kernelMetadata?.errorCode).to.equal('FILE_TRANSFER_ERROR'); + }); + }); ['FILE_TRANSFER_ERROR', 'FILE_TRANSFER_PERMISSION_ERROR'].forEach((errorCode) => { it(`preserves StagingError and structured diagnostics for ${errorCode}`, () => { const error = decodeNapiKernelError( From e60eaf3c7d94e01194569386710f4bc5357faf50 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 04:47:37 +0000 Subject: [PATCH 27/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index 0d8e1b74..c034890e 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -b92a86716b49f48f577e2299989f235c20b50ee7 +ea26771a5122c8558066638c292cff01df1ebb82 From d9081aab95b6cfe10e709708586f8e01d9a0d205 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 04:57:46 +0000 Subject: [PATCH 28/30] test: cover transfer errors before auth and argument errors Signed-off-by: Aakash Saravanan --- tests/unit/kernel/error-mapping.test.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/unit/kernel/error-mapping.test.ts b/tests/unit/kernel/error-mapping.test.ts index 1a3c3397..87038b4f 100644 --- a/tests/unit/kernel/error-mapping.test.ts +++ b/tests/unit/kernel/error-mapping.test.ts @@ -331,13 +331,18 @@ describe('file transfer errors', () => { expect(error.kernelMetadata?.errorCode).to.equal('FILE_TRANSFER_ERROR'); }); }); - ['FILE_TRANSFER_ERROR', 'FILE_TRANSFER_PERMISSION_ERROR'].forEach((errorCode) => { - it(`preserves StagingError and structured diagnostics for ${errorCode}`, () => { + [ + ['NotFound', 'FILE_TRANSFER_ERROR'], + ['NotFound', 'FILE_TRANSFER_PERMISSION_ERROR'], + ['PermissionDenied', 'FILE_TRANSFER_PERMISSION_ERROR'], + ['InvalidArgument', 'FILE_TRANSFER_ERROR'], + ].forEach(([code, errorCode]) => { + it(`preserves StagingError and structured diagnostics for ${code}/${errorCode}`, () => { const error = decodeNapiKernelError( new Error( '__databricks_error__:' + JSON.stringify({ - code: 'NotFound', + code, message: 'File transfer failed with HTTP 404', errorCode, sqlState: 'HY000', From 916a9d3cf85301dce27c3507a77208a951f153c0 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 05:19:01 +0000 Subject: [PATCH 29/30] build(kernel): pin reviewed file transfer source Signed-off-by: Aakash Saravanan --- KERNEL_REV | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KERNEL_REV b/KERNEL_REV index c034890e..b1078d37 100644 --- a/KERNEL_REV +++ b/KERNEL_REV @@ -1 +1 @@ -ea26771a5122c8558066638c292cff01df1ebb82 +37c7a3ec1b54f4690d9325d65f2220e0bb64b83e From 17f71ae3c04b75423f25cb090c341189812183ca Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Thu, 12 Nov 2026 06:54:22 +0000 Subject: [PATCH 30/30] Explain the specific kernel transfer error code Signed-off-by: Aakash Saravanan --- lib/kernel/KernelErrorMapping.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/kernel/KernelErrorMapping.ts b/lib/kernel/KernelErrorMapping.ts index 9182118b..a704002e 100644 --- a/lib/kernel/KernelErrorMapping.ts +++ b/lib/kernel/KernelErrorMapping.ts @@ -28,6 +28,7 @@ export interface KernelErrorShape { message: string; /** Optional SQLSTATE — five-char alphanumeric, when the kernel was able to surface it. */ sqlstate?: string; + /** Distinguishes transfer failures from broader kernel error categories. */ errorCode?: string; }