From 6cac237acc544c298abb2f3fb5ab61cb99d2924b Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sun, 19 Jan 2025 10:38:41 -0800 Subject: [PATCH] json result helpers --- api/src/DuckDBDataChunk.ts | 80 ++- api/src/DuckDBResult.ts | 29 + api/src/DuckDBResultReader.ts | 42 +- api/src/DuckDBType.ts | 15 + api/src/DuckDBValueConverter.ts | 6 + api/src/DuckDBValueToJsonConverter.ts | 123 ++++ api/src/convertColumnsFromChunks.ts | 29 + api/src/convertColumnsObjectFromChunks.ts | 29 + api/src/convertRowObjectsFromChunks.ts | 24 + api/src/convertRowsFromChunks.ts | 16 + api/src/index.ts | 2 + api/test/api.test.ts | 156 +++-- api/test/util/testAllTypes.ts | 814 +++++++++++++++------- 13 files changed, 1038 insertions(+), 327 deletions(-) create mode 100644 api/src/DuckDBValueConverter.ts create mode 100644 api/src/DuckDBValueToJsonConverter.ts create mode 100644 api/src/convertColumnsFromChunks.ts create mode 100644 api/src/convertColumnsObjectFromChunks.ts create mode 100644 api/src/convertRowObjectsFromChunks.ts create mode 100644 api/src/convertRowsFromChunks.ts diff --git a/api/src/DuckDBDataChunk.ts b/api/src/DuckDBDataChunk.ts index fd5d633c..55e4ebd2 100644 --- a/api/src/DuckDBDataChunk.ts +++ b/api/src/DuckDBDataChunk.ts @@ -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'; @@ -49,12 +50,14 @@ 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[] { @@ -62,6 +65,17 @@ export class DuckDBDataChunk { this.visitColumnValues(columnIndex, (value) => values.push(value)); return values; } + public convertColumnValues( + columnIndex: number, + converter: DuckDBValueConverter + ): 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) { @@ -73,11 +87,19 @@ 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[][] { @@ -85,6 +107,14 @@ export class DuckDBDataChunk { this.visitColumns((column) => columns.push(column)); return columns; } + public convertColumns(converter: DuckDBValueConverter): 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; @@ -97,7 +127,8 @@ export class DuckDBDataChunk { visitValue: ( value: DuckDBValue, rowIndex: number, - columnIndex: number + columnIndex: number, + type: DuckDBType ) => void ) { const columnCount = this.columnCount; @@ -110,16 +141,14 @@ 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[] { @@ -127,6 +156,18 @@ export class DuckDBDataChunk { this.visitRowValues(rowIndex, (value) => values.push(value)); return values; } + public convertRowValues( + rowIndex: number, + converter: DuckDBValueConverter + ): 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++) { @@ -138,6 +179,14 @@ export class DuckDBDataChunk { this.visitRows((row) => rows.push(row)); return rows; } + public convertRows(converter: DuckDBValueConverter): 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; @@ -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 ); } } diff --git a/api/src/DuckDBResult.ts b/api/src/DuckDBResult.ts index a71a62eb..0df6ab74 100644 --- a/api/src/DuckDBResult.ts +++ b/api/src/DuckDBResult.ts @@ -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'; @@ -99,16 +104,40 @@ export class DuckDBResult { const chunks = await this.fetchAllChunks(); return getColumnsFromChunks(chunks); } + public async getColumnsJson(): Promise { + const chunks = await this.fetchAllChunks(); + return convertColumnsFromChunks(chunks, DuckDBValueToJsonConverter.default); + } public async getColumnsObject(): Promise> { const chunks = await this.fetchAllChunks(); return getColumnsObjectFromChunks(chunks, this.deduplicatedColumnNames()); } + public async getColumnsObjectJson(): Promise> { + const chunks = await this.fetchAllChunks(); + return convertColumnsObjectFromChunks( + chunks, + this.deduplicatedColumnNames(), + DuckDBValueToJsonConverter.default + ); + } public async getRows(): Promise { const chunks = await this.fetchAllChunks(); return getRowsFromChunks(chunks); } + public async getRowsJson(): Promise { + const chunks = await this.fetchAllChunks(); + return convertRowsFromChunks(chunks, DuckDBValueToJsonConverter.default); + } public async getRowObjects(): Promise[]> { const chunks = await this.fetchAllChunks(); return getRowObjectsFromChunks(chunks, this.deduplicatedColumnNames()); } + public async getRowObjectsJson(): Promise[]> { + const chunks = await this.fetchAllChunks(); + return convertRowObjectsFromChunks( + chunks, + this.deduplicatedColumnNames(), + DuckDBValueToJsonConverter.default + ); + } } diff --git a/api/src/DuckDBResultReader.ts b/api/src/DuckDBResultReader.ts index 7bd2f87d..8a28cb24 100644 --- a/api/src/DuckDBResultReader.ts +++ b/api/src/DuckDBResultReader.ts @@ -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'; @@ -163,15 +168,48 @@ export class DuckDBResultReader { return getColumnsFromChunks(this.chunks); } + public getColumnsJson(): Json[][] { + return convertColumnsFromChunks( + this.chunks, + DuckDBValueToJsonConverter.default + ); + } + public getColumnsObject(): Record { - return getColumnsObjectFromChunks(this.chunks, this.deduplicatedColumnNames()); + return getColumnsObjectFromChunks( + this.chunks, + this.deduplicatedColumnNames() + ); + } + + public getColumnsObjectJson(): Record { + return convertColumnsObjectFromChunks( + this.chunks, + this.deduplicatedColumnNames(), + DuckDBValueToJsonConverter.default + ); } public getRows(): DuckDBValue[][] { return getRowsFromChunks(this.chunks); } - public getRowObjecs(): Record[] { + public getRowsJson(): Json[][] { + return convertRowsFromChunks( + this.chunks, + DuckDBValueToJsonConverter.default + ); + } + + public getRowObjects(): Record[] { return getRowObjectsFromChunks(this.chunks, this.deduplicatedColumnNames()); } + + public getRowObjectsJson(): Record[] { + return convertRowObjectsFromChunks( + this.chunks, + this.deduplicatedColumnNames(), + DuckDBValueToJsonConverter.default + ); + } } diff --git a/api/src/DuckDBType.ts b/api/src/DuckDBType.ts index 21386e25..59ff3b34 100644 --- a/api/src/DuckDBType.ts +++ b/api/src/DuckDBType.ts @@ -576,6 +576,7 @@ export function LIST(valueType: DuckDBType, alias?: string): DuckDBListType { export class DuckDBStructType extends BaseDuckDBType { public readonly entryNames: readonly string[]; public readonly entryTypes: readonly DuckDBType[]; + public readonly entryIndexes: Readonly>; public constructor( entryNames: readonly string[], entryTypes: readonly DuckDBType[], @@ -588,10 +589,21 @@ export class DuckDBStructType extends BaseDuckDBType { } this.entryNames = entryNames; this.entryTypes = entryTypes; + const entryIndexes: Record = {}; + 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++) { @@ -727,6 +739,9 @@ export class DuckDBUnionType extends BaseDuckDBType { 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; } diff --git a/api/src/DuckDBValueConverter.ts b/api/src/DuckDBValueConverter.ts new file mode 100644 index 00000000..7b77db3b --- /dev/null +++ b/api/src/DuckDBValueConverter.ts @@ -0,0 +1,6 @@ +import { DuckDBType } from './DuckDBType'; +import { DuckDBValue } from './values'; + +export interface DuckDBValueConverter { + convertValue(value: DuckDBValue, type: DuckDBType): T; +} diff --git a/api/src/DuckDBValueToJsonConverter.ts b/api/src/DuckDBValueToJsonConverter.ts new file mode 100644 index 00000000..3a0354d4 --- /dev/null +++ b/api/src/DuckDBValueToJsonConverter.ts @@ -0,0 +1,123 @@ +import { DuckDBType } from './DuckDBType'; +import { DuckDBTypeId } from './DuckDBTypeId'; +import { DuckDBValueConverter } from './DuckDBValueConverter'; +import { + DuckDBArrayValue, + DuckDBIntervalValue, + DuckDBListValue, + DuckDBMapValue, + DuckDBStructValue, + DuckDBUnionValue, + DuckDBValue, +} from './values'; + +export type Json = + | null + | boolean + | number + | string + | Json[] + | { [key: string]: Json }; + +export class DuckDBValueToJsonConverter implements DuckDBValueConverter { + public static readonly default = new DuckDBValueToJsonConverter(); + + public convertValue(value: DuckDBValue, type: DuckDBType): Json { + if (value == null) { + return null; + } + switch (type.typeId) { + case DuckDBTypeId.BOOLEAN: + return Boolean(value); + case DuckDBTypeId.TINYINT: + case DuckDBTypeId.SMALLINT: + case DuckDBTypeId.INTEGER: + case DuckDBTypeId.UTINYINT: + case DuckDBTypeId.USMALLINT: + case DuckDBTypeId.UINTEGER: + return Number(value); + case DuckDBTypeId.FLOAT: + case DuckDBTypeId.DOUBLE: + if (Number.isFinite(value)) { + return Number(value); + } + return String(value); + case DuckDBTypeId.BIGINT: + case DuckDBTypeId.UBIGINT: + case DuckDBTypeId.HUGEINT: + case DuckDBTypeId.UHUGEINT: + return String(value); + case DuckDBTypeId.DATE: + case DuckDBTypeId.TIME: + case DuckDBTypeId.TIMESTAMP: + case DuckDBTypeId.TIMESTAMP_S: + case DuckDBTypeId.TIMESTAMP_MS: + case DuckDBTypeId.TIMESTAMP_NS: + case DuckDBTypeId.TIME_TZ: + case DuckDBTypeId.TIMESTAMP_TZ: + return String(value); + case DuckDBTypeId.INTERVAL: + if (value instanceof DuckDBIntervalValue) { + return { + months: value.months, + days: value.days, + micros: String(value.micros), + }; + } + return null; + case DuckDBTypeId.VARCHAR: + case DuckDBTypeId.BLOB: + case DuckDBTypeId.BIT: + return String(value); + case DuckDBTypeId.DECIMAL: + case DuckDBTypeId.VARINT: + return String(value); + case DuckDBTypeId.ENUM: + return String(value); + case DuckDBTypeId.LIST: + if (value instanceof DuckDBListValue) { + return value.items.map((v) => this.convertValue(v, type.valueType)); + } + return null; + case DuckDBTypeId.STRUCT: + if (value instanceof DuckDBStructValue) { + const result: { [key: string]: Json } = {}; + for (const key in value.entries) { + result[key] = this.convertValue( + value.entries[key], + type.typeForEntry(key) + ); + } + return result; + } + return null; + case DuckDBTypeId.MAP: + if (value instanceof DuckDBMapValue) { + return value.entries.map((entry) => ({ + key: this.convertValue(entry.key, type.keyType), + value: this.convertValue(entry.value, type.valueType), + })); + } + return null; + case DuckDBTypeId.ARRAY: + if (value instanceof DuckDBArrayValue) { + return value.items.map((v) => this.convertValue(v, type.valueType)); + } + return null; + case DuckDBTypeId.UNION: + if (value instanceof DuckDBUnionValue) { + return { + tag: value.tag, + value: this.convertValue( + value.value, + type.memberTypeForTag(value.tag) + ), + }; + } + return null; + case DuckDBTypeId.UUID: + return String(value); + } + return null; + } +} diff --git a/api/src/convertColumnsFromChunks.ts b/api/src/convertColumnsFromChunks.ts new file mode 100644 index 00000000..2ab333ca --- /dev/null +++ b/api/src/convertColumnsFromChunks.ts @@ -0,0 +1,29 @@ +import { DuckDBDataChunk } from './DuckDBDataChunk'; +import { DuckDBValueConverter } from './DuckDBValueConverter'; + +export function convertColumnsFromChunks( + chunks: readonly DuckDBDataChunk[], + converter: DuckDBValueConverter +): T[][] { + if (chunks.length === 0) { + return []; + } + const convertedColumns = chunks[0].convertColumns(converter); + for (let chunkIndex = 1; chunkIndex < chunks.length; chunkIndex++) { + for ( + let columnIndex = 0; + columnIndex < convertedColumns.length; + columnIndex++ + ) { + const chunk = chunks[chunkIndex]; + chunk.visitColumnValues( + columnIndex, + (value, _rowIndex, _columnIndex, type) => + convertedColumns[columnIndex].push( + converter.convertValue(value, type) + ) + ); + } + } + return convertedColumns; +} diff --git a/api/src/convertColumnsObjectFromChunks.ts b/api/src/convertColumnsObjectFromChunks.ts new file mode 100644 index 00000000..fa17f0eb --- /dev/null +++ b/api/src/convertColumnsObjectFromChunks.ts @@ -0,0 +1,29 @@ +import { DuckDBDataChunk } from './DuckDBDataChunk'; +import { DuckDBValueConverter } from './DuckDBValueConverter'; + +export function convertColumnsObjectFromChunks( + chunks: readonly DuckDBDataChunk[], + columnNames: readonly string[], + converter: DuckDBValueConverter +): Record { + const convertedColumnsObject: Record = {}; + for (const columnName of columnNames) { + convertedColumnsObject[columnName] = []; + } + if (chunks.length === 0) { + return convertedColumnsObject; + } + const columnCount = chunks[0].columnCount; + for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) { + for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) { + chunks[chunkIndex].visitColumnValues( + columnIndex, + (value, _rowIndex, _columnIndex, type) => + convertedColumnsObject[columnNames[columnIndex]].push( + converter.convertValue(value, type) + ) + ); + } + } + return convertedColumnsObject; +} diff --git a/api/src/convertRowObjectsFromChunks.ts b/api/src/convertRowObjectsFromChunks.ts new file mode 100644 index 00000000..90b755c2 --- /dev/null +++ b/api/src/convertRowObjectsFromChunks.ts @@ -0,0 +1,24 @@ +import { DuckDBDataChunk } from './DuckDBDataChunk'; +import { DuckDBValueConverter } from './DuckDBValueConverter'; + +export function convertRowObjectsFromChunks( + chunks: readonly DuckDBDataChunk[], + columnNames: readonly string[], + converter: DuckDBValueConverter +): Record[] { + const rowObjects: Record[] = []; + for (const chunk of chunks) { + const rowCount = chunk.rowCount; + for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { + const rowObject: Record = {}; + chunk.visitRowValues(rowIndex, (value, _rowIndex, columnIndex, type) => { + rowObject[columnNames[columnIndex]] = converter.convertValue( + value, + type + ); + }); + rowObjects.push(rowObject); + } + } + return rowObjects; +} diff --git a/api/src/convertRowsFromChunks.ts b/api/src/convertRowsFromChunks.ts new file mode 100644 index 00000000..4969305d --- /dev/null +++ b/api/src/convertRowsFromChunks.ts @@ -0,0 +1,16 @@ +import { DuckDBDataChunk } from './DuckDBDataChunk'; +import { DuckDBValueConverter } from './DuckDBValueConverter'; + +export function convertRowsFromChunks( + chunks: readonly DuckDBDataChunk[], + converter: DuckDBValueConverter +): T[][] { + const rows: T[][] = []; + for (const chunk of chunks) { + const rowCount = chunk.rowCount; + for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) { + rows.push(chunk.convertRowValues(rowIndex, converter)); + } + } + return rows; +} diff --git a/api/src/index.ts b/api/src/index.ts index d5995bc5..5f65a252 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -16,6 +16,8 @@ export * from './DuckDBPreparedStatement'; export * from './DuckDBResult'; export * from './DuckDBType'; export * from './DuckDBTypeId'; +export * from './DuckDBValueConverter'; +export * from './DuckDBValueToJsonConverter'; export * from './DuckDBVector'; export * from './configurationOptionDescriptions'; export * from './enums'; diff --git a/api/test/api.test.ts b/api/test/api.test.ts index f86d241f..3df606c8 100644 --- a/api/test/api.test.ts +++ b/api/test/api.test.ts @@ -113,7 +113,11 @@ import { ColumnNameAndType, testAllTypesColumnTypes, testAllTypesColumns, + testAllTypesColumnsJson, testAllTypesColumnsNamesAndTypes, + testAllTypesColumnsObjectJson, + testAllTypesRowObjectsJson, + testAllTypesRowsJson, } from './util/testAllTypes'; async function sleep(ms: number): Promise { @@ -407,7 +411,11 @@ describe('api', () => { prepared.bindBoolean(3, true); prepared.bindTimeTZ(4, TIMETZ.max); prepared.bindList(5, listValue([100, 200, 300]), LIST(INTEGER)); - prepared.bindStruct(6, structValue({ 'a': 42, 'b': 'duck' }), STRUCT({ 'a': INTEGER, 'b': VARCHAR })); + prepared.bindStruct( + 6, + structValue({ 'a': 42, 'b': 'duck' }), + STRUCT({ 'a': INTEGER, 'b': VARCHAR }) + ); prepared.bindArray(7, arrayValue([100, 200, 300]), ARRAY(INTEGER, 3)); prepared.bindNull(8); const result = await prepared.run(); @@ -444,30 +452,14 @@ describe('api', () => { DuckDBBooleanVector, [true] ); - assertValues( - chunk, - 3, - DuckDBTimeTZVector, - [TIMETZ.max] - ); - assertValues( - chunk, - 4, - DuckDBListVector, - [listValue([100, 200, 300])] - ); - assertValues( - chunk, - 5, - DuckDBStructVector, - [structValue({ 'a': 42, 'b': 'duck' })] - ); - assertValues( - chunk, - 6, - DuckDBArrayVector, - [arrayValue([100, 200, 300])] - ); + assertValues(chunk, 3, DuckDBTimeTZVector, [TIMETZ.max]); + assertValues(chunk, 4, DuckDBListVector, [listValue([100, 200, 300])]); + assertValues(chunk, 5, DuckDBStructVector, [ + structValue({ 'a': 42, 'b': 'duck' }), + ]); + assertValues(chunk, 6, DuckDBArrayVector, [ + arrayValue([100, 200, 300]), + ]); assertValues( chunk, 7, @@ -506,12 +498,7 @@ describe('api', () => { DuckDBVarCharVector, ['duck'] ); - assertValues( - chunk, - 2, - DuckDBListVector, - [listValue([10, 11, 12])] - ); + assertValues(chunk, 2, DuckDBListVector, [listValue([10, 11, 12])]); } }); }); @@ -520,16 +507,19 @@ describe('api', () => { const prepared = await connection.prepare( 'select $a as a, $b as b, $c as c, $d as d, $e as e, $f as f' ); - prepared.bind({ - a: 42, - b: 42.3, - c: 'duck', - d: listValue([10, 11, 12]), - e: arrayValue([10.1, 11.2, 12.3]), - f: arrayValue([10, 11, 12]), - }, { - f: ARRAY(FLOAT, 2), - }); + prepared.bind( + { + a: 42, + b: 42.3, + c: 'duck', + d: listValue([10, 11, 12]), + e: arrayValue([10.1, 11.2, 12.3]), + f: arrayValue([10, 11, 12]), + }, + { + f: ARRAY(FLOAT, 2), + } + ); const result = await prepared.run(); assertColumns(result, [ { name: 'a', type: INTEGER }, @@ -550,36 +540,20 @@ describe('api', () => { DuckDBIntegerVector, [42] ); - assertValues( - chunk, - 1, - DuckDBDoubleVector, - [42.3] - ); + assertValues(chunk, 1, DuckDBDoubleVector, [ + 42.3, + ]); assertValues( chunk, 2, DuckDBVarCharVector, ['duck'] ); - assertValues( - chunk, - 3, - DuckDBListVector, - [listValue([10, 11, 12])] - ); - assertValues( - chunk, - 4, - DuckDBArrayVector, - [arrayValue([10.1, 11.2, 12.3])] - ); - assertValues( - chunk, - 5, - DuckDBArrayVector, - [arrayValue([10, 11, 12])] - ); + assertValues(chunk, 3, DuckDBListVector, [listValue([10, 11, 12])]); + assertValues(chunk, 4, DuckDBArrayVector, [ + arrayValue([10.1, 11.2, 12.3]), + ]); + assertValues(chunk, 5, DuckDBArrayVector, [arrayValue([10, 11, 12])]); } }); }); @@ -1062,7 +1036,7 @@ describe('api', () => { assert.deepEqual(reader.columnNames(), ['a', 'b', 'a']); assert.deepEqual(reader.deduplicatedColumnNames(), ['a', 'b', 'a:1']); assert.deepEqual(reader.columnTypes(), [INTEGER, INTEGER, VARCHAR]); - assert.deepEqual(reader.getRowObjecs(), [ + assert.deepEqual(reader.getRowObjects(), [ { 'a': 0, 'b': 10, 'a:1': '100' }, { 'a': 1, 'b': 11, 'a:1': '101' }, { 'a': 2, 'b': 12, 'a:1': '102' }, @@ -1074,6 +1048,34 @@ describe('api', () => { }); }); }); + test('columns json', async () => { + await withConnection(async (connection) => { + const reader = await connection.runAndReadAll(`from test_all_types()`); + const columnsJson = reader.getColumnsJson(); + assert.deepEqual(columnsJson, testAllTypesColumnsJson); + }); + }); + test('columns object json', async () => { + await withConnection(async (connection) => { + const reader = await connection.runAndReadAll(`from test_all_types()`); + const columnsJson = reader.getColumnsObjectJson(); + assert.deepEqual(columnsJson, testAllTypesColumnsObjectJson); + }); + }); + test('rows json', async () => { + await withConnection(async (connection) => { + const reader = await connection.runAndReadAll(`from test_all_types()`); + const rowsJson = reader.getRowsJson(); + assert.deepEqual(rowsJson, testAllTypesRowsJson); + }); + }); + test('row objects json', async () => { + await withConnection(async (connection) => { + const reader = await connection.runAndReadAll(`from test_all_types()`); + const rowObjectsJson = reader.getRowObjectsJson(); + assert.deepEqual(rowObjectsJson, testAllTypesRowObjectsJson); + }); + }); test('result reader', async () => { await withConnection(async (connection) => { const reader = await connection.runAndReadAll( @@ -1340,11 +1342,7 @@ describe('api', () => { test('create and append data chunk , modify nested list vector', async () => { await withConnection(async (connection) => { const originalValues = [ - listValue([ - listValue([110, 111]), - listValue([]), - listValue([130]), - ]), + listValue([listValue([110, 111]), listValue([]), listValue([130])]), listValue([]), listValue([ listValue([310, 311, 312]), @@ -1353,11 +1351,16 @@ describe('api', () => { ]), ]; - const chunk = DuckDBDataChunk.create([LIST(LIST(INTEGER))], originalValues.length); + const chunk = DuckDBDataChunk.create( + [LIST(LIST(INTEGER))], + originalValues.length + ); chunk.setColumnValues(0, originalValues); const outerListVector = chunk.getColumnVector(0) as DuckDBListVector; - const innerListVector = outerListVector.getItemVector(2) as DuckDBListVector; + const innerListVector = outerListVector.getItemVector( + 2 + ) as DuckDBListVector; innerListVector.setItem(1, listValue([350, 351, 352, 353, 354])); innerListVector.flush(); @@ -1446,9 +1449,10 @@ describe('api', () => { null, ]; - const chunk = DuckDBDataChunk.create([ - STRUCT({ 'num': INTEGER, 'str': VARCHAR }), - ], values.length); + const chunk = DuckDBDataChunk.create( + [STRUCT({ 'num': INTEGER, 'str': VARCHAR })], + values.length + ); chunk.setColumnValues(0, values); await connection.run( diff --git a/api/test/util/testAllTypes.ts b/api/test/util/testAllTypes.ts index 907022ef..91475af5 100644 --- a/api/test/util/testAllTypes.ts +++ b/api/test/util/testAllTypes.ts @@ -22,6 +22,7 @@ import { INTEGER, INTERVAL, intervalValue, + Json, LIST, listValue, MAP, @@ -71,229 +72,511 @@ export interface ColumnNameTypeAndRows extends ColumnNameAndType { readonly name: string; readonly type: DuckDBType; readonly rows: readonly DuckDBValue[]; + readonly json: readonly Json[]; } function col( name: string, type: DuckDBType, - rows: readonly DuckDBValue[] + rows: readonly DuckDBValue[], + json: readonly Json[] ): ColumnNameTypeAndRows { - return { name, type, rows }; + return { name, type, rows, json }; } export const testAllTypesData: ColumnNameTypeAndRows[] = [ - col('bool', BOOLEAN, [false, true, null]), - col('tinyint', TINYINT, [TINYINT.min, TINYINT.max, null]), - col('smallint', SMALLINT, [SMALLINT.min, SMALLINT.max, null]), - col('int', INTEGER, [INTEGER.min, INTEGER.max, null]), - col('bigint', BIGINT, [BIGINT.min, BIGINT.max, null]), - col('hugeint', HUGEINT, [HUGEINT.min, HUGEINT.max, null]), - col('uhugeint', UHUGEINT, [UHUGEINT.min, UHUGEINT.max, null]), - col('utinyint', UTINYINT, [UTINYINT.min, UTINYINT.max, null]), - col('usmallint', USMALLINT, [USMALLINT.min, USMALLINT.max, null]), - col('uint', UINTEGER, [UINTEGER.min, UINTEGER.max, null]), - col('ubigint', UBIGINT, [UBIGINT.min, UBIGINT.max, null]), - col('varint', VARINT, [VARINT.min, VARINT.max, null]), - col('date', DATE, [DATE.min, DATE.max, null]), - col('time', TIME, [TIME.min, TIME.max, null]), - col('timestamp', TIMESTAMP, [TIMESTAMP.min, TIMESTAMP.max, null]), - col('timestamp_s', TIMESTAMP_S, [TIMESTAMP_S.min, TIMESTAMP_S.max, null]), - col('timestamp_ms', TIMESTAMP_MS, [TIMESTAMP_MS.min, TIMESTAMP_MS.max, null]), - col('timestamp_ns', TIMESTAMP_NS, [TIMESTAMP_NS.min, TIMESTAMP_NS.max, null]), - col('time_tz', TIMETZ, [TIMETZ.min, TIMETZ.max, null]), - col('timestamp_tz', TIMESTAMPTZ, [TIMESTAMPTZ.min, TIMESTAMPTZ.max, null]), - col('float', FLOAT, [FLOAT.min, FLOAT.max, null]), - col('double', DOUBLE, [DOUBLE.min, DOUBLE.max, null]), - col('dec_4_1', DECIMAL(4, 1), [ - decimalValue(-9999n, 4, 1), - decimalValue(9999n, 4, 1), - null, - ]), - col('dec_9_4', DECIMAL(9, 4), [ - decimalValue(-999999999n, 9, 4), - decimalValue(999999999n, 9, 4), - null, - ]), - col('dec_18_6', DECIMAL(18, 6), [ - decimalValue(-BI_18_9s, 18, 6), - decimalValue(BI_18_9s, 18, 6), - null, - ]), - col('dec38_10', DECIMAL(38, 10), [ - decimalValue(-BI_38_9s, 38, 10), - decimalValue(BI_38_9s, 38, 10), - null, - ]), - col('uuid', UUID, [UUID.min, UUID.max, null]), - col('interval', INTERVAL, [ - intervalValue(0, 0, 0n), - intervalValue(999, 999, 999999999n), - null, - ]), - col('varchar', VARCHAR, ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null]), - col('blob', BLOB, [ - blobValue('thisisalongblob\x00withnullbytes'), - blobValue('\x00\x00\x00a'), - null, - ]), - col('bit', BIT, [ - bitValue('0010001001011100010101011010111'), - bitValue('10101'), - null, - ]), - col('small_enum', ENUM8(smallEnumValues), [ - smallEnumValues[0], - smallEnumValues[smallEnumValues.length - 1], - null, - ]), - col('medium_enum', ENUM16(mediumEnumValues), [ - mediumEnumValues[0], - mediumEnumValues[mediumEnumValues.length - 1], - null, - ]), - col('large_enum', ENUM32(largeEnumValues), [ - largeEnumValues[0], - largeEnumValues[largeEnumValues.length - 1], - null, - ]), - col('int_array', LIST(INTEGER), [ - listValue([]), - listValue([42, 999, null, null, -42]), - null, - ]), - col('double_array', LIST(DOUBLE), [ - listValue([]), - listValue([42.0, NaN, Infinity, -Infinity, null, -42.0]), - null, - ]), - col('date_array', LIST(DATE), [ - listValue([]), - // 19124 days from the epoch is 2022-05-12 - listValue([DATE.epoch, DATE.posInf, DATE.negInf, null, dateValue(19124)]), - null, - ]), - col('timestamp_array', LIST(TIMESTAMP), [ - listValue([]), - listValue([ - TIMESTAMP.epoch, - TIMESTAMP.posInf, - TIMESTAMP.negInf, - null, - // 1652372625 seconds from the epoch is 2022-05-12 16:23:45 - timestampValue(1652372625n * 1000n * 1000n), - ]), - null, - ]), - col('timestamptz_array', LIST(TIMESTAMPTZ), [ - listValue([]), - listValue([ - TIMESTAMPTZ.epoch, - TIMESTAMPTZ.posInf, - TIMESTAMPTZ.negInf, - null, - // 1652397825 = 1652372625 + 25200, 25200 = 7 * 60 * 60 = 7 hours in seconds - // This 7 hour difference is hard coded into test_all_types (value is 2022-05-12 16:23:45-07) - timestampTZValue(1652397825n * 1000n * 1000n), - ]), - null, - ]), - col('varchar_array', LIST(VARCHAR), [ - listValue([]), - // Note that the string 'goose' in varchar_array does NOT have an embedded null character. - listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), - null, - ]), - col('nested_int_array', LIST(LIST(INTEGER)), [ - listValue([]), - listValue([ + col('bool', BOOLEAN, [false, true, null], [false, true, null]), + col( + 'tinyint', + TINYINT, + [TINYINT.min, TINYINT.max, null], + [TINYINT.min, TINYINT.max, null] + ), + col( + 'smallint', + SMALLINT, + [SMALLINT.min, SMALLINT.max, null], + [SMALLINT.min, SMALLINT.max, null] + ), + col( + 'int', + INTEGER, + [INTEGER.min, INTEGER.max, null], + [INTEGER.min, INTEGER.max, null] + ), + col( + 'bigint', + BIGINT, + [BIGINT.min, BIGINT.max, null], + [String(BIGINT.min), String(BIGINT.max), null] + ), + col( + 'hugeint', + HUGEINT, + [HUGEINT.min, HUGEINT.max, null], + [String(HUGEINT.min), String(HUGEINT.max), null] + ), + col( + 'uhugeint', + UHUGEINT, + [UHUGEINT.min, UHUGEINT.max, null], + [String(UHUGEINT.min), String(UHUGEINT.max), null] + ), + col( + 'utinyint', + UTINYINT, + [UTINYINT.min, UTINYINT.max, null], + [UTINYINT.min, UTINYINT.max, null] + ), + col( + 'usmallint', + USMALLINT, + [USMALLINT.min, USMALLINT.max, null], + [USMALLINT.min, USMALLINT.max, null] + ), + col( + 'uint', + UINTEGER, + [UINTEGER.min, UINTEGER.max, null], + [UINTEGER.min, UINTEGER.max, null] + ), + col( + 'ubigint', + UBIGINT, + [UBIGINT.min, UBIGINT.max, null], + [String(UBIGINT.min), String(UBIGINT.max), null] + ), + col( + 'varint', + VARINT, + [VARINT.min, VARINT.max, null], + [String(VARINT.min), String(VARINT.max), null] + ), + col( + 'date', + DATE, + [DATE.min, DATE.max, null], + [String(DATE.min), String(DATE.max), null] + ), + col( + 'time', + TIME, + [TIME.min, TIME.max, null], + [String(TIME.min), String(TIME.max), null] + ), + col( + 'timestamp', + TIMESTAMP, + [TIMESTAMP.min, TIMESTAMP.max, null], + [String(TIMESTAMP.min), String(TIMESTAMP.max), null] + ), + col( + 'timestamp_s', + TIMESTAMP_S, + [TIMESTAMP_S.min, TIMESTAMP_S.max, null], + [String(TIMESTAMP_S.min), String(TIMESTAMP_S.max), null] + ), + col( + 'timestamp_ms', + TIMESTAMP_MS, + [TIMESTAMP_MS.min, TIMESTAMP_MS.max, null], + [String(TIMESTAMP_MS.min), String(TIMESTAMP_MS.max), null] + ), + col( + 'timestamp_ns', + TIMESTAMP_NS, + [TIMESTAMP_NS.min, TIMESTAMP_NS.max, null], + [String(TIMESTAMP_NS.min), String(TIMESTAMP_NS.max), null] + ), + col( + 'time_tz', + TIMETZ, + [TIMETZ.min, TIMETZ.max, null], + [String(TIMETZ.min), String(TIMETZ.max), null] + ), + col( + 'timestamp_tz', + TIMESTAMPTZ, + [TIMESTAMPTZ.min, TIMESTAMPTZ.max, null], + [String(TIMESTAMPTZ.min), String(TIMESTAMPTZ.max), null] + ), + col( + 'float', + FLOAT, + [FLOAT.min, FLOAT.max, null], + [FLOAT.min, FLOAT.max, null] + ), + col( + 'double', + DOUBLE, + [DOUBLE.min, DOUBLE.max, null], + [DOUBLE.min, DOUBLE.max, null] + ), + col( + 'dec_4_1', + DECIMAL(4, 1), + [decimalValue(-9999n, 4, 1), decimalValue(9999n, 4, 1), null], + ['-999.9', '999.9', null] + ), + col( + 'dec_9_4', + DECIMAL(9, 4), + [decimalValue(-999999999n, 9, 4), decimalValue(999999999n, 9, 4), null], + ['-99999.9999', '99999.9999', null] + ), + col( + 'dec_18_6', + DECIMAL(18, 6), + [decimalValue(-BI_18_9s, 18, 6), decimalValue(BI_18_9s, 18, 6), null], + ['-999999999999.999999', '999999999999.999999', null] + ), + col( + 'dec38_10', + DECIMAL(38, 10), + [decimalValue(-BI_38_9s, 38, 10), decimalValue(BI_38_9s, 38, 10), null], + [ + '-9999999999999999999999999999.9999999999', + '9999999999999999999999999999.9999999999', + null, + ] + ), + col( + 'uuid', + UUID, + [UUID.min, UUID.max, null], + [String(UUID.min), String(UUID.max), null] + ), + col( + 'interval', + INTERVAL, + [intervalValue(0, 0, 0n), intervalValue(999, 999, 999999999n), null], + [ + { months: 0, days: 0, micros: '0' }, + { months: 999, days: 999, micros: '999999999' }, + null, + ] + ), + col( + 'varchar', + VARCHAR, + ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null], + ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null] + ), + col( + 'blob', + BLOB, + [ + blobValue('thisisalongblob\x00withnullbytes'), + blobValue('\x00\x00\x00a'), + null, + ], + ['thisisalongblob\\x00withnullbytes', '\\x00\\x00\\x00a', null] + ), + col( + 'bit', + BIT, + [bitValue('0010001001011100010101011010111'), bitValue('10101'), null], + ['0010001001011100010101011010111', '10101', null] + ), + col( + 'small_enum', + ENUM8(smallEnumValues), + [smallEnumValues[0], smallEnumValues[smallEnumValues.length - 1], null], + [smallEnumValues[0], smallEnumValues[smallEnumValues.length - 1], null] + ), + col( + 'medium_enum', + ENUM16(mediumEnumValues), + [mediumEnumValues[0], mediumEnumValues[mediumEnumValues.length - 1], null], + [mediumEnumValues[0], mediumEnumValues[mediumEnumValues.length - 1], null] + ), + col( + 'large_enum', + ENUM32(largeEnumValues), + [largeEnumValues[0], largeEnumValues[largeEnumValues.length - 1], null], + [largeEnumValues[0], largeEnumValues[largeEnumValues.length - 1], null] + ), + col( + 'int_array', + LIST(INTEGER), + [listValue([]), listValue([42, 999, null, null, -42]), null], + [[], [42, 999, null, null, -42], null] + ), + col( + 'double_array', + LIST(DOUBLE), + [ listValue([]), - listValue([42, 999, null, null, -42]), + listValue([42.0, NaN, Infinity, -Infinity, null, -42.0]), null, + ], + [[], [42, 'NaN', 'Infinity', '-Infinity', null, -42], null] + ), + col( + 'date_array', + LIST(DATE), + [ listValue([]), - listValue([42, 999, null, null, -42]), - ]), - null, - ]), - col('struct', STRUCT({ 'a': INTEGER, 'b': VARCHAR }), [ - structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), - null, - ]), - col('struct_of_arrays', STRUCT({ 'a': LIST(INTEGER), 'b': LIST(VARCHAR) }), [ - structValue({ 'a': null, 'b': null }), - structValue({ - 'a': listValue([42, 999, null, null, -42]), - 'b': listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), - }), - null, - ]), - col('array_of_structs', LIST(STRUCT({ 'a': INTEGER, 'b': VARCHAR })), [ - listValue([]), - listValue([ - structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + // 19124 days from the epoch is 2022-05-12 + listValue([DATE.epoch, DATE.posInf, DATE.negInf, null, dateValue(19124)]), null, - ]), - null, - ]), - col('map', MAP(VARCHAR, VARCHAR), [ - mapValue([]), - mapValue([ - { key: 'key1', value: '🦆🦆🦆🦆🦆🦆' }, - { key: 'key2', value: 'goose' }, - ]), - null, - ]), - col('union', UNION({ 'name': VARCHAR, 'age': SMALLINT }), [ - unionValue('name', 'Frank'), - unionValue('age', 5), - null, - ]), - col('fixed_int_array', ARRAY(INTEGER, 3), [ - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - null, - ]), - col('fixed_varchar_array', ARRAY(VARCHAR, 3), [ - arrayValue(['a', null, 'c']), - arrayValue(['d', 'e', 'f']), - null, - ]), - col('fixed_nested_int_array', ARRAY(ARRAY(INTEGER, 3), 3), [ - arrayValue([arrayValue([null, 2, 3]), null, arrayValue([null, 2, 3])]), - arrayValue([ - arrayValue([4, 5, 6]), - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - ]), - null, - ]), - col('fixed_nested_varchar_array', ARRAY(ARRAY(VARCHAR, 3), 3), [ - arrayValue([ - arrayValue(['a', null, 'c']), - null, - arrayValue(['a', null, 'c']), - ]), - arrayValue([ - arrayValue(['d', 'e', 'f']), - arrayValue(['a', null, 'c']), - arrayValue(['d', 'e', 'f']), - ]), - null, - ]), - col('fixed_struct_array', ARRAY(STRUCT({ 'a': INTEGER, 'b': VARCHAR }), 3), [ - arrayValue([ - structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + ], + [ + [], + ['1970-01-01', '5881580-07-11', '5877642-06-24 (BC)', null, '2022-05-12'], + null, + ] + ), + col( + 'timestamp_array', + LIST(TIMESTAMP), + [ + listValue([]), + listValue([ + TIMESTAMP.epoch, + TIMESTAMP.posInf, + TIMESTAMP.negInf, + null, + // 1652372625 seconds from the epoch is 2022-05-12 16:23:45 + timestampValue(1652372625n * 1000n * 1000n), + ]), + null, + ], + [ + [], + [ + '1970-01-01 00:00:00', + 'infinity', + '-infinity', + null, + '2022-05-12 16:23:45', + ], + null, + ] + ), + col( + 'timestamptz_array', + LIST(TIMESTAMPTZ), + [ + listValue([]), + listValue([ + TIMESTAMPTZ.epoch, + TIMESTAMPTZ.posInf, + TIMESTAMPTZ.negInf, + null, + // 1652397825 = 1652372625 + 25200, 25200 = 7 * 60 * 60 = 7 hours in seconds + // This 7 hour difference is hard coded into test_all_types (value is 2022-05-12 16:23:45-07) + timestampTZValue(1652397825n * 1000n * 1000n), + ]), + null, + ], + [ + [], + [ + '1970-01-01 00:00:00', // TODO: timestamptz timezone offset + 'infinity', + '-infinity', + null, + '2022-05-12 23:23:45', + ], + null, + ] + ), + col( + 'varchar_array', + LIST(VARCHAR), + [ + listValue([]), + // Note that the string 'goose' in varchar_array does NOT have an embedded null character. + listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), + null, + ], + [[], ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], null] + ), + col( + 'nested_int_array', + LIST(LIST(INTEGER)), + [ + listValue([]), + listValue([ + listValue([]), + listValue([42, 999, null, null, -42]), + null, + listValue([]), + listValue([42, 999, null, null, -42]), + ]), + null, + ], + [ + [], + [[], [42, 999, null, null, -42], null, [], [42, 999, null, null, -42]], + null, + ] + ), + col( + 'struct', + STRUCT({ 'a': INTEGER, 'b': VARCHAR }), + [ structValue({ 'a': null, 'b': null }), - ]), - arrayValue([ structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + null, + ], + [{ 'a': null, 'b': null }, { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, null] + ), + col( + 'struct_of_arrays', + STRUCT({ 'a': LIST(INTEGER), 'b': LIST(VARCHAR) }), + [ structValue({ 'a': null, 'b': null }), - structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), - ]), - null, - ]), + structValue({ + 'a': listValue([42, 999, null, null, -42]), + 'b': listValue(['🦆🦆🦆🦆🦆🦆', 'goose', null, '']), + }), + null, + ], + [ + { 'a': null, 'b': null }, + { + 'a': [42, 999, null, null, -42], + 'b': ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], + }, + null, + ] + ), + col( + 'array_of_structs', + LIST(STRUCT({ 'a': INTEGER, 'b': VARCHAR })), + [ + listValue([]), + listValue([ + structValue({ 'a': null, 'b': null }), + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + null, + ]), + null, + ], + [ + [], + [{ 'a': null, 'b': null }, { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, null], + null, + ] + ), + col( + 'map', + MAP(VARCHAR, VARCHAR), + [ + mapValue([]), + mapValue([ + { key: 'key1', value: '🦆🦆🦆🦆🦆🦆' }, + { key: 'key2', value: 'goose' }, + ]), + null, + ], + [ + [], + [ + { 'key': 'key1', 'value': '🦆🦆🦆🦆🦆🦆' }, + { 'key': 'key2', 'value': 'goose' }, + ], + null, + ] + ), + col( + 'union', + UNION({ 'name': VARCHAR, 'age': SMALLINT }), + [unionValue('name', 'Frank'), unionValue('age', 5), null], + [{ 'tag': 'name', 'value': 'Frank' }, { 'tag': 'age', 'value': 5 }, null] + ), + col( + 'fixed_int_array', + ARRAY(INTEGER, 3), + [arrayValue([null, 2, 3]), arrayValue([4, 5, 6]), null], + [[null, 2, 3], [4, 5, 6], null] + ), + col( + 'fixed_varchar_array', + ARRAY(VARCHAR, 3), + [arrayValue(['a', null, 'c']), arrayValue(['d', 'e', 'f']), null], + [['a', null, 'c'], ['d', 'e', 'f'], null] + ), + col( + 'fixed_nested_int_array', + ARRAY(ARRAY(INTEGER, 3), 3), + [ + arrayValue([arrayValue([null, 2, 3]), null, arrayValue([null, 2, 3])]), + arrayValue([ + arrayValue([4, 5, 6]), + arrayValue([null, 2, 3]), + arrayValue([4, 5, 6]), + ]), + null, + ], + [ + [[null, 2, 3], null, [null, 2, 3]], + [ + [4, 5, 6], + [null, 2, 3], + [4, 5, 6], + ], + null, + ] + ), + col( + 'fixed_nested_varchar_array', + ARRAY(ARRAY(VARCHAR, 3), 3), + [ + arrayValue([ + arrayValue(['a', null, 'c']), + null, + arrayValue(['a', null, 'c']), + ]), + arrayValue([ + arrayValue(['d', 'e', 'f']), + arrayValue(['a', null, 'c']), + arrayValue(['d', 'e', 'f']), + ]), + null, + ], + [ + [['a', null, 'c'], null, ['a', null, 'c']], + [ + ['d', 'e', 'f'], + ['a', null, 'c'], + ['d', 'e', 'f'], + ], + null, + ] + ), + col( + 'fixed_struct_array', + ARRAY(STRUCT({ 'a': INTEGER, 'b': VARCHAR }), 3), + [ + arrayValue([ + structValue({ 'a': null, 'b': null }), + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + structValue({ 'a': null, 'b': null }), + ]), + arrayValue([ + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + structValue({ 'a': null, 'b': null }), + structValue({ 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }), + ]), + null, + ], + [ + [ + { 'a': null, 'b': null }, + { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + { 'a': null, 'b': null }, + ], + [ + { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + { 'a': null, 'b': null }, + { 'a': 42, 'b': '🦆🦆🦆🦆🦆🦆' }, + ], + null, + ] + ), col( 'struct_of_fixed_array', STRUCT({ 'a': ARRAY(INTEGER, 3), 'b': ARRAY(VARCHAR, 3) }), @@ -307,38 +590,70 @@ export const testAllTypesData: ColumnNameTypeAndRows[] = [ 'b': arrayValue(['d', 'e', 'f']), }), null, + ], + [ + { 'a': [null, 2, 3], 'b': ['a', null, 'c'] }, + { 'a': [4, 5, 6], 'b': ['d', 'e', 'f'] }, + null, + ] + ), + col( + 'fixed_array_of_int_list', + ARRAY(LIST(INTEGER), 3), + [ + arrayValue([ + listValue([]), + listValue([42, 999, null, null, -42]), + listValue([]), + ]), + arrayValue([ + listValue([42, 999, null, null, -42]), + listValue([]), + listValue([42, 999, null, null, -42]), + ]), + null, + ], + [ + [[], [42, 999, null, null, -42], []], + [[42, 999, null, null, -42], [], [42, 999, null, null, -42]], + null, + ] + ), + col( + 'list_of_fixed_int_array', + LIST(ARRAY(INTEGER, 3)), + [ + listValue([ + arrayValue([null, 2, 3]), + arrayValue([4, 5, 6]), + arrayValue([null, 2, 3]), + ]), + listValue([ + arrayValue([4, 5, 6]), + arrayValue([null, 2, 3]), + arrayValue([4, 5, 6]), + ]), + null, + ], + [ + [ + [null, 2, 3], + [4, 5, 6], + [null, 2, 3], + ], + [ + [4, 5, 6], + [null, 2, 3], + [4, 5, 6], + ], + null, ] ), - col('fixed_array_of_int_list', ARRAY(LIST(INTEGER), 3), [ - arrayValue([ - listValue([]), - listValue([42, 999, null, null, -42]), - listValue([]), - ]), - arrayValue([ - listValue([42, 999, null, null, -42]), - listValue([]), - listValue([42, 999, null, null, -42]), - ]), - null, - ]), - col('list_of_fixed_int_array', LIST(ARRAY(INTEGER, 3)), [ - listValue([ - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - arrayValue([null, 2, 3]), - ]), - listValue([ - arrayValue([4, 5, 6]), - arrayValue([null, 2, 3]), - arrayValue([4, 5, 6]), - ]), - null, - ]), ]; -export const testAllTypesColumnNames: readonly string[] = - testAllTypesData.map(({ name }) => name); +export const testAllTypesColumnNames: readonly string[] = testAllTypesData.map( + ({ name }) => name +); export const testAllTypesColumnTypes: readonly DuckDBType[] = testAllTypesData.map(({ type }) => type); @@ -348,3 +663,32 @@ export const testAllTypesColumnsNamesAndTypes: readonly ColumnNameAndType[] = export const testAllTypesColumns: readonly (readonly DuckDBValue[])[] = testAllTypesData.map(({ rows }) => rows); + +export const testAllTypesColumnsJson: readonly (readonly Json[])[] = + testAllTypesData.map(({ json }) => json); + +export const testAllTypesColumnsObjectJson: Record = + (function () { + const columnsObject: Record = {}; + for (const columnData of testAllTypesData) { + columnsObject[columnData.name] = columnData.json; + } + return columnsObject; + })(); + +export const testAllTypesRowsJson: readonly (readonly Json[])[] = [ + testAllTypesData.map(({ json }) => json[0]), + testAllTypesData.map(({ json }) => json[1]), + testAllTypesData.map(({ json }) => json[2]), +]; + +export const testAllTypesRowObjectsJson: readonly Record[] = + (function () { + const rowObjects: Record[] = [{}, {}, {}]; + for (const columnData of testAllTypesData) { + rowObjects[0][columnData.name] = columnData.json[0]; + rowObjects[1][columnData.name] = columnData.json[1]; + rowObjects[2][columnData.name] = columnData.json[2]; + } + return rowObjects; + })();