Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/sea/SeaBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getSeaNative, SeaNativeBinding, SeaConnection } from './SeaNativeLoader
import { decodeNapiKernelError } from './SeaErrorMapping';
import { buildSeaConnectionOptions, buildSeaRetryOptions, SeaNativeConnectionOptions } from './SeaAuth';
import { installKernelLogBridge } from './SeaLogging';
import { serializeQueryTags } from '../utils';
import SeaSessionBackend from './SeaSessionBackend';

export interface SeaBackendOptions {
Expand Down Expand Up @@ -145,6 +146,20 @@ export default class SeaBackend implements IBackend {
if (request.configuration !== undefined) {
sessionOptions.sessionConf = { ...request.configuration };
}
// Session-level query tags: serialize into the reserved `QUERY_TAGS`
// session conf (the kernel allowlists `QUERY_TAGS` and forwards it onto
// the SEA `CreateSession` `session_confs`), mirroring the Thrift
// backend's `ThriftBackend.openSession`. Runs after the `configuration`
// merge so `queryTags` takes precedence over an explicit
// `configuration.QUERY_TAGS`, matching the documented contract.
if (request.queryTags !== undefined) {
const serialized = serializeQueryTags(request.queryTags);
if (serialized) {
sessionOptions.sessionConf = { ...(sessionOptions.sessionConf ?? {}), QUERY_TAGS: serialized };
} else if (sessionOptions.sessionConf) {
delete sessionOptions.sessionConf.QUERY_TAGS;
}
}

let nativeConnection: SeaConnection;
try {
Expand Down
30 changes: 30 additions & 0 deletions tests/unit/sea/execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,36 @@ describe('SeaBackend', () => {
expect(connection.lastSql).to.equal('SELECT 1');
});

it('openSession() serializes session-level queryTags into sessionConf.QUERY_TAGS', async () => {
const connection = new FakeNativeConnection();
const binding = makeBinding(connection);
const backend = new SeaBackend({ context: makeContext(), nativeBinding: binding });
await backend.connect({ host: 'h', path: '/p', token: 't' } as ConnectionOptions);

await backend.openSession({ queryTags: { team: 'eng', env: 'prod' } });

// Session-level tags land in the reserved QUERY_TAGS session conf (the
// kernel allowlists it → SEA CreateSession session_confs), mirroring Thrift.
const conf = (binding.openSessionStub.firstCall.args[0] as { sessionConf?: Record<string, string> }).sessionConf;
expect(conf?.QUERY_TAGS).to.be.a('string');
expect(conf?.QUERY_TAGS).to.contain('team:eng').and.to.contain('env:prod');
});

it('openSession() queryTags takes precedence over an explicit configuration.QUERY_TAGS', async () => {
const connection = new FakeNativeConnection();
const binding = makeBinding(connection);
const backend = new SeaBackend({ context: makeContext(), nativeBinding: binding });
await backend.connect({ host: 'h', path: '/p', token: 't' } as ConnectionOptions);

await backend.openSession({
configuration: { QUERY_TAGS: 'manual-raw-value' },
queryTags: { team: 'eng' },
});

const conf = (binding.openSessionStub.firstCall.args[0] as { sessionConf?: Record<string, string> }).sessionConf;
expect(conf?.QUERY_TAGS).to.contain('team:eng').and.to.not.equal('manual-raw-value');
});

it('close() clears connection state without throwing', async () => {
const connection = new FakeNativeConnection();
const binding = makeBinding(connection);
Expand Down
Loading