Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Add the default-enabled `enableGeoSpatialSupport` option for kernel-backed GEOMETRY / GEOGRAPHY results. Enabled mode returns the canonical `{ srid, wkb }` Arrow value; disabled mode returns WKT / EWKT.
Comment thread
cathleeny marked this conversation as resolved.
- Preserve DECIMAL precision and scale qualifiers in kernel-backed result metadata, matching the Thrift path.
- Pin transitive `brace-expansion` 1.x / 2.x and `ip-address` dependencies for repository and CI installs to patched releases for GHSA-6j4f-fj2g-mc7p, GHSA-qhr7-859c-m2p7, GHSA-j6r3-76f7-8jcv, and GHSA-h3mg-xc3c-68pw.
- Add `escapeMetadataCatalogName` (default `false`) to escape catalog underscores in Thrift and kernel metadata requests.
- Fix Thrift `getFunctions()` to report the requested `FUNCTION_CAT`, matching JDBC and kernel.

Expand Down
11 changes: 6 additions & 5 deletions CONNECTION_PARAMETERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,12 @@ disabling verification.

## Results and type rendering

| Option | Type | Thrift | Kernel | Default Value | Note |
| ----------------------------- | --------- | :----: | :----: | ------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `preserveBigNumericPrecision` | `boolean` | ✅ | ✅ | `false` | Returns DECIMAL as an exact string and BIGINT as `bigint` on both. |
| `disableRowMaterialization` | `boolean` | ✅ | ✅ | `false` | Fetches and parses Arrow batches but returns `null` row placeholders instead of converting cells. Intended for fetch-throughput tests. |
| `enableMetricViewMetadata` | `boolean` | ✅ | ⚠️ | `false` | Injected into session configuration on both paths. Kernel may drop its non-allowlisted configuration key. |
| Option | Type | Thrift | Kernel | Default Value | Note |
| ----------------------------- | --------- | :----: | :----: | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `preserveBigNumericPrecision` | `boolean` | ✅ | ✅ | `false` | Returns DECIMAL as an exact string and BIGINT as `bigint` on both. |
| `disableRowMaterialization` | `boolean` | ✅ | ✅ | `false` | Fetches and parses Arrow batches but returns `null` row placeholders instead of converting cells. Intended for fetch-throughput tests. |
| `enableGeoSpatialSupport` | `boolean` | ❌ | ✅ | `true` | Returns GEOMETRY / GEOGRAPHY as canonical `{ srid: number, wkb: Buffer }` values when `true`, or WKT / EWKT strings when `false`. The choice is local and is not forwarded to SEA. |
| `enableMetricViewMetadata` | `boolean` | ✅ | ⚠️ | `false` | Injected into session configuration on both paths. Kernel may drop its non-allowlisted configuration key. |

## Metadata catalog filters

Expand Down
2 changes: 1 addition & 1 deletion KERNEL_REV
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2c958bfba476a0b0f165c829988959a32bf12f3e
9c2e2e6182fc7285bcfff39b2177941bc804897c
11 changes: 11 additions & 0 deletions lib/contracts/IDBSQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,17 @@ export type ConnectionOptions = {
*/
disableRowMaterialization?: boolean;

/**
* Enable native kernel-backed `GEOMETRY` and `GEOGRAPHY` results. `true`
* (the default) returns the canonical Arrow value as
* `{ srid: number, wkb: Buffer }`; `false` returns WKT / EWKT strings.
* This is a client-side conversion choice and is never forwarded to the SQL
* Execution API.
*
* Only the kernel backend uses this option.
*/
enableGeoSpatialSupport?: boolean;

/**
* Extra HTTP headers attached to driver-owned out-of-band requests
* (telemetry POSTs and feature-flag GETs). Not applied to the primary
Expand Down
16 changes: 15 additions & 1 deletion lib/kernel/KernelArrowIpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

import { RecordBatchReader, MessageReader, MessageHeader, Schema, Field, DataType, TypeMap } from 'apache-arrow';
import { TTableSchema, TTypeId, TPrimitiveTypeEntry } from '../../thrift/TCLIService_types';
import { TTableSchema, TTypeId, TPrimitiveTypeEntry, TTypeQualifiers } from '../../thrift/TCLIService_types';
import { rewriteDurationToInt64, DURATION_UNIT_METADATA_KEY } from './KernelArrowIpcDurationFix';
import HiveDriverError from '../errors/HiveDriverError';

Expand Down Expand Up @@ -264,6 +264,20 @@ export function arrowSchemaToThriftSchema(arrowSchema: Schema<TypeMap>): TTableS
const primitiveEntry: TPrimitiveTypeEntry = {
type: arrowTypeToTTypeId(field),
};
if (primitiveEntry.type === TTypeId.DECIMAL_TYPE) {
Comment thread
cathleeny marked this conversation as resolved.
const qualifiers: TTypeQualifiers['qualifiers'] = {};
const decimal = DataType.isDecimal(field.type) ? field.type : undefined;
for (const key of ['precision', 'scale'] as const) {
// Prefer manifest qualifiers, including for text-encoded decimals.
// Native Arrow decimals also carry these when metadata is absent.
const text = field.metadata.get(`databricks.type_${key}`);
const parsed = text !== undefined && /^-?\d+$/.test(text) ? Number(text) : NaN;
const minimum = key === 'precision' ? 1 : -0x80000000;
const value = Number.isInteger(parsed) && parsed >= minimum && parsed <= 0x7fffffff ? parsed : decimal?.[key];
if (value !== undefined) qualifiers[key] = { i32Value: value };
}
if (Object.keys(qualifiers).length > 0) primitiveEntry.typeQualifiers = { qualifiers };
}
return {
columnName: field.name,
typeDesc: {
Expand Down
19 changes: 19 additions & 0 deletions lib/kernel/KernelAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ export interface KernelSessionDefaults {
* remove one.
*/
complexTypesAsJson?: boolean;
/**
* Enable native GEOMETRY / GEOGRAPHY values. `true` (the default) returns
* canonical `struct<srid:int32,wkb:binary>` values; `false` returns WKT /
* EWKT strings. Applied locally by the kernel and never sent to SEA.
*/
enableGeoSpatialSupport?: boolean;
/**
* Per-session kernel connection-pool size
* (kernel `ConnectionOptions.max_connections`). Validated as a positive
Expand Down Expand Up @@ -819,6 +825,7 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
hostName: string;
httpPath: string;
intervalsAsString: boolean;
enableGeoSpatialSupport: boolean;
maxConnections?: number;
} & KernelTlsOptions &
KernelHttpOptions &
Expand All @@ -833,6 +840,9 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
// (native Arrow) — they already decode identically to Thrift via the
// shared Arrow converter, so `complexTypesAsJson` is not forced on.
intervalsAsString: true,
// Match the public driver contract and the kernel default: geospatial
Comment thread
cathleeny marked this conversation as resolved.
// support is enabled unless the caller explicitly requests WKT / EWKT.
enableGeoSpatialSupport: true,
// TLS knobs (server-cert verification toggle + custom CA + mTLS client
// identity). Validated and normalised (string PEM → Buffer) here so the
// napi shape only sees a Buffer.
Expand All @@ -843,6 +853,15 @@ export function buildKernelConnectionOptions(options: ConnectionOptions): Kernel
...buildKernelProxyOptions(options),
};

if (options.enableGeoSpatialSupport !== undefined) {
if (typeof options.enableGeoSpatialSupport !== 'boolean') {
throw new HiveDriverError(
`kernel backend: \`enableGeoSpatialSupport\` must be a boolean; got ${typeof options.enableGeoSpatialSupport}.`,
);
}
base.enableGeoSpatialSupport = options.enableGeoSpatialSupport;
}

// kernel-only pool sizing; read via cast to match how this function reads the
// other kernel-specific options (TLS) — they live on the internal options
// surface, not the published public `ConnectionOptions` `.d.ts`.
Expand Down
20 changes: 16 additions & 4 deletions native/kernel/index.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,11 @@
"basic-ftp": "^6.2.1",
"@75lb/deep-merge": "^1.1.2",
"ws": "^8.18.0",
"ip-address": "^10.1.1",
"ip-address": "^10.7.1",
"form-data": "^4.0.4",
"uuid": "^11.1.1",
"serialize-javascript": "^7.0.5"
"serialize-javascript": "^7.0.5",
"brace-expansion@1": "1.1.21",
"brace-expansion@2": "2.1.7"
}
}
64 changes: 62 additions & 2 deletions tests/unit/kernel/KernelArrowIpc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,14 @@
// limitations under the License.

import { expect } from 'chai';
import { tableFromArrays, RecordBatchStreamWriter } from 'apache-arrow';
import { countRowsInIpc, decodeIpcSchema, patchIpcBytes } from '../../../lib/kernel/KernelArrowIpc';
import { tableFromArrays, RecordBatchStreamWriter, Schema, Field, Decimal, Utf8, Int32 } from 'apache-arrow';
import {
arrowSchemaToThriftSchema,
countRowsInIpc,
decodeIpcSchema,
patchIpcBytes,
} from '../../../lib/kernel/KernelArrowIpc';
import { TTypeId } from '../../../thrift/TCLIService_types';
import { rewriteDurationToInt64 } from '../../../lib/kernel/KernelArrowIpcDurationFix';
import HiveDriverError from '../../../lib/errors/HiveDriverError';

Expand Down Expand Up @@ -100,3 +106,57 @@ describe('KernelArrowIpc.decodeIpcSchema', () => {
expect(() => decodeIpcSchema(Buffer.alloc(0))).to.throw(HiveDriverError);
});
});

describe('KernelArrowIpc.arrowSchemaToThriftSchema decimal qualifiers', () => {
for (const [precision, scale] of [
[10, 2],
[38, 0],
[38, 38],
[10, -2],
]) {
it(`preserves Arrow DECIMAL(${precision},${scale}) precision and scale`, () => {
const schema = new Schema([new Field('amount', new Decimal(scale, precision, 128), true)]);
expect(arrowSchemaToThriftSchema(schema).columns[0].typeDesc.types[0].primitiveEntry).to.deep.equal({
type: TTypeId.DECIMAL_TYPE,
typeQualifiers: { qualifiers: { precision: { i32Value: precision }, scale: { i32Value: scale } } },
});
});
}

for (const type of [new Utf8(), new Decimal(2, 10, 128)]) {
it(`prefers logical decimal qualifiers over the ${type} physical type`, () => {
const metadata = new Map([
['databricks.type_name', 'DECIMAL'],
['databricks.type_precision', '20'],
['databricks.type_scale', '0'],
]);
const schema = new Schema([new Field('amount', type, true, metadata)]);
expect(arrowSchemaToThriftSchema(schema).columns[0].typeDesc.types[0].primitiveEntry).to.deep.equal({
type: TTypeId.DECIMAL_TYPE,
typeQualifiers: { qualifiers: { precision: { i32Value: 20 }, scale: { i32Value: 0 } } },
});
});
}

for (const invalid of ['', 'invalid', '10.5', '2147483648']) {
it(`falls back to Arrow for invalid decimal qualifiers ${JSON.stringify(invalid)}`, () => {
const metadata = new Map([
['databricks.type_precision', invalid],
['databricks.type_scale', invalid],
]);
const schema = new Schema([new Field('amount', new Decimal(2, 10, 128), true, metadata)]);
expect(
arrowSchemaToThriftSchema(schema).columns[0].typeDesc.types[0].primitiveEntry?.typeQualifiers,
).to.deep.equal({ qualifiers: { precision: { i32Value: 10 }, scale: { i32Value: 2 } } });
});
}

it('does not invent missing qualifiers or attach decimal qualifiers to other types', () => {
const schema = new Schema([
new Field('unknown_decimal', new Utf8(), true, new Map([['databricks.type_name', 'DECIMAL']])),
new Field('integer', new Int32(), true, new Map([['databricks.type_precision', '10']])),
]);
const entries = arrowSchemaToThriftSchema(schema).columns.map((column) => column.typeDesc.types[0].primitiveEntry);
expect(entries).to.deep.equal([{ type: TTypeId.DECIMAL_TYPE }, { type: TTypeId.INT_TYPE }]);
});
});
Loading
Loading