Skip to content

Commit 27851cc

Browse files
committed
fix(sea): forward queryTags through executeStatement (was silently dropped)
`ExecuteStatementOptions.queryTags` is a public option but the SEA executeStatement never forwarded it, so tags were silently dropped versus the Thrift backend. Serialise them JS-side via `serializeQueryTags` into the conf overlay's `query_tags` key (the same wire shape Thrift produces) — not via the napi `queryTags` field, which is a `HashMap<String,String>` that can't represent a null-valued tag, and the kernel rejects setting both the field and a `query_tags` conf key. Null-valued tags round-trip as key-only segments. Co-authored-by: Isaac Signed-off-by: Madhavendra Rathore <madhavendra.rathore@databricks.com>
1 parent 88c10aa commit 27851cc

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

lib/sea/SeaSessionBackend.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { seaServerInfoValue } from './SeaServerInfo';
3939
import { buildSeaPositionalParams, buildSeaNamedParams } from './SeaPositionalParams';
4040
import ParameterError from '../errors/ParameterError';
4141
import { emptyToUndefined, countParameterMarkers } from './SeaInputValidation';
42+
import { serializeQueryTags } from '../utils';
4243

4344
export interface SeaSessionBackendOptions {
4445
/** The opaque napi `Connection` handle returned by `openSession`. */
@@ -168,6 +169,16 @@ export default class SeaSessionBackend implements ISessionBackend {
168169
if (options.queryTimeout !== undefined) {
169170
nativeOptions.queryTimeoutSecs = Number(options.queryTimeout);
170171
}
172+
// Query tags: serialise JS-side into the conf overlay's `query_tags` key
173+
// (the same wire shape the Thrift backend produces via `serializeQueryTags`
174+
// → `confOverlay`). Not forwarded via the napi `queryTags` field: that's a
175+
// `HashMap<String,String>` which can't represent a null-valued tag, and the
176+
// kernel rejects setting both the field and a `query_tags` conf key. A
177+
// null-valued tag therefore round-trips as a key-only segment.
178+
const serializedQueryTags = serializeQueryTags(options.queryTags);
179+
if (serializedQueryTags !== undefined) {
180+
nativeOptions.statementConf = { query_tags: serializedQueryTags };
181+
}
171182
const hasOptions = Object.keys(nativeOptions).length > 0;
172183

173184
let nativeStatement;

tests/unit/sea/execution.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,23 @@ describe('SeaSessionBackend', () => {
379379
expect(connection.lastOptions?.queryTimeoutSecs).to.equal(30);
380380
});
381381

382+
it('executeStatement serialises queryTags into statementConf.query_tags (Thrift wire shape)', async () => {
383+
const connection = new FakeNativeConnection();
384+
const session = makeSession(connection);
385+
await session.executeStatement('SELECT 1', { queryTags: { team: 'data', env: 'prod' } });
386+
expect(connection.lastOptions?.statementConf?.query_tags).to.equal('team:data,env:prod');
387+
// Must NOT use the napi `queryTags` field (can't carry null tags; kernel
388+
// rejects both field + conf key).
389+
expect(connection.lastOptions?.queryTags).to.equal(undefined);
390+
});
391+
392+
it('executeStatement carries a null-valued queryTag as a key-only segment', async () => {
393+
const connection = new FakeNativeConnection();
394+
const session = makeSession(connection);
395+
await session.executeStatement('SELECT 1', { queryTags: { audited: null } });
396+
expect(connection.lastOptions?.statementConf?.query_tags).to.equal('audited');
397+
});
398+
382399
it('executeStatement forwards namedParameters as napi namedParams ({name,sqlType,value})', async () => {
383400
const connection = new FakeNativeConnection();
384401
const session = makeSession(connection);

0 commit comments

Comments
 (0)