Skip to content

Commit

Permalink
Merge pull request #114 from duckdb/jray/json-result-helpers
Browse files Browse the repository at this point in the history
json result helpers
  • Loading branch information
jraymakers authored Jan 19, 2025
2 parents 99dc2b1 + 6cac237 commit 73edd12
Show file tree
Hide file tree
Showing 13 changed files with 1,038 additions and 327 deletions.
80 changes: 66 additions & 14 deletions api/src/DuckDBDataChunk.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import duckdb from '@duckdb/node-bindings';
import { DuckDBType } from './DuckDBType';
import { DuckDBValueConverter } from './DuckDBValueConverter';
import { DuckDBVector } from './DuckDBVector';
import { DuckDBValue } from './values';

Expand Down Expand Up @@ -49,19 +50,32 @@ export class DuckDBDataChunk {
visitValue: (
value: DuckDBValue,
rowIndex: number,
columnIndex: number
columnIndex: number,
type: DuckDBType
) => void
) {
const vector = this.getColumnVector(columnIndex);
const type = vector.type;
for (let rowIndex = 0; rowIndex < vector.itemCount; rowIndex++) {
visitValue(vector.getItem(rowIndex), rowIndex, columnIndex);
visitValue(vector.getItem(rowIndex), rowIndex, columnIndex, type);
}
}
public getColumnValues(columnIndex: number): DuckDBValue[] {
const values: DuckDBValue[] = [];
this.visitColumnValues(columnIndex, (value) => values.push(value));
return values;
}
public convertColumnValues<T>(
columnIndex: number,
converter: DuckDBValueConverter<T>
): T[] {
const convertedValues: T[] = [];
const type = this.getColumnVector(columnIndex).type;
this.visitColumnValues(columnIndex, (value) =>
convertedValues.push(converter.convertValue(value, type))
);
return convertedValues;
}
public setColumnValues(columnIndex: number, values: readonly DuckDBValue[]) {
const vector = this.getColumnVector(columnIndex);
if (vector.itemCount !== values.length) {
Expand All @@ -73,18 +87,34 @@ export class DuckDBDataChunk {
vector.flush();
}
public visitColumns(
visitColumn: (column: DuckDBValue[], columnIndex: number) => void
visitColumn: (
column: DuckDBValue[],
columnIndex: number,
type: DuckDBType
) => void
) {
const columnCount = this.columnCount;
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
visitColumn(this.getColumnValues(columnIndex), columnIndex);
visitColumn(
this.getColumnValues(columnIndex),
columnIndex,
this.getColumnVector(columnIndex).type
);
}
}
public getColumns(): DuckDBValue[][] {
const columns: DuckDBValue[][] = [];
this.visitColumns((column) => columns.push(column));
return columns;
}
public convertColumns<T>(converter: DuckDBValueConverter<T>): T[][] {
const convertedColumns: T[][] = [];
const columnCount = this.columnCount;
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
convertedColumns.push(this.convertColumnValues(columnIndex, converter));
}
return convertedColumns;
}
public setColumns(columns: readonly (readonly DuckDBValue[])[]) {
if (columns.length > 0) {
this.rowCount = columns[0].length;
Expand All @@ -97,7 +127,8 @@ export class DuckDBDataChunk {
visitValue: (
value: DuckDBValue,
rowIndex: number,
columnIndex: number
columnIndex: number,
type: DuckDBType
) => void
) {
const columnCount = this.columnCount;
Expand All @@ -110,23 +141,33 @@ export class DuckDBDataChunk {
visitValue: (
value: DuckDBValue,
rowIndex: number,
columnIndex: number
columnIndex: number,
type: DuckDBType
) => void
) {
const columnCount = this.columnCount;
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
visitValue(
this.getColumnVector(columnIndex).getItem(rowIndex),
rowIndex,
columnIndex
);
const vector = this.getColumnVector(columnIndex);
visitValue(vector.getItem(rowIndex), rowIndex, columnIndex, vector.type);
}
}
public getRowValues(rowIndex: number): DuckDBValue[] {
const values: DuckDBValue[] = [];
this.visitRowValues(rowIndex, (value) => values.push(value));
return values;
}
public convertRowValues<T>(
rowIndex: number,
converter: DuckDBValueConverter<T>
): T[] {
const convertedValues: T[] = [];
this.visitRowValues(rowIndex, (value, _, columnIndex) =>
convertedValues.push(
converter.convertValue(value, this.getColumnVector(columnIndex).type)
)
);
return convertedValues;
}
public visitRows(visitRow: (row: DuckDBValue[], rowIndex: number) => void) {
const rowCount = this.rowCount;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
Expand All @@ -138,6 +179,14 @@ export class DuckDBDataChunk {
this.visitRows((row) => rows.push(row));
return rows;
}
public convertRows<T>(converter: DuckDBValueConverter<T>): T[][] {
const convertedRows: T[][] = [];
const rowCount = this.rowCount;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
convertedRows.push(this.convertRowValues(rowIndex, converter));
}
return convertedRows;
}
public setRows(rows: readonly (readonly DuckDBValue[])[]) {
this.rowCount = rows.length;
const columnCount = this.columnCount;
Expand All @@ -153,17 +202,20 @@ export class DuckDBDataChunk {
visitValue: (
value: DuckDBValue,
rowIndex: number,
columnIndex: number
columnIndex: number,
type: DuckDBType
) => void
) {
const rowCount = this.rowCount;
const columnCount = this.columnCount;
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
const vector = this.getColumnVector(columnIndex);
visitValue(
this.getColumnVector(columnIndex).getItem(rowIndex),
vector.getItem(rowIndex),
rowIndex,
columnIndex
columnIndex,
vector.type
);
}
}
Expand Down
29 changes: 29 additions & 0 deletions api/src/DuckDBResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import { DuckDBDataChunk } from './DuckDBDataChunk';
import { DuckDBLogicalType } from './DuckDBLogicalType';
import { DuckDBType } from './DuckDBType';
import { DuckDBTypeId } from './DuckDBTypeId';
import { DuckDBValueToJsonConverter, Json } from './DuckDBValueToJsonConverter';
import { convertColumnsFromChunks } from './convertColumnsFromChunks';
import { convertColumnsObjectFromChunks } from './convertColumnsObjectFromChunks';
import { convertRowObjectsFromChunks } from './convertRowObjectsFromChunks';
import { convertRowsFromChunks } from './convertRowsFromChunks';
import { ResultReturnType, StatementType } from './enums';
import { getColumnsFromChunks } from './getColumnsFromChunks';
import { getColumnsObjectFromChunks } from './getColumnsObjectFromChunks';
Expand Down Expand Up @@ -99,16 +104,40 @@ export class DuckDBResult {
const chunks = await this.fetchAllChunks();
return getColumnsFromChunks(chunks);
}
public async getColumnsJson(): Promise<Json[][]> {
const chunks = await this.fetchAllChunks();
return convertColumnsFromChunks(chunks, DuckDBValueToJsonConverter.default);
}
public async getColumnsObject(): Promise<Record<string, DuckDBValue[]>> {
const chunks = await this.fetchAllChunks();
return getColumnsObjectFromChunks(chunks, this.deduplicatedColumnNames());
}
public async getColumnsObjectJson(): Promise<Record<string, Json[]>> {
const chunks = await this.fetchAllChunks();
return convertColumnsObjectFromChunks(
chunks,
this.deduplicatedColumnNames(),
DuckDBValueToJsonConverter.default
);
}
public async getRows(): Promise<DuckDBValue[][]> {
const chunks = await this.fetchAllChunks();
return getRowsFromChunks(chunks);
}
public async getRowsJson(): Promise<Json[][]> {
const chunks = await this.fetchAllChunks();
return convertRowsFromChunks(chunks, DuckDBValueToJsonConverter.default);
}
public async getRowObjects(): Promise<Record<string, DuckDBValue>[]> {
const chunks = await this.fetchAllChunks();
return getRowObjectsFromChunks(chunks, this.deduplicatedColumnNames());
}
public async getRowObjectsJson(): Promise<Record<string, Json>[]> {
const chunks = await this.fetchAllChunks();
return convertRowObjectsFromChunks(
chunks,
this.deduplicatedColumnNames(),
DuckDBValueToJsonConverter.default
);
}
}
42 changes: 40 additions & 2 deletions api/src/DuckDBResultReader.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { convertColumnsFromChunks } from './convertColumnsFromChunks';
import { convertColumnsObjectFromChunks } from './convertColumnsObjectFromChunks';
import { convertRowObjectsFromChunks } from './convertRowObjectsFromChunks';
import { convertRowsFromChunks } from './convertRowsFromChunks';
import { DuckDBDataChunk } from './DuckDBDataChunk';
import { DuckDBLogicalType } from './DuckDBLogicalType';
import { DuckDBResult } from './DuckDBResult';
import { DuckDBType } from './DuckDBType';
import { DuckDBTypeId } from './DuckDBTypeId';
import { DuckDBValueToJsonConverter, Json } from './DuckDBValueToJsonConverter';
import { ResultReturnType, StatementType } from './enums';
import { getColumnsFromChunks } from './getColumnsFromChunks';
import { getColumnsObjectFromChunks } from './getColumnsObjectFromChunks';
Expand Down Expand Up @@ -163,15 +168,48 @@ export class DuckDBResultReader {
return getColumnsFromChunks(this.chunks);
}

public getColumnsJson(): Json[][] {
return convertColumnsFromChunks(
this.chunks,
DuckDBValueToJsonConverter.default
);
}

public getColumnsObject(): Record<string, DuckDBValue[]> {
return getColumnsObjectFromChunks(this.chunks, this.deduplicatedColumnNames());
return getColumnsObjectFromChunks(
this.chunks,
this.deduplicatedColumnNames()
);
}

public getColumnsObjectJson(): Record<string, Json[]> {
return convertColumnsObjectFromChunks(
this.chunks,
this.deduplicatedColumnNames(),
DuckDBValueToJsonConverter.default
);
}

public getRows(): DuckDBValue[][] {
return getRowsFromChunks(this.chunks);
}

public getRowObjecs(): Record<string, DuckDBValue>[] {
public getRowsJson(): Json[][] {
return convertRowsFromChunks(
this.chunks,
DuckDBValueToJsonConverter.default
);
}

public getRowObjects(): Record<string, DuckDBValue>[] {
return getRowObjectsFromChunks(this.chunks, this.deduplicatedColumnNames());
}

public getRowObjectsJson(): Record<string, Json>[] {
return convertRowObjectsFromChunks(
this.chunks,
this.deduplicatedColumnNames(),
DuckDBValueToJsonConverter.default
);
}
}
15 changes: 15 additions & 0 deletions api/src/DuckDBType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ export function LIST(valueType: DuckDBType, alias?: string): DuckDBListType {
export class DuckDBStructType extends BaseDuckDBType<DuckDBTypeId.STRUCT> {
public readonly entryNames: readonly string[];
public readonly entryTypes: readonly DuckDBType[];
public readonly entryIndexes: Readonly<Record<string, number>>;
public constructor(
entryNames: readonly string[],
entryTypes: readonly DuckDBType[],
Expand All @@ -588,10 +589,21 @@ export class DuckDBStructType extends BaseDuckDBType<DuckDBTypeId.STRUCT> {
}
this.entryNames = entryNames;
this.entryTypes = entryTypes;
const entryIndexes: Record<string, number> = {};
for (let i = 0; i < entryNames.length; i++) {
entryIndexes[entryNames[i]] = i;
}
this.entryIndexes = entryIndexes;
}
public get entryCount() {
return this.entryNames.length;
}
public indexForEntry(entryName: string): number {
return this.entryIndexes[entryName];
}
public typeForEntry(entryName: string): DuckDBType {
return this.entryTypes[this.entryIndexes[entryName]];
}
public toString(): string {
const parts: string[] = [];
for (let i = 0; i < this.entryNames.length; i++) {
Expand Down Expand Up @@ -727,6 +739,9 @@ export class DuckDBUnionType extends BaseDuckDBType<DuckDBTypeId.UNION> {
public memberIndexForTag(tag: string): number {
return this.tagMemberIndexes[tag];
}
public memberTypeForTag(tag: string): DuckDBType {
return this.memberTypes[this.tagMemberIndexes[tag]];
}
public get memberCount() {
return this.memberTags.length;
}
Expand Down
6 changes: 6 additions & 0 deletions api/src/DuckDBValueConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { DuckDBType } from './DuckDBType';
import { DuckDBValue } from './values';

export interface DuckDBValueConverter<T> {
convertValue(value: DuckDBValue, type: DuckDBType): T;
}
Loading

0 comments on commit 73edd12

Please sign in to comment.