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
7 changes: 7 additions & 0 deletions lib/DBSQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I

preserveBigNumericPrecision: false,

disableRowMaterialization: false,

// Telemetry defaults are sourced from DEFAULT_TELEMETRY_CONFIG so
// every component reads from the same single frozen const. Mapping the
// unprefixed TelemetryConfiguration keys to the `telemetry`-prefixed
Expand Down Expand Up @@ -611,6 +613,11 @@ export default class DBSQLClient extends EventEmitter implements IDBSQLClient, I
this.config.preserveBigNumericPrecision = options.preserveBigNumericPrecision;
}

// Opt-in: fetch and parse batches but skip per-cell row materialization.
if (options.disableRowMaterialization !== undefined) {
this.config.disableRowMaterialization = options.disableRowMaterialization;
}

// Retry-policy knobs. These live in ClientConfig (consumed by the Thrift
// HttpRetryPolicy and forwarded to the kernel on the SEA path), so copying
// them here makes them configurable from connect() on BOTH backends. A
Expand Down
4 changes: 4 additions & 0 deletions lib/contracts/IClientContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ export interface ClientConfig {
// Thrift and kernel backends. See `ConnectionOptions.preserveBigNumericPrecision`.
preserveBigNumericPrecision?: boolean;

// When true, fetched Arrow batches are counted but not materialized into JS
// row objects. Off by default. See `ConnectionOptions.disableRowMaterialization`.
disableRowMaterialization?: boolean;

// Telemetry configuration
telemetryEnabled?: boolean;
telemetryBatchSize?: number;
Expand Down
9 changes: 9 additions & 0 deletions lib/contracts/IDBSQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ export type ConnectionOptions = {
*/
preserveBigNumericPrecision?: boolean;

/**
* Skip materializing fetched rows into JS objects — the driver still fetches,
* decompresses and parses each Arrow batch, but returns `null` row placeholders
* instead of running the per-cell type conversion. Only the row count is then
* meaningful, so this is for throughput benchmarks that measure fetch cost
* without the per-cell decode. Applies to both backends. Defaults to `false`.
*/
disableRowMaterialization?: 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
1 change: 1 addition & 0 deletions lib/kernel/KernelOperationBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,7 @@ export default class KernelOperationBackend implements IOperationBackend {
// converter renders DECIMAL as an exact string and BIGINT as a `bigint`.
const converter = new ArrowResultConverter(this.context, this.resultsProvider, metadata, {
preserveBigNumericPrecision: this.context.getConfig().preserveBigNumericPrecision ?? false,
disableRowMaterialization: this.context.getConfig().disableRowMaterialization ?? false,
});
this.resultSlicer = new ResultSlicer(this.context, converter);
return this.resultSlicer;
Expand Down
20 changes: 16 additions & 4 deletions lib/result/ArrowResultConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ export default class ArrowResultConverter implements IResultsProvider<Array<any>
// path keeps its long-standing `number` representation unchanged.
private readonly preserveBigNumericPrecision: boolean;

// When true, skip per-cell materialization and return `null` row placeholders
// (only the row count is meaningful). See `ConnectionOptions.disableRowMaterialization`.
private readonly disableRowMaterialization: boolean;

private recordBatchReader?: IterableIterator<RecordBatch<TypeMap>>;

// Remaining rows in current Arrow batch (not the record batch!)
Expand All @@ -225,12 +229,16 @@ export default class ArrowResultConverter implements IResultsProvider<Array<any>
context: IClientContext,
source: IResultsProvider<ArrowBatch>,
{ schema }: { schema?: TTableSchema },
{ preserveBigNumericPrecision = false }: { preserveBigNumericPrecision?: boolean } = {},
{
preserveBigNumericPrecision = false,
disableRowMaterialization = false,
}: { preserveBigNumericPrecision?: boolean; disableRowMaterialization?: boolean } = {},
) {
this.context = context;
this.source = source;
this.schema = getSchemaColumns(schema);
this.preserveBigNumericPrecision = preserveBigNumericPrecision;
this.disableRowMaterialization = disableRowMaterialization;
}

public async hasMore() {
Expand Down Expand Up @@ -262,9 +270,13 @@ export default class ArrowResultConverter implements IResultsProvider<Array<any>
// Consume a record batch fetched during previous call to `fetchNext`
const table = new Table(this.prefetchedRecordBatch);
this.prefetchedRecordBatch = undefined;
// Get table rows, but not more than remaining count
const arrowRows = table.toArray().slice(0, this.remainingRows);
const result = this.getRows(table.schema, arrowRows);
// Get table rows, but not more than remaining count. When materialization
// is disabled, skip `toArray()`/`getRows()` and return `null` placeholders
// (filled, not sparse, so `fetchAll`'s `.flat()` keeps the count).
const batchRows = Math.min(table.numRows, this.remainingRows);
const result = this.disableRowMaterialization
? new Array(batchRows).fill(null)
: this.getRows(table.schema, table.toArray().slice(0, this.remainingRows));

// Reduce remaining rows count by a count of rows we just processed.
// If the remaining count reached zero - we're done with current arrow
Expand Down
10 changes: 8 additions & 2 deletions lib/thrift-backend/ThriftOperationBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,21 @@ export default class ThriftOperationBackend implements IOperationBackend {
this.context,
new ArrowResultHandler(this.context, this._data, metadata),
metadata,
{ preserveBigNumericPrecision: this.context.getConfig().preserveBigNumericPrecision ?? false },
{
preserveBigNumericPrecision: this.context.getConfig().preserveBigNumericPrecision ?? false,
disableRowMaterialization: this.context.getConfig().disableRowMaterialization ?? false,
},
);
break;
case TSparkRowSetType.URL_BASED_SET:
resultSource = new ArrowResultConverter(
this.context,
new CloudFetchResultHandler(this.context, this._data, metadata, this.id),
metadata,
{ preserveBigNumericPrecision: this.context.getConfig().preserveBigNumericPrecision ?? false },
{
preserveBigNumericPrecision: this.context.getConfig().preserveBigNumericPrecision ?? false,
disableRowMaterialization: this.context.getConfig().disableRowMaterialization ?? false,
},
);
break;
// no default
Expand Down
37 changes: 37 additions & 0 deletions tests/unit/result/ArrowResultConverter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,43 @@ describe('ArrowResultConverter', () => {
expect(await result.hasMore()).to.be.false;
});

it('returns null placeholders with exact counts when disableRowMaterialization is set', async () => {
// Same batch/row-count layout as 'should respect row count in batch', but
// materialization is off: each fetch must return the identical *count* of
// rows, every element `null` (no per-cell conversion).
const rowSetProvider = new ResultsProviderStub(
[
{
batches: [createSampleArrowBatch(createSampleRecordBatch(10, 5), createSampleRecordBatch(20, 5))],
rowCount: 8,
},
{
batches: [createSampleArrowBatch(createSampleRecordBatch(30, 5))],
rowCount: 2,
},
],
emptyItem,
);
const result = new ArrowResultConverter(
new ClientContextStub(),
rowSetProvider,
{ schema: createSampleThriftSchema('id') },
{ disableRowMaterialization: true },
);

const rows1 = await result.fetchNext({ limit: 10000 });
expect(rows1).to.deep.equal([null, null, null, null, null]);
expect(await result.hasMore()).to.be.true;

const rows2 = await result.fetchNext({ limit: 10000 });
expect(rows2).to.deep.equal([null, null, null]);
expect(await result.hasMore()).to.be.true;

const rows3 = await result.fetchNext({ limit: 10000 });
expect(rows3).to.deep.equal([null, null]);
expect(await result.hasMore()).to.be.false;
});

function bigintThriftSchema(columnName: string): TTableSchema {
return {
columns: [
Expand Down
Loading