From 4819ba8dd6a7d7cf0d17bbfb9a10755948497d73 Mon Sep 17 00:00:00 2001 From: Aakash Saravanan Date: Tue, 10 Nov 2026 23:52:05 +0000 Subject: [PATCH] fix(kernel): honor the client CloudFetch default Signed-off-by: Aakash Saravanan --- CHANGELOG.md | 2 ++ CONNECTION_PARAMETERS.md | 7 +++++++ lib/kernel/KernelBackend.ts | 6 ++++++ tests/unit/kernel/execution.test.ts | 30 +++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04174c6e..26b0d215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +- Honor the client `useCloudFetch: false` default on the kernel backend so complete results can be fetched without cloud-storage downloads. + - 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 af46726c..8e4e2b17 100644 --- a/CONNECTION_PARAMETERS.md +++ b/CONNECTION_PARAMETERS.md @@ -47,6 +47,13 @@ backend (the default), the **SEA / Kernel** backend, or both. | `useKernel` (internal) | `boolean` | — | — | `false` | Selects the backend: `false`/omitted → Thrift, `true` → Kernel. | | `clientId` (deprecated) | `string` | ✅ | ✅ | — | Runtime-only alias for `userAgentEntry`. `userAgentEntry` wins when both are supplied. | +The `DBSQLClient` constructor configuration accepts `useCloudFetch` (default +`true`) on both backends. Set it to `false` before opening a session to fetch +complete results without cloud-storage downloads. On the kernel backend this +connection setting takes precedence over case-insensitive copies of +`can_cloud_download` in session configuration. Per-statement `useCloudFetch` +overrides remain unsupported on the kernel backend. + ## Authentication | Option | Type | Thrift | Kernel | Default Value | Note | diff --git a/lib/kernel/KernelBackend.ts b/lib/kernel/KernelBackend.ts index 798427e8..77f0166c 100644 --- a/lib/kernel/KernelBackend.ts +++ b/lib/kernel/KernelBackend.ts @@ -152,6 +152,12 @@ export default class KernelBackend implements IBackend { if (request.configuration !== undefined) { sessionOptions.sessionConf = { ...request.configuration }; } + if (this.context.getConfig().useCloudFetch === false) { + sessionOptions.sessionConf = Object.fromEntries( + Object.entries(sessionOptions.sessionConf ?? {}).filter(([key]) => key.toLowerCase() !== 'can_cloud_download'), + ); + sessionOptions.sessionConf.can_cloud_download = 'false'; + } // Session-level query tags: serialize into the reserved `QUERY_TAGS` // session conf. The kernel allowlists `QUERY_TAGS` (SESSION_CONF_ALLOWLIST) // and forwards it onto the SEA `CreateSession` `session_confs`, mirroring diff --git a/tests/unit/kernel/execution.test.ts b/tests/unit/kernel/execution.test.ts index 73e37822..b62b298d 100644 --- a/tests/unit/kernel/execution.test.ts +++ b/tests/unit/kernel/execution.test.ts @@ -735,6 +735,36 @@ describe('KernelBackend', () => { } }); + [undefined, true, false].forEach((useCloudFetch) => { + it(`openSession() honors useCloudFetch=${useCloudFetch}`, async () => { + const binding = makeBinding(new FakeNativeConnection()); + const backend = new KernelBackend({ context: makeContext(undefined, { useCloudFetch }), nativeBinding: binding }); + await backend.connect({ host: 'h', path: '/p', token: 't' } as ConnectionOptions); + const configuration = { TIMEZONE: 'UTC' }; + await backend.openSession({ configuration }); + const conf = (binding.openSessionStub.firstCall.args[0] as { sessionConf?: Record }).sessionConf; + expect(conf?.can_cloud_download).to.equal(useCloudFetch === false ? 'false' : undefined); + expect(conf?.TIMEZONE).to.equal('UTC'); + expect(configuration).to.deep.equal({ TIMEZONE: 'UTC' }); + }); + }); + + ['can_cloud_download', 'CAN_CLOUD_DOWNLOAD'].forEach((key) => { + it(`openSession() gives useCloudFetch=false precedence over ${key}`, async () => { + const binding = makeBinding(new FakeNativeConnection()); + const backend = new KernelBackend({ + context: makeContext(undefined, { useCloudFetch: false }), + nativeBinding: binding, + }); + await backend.connect({ host: 'h', path: '/p', token: 't' } as ConnectionOptions); + const configuration = { [key]: 'true', TIMEZONE: 'UTC' }; + await backend.openSession({ configuration }); + const conf = (binding.openSessionStub.firstCall.args[0] as { sessionConf?: Record }).sessionConf; + expect(conf).to.deep.equal({ can_cloud_download: 'false', TIMEZONE: 'UTC' }); + expect(configuration).to.deep.equal({ [key]: 'true', TIMEZONE: 'UTC' }); + }); + }); + it('openSession() serializes session-level queryTags into sessionConf.QUERY_TAGS', async () => { const connection = new FakeNativeConnection(); const binding = makeBinding(connection);