Skip to content

Commit 4750bc2

Browse files
authored
Merge branch 'main' into telemetry-4-event-aggregation
2 parents 5669c30 + f05f8a9 commit 4750bc2

14 files changed

Lines changed: 296 additions & 7 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release History
22

3+
## 1.14.0
4+
5+
- Add statement-level query tag support (databricks/databricks-sql-nodejs#366 by @sreekanth-db)
6+
- Add AI coding agent detection to User-Agent header (databricks/databricks-sql-nodejs#333 by @vikrantpuppala)
7+
- Internal: telemetry infrastructure improvements — circuit breaker, feature flag cache, telemetry client management (off by default) (databricks/databricks-sql-nodejs#325, #326, #362)
8+
39
## 1.13.0
410

511
- Add token federation support with custom token providers (databricks/databricks-sql-nodejs#318, databricks/databricks-sql-nodejs#319, databricks/databricks-sql-nodejs#320 by @madhav-db)

examples/query_tags.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const { DBSQLClient } = require('..');
2+
3+
const client = new DBSQLClient();
4+
5+
const host = process.env.DATABRICKS_HOST;
6+
const path = process.env.DATABRICKS_HTTP_PATH;
7+
const token = process.env.DATABRICKS_TOKEN;
8+
9+
client
10+
.connect({ host, path, token })
11+
.then(async (client) => {
12+
// Session-level query tags: applied to every statement run on this session
13+
// (serialized into the session's QUERY_TAGS configuration).
14+
const session = await client.openSession({
15+
queryTags: {
16+
team: 'engineering',
17+
env: 'dev',
18+
driver: 'node',
19+
},
20+
});
21+
22+
// Statement A: inherits session-level tags only.
23+
const opA = await session.executeStatement('SELECT 1 AS inherits_session_tags');
24+
console.log(await opA.fetchAll());
25+
await opA.close();
26+
27+
// Statement B: statement-level query tags via executeStatement options.
28+
// These are passed via confOverlay as "query_tags" and apply ONLY to this statement.
29+
// Note: `env` here overrides the session-level `env: 'dev'` — for this statement
30+
// it will be `env: 'prod'`. Subsequent statements without statement-level tags
31+
// revert to the session-level values.
32+
const opB = await session.executeStatement('SELECT 2 AS has_statement_tags', {
33+
queryTags: {
34+
env: 'prod',
35+
request_id: 'abc-123',
36+
feature: 'reporting',
37+
},
38+
});
39+
console.log(await opB.fetchAll());
40+
await opB.close();
41+
42+
// Statement C: demonstrates escaping of special characters (`\`, `:`, `,`)
43+
// in tag values, plus null/undefined values which serialize as bare keys.
44+
const opC = await session.executeStatement('SELECT 3 AS escaped_and_null_tags', {
45+
queryTags: {
46+
path: 'C:\\users\\me',
47+
note: 'hello, world',
48+
flag: null,
49+
},
50+
});
51+
console.log(await opC.fetchAll());
52+
await opC.close();
53+
54+
await session.close();
55+
await client.close();
56+
})
57+
.catch((error) => {
58+
console.log(error);
59+
});

examples/session_params.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ client
1010
.connect({ host, path, token })
1111
.then(async (client) => {
1212
const session = await client.openSession({
13+
queryTags: {
14+
team: 'engineering',
15+
test: 'session-params',
16+
driver: 'node',
17+
},
1318
configuration: {
14-
QUERY_TAGS: 'team:engineering,test:session-params,driver:node',
1519
ansi_mode: 'false',
1620
},
1721
});

lib/DBSQLClient.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import HttpConnection from './connection/connections/HttpConnection';
1717
import IConnectionOptions from './connection/contracts/IConnectionOptions';
1818
import Status from './dto/Status';
1919
import HiveDriverError from './errors/HiveDriverError';
20-
import { buildUserAgentString, definedOrError } from './utils';
20+
import { buildUserAgentString, definedOrError, serializeQueryTags } from './utils';
2121
import PlainHttpAuthentication from './connection/auth/PlainHttpAuthentication';
2222
import DatabricksOAuth, { OAuthFlow } from './connection/auth/DatabricksOAuth';
2323
import {
@@ -644,6 +644,16 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I
644644
configuration['spark.sql.thriftserver.metadata.metricview.enabled'] = 'true';
645645
}
646646

647+
// Serialize queryTags dict and set in configuration; takes precedence over configuration.QUERY_TAGS
648+
if (request.queryTags !== undefined) {
649+
const serialized = serializeQueryTags(request.queryTags);
650+
if (serialized) {
651+
configuration.QUERY_TAGS = serialized;
652+
} else {
653+
delete configuration.QUERY_TAGS;
654+
}
655+
}
656+
647657
const response = await this.driver.openSession({
648658
client_protocol_i64: new Int64(TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V8),
649659
...getInitialNamespaceOptions(request.initialCatalog, request.initialSchema),

lib/DBSQLSession.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import IOperation from './contracts/IOperation';
3131
import DBSQLOperation from './DBSQLOperation';
3232
import Status from './dto/Status';
3333
import InfoValue from './dto/InfoValue';
34-
import { definedOrError, LZ4, ProtocolVersion } from './utils';
34+
import { definedOrError, LZ4, ProtocolVersion, serializeQueryTags } from './utils';
3535
import CloseableCollection from './utils/CloseableCollection';
3636
import { LogLevel } from './contracts/IDBSQLLogger';
3737
import HiveDriverError from './errors/HiveDriverError';
@@ -231,6 +231,11 @@ export default class DBSQLSession implements IDBSQLSession {
231231
request.parameters = getQueryParameters(options.namedParameters, options.ordinalParameters);
232232
}
233233

234+
const serializedQueryTags = serializeQueryTags(options.queryTags);
235+
if (serializedQueryTags !== undefined) {
236+
request.confOverlay = { ...request.confOverlay, query_tags: serializedQueryTags };
237+
}
238+
234239
if (ProtocolVersion.supportsCloudFetch(this.serverProtocolVersion)) {
235240
request.canDownloadResult = options.useCloudFetch ?? clientConfig.useCloudFetch;
236241
}

lib/contracts/IDBSQLClient.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@ export interface OpenSessionRequest {
144144
initialCatalog?: string;
145145
initialSchema?: string;
146146
configuration?: { [key: string]: string };
147+
/**
148+
* Session-level query tags as key-value pairs. Serialized and passed via session configuration
149+
* as "QUERY_TAGS". Values may be null/undefined to include a key without a value.
150+
* If both queryTags and configuration.QUERY_TAGS are specified, queryTags takes precedence.
151+
*/
152+
queryTags?: Record<string, string | null | undefined>;
147153
}
148154

149155
export default interface IDBSQLClient {

lib/contracts/IDBSQLSession.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ export type ExecuteStatementOptions = {
2121
stagingAllowedLocalPath?: string | string[];
2222
namedParameters?: Record<string, DBSQLParameter | DBSQLParameterValue>;
2323
ordinalParameters?: Array<DBSQLParameter | DBSQLParameterValue>;
24+
/**
25+
* Per-statement query tags as key-value pairs. Serialized and passed via confOverlay
26+
* as "query_tags". Values may be null/undefined to include a key without a value.
27+
* These tags apply only to this statement and do not persist across queries.
28+
*/
29+
queryTags?: Record<string, string | null | undefined>;
2430
};
2531

2632
export type TypeInfoRequest = {

lib/utils/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,14 @@ import buildUserAgentString from './buildUserAgentString';
33
import formatProgress, { ProgressUpdateTransformer } from './formatProgress';
44
import LZ4 from './lz4';
55
import * as ProtocolVersion from './protocolVersion';
6+
import serializeQueryTags from './queryTags';
67

7-
export { definedOrError, buildUserAgentString, formatProgress, ProgressUpdateTransformer, LZ4, ProtocolVersion };
8+
export {
9+
definedOrError,
10+
buildUserAgentString,
11+
formatProgress,
12+
ProgressUpdateTransformer,
13+
LZ4,
14+
ProtocolVersion,
15+
serializeQueryTags,
16+
};

lib/utils/queryTags.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Serializes a query tags dictionary into a string for use in confOverlay.
3+
*
4+
* Format: comma-separated key:value pairs, e.g. "key1:value1,key2:value2"
5+
* - If a value is null or undefined, the key is included without a colon or value
6+
* - Backslashes in keys are escaped; other special characters in keys are not escaped
7+
* - Special characters (backslash, colon, comma) in values are backslash-escaped
8+
*
9+
* @param queryTags - dictionary of query tag key-value pairs
10+
* @returns serialized string, or undefined if input is empty/null/undefined
11+
*/
12+
export default function serializeQueryTags(
13+
queryTags: Record<string, string | null | undefined> | null | undefined,
14+
): string | undefined {
15+
if (queryTags == null) {
16+
return undefined;
17+
}
18+
19+
const keys = Object.keys(queryTags);
20+
if (keys.length === 0) {
21+
return undefined;
22+
}
23+
24+
return keys
25+
.map((key) => {
26+
const escapedKey = key.replace(/\\/g, '\\\\');
27+
const value = queryTags[key];
28+
if (value == null) {
29+
return escapedKey;
30+
}
31+
const escapedValue = value.replace(/[\\:,]/g, (c) => `\\${c}`);
32+
return `${escapedKey}:${escapedValue}`;
33+
})
34+
.join(',');
35+
}

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)