From 66089a5867ca8bf18f3959cc76a77358730254c1 Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sun, 27 Oct 2024 20:17:16 -0700 Subject: [PATCH] data value overhaul --- api/src/DuckDBAppender.ts | 19 +- api/src/DuckDBLogicalType.ts | 2 +- api/src/DuckDBPreparedStatement.ts | 18 +- api/src/DuckDBType.ts | 79 ++-- api/src/DuckDBValue.ts | 23 - api/src/DuckDBVector.ts | 350 +++++---------- api/src/index.ts | 4 +- api/src/values/DuckDBArrayValue.ts | 13 + api/src/values/DuckDBBitValue.ts | 80 ++++ api/src/values/DuckDBBlobValue.ts | 13 + api/src/values/DuckDBDateValue.ts | 14 + api/src/values/DuckDBDecimalValue.ts | 24 ++ api/src/values/DuckDBIntervalValue.ts | 18 + api/src/values/DuckDBListValue.ts | 13 + api/src/values/DuckDBMapValue.ts | 23 + api/src/values/DuckDBStructValue.ts | 12 + api/src/values/DuckDBTimeTZValue.ts | 38 ++ api/src/values/DuckDBTimeValue.ts | 14 + .../DuckDBTimestampMillisecondsValue.ts | 13 + .../values/DuckDBTimestampNanosecondsValue.ts | 13 + api/src/values/DuckDBTimestampSecondsValue.ts | 13 + api/src/values/DuckDBTimestampTZValue.ts | 13 + api/src/values/DuckDBTimestampValue.ts | 17 + api/src/values/DuckDBUUIDValue.ts | 13 + api/src/values/DuckDBUnionValue.ts | 11 + api/src/values/DuckDBValue.ts | 44 ++ api/src/values/index.ts | 19 + api/test/api.test.ts | 407 ++++++++++-------- .../pkgs/@duckdb/node-bindings/duckdb.d.ts | 2 +- bindings/src/duckdb_node_bindings.cpp | 18 +- bindings/test/appender.test.ts | 2 +- bindings/test/conversion.test.ts | 12 +- bindings/test/prepared_statements.test.ts | 2 +- bindings/test/values.test.ts | 2 +- 34 files changed, 837 insertions(+), 521 deletions(-) delete mode 100644 api/src/DuckDBValue.ts create mode 100644 api/src/values/DuckDBArrayValue.ts create mode 100644 api/src/values/DuckDBBitValue.ts create mode 100644 api/src/values/DuckDBBlobValue.ts create mode 100644 api/src/values/DuckDBDateValue.ts create mode 100644 api/src/values/DuckDBDecimalValue.ts create mode 100644 api/src/values/DuckDBIntervalValue.ts create mode 100644 api/src/values/DuckDBListValue.ts create mode 100644 api/src/values/DuckDBMapValue.ts create mode 100644 api/src/values/DuckDBStructValue.ts create mode 100644 api/src/values/DuckDBTimeTZValue.ts create mode 100644 api/src/values/DuckDBTimeValue.ts create mode 100644 api/src/values/DuckDBTimestampMillisecondsValue.ts create mode 100644 api/src/values/DuckDBTimestampNanosecondsValue.ts create mode 100644 api/src/values/DuckDBTimestampSecondsValue.ts create mode 100644 api/src/values/DuckDBTimestampTZValue.ts create mode 100644 api/src/values/DuckDBTimestampValue.ts create mode 100644 api/src/values/DuckDBUUIDValue.ts create mode 100644 api/src/values/DuckDBUnionValue.ts create mode 100644 api/src/values/DuckDBValue.ts create mode 100644 api/src/values/index.ts diff --git a/api/src/DuckDBAppender.ts b/api/src/DuckDBAppender.ts index b5585814..797bfd83 100644 --- a/api/src/DuckDBAppender.ts +++ b/api/src/DuckDBAppender.ts @@ -1,8 +1,13 @@ import duckdb from '@duckdb/node-bindings'; -import { DuckDBType } from './DuckDBType'; -import { DuckDBLogicalType } from './DuckDBLogicalType'; -import { Date_, Interval, Time, Timestamp } from './DuckDBValue'; import { DuckDBDataChunk } from './DuckDBDataChunk'; +import { DuckDBLogicalType } from './DuckDBLogicalType'; +import { DuckDBType } from './DuckDBType'; +import { + DuckDBDateValue, + DuckDBIntervalValue, + DuckDBTimestampValue, + DuckDBTimeValue, +} from './values'; export class DuckDBAppender { private readonly appender: duckdb.Appender; @@ -70,18 +75,18 @@ export class DuckDBAppender { public appendDouble(value: number) { duckdb.append_double(this.appender, value); } - public appendDate(value: Date_) { + public appendDate(value: DuckDBDateValue) { duckdb.append_date(this.appender, value); } - public appendTime(value: Time) { + public appendTime(value: DuckDBTimeValue) { duckdb.append_time(this.appender, value); } - public appendTimestamp(value: Timestamp) { + public appendTimestamp(value: DuckDBTimestampValue) { duckdb.append_timestamp(this.appender, value); } // TODO: append TIMESTAMPS_S/_MS/_NS? // TODO: append TIME_TZ/TIMESTAMP_TZ? - public appendInterval(value: Interval) { + public appendInterval(value: DuckDBIntervalValue) { duckdb.append_interval(this.appender, value); } public appendVarchar(value: string) { diff --git a/api/src/DuckDBLogicalType.ts b/api/src/DuckDBLogicalType.ts index 1108f71d..a6c1d699 100644 --- a/api/src/DuckDBLogicalType.ts +++ b/api/src/DuckDBLogicalType.ts @@ -340,7 +340,7 @@ export class DuckDBArrayLogicalType extends DuckDBLogicalType { public get length(): number { return duckdb.array_type_array_size(this.logical_type); } - public override asType(): DuckDBListType { + public override asType(): DuckDBArrayType { return new DuckDBArrayType(this.valueType.asType(), this.length); } } diff --git a/api/src/DuckDBPreparedStatement.ts b/api/src/DuckDBPreparedStatement.ts index 85a49c08..57fa2224 100644 --- a/api/src/DuckDBPreparedStatement.ts +++ b/api/src/DuckDBPreparedStatement.ts @@ -2,8 +2,14 @@ import duckdb from '@duckdb/node-bindings'; import { DuckDBPendingResult } from './DuckDBPendingResult'; import { DuckDBResult } from './DuckDBResult'; import { DuckDBTypeId } from './DuckDBTypeId'; -import { Date_, Decimal, Interval, Time, Timestamp } from './DuckDBValue'; import { StatementType } from './enums'; +import { + DuckDBDateValue, + DuckDBDecimalValue, + DuckDBIntervalValue, + DuckDBTimestampValue, + DuckDBTimeValue, +} from './values'; export class DuckDBPreparedStatement { private readonly prepared_statement: duckdb.PreparedStatement; @@ -67,7 +73,7 @@ export class DuckDBPreparedStatement { public bindUHugeInt(parameterIndex: number, value: bigint) { duckdb.bind_uhugeint(this.prepared_statement, parameterIndex, value); } - public bindDecimal(parameterIndex: number, value: Decimal) { + public bindDecimal(parameterIndex: number, value: DuckDBDecimalValue) { duckdb.bind_decimal(this.prepared_statement, parameterIndex, value); } public bindFloat(parameterIndex: number, value: number) { @@ -76,18 +82,18 @@ export class DuckDBPreparedStatement { public bindDouble(parameterIndex: number, value: number) { duckdb.bind_double(this.prepared_statement, parameterIndex, value); } - public bindDate(parameterIndex: number, value: Date_) { + public bindDate(parameterIndex: number, value: DuckDBDateValue) { duckdb.bind_date(this.prepared_statement, parameterIndex, value); } - public bindTime(parameterIndex: number, value: Time) { + public bindTime(parameterIndex: number, value: DuckDBTimeValue) { duckdb.bind_time(this.prepared_statement, parameterIndex, value); } - public bindTimestamp(parameterIndex: number, value: Timestamp) { + public bindTimestamp(parameterIndex: number, value: DuckDBTimestampValue) { duckdb.bind_timestamp(this.prepared_statement, parameterIndex, value); } // TODO: bind TIMESTAMPS_S/_MS/_NS? // TODO: bind TIME_TZ/TIMESTAMP_TZ? - public bindInterval(parameterIndex: number, value: Interval) { + public bindInterval(parameterIndex: number, value: DuckDBIntervalValue) { duckdb.bind_interval(this.prepared_statement, parameterIndex, value); } public bindVarchar(parameterIndex: number, value: string) { diff --git a/api/src/DuckDBType.ts b/api/src/DuckDBType.ts index ddb629c8..cb11c9f6 100644 --- a/api/src/DuckDBType.ts +++ b/api/src/DuckDBType.ts @@ -1,9 +1,9 @@ import { DuckDBTypeId } from './DuckDBTypeId'; import { quotedIdentifier, quotedString } from './sql'; -export abstract class BaseDuckDBType { - public readonly typeId: DuckDBTypeId; - protected constructor(typeId: DuckDBTypeId) { +export abstract class BaseDuckDBType { + public readonly typeId: T; + protected constructor(typeId: T) { this.typeId = typeId; } public toString(): string { @@ -11,84 +11,84 @@ export abstract class BaseDuckDBType { } } -export class DuckDBBooleanType extends BaseDuckDBType { +export class DuckDBBooleanType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.BOOLEAN); } public static readonly instance = new DuckDBBooleanType(); } -export class DuckDBTinyIntType extends BaseDuckDBType { +export class DuckDBTinyIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TINYINT); } public static readonly instance = new DuckDBTinyIntType(); } -export class DuckDBSmallIntType extends BaseDuckDBType { +export class DuckDBSmallIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.SMALLINT); } public static readonly instance = new DuckDBSmallIntType(); } -export class DuckDBIntegerType extends BaseDuckDBType { +export class DuckDBIntegerType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.INTEGER); } public static readonly instance = new DuckDBIntegerType(); } -export class DuckDBBigIntType extends BaseDuckDBType { +export class DuckDBBigIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.BIGINT); } public static readonly instance = new DuckDBBigIntType(); } -export class DuckDBUTinyIntType extends BaseDuckDBType { +export class DuckDBUTinyIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.UTINYINT); } public static readonly instance = new DuckDBUTinyIntType(); } -export class DuckDBUSmallIntType extends BaseDuckDBType { +export class DuckDBUSmallIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.USMALLINT); } public static readonly instance = new DuckDBUSmallIntType(); } -export class DuckDBUIntegerType extends BaseDuckDBType { +export class DuckDBUIntegerType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.UINTEGER); } public static readonly instance = new DuckDBUIntegerType(); } -export class DuckDBUBigIntType extends BaseDuckDBType { +export class DuckDBUBigIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.UBIGINT); } public static readonly instance = new DuckDBUBigIntType(); } -export class DuckDBFloatType extends BaseDuckDBType { +export class DuckDBFloatType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.FLOAT); } public static readonly instance = new DuckDBFloatType(); } -export class DuckDBDoubleType extends BaseDuckDBType { +export class DuckDBDoubleType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.DOUBLE); } public static readonly instance = new DuckDBDoubleType(); } -export class DuckDBTimestampType extends BaseDuckDBType { +export class DuckDBTimestampType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIMESTAMP); } @@ -96,57 +96,58 @@ export class DuckDBTimestampType extends BaseDuckDBType { } export type DuckDBTimestampMicrosecondsType = DuckDBTimestampType; +export const DuckDBTimestampMicrosecondsType = DuckDBTimestampType; -export class DuckDBDateType extends BaseDuckDBType { +export class DuckDBDateType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.DATE); } public static readonly instance = new DuckDBDateType(); } -export class DuckDBTimeType extends BaseDuckDBType { +export class DuckDBTimeType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIME); } public static readonly instance = new DuckDBTimeType(); } -export class DuckDBIntervalType extends BaseDuckDBType { +export class DuckDBIntervalType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.INTERVAL); } public static readonly instance = new DuckDBIntervalType(); } -export class DuckDBHugeIntType extends BaseDuckDBType { +export class DuckDBHugeIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.HUGEINT); } public static readonly instance = new DuckDBHugeIntType(); } -export class DuckDBUHugeIntType extends BaseDuckDBType { +export class DuckDBUHugeIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.UHUGEINT); } public static readonly instance = new DuckDBUHugeIntType(); } -export class DuckDBVarCharType extends BaseDuckDBType { +export class DuckDBVarCharType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.VARCHAR); } public static readonly instance = new DuckDBVarCharType(); } -export class DuckDBBlobType extends BaseDuckDBType { +export class DuckDBBlobType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.BLOB); } public static readonly instance = new DuckDBBlobType(); } -export class DuckDBDecimalType extends BaseDuckDBType { +export class DuckDBDecimalType extends BaseDuckDBType { public readonly width: number; public readonly scale: number; public constructor(width: number, scale: number) { @@ -160,28 +161,28 @@ export class DuckDBDecimalType extends BaseDuckDBType { public static readonly default = new DuckDBDecimalType(18, 3); } -export class DuckDBTimestampSecondsType extends BaseDuckDBType { +export class DuckDBTimestampSecondsType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIMESTAMP_S); } public static readonly instance = new DuckDBTimestampSecondsType(); } -export class DuckDBTimestampMillisecondsType extends BaseDuckDBType { +export class DuckDBTimestampMillisecondsType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIMESTAMP_MS); } public static readonly instance = new DuckDBTimestampMillisecondsType(); } -export class DuckDBTimestampNanosecondsType extends BaseDuckDBType { +export class DuckDBTimestampNanosecondsType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIMESTAMP_NS); } public static readonly instance = new DuckDBTimestampNanosecondsType(); } -export class DuckDBEnumType extends BaseDuckDBType { +export class DuckDBEnumType extends BaseDuckDBType { public readonly values: readonly string[]; public readonly internalTypeId: DuckDBTypeId; public constructor(values: readonly string[], internalTypeId: DuckDBTypeId) { @@ -194,7 +195,7 @@ export class DuckDBEnumType extends BaseDuckDBType { } } -export class DuckDBListType extends BaseDuckDBType { +export class DuckDBListType extends BaseDuckDBType { public readonly valueType: DuckDBType; public constructor(valueType: DuckDBType) { super(DuckDBTypeId.LIST); @@ -210,7 +211,7 @@ export interface DuckDBStructEntryType { readonly valueType: DuckDBType; } -export class DuckDBStructType extends BaseDuckDBType { +export class DuckDBStructType extends BaseDuckDBType { public readonly entries: readonly DuckDBStructEntryType[]; public constructor(entries: readonly DuckDBStructEntryType[]) { super(DuckDBTypeId.STRUCT); @@ -223,7 +224,7 @@ export class DuckDBStructType extends BaseDuckDBType { } } -export class DuckDBMapType extends BaseDuckDBType { +export class DuckDBMapType extends BaseDuckDBType { public readonly keyType: DuckDBType; public readonly valueType: DuckDBType; public constructor(keyType: DuckDBType, valueType: DuckDBType) { @@ -236,7 +237,7 @@ export class DuckDBMapType extends BaseDuckDBType { } } -export class DuckDBArrayType extends BaseDuckDBType { +export class DuckDBArrayType extends BaseDuckDBType { public readonly valueType: DuckDBType; public readonly length: number; public constructor(valueType: DuckDBType, length: number) { @@ -249,7 +250,7 @@ export class DuckDBArrayType extends BaseDuckDBType { } } -export class DuckDBUUIDType extends BaseDuckDBType { +export class DuckDBUUIDType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.UUID); } @@ -261,7 +262,7 @@ export interface DuckDBUnionAlternativeType { readonly valueType: DuckDBType; } -export class DuckDBUnionType extends BaseDuckDBType { +export class DuckDBUnionType extends BaseDuckDBType { public readonly alternatives: readonly DuckDBUnionAlternativeType[]; public constructor(alternatives: readonly DuckDBUnionAlternativeType[]) { super(DuckDBTypeId.UNION); @@ -274,14 +275,14 @@ export class DuckDBUnionType extends BaseDuckDBType { } } -export class DuckDBBitType extends BaseDuckDBType { +export class DuckDBBitType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.BIT); } public static readonly instance = new DuckDBBitType(); } -export class DuckDBTimeTZType extends BaseDuckDBType { +export class DuckDBTimeTZType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIME_TZ); } @@ -291,7 +292,7 @@ export class DuckDBTimeTZType extends BaseDuckDBType { public static readonly instance = new DuckDBTimeTZType(); } -export class DuckDBTimestampTZType extends BaseDuckDBType { +export class DuckDBTimestampTZType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.TIMESTAMP_TZ); } @@ -301,21 +302,21 @@ export class DuckDBTimestampTZType extends BaseDuckDBType { public static readonly instance = new DuckDBTimestampTZType(); } -export class DuckDBAnyType extends BaseDuckDBType { +export class DuckDBAnyType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.ANY); } public static readonly instance = new DuckDBAnyType(); } -export class DuckDBVarIntType extends BaseDuckDBType { +export class DuckDBVarIntType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.VARINT); } public static readonly instance = new DuckDBVarIntType(); } -export class DuckDBSQLNullType extends BaseDuckDBType { +export class DuckDBSQLNullType extends BaseDuckDBType { private constructor() { super(DuckDBTypeId.SQLNULL); } diff --git a/api/src/DuckDBValue.ts b/api/src/DuckDBValue.ts deleted file mode 100644 index 0d02160c..00000000 --- a/api/src/DuckDBValue.ts +++ /dev/null @@ -1,23 +0,0 @@ -import duckdb from '@duckdb/node-bindings'; - -type Date_ = duckdb.Date_; -type Decimal = duckdb.Decimal; -type Interval = duckdb.Interval; -type Time = duckdb.Time; -type Timestamp = duckdb.Timestamp; - -export type { Date_, Decimal, Interval, Time, Timestamp }; - -export type DuckDBValue = - | null - | boolean - | number - | bigint - | string - | Uint8Array - | Date_ - | Decimal - | Interval - | Time - | Timestamp - ; diff --git a/api/src/DuckDBVector.ts b/api/src/DuckDBVector.ts index dd3c4a2e..c88622e9 100644 --- a/api/src/DuckDBVector.ts +++ b/api/src/DuckDBVector.ts @@ -40,6 +40,28 @@ import { DuckDBVarIntType, } from './DuckDBType'; import { DuckDBTypeId } from './DuckDBTypeId'; +import { + DuckDBArrayValue, + DuckDBBitValue, + DuckDBBlobValue, + DuckDBDateValue, + DuckDBDecimalValue, + DuckDBIntervalValue, + DuckDBListValue, + DuckDBMapEntry, + DuckDBMapValue, + DuckDBStructValue, + DuckDBTimeTZValue, + DuckDBTimeValue, + DuckDBTimestampMillisecondsValue, + DuckDBTimestampNanosecondsValue, + DuckDBTimestampSecondsValue, + DuckDBTimestampTZValue, + DuckDBTimestampValue, + DuckDBUUIDValue, + DuckDBUnionValue, + DuckDBValue, +} from './values'; const littleEndian = os.endianness() === 'LE'; @@ -106,21 +128,13 @@ function getStringBytes(dataView: DataView, offset: number): Uint8Array { const textDecoder = new TextDecoder(); -function getString(dataView: DataView, offset: number): string | null { +function getString(dataView: DataView, offset: number): string { const stringBytes = getStringBytes(dataView, offset); - if (!stringBytes) { - // should we throw here instead? - return null; - } return textDecoder.decode(stringBytes); } -function getBuffer(dataView: DataView, offset: number): Buffer | null { +function getBuffer(dataView: DataView, offset: number): Buffer { const stringBytes = getStringBytes(dataView, offset); - if (!stringBytes) { - // should we throw here instead? - return null; - } return Buffer.from(stringBytes); } @@ -177,44 +191,24 @@ function makeGetBoolean(): (dataView: DataView, offset: number) => boolean { const getBoolean = makeGetBoolean(); -export class DuckDBSmallDecimal { - public readonly scaledValue: number; - public readonly type: DuckDBDecimalType; - - public constructor(scaledValue: number, type: DuckDBDecimalType) { - this.scaledValue = scaledValue; - this.type = type; - } -} - -export class DuckDBLargeDecimal { - public readonly scaledValue: bigint; - public readonly type: DuckDBDecimalType; - - public constructor(scaledValue: bigint, type: DuckDBDecimalType) { - this.scaledValue = scaledValue; - this.type = type; - } -} - -function getDecimal2(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBSmallDecimal { +function getDecimal2(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue { const scaledValue = getInt16(dataView, offset); - return new DuckDBSmallDecimal(scaledValue, type); + return new DuckDBDecimalValue(type, scaledValue); } -function getDecimal4(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBSmallDecimal { +function getDecimal4(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue { const scaledValue = getInt32(dataView, offset); - return new DuckDBSmallDecimal(scaledValue, type); + return new DuckDBDecimalValue(type, scaledValue); } -function getDecimal8(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBLargeDecimal { +function getDecimal8(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue { const scaledValue = getInt64(dataView, offset); - return new DuckDBLargeDecimal(scaledValue, type); + return new DuckDBDecimalValue(type, scaledValue); } -function getDecimal16(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBLargeDecimal { +function getDecimal16(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue { const scaledValue = getInt128(dataView, offset); - return new DuckDBLargeDecimal(scaledValue, type); + return new DuckDBDecimalValue(type, scaledValue); } function vectorData(vector: duckdb.Vector, byteCount: number): Uint8Array { @@ -266,11 +260,11 @@ class DuckDBValidity { } } -export abstract class DuckDBVector { +export abstract class DuckDBVector { public static standardSize(): number { return duckdb.vector_size(); } - public static create(vector: duckdb.Vector, itemCount: number, knownType?: DuckDBType): DuckDBVector { + public static create(vector: duckdb.Vector, itemCount: number, knownType?: DuckDBType): DuckDBVector { const vectorType = knownType ? knownType : DuckDBLogicalType.consumeAsType(duckdb.vector_get_column_type(vector)); switch (vectorType.typeId) { case DuckDBTypeId.BOOLEAN: @@ -390,13 +384,13 @@ export abstract class DuckDBVector { case DuckDBTypeId.SQLNULL: throw new Error(`Invalid vector type: SQLNULL`); default: - throw new Error(`Invalid type id: ${vectorType.typeId}`); + throw new Error(`Invalid type id: ${(vectorType as DuckDBType).typeId}`); } } public abstract get type(): DuckDBType; public abstract get itemCount(): number; - public abstract getItem(itemIndex: number): T | null; - public abstract slice(offset: number, length: number): DuckDBVector; + public abstract getItem(itemIndex: number): TValue | null; + public abstract slice(offset: number, length: number): DuckDBVector; } export class DuckDBBooleanVector extends DuckDBVector { @@ -713,7 +707,7 @@ export class DuckDBDoubleVector extends DuckDBVector { } } -export class DuckDBTimestampVector extends DuckDBVector { +export class DuckDBTimestampVector extends DuckDBVector { private readonly items: BigInt64Array; private readonly validity: DuckDBValidity; constructor(items: BigInt64Array, validity: DuckDBValidity) { @@ -733,15 +727,15 @@ export class DuckDBTimestampVector extends DuckDBVector { public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): bigint | null { // microseconds - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBTimestampValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBTimestampValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBTimestampVector { return new DuckDBTimestampVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); } } -export class DuckDBDateVector extends DuckDBVector { +export class DuckDBDateVector extends DuckDBVector { private readonly items: Int32Array; private readonly validity: DuckDBValidity; constructor(items: Int32Array, validity: DuckDBValidity) { @@ -761,15 +755,15 @@ export class DuckDBDateVector extends DuckDBVector { public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): number | null { // days - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBDateValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBDateValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBDateVector { return new DuckDBDateVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); } } -export class DuckDBTimeVector extends DuckDBVector { +export class DuckDBTimeVector extends DuckDBVector { private readonly items: BigInt64Array; private readonly validity: DuckDBValidity; constructor(items: BigInt64Array, validity: DuckDBValidity) { @@ -789,27 +783,15 @@ export class DuckDBTimeVector extends DuckDBVector { public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): bigint | null { // microseconds - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBTimeValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBTimeValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBTimeVector { return new DuckDBTimeVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); } } -export class DuckDBInterval { - public readonly months: number; - public readonly days: number; - public readonly micros: bigint; - - public constructor(months: number, days: number, micros: bigint) { - this.months = months; - this.days = days; - this.micros = micros; - } -} - -export class DuckDBIntervalVector extends DuckDBVector { +export class DuckDBIntervalVector extends DuckDBVector { private readonly dataView: DataView; private readonly validity: DuckDBValidity; private readonly _itemCount: number; @@ -825,13 +807,13 @@ export class DuckDBIntervalVector extends DuckDBVector { const validity = DuckDBValidity.fromVector(vector, itemCount); return new DuckDBIntervalVector(dataView, validity, itemCount); } - public override get type(): DuckDBHugeIntType { + public override get type(): DuckDBIntervalType { return DuckDBIntervalType.instance; } public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): DuckDBInterval | null { + public override getItem(itemIndex: number): DuckDBIntervalValue | null { if (!this.validity.itemValid(itemIndex)) { return null; } @@ -839,7 +821,7 @@ export class DuckDBIntervalVector extends DuckDBVector { const months = getInt32(this.dataView, itemStart); const days = getInt32(this.dataView, itemStart + 4); const micros = getInt64(this.dataView, itemStart + 8); - return new DuckDBInterval(months, days, micros); + return new DuckDBIntervalValue(months, days, micros); } public override slice(offset: number, length: number): DuckDBIntervalVector { return new DuckDBIntervalVector( @@ -952,7 +934,7 @@ export class DuckDBVarCharVector extends DuckDBVector { } } -export class DuckDBBlobVector extends DuckDBVector { +export class DuckDBBlobVector extends DuckDBVector { private readonly dataView: DataView; private readonly validity: DuckDBValidity; private readonly _itemCount: number; @@ -974,8 +956,8 @@ export class DuckDBBlobVector extends DuckDBVector { public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): Uint8Array | null { - return this.validity.itemValid(itemIndex) ? getBuffer(this.dataView, itemIndex * 16) : null; + public override getItem(itemIndex: number): DuckDBBlobValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBBlobValue(getBuffer(this.dataView, itemIndex * 16)) : null; } public override slice(offset: number, length: number): DuckDBBlobVector { return new DuckDBBlobVector( @@ -986,7 +968,7 @@ export class DuckDBBlobVector extends DuckDBVector { } } -export class DuckDBDecimal2Vector extends DuckDBVector { +export class DuckDBDecimal2Vector extends DuckDBVector> { private readonly decimalType: DuckDBDecimalType; private readonly dataView: DataView; private readonly validity: DuckDBValidity; @@ -1010,7 +992,7 @@ export class DuckDBDecimal2Vector extends DuckDBVector { public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): DuckDBSmallDecimal | null { + public override getItem(itemIndex: number): DuckDBDecimalValue | null { return this.validity.itemValid(itemIndex) ? getDecimal2(this.dataView, itemIndex * 2, this.decimalType) : null; } public getScaledValue(itemIndex: number): number | null { @@ -1026,7 +1008,7 @@ export class DuckDBDecimal2Vector extends DuckDBVector { } } -export class DuckDBDecimal4Vector extends DuckDBVector { +export class DuckDBDecimal4Vector extends DuckDBVector> { private readonly decimalType: DuckDBDecimalType; private readonly dataView: DataView; private readonly validity: DuckDBValidity; @@ -1050,7 +1032,7 @@ export class DuckDBDecimal4Vector extends DuckDBVector { public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): DuckDBSmallDecimal | null { + public override getItem(itemIndex: number): DuckDBDecimalValue | null { return this.validity.itemValid(itemIndex) ? getDecimal4(this.dataView, itemIndex * 4, this.decimalType) : null; } public getScaledValue(itemIndex: number): number | null { @@ -1066,7 +1048,7 @@ export class DuckDBDecimal4Vector extends DuckDBVector { } } -export class DuckDBDecimal8Vector extends DuckDBVector { +export class DuckDBDecimal8Vector extends DuckDBVector> { private readonly decimalType: DuckDBDecimalType; private readonly dataView: DataView; private readonly validity: DuckDBValidity; @@ -1090,7 +1072,7 @@ export class DuckDBDecimal8Vector extends DuckDBVector { public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): DuckDBLargeDecimal | null { + public override getItem(itemIndex: number): DuckDBDecimalValue | null { return this.validity.itemValid(itemIndex) ? getDecimal8(this.dataView, itemIndex * 8, this.decimalType) : null; } public getScaledValue(itemIndex: number): bigint | null { @@ -1106,7 +1088,7 @@ export class DuckDBDecimal8Vector extends DuckDBVector { } } -export class DuckDBDecimal16Vector extends DuckDBVector { +export class DuckDBDecimal16Vector extends DuckDBVector> { private readonly decimalType: DuckDBDecimalType; private readonly dataView: DataView; private readonly validity: DuckDBValidity; @@ -1130,7 +1112,7 @@ export class DuckDBDecimal16Vector extends DuckDBVector { public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): DuckDBLargeDecimal | null { + public override getItem(itemIndex: number): DuckDBDecimalValue | null { return this.validity.itemValid(itemIndex) ? getDecimal16(this.dataView, itemIndex * 16, this.decimalType) : null; } public getScaledValue(itemIndex: number): bigint | null { @@ -1146,7 +1128,7 @@ export class DuckDBDecimal16Vector extends DuckDBVector { } } -export class DuckDBTimestampSecondsVector extends DuckDBVector { +export class DuckDBTimestampSecondsVector extends DuckDBVector { private readonly items: BigInt64Array; private readonly validity: DuckDBValidity; constructor(items: BigInt64Array, validity: DuckDBValidity) { @@ -1166,15 +1148,15 @@ export class DuckDBTimestampSecondsVector extends DuckDBVector { public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): bigint | null { // seconds - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBTimestampSecondsValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBTimestampSecondsValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBTimestampSecondsVector { return new DuckDBTimestampSecondsVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); } } -export class DuckDBTimestampMillisecondsVector extends DuckDBVector { +export class DuckDBTimestampMillisecondsVector extends DuckDBVector { private readonly items: BigInt64Array; private readonly validity: DuckDBValidity; constructor(items: BigInt64Array, validity: DuckDBValidity) { @@ -1194,15 +1176,15 @@ export class DuckDBTimestampMillisecondsVector extends DuckDBVector { public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): bigint | null { // milliseconds - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBTimestampMillisecondsValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBTimestampMillisecondsValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBTimestampMillisecondsVector { return new DuckDBTimestampMillisecondsVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); } } -export class DuckDBTimestampNanosecondsVector extends DuckDBVector { +export class DuckDBTimestampNanosecondsVector extends DuckDBVector { private readonly items: BigInt64Array; private readonly validity: DuckDBValidity; constructor(items: BigInt64Array, validity: DuckDBValidity) { @@ -1222,8 +1204,8 @@ export class DuckDBTimestampNanosecondsVector extends DuckDBVector { public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): bigint | null { // nanoseconds - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBTimestampNanosecondsValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBTimestampNanosecondsValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBTimestampNanosecondsVector { return new DuckDBTimestampNanosecondsVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); @@ -1320,7 +1302,7 @@ export class DuckDBEnum4Vector extends DuckDBVector { } } -export class DuckDBListVector extends DuckDBVector> { +export class DuckDBListVector extends DuckDBVector { private readonly listType: DuckDBListType; private readonly entryData: BigUint64Array; private readonly validity: DuckDBValidity; @@ -1358,7 +1340,7 @@ export class DuckDBListVector extends DuckDBVector | null { + public getItemVector(itemIndex: number): DuckDBVector | null { if (!this.validity.itemValid(itemIndex)) { return null; } @@ -1367,6 +1349,13 @@ export class DuckDBListVector extends DuckDBVector | null { + const vector = this.getItemVector(itemIndex); + if (!vector) { + return null; + } + return new DuckDBListValue(this.listType, vector); + } public override slice(offset: number, length: number): DuckDBListVector { const entryDataStartIndex = offset * 2; return new DuckDBListVector( @@ -1379,17 +1368,12 @@ export class DuckDBListVector extends DuckDBVector { +export class DuckDBStructVector extends DuckDBVector { private readonly structType: DuckDBStructType; private readonly _itemCount: number; - private readonly entryVectors: readonly DuckDBVector[]; + private readonly entryVectors: readonly DuckDBVector[]; private readonly validity: DuckDBValidity; - constructor(structType: DuckDBStructType, itemCount: number, entryVectors: readonly DuckDBVector[], validity: DuckDBValidity) { + constructor(structType: DuckDBStructType, itemCount: number, entryVectors: readonly DuckDBVector[], validity: DuckDBValidity) { super(); this.structType = structType; this._itemCount = itemCount; @@ -1398,7 +1382,7 @@ export class DuckDBStructVector extends DuckDBVector[] = []; + const entryVectors: DuckDBVector[] = []; for (let i = 0; i < entryCount; i++) { const entry = structType.entries[i]; const child_vector = duckdb.struct_vector_get_child(vector, i); @@ -1413,25 +1397,22 @@ export class DuckDBStructVector extends DuckDBVector { +export class DuckDBMapVector extends DuckDBVector { private readonly mapType: DuckDBMapType; private readonly listVector: DuckDBListVector; constructor(mapType: DuckDBMapType, listVector: DuckDBListVector) { @@ -1464,14 +1440,14 @@ export class DuckDBMapVector extends DuckDBVector { ])); return new DuckDBMapVector(mapType, DuckDBListVector.fromRawVector(listVectorType, vector, itemCount)); } - public override get type(): DuckDBType { + public override get type(): DuckDBMapType { return this.mapType; } public override get itemCount(): number { return this.listVector.itemCount; } - public override getItem(itemIndex: number): readonly DuckDBMapEntry[] | null { - const itemVector = this.listVector.getItem(itemIndex); + public override getItem(itemIndex: number): DuckDBMapValue | null { + const itemVector = this.listVector.getItemVector(itemIndex); if (!itemVector) { return null; } @@ -1481,15 +1457,11 @@ export class DuckDBMapVector extends DuckDBVector { const entries: DuckDBMapEntry[] = []; const itemEntryCount = itemVector.itemCount; for (let i = 0; i < itemEntryCount; i++) { - const entry = itemVector.getItem(i); - if (!entry) { - throw new Error('null entry in map struct'); - } - const keyEntry = entry[0]; - const valueEntry = entry[1]; - entries.push({ key: keyEntry.value, value: valueEntry.value }); + const key = itemVector.getItemValue(i, 0); + const value = itemVector.getItemValue(i, 1); + entries.push({ key, value }); } - return entries; + return new DuckDBMapValue(this.mapType, entries); } public override slice(offset: number, length: number): DuckDBMapVector { return new DuckDBMapVector( @@ -1499,7 +1471,7 @@ export class DuckDBMapVector extends DuckDBVector { } } -export class DuckDBArrayVector extends DuckDBVector> { +export class DuckDBArrayVector extends DuckDBVector> { private readonly arrayType: DuckDBArrayType; private readonly validity: DuckDBValidity; private readonly childData: DuckDBVector; @@ -1536,11 +1508,11 @@ export class DuckDBArrayVector extends DuckDBVector | null { + public override getItem(itemIndex: number): DuckDBArrayValue | null { if (!this.validity.itemValid(itemIndex)) { return null; } - return this.childData.slice(itemIndex * this.arrayType.length, this.arrayType.length); + return new DuckDBArrayValue(this.arrayType, this.childData.slice(itemIndex * this.arrayType.length, this.arrayType.length)); } public override slice(offset: number, length: number): DuckDBArrayVector { return new DuckDBArrayVector( @@ -1552,7 +1524,7 @@ export class DuckDBArrayVector extends DuckDBVector { +export class DuckDBUUIDVector extends DuckDBVector { private readonly dataView: DataView; private readonly validity: DuckDBValidity; private readonly _itemCount: number; @@ -1574,8 +1546,8 @@ export class DuckDBUUIDVector extends DuckDBVector { public override get itemCount(): number { return this._itemCount; } - public override getItem(itemIndex: number): bigint | null { - return this.validity.itemValid(itemIndex) ? getInt128(this.dataView, itemIndex * 16) : null; + public override getItem(itemIndex: number): DuckDBUUIDValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBUUIDValue(getInt128(this.dataView, itemIndex * 16)) : null; } public override slice(offset: number, length: number): DuckDBUUIDVector { return new DuckDBUUIDVector( @@ -1586,13 +1558,8 @@ export class DuckDBUUIDVector extends DuckDBVector { } } -export interface DuckDBUnionAlternative { - readonly tag: string; - readonly value: any; -} - // UNION = STRUCT with first entry named "tag" -export class DuckDBUnionVector extends DuckDBVector { +export class DuckDBUnionVector extends DuckDBVector { private readonly unionType: DuckDBUnionType; private readonly structVector: DuckDBStructVector; constructor(unionType: DuckDBUnionType, structVector: DuckDBStructVector) { @@ -1614,7 +1581,7 @@ export class DuckDBUnionVector extends DuckDBVector { public override get itemCount(): number { return this.structVector.itemCount; } - public override getItem(itemIndex: number): DuckDBUnionAlternative | null { + public override getItem(itemIndex: number): DuckDBUnionValue | null { const tagValue = this.structVector.getItemValue(itemIndex, 0); if (tagValue == null) { return null; @@ -1623,7 +1590,7 @@ export class DuckDBUnionVector extends DuckDBVector { const tag = this.unionType.alternatives[alternativeIndex].tag; const entryIndex = alternativeIndex + 1; const value = this.structVector.getItemValue(itemIndex, entryIndex); - return { tag, value }; + return new DuckDBUnionValue(tag, value); } public override slice(offset: number, length: number): DuckDBUnionVector { return new DuckDBUnionVector( @@ -1633,80 +1600,6 @@ export class DuckDBUnionVector extends DuckDBVector { } } -export class DuckDBBitValue { - private readonly data: Uint8Array; - - constructor(data: Uint8Array) { - this.data = data; - } - - public static fromString(str: string): DuckDBBitValue { - if (!/^[01]*$/.test(str)) { - throw new Error(`input string must only contain '0's and '1's`); - } - - const byteCount = Math.ceil(str.length / 8) + 1; - const paddingBitCount = (8 - (str.length % 8)) % 8; - - const data = new Uint8Array(byteCount); - let byteIndex = 0; - - // first byte contains count of padding bits - data[byteIndex++] = paddingBitCount; - - let byte = 0; - let bitIndex = 0; - - // padding consists of 1s in MSB of second byte - while (bitIndex < paddingBitCount) { - byte <<= 1; - byte |= 1; - bitIndex++; - } - - let charIndex = 0; - - while (byteIndex < byteCount) { - while (bitIndex < 8) { - byte <<= 1; - if (str[charIndex++] === '1') { - byte |= 1; - } - bitIndex++; - } - data[byteIndex++] = byte; - byte = 0; - bitIndex = 0; - } - - return new DuckDBBitValue(data); - } - - private padding(): number { - return this.data[0]; - } - - public length(): number { - return (this.data.length - 1) * 8 - this.padding(); - } - - public getBool(index: number): boolean { - const dataIndex = Math.floor(index / 8) + 1; - return (this.data[dataIndex] & (1 << (index % 8))) !== 0; - } - - public getBit(index: number): 0 | 1 { - return this.getBool(index) ? 1 : 0; - } - - public toString(): string { - const chars = Array.from({ length: this.length() }); - for (let i = 0; i < this.length(); i++) { - chars[i] = this.getBool(i) ? '1' : '0'; - } - return chars.join(''); - } -} export class DuckDBBitVector extends DuckDBVector { private readonly dataView: DataView; @@ -1746,29 +1639,6 @@ export class DuckDBBitVector extends DuckDBVector { } } -export class DuckDBTimeTZValue { - /** Ranges from 0 to 86400000000 (= 24 * 60 * 60 * 1000 * 1000) */ - public readonly microseconds: number; - - /** In seconds, ranges from -57599 to 57599 (= 16 * 60 * 60 - 1) */ - public readonly offset: number; - - public constructor(microseconds: number, offset: number) { - this.microseconds = microseconds; - this.offset = offset; - } - - public static TIME_BITS = 40; - public static OFFSET_BITS = 24; - public static MAX_OFFSET = 16 * 60 * 60 - 1; // ±15:59:59 = 57599 seconds - - public static fromBits(bits: bigint): DuckDBTimeTZValue { - const microseconds = Number(BigInt.asUintN(DuckDBTimeTZValue.TIME_BITS, bits >> BigInt(DuckDBTimeTZValue.OFFSET_BITS))); - const offset = DuckDBTimeTZValue.MAX_OFFSET - Number(BigInt.asUintN(DuckDBTimeTZValue.OFFSET_BITS, bits)); - return new DuckDBTimeTZValue(microseconds, offset); - } -} - export class DuckDBTimeTZVector extends DuckDBVector { private readonly items: BigUint64Array; private readonly validity: DuckDBValidity; @@ -1797,7 +1667,7 @@ export class DuckDBTimeTZVector extends DuckDBVector { } } -export class DuckDBTimestampTZVector extends DuckDBVector { +export class DuckDBTimestampTZVector extends DuckDBVector { private readonly items: BigInt64Array; private readonly validity: DuckDBValidity; constructor(items: BigInt64Array, validity: DuckDBValidity) { @@ -1811,14 +1681,14 @@ export class DuckDBTimestampTZVector extends DuckDBVector { const validity = DuckDBValidity.fromVector(vector, itemCount); return new DuckDBTimestampTZVector(items, validity); } - public override get type(): DuckDBTimestampType { + public override get type(): DuckDBTimestampTZType { return DuckDBTimestampTZType.instance; } public override get itemCount(): number { return this.items.length; } - public override getItem(itemIndex: number): bigint | null { // microseconds - return this.validity.itemValid(itemIndex) ? this.items[itemIndex] : null; + public override getItem(itemIndex: number): DuckDBTimestampTZValue | null { + return this.validity.itemValid(itemIndex) ? new DuckDBTimestampTZValue(this.items[itemIndex]) : null; } public override slice(offset: number, length: number): DuckDBTimestampTZVector { return new DuckDBTimestampTZVector(this.items.slice(offset, offset + length), this.validity.slice(offset)); diff --git a/api/src/index.ts b/api/src/index.ts index f81a4239..b62091ad 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,4 +1,3 @@ -export * from './configurationOptionDescriptions'; export * from './DuckDBAppender'; export * from './DuckDBConnection'; export * from './DuckDBDataChunk'; @@ -10,8 +9,9 @@ export * from './DuckDBPreparedStatement'; export * from './DuckDBResult'; export * from './DuckDBType'; export * from './DuckDBTypeId'; -export * from './DuckDBValue'; export * from './DuckDBVector'; +export * from './configurationOptionDescriptions'; export * from './enums'; export * from './sql'; +export * from './values'; export * from './version'; diff --git a/api/src/values/DuckDBArrayValue.ts b/api/src/values/DuckDBArrayValue.ts new file mode 100644 index 00000000..003cc7d5 --- /dev/null +++ b/api/src/values/DuckDBArrayValue.ts @@ -0,0 +1,13 @@ +import { DuckDBArrayType } from '../DuckDBType'; +import { DuckDBVector } from '../DuckDBVector'; +import { DuckDBValue } from './DuckDBValue'; + +export class DuckDBArrayValue { + public readonly type: DuckDBArrayType; + public readonly vector: DuckDBVector; + + public constructor(type: DuckDBArrayType, vector: DuckDBVector) { + this.type = type; + this.vector = vector; + } +} diff --git a/api/src/values/DuckDBBitValue.ts b/api/src/values/DuckDBBitValue.ts new file mode 100644 index 00000000..02777465 --- /dev/null +++ b/api/src/values/DuckDBBitValue.ts @@ -0,0 +1,80 @@ +import { DuckDBBitType } from '../DuckDBType'; + +export class DuckDBBitValue { + public readonly data: Uint8Array; + + constructor(data: Uint8Array) { + this.data = data; + } + + public get type(): DuckDBBitType { + return DuckDBBitType.instance; + } + + private padding(): number { + return this.data[0]; + } + + public length(): number { + return (this.data.length - 1) * 8 - this.padding(); + } + + public getBool(index: number): boolean { + const dataIndex = Math.floor(index / 8) + 1; + return (this.data[dataIndex] & (1 << (index % 8))) !== 0; + } + + public getBit(index: number): 0 | 1 { + return this.getBool(index) ? 1 : 0; + } + + public toString(): string { + const chars = Array.from({ length: this.length() }); + for (let i = 0; i < this.length(); i++) { + chars[i] = this.getBool(i) ? '1' : '0'; + } + return chars.join(''); + } + + public static fromString(str: string): DuckDBBitValue { + if (!/^[01]*$/.test(str)) { + throw new Error(`input string must only contain '0's and '1's`); + } + + const byteCount = Math.ceil(str.length / 8) + 1; + const paddingBitCount = (8 - (str.length % 8)) % 8; + + const data = new Uint8Array(byteCount); + let byteIndex = 0; + + // first byte contains count of padding bits + data[byteIndex++] = paddingBitCount; + + let byte = 0; + let bitIndex = 0; + + // padding consists of 1s in MSB of second byte + while (bitIndex < paddingBitCount) { + byte <<= 1; + byte |= 1; + bitIndex++; + } + + let charIndex = 0; + + while (byteIndex < byteCount) { + while (bitIndex < 8) { + byte <<= 1; + if (str[charIndex++] === '1') { + byte |= 1; + } + bitIndex++; + } + data[byteIndex++] = byte; + byte = 0; + bitIndex = 0; + } + + return new DuckDBBitValue(data); + } +} diff --git a/api/src/values/DuckDBBlobValue.ts b/api/src/values/DuckDBBlobValue.ts new file mode 100644 index 00000000..9dcbc19e --- /dev/null +++ b/api/src/values/DuckDBBlobValue.ts @@ -0,0 +1,13 @@ +import { DuckDBBlobType } from '../DuckDBType'; + +export class DuckDBBlobValue { + public readonly bytes: Uint8Array; + + public constructor(bytes: Uint8Array) { + this.bytes = bytes; + } + + public get type(): DuckDBBlobType { + return DuckDBBlobType.instance; + } +} diff --git a/api/src/values/DuckDBDateValue.ts b/api/src/values/DuckDBDateValue.ts new file mode 100644 index 00000000..a4daac62 --- /dev/null +++ b/api/src/values/DuckDBDateValue.ts @@ -0,0 +1,14 @@ +import { Date_ } from '@duckdb/node-bindings'; +import { DuckDBDateType } from '../DuckDBType'; + +export class DuckDBDateValue implements Date_ { + public readonly days: number; + + public constructor(days: number) { + this.days = days; + } + + public get type(): DuckDBDateType { + return DuckDBDateType.instance; + } +} diff --git a/api/src/values/DuckDBDecimalValue.ts b/api/src/values/DuckDBDecimalValue.ts new file mode 100644 index 00000000..646b5ead --- /dev/null +++ b/api/src/values/DuckDBDecimalValue.ts @@ -0,0 +1,24 @@ +import { Decimal } from '@duckdb/node-bindings'; +import { DuckDBDecimalType } from '../DuckDBType'; + +export class DuckDBDecimalValue implements Decimal { + public readonly type: DuckDBDecimalType; + public readonly scaledValue: T; + + public constructor(type: DuckDBDecimalType, scaledValue: T) { + this.type = type; + this.scaledValue = scaledValue; + } + + get width(): number { + return this.type.width; + } + + get scale(): number { + return this.type.scale; + } + + get value(): bigint { + return BigInt(this.scaledValue); + } +} diff --git a/api/src/values/DuckDBIntervalValue.ts b/api/src/values/DuckDBIntervalValue.ts new file mode 100644 index 00000000..907c873f --- /dev/null +++ b/api/src/values/DuckDBIntervalValue.ts @@ -0,0 +1,18 @@ +import { Interval } from '@duckdb/node-bindings'; +import { DuckDBIntervalType } from '../DuckDBType'; + +export class DuckDBIntervalValue implements Interval { + public readonly months: number; + public readonly days: number; + public readonly micros: bigint; + + public constructor(months: number, days: number, micros: bigint) { + this.months = months; + this.days = days; + this.micros = micros; + } + + public get type(): DuckDBIntervalType { + return DuckDBIntervalType.instance; + } +} diff --git a/api/src/values/DuckDBListValue.ts b/api/src/values/DuckDBListValue.ts new file mode 100644 index 00000000..a8600d8d --- /dev/null +++ b/api/src/values/DuckDBListValue.ts @@ -0,0 +1,13 @@ +import { DuckDBListType } from '../DuckDBType'; +import { DuckDBVector } from '../DuckDBVector'; +import { DuckDBValue } from './DuckDBValue'; + +export class DuckDBListValue { + public readonly type: DuckDBListType; + public readonly vector: DuckDBVector; + + public constructor(type: DuckDBListType, vector: DuckDBVector) { + this.type = type; + this.vector = vector; + } +} diff --git a/api/src/values/DuckDBMapValue.ts b/api/src/values/DuckDBMapValue.ts new file mode 100644 index 00000000..f16aa22e --- /dev/null +++ b/api/src/values/DuckDBMapValue.ts @@ -0,0 +1,23 @@ +import { DuckDBMapType } from '../DuckDBType'; +import { DuckDBValue } from './DuckDBValue'; + +export interface DuckDBMapEntry< + TKey extends DuckDBValue = DuckDBValue, + TValue extends DuckDBValue = DuckDBValue, +> { + key: TKey; + value: TValue; +} + +export class DuckDBMapValue< + TKey extends DuckDBValue = DuckDBValue, + TValue extends DuckDBValue = DuckDBValue, +> { + public readonly type: DuckDBMapType; + public readonly entries: DuckDBMapEntry[]; + + public constructor(type: DuckDBMapType, entries: DuckDBMapEntry[]) { + this.type = type; + this.entries = entries; + } +} diff --git a/api/src/values/DuckDBStructValue.ts b/api/src/values/DuckDBStructValue.ts new file mode 100644 index 00000000..628fe320 --- /dev/null +++ b/api/src/values/DuckDBStructValue.ts @@ -0,0 +1,12 @@ +import { DuckDBStructType } from '../DuckDBType'; +import { DuckDBValue } from './DuckDBValue'; + +export class DuckDBStructValue { + public readonly type: DuckDBStructType; + public readonly values: readonly DuckDBValue[]; + + public constructor(type: DuckDBStructType, values: readonly DuckDBValue[]) { + this.type = type; + this.values = values; + } +} diff --git a/api/src/values/DuckDBTimeTZValue.ts b/api/src/values/DuckDBTimeTZValue.ts new file mode 100644 index 00000000..f0ba1675 --- /dev/null +++ b/api/src/values/DuckDBTimeTZValue.ts @@ -0,0 +1,38 @@ +import { TimeTZ } from '@duckdb/node-bindings'; +import { DuckDBTimeTZType } from '../DuckDBType'; + +export class DuckDBTimeTZValue implements TimeTZ { + public readonly bits: bigint; + + /** Ranges from 0 to 86400000000 (= 24 * 60 * 60 * 1000 * 1000) */ + public readonly microseconds: number; + + /** In seconds, ranges from -57599 to 57599 (= 16 * 60 * 60 - 1) */ + public readonly offset: number; + + public constructor(bits: bigint, microseconds: number, offset: number) { + this.bits = bits; + this.microseconds = microseconds; + this.offset = offset; + } + + public get type(): DuckDBTimeTZType { + return DuckDBTimeTZType.instance; + } + + public static TIME_BITS = 40; + public static OFFSET_BITS = 24; + public static MAX_OFFSET = 16 * 60 * 60 - 1; // ±15:59:59 = 57599 seconds + + public static fromBits(bits: bigint): DuckDBTimeTZValue { + const microseconds = Number(BigInt.asUintN(DuckDBTimeTZValue.TIME_BITS, bits >> BigInt(DuckDBTimeTZValue.OFFSET_BITS))); + const offset = DuckDBTimeTZValue.MAX_OFFSET - Number(BigInt.asUintN(DuckDBTimeTZValue.OFFSET_BITS, bits)); + return new DuckDBTimeTZValue(bits, microseconds, offset); + } + + public static fromParts(microseconds: number, offset: number): DuckDBTimeTZValue { + const bits = BigInt.asUintN(DuckDBTimeTZValue.TIME_BITS, BigInt(microseconds)) << BigInt(DuckDBTimeTZValue.OFFSET_BITS) + | BigInt.asUintN(DuckDBTimeTZValue.OFFSET_BITS, BigInt(DuckDBTimeTZValue.MAX_OFFSET - offset)); + return new DuckDBTimeTZValue(bits, microseconds, offset); + } +} diff --git a/api/src/values/DuckDBTimeValue.ts b/api/src/values/DuckDBTimeValue.ts new file mode 100644 index 00000000..29b401a6 --- /dev/null +++ b/api/src/values/DuckDBTimeValue.ts @@ -0,0 +1,14 @@ +import { Time } from '@duckdb/node-bindings'; +import { DuckDBTimeType } from '../DuckDBType'; + +export class DuckDBTimeValue implements Time { + public readonly micros: bigint; + + public constructor(micros: bigint) { + this.micros = micros; + } + + public get type(): DuckDBTimeType { + return DuckDBTimeType.instance; + } +} diff --git a/api/src/values/DuckDBTimestampMillisecondsValue.ts b/api/src/values/DuckDBTimestampMillisecondsValue.ts new file mode 100644 index 00000000..4b11d478 --- /dev/null +++ b/api/src/values/DuckDBTimestampMillisecondsValue.ts @@ -0,0 +1,13 @@ +import { DuckDBTimestampMillisecondsType } from '../DuckDBType'; + +export class DuckDBTimestampMillisecondsValue { + public readonly milliseconds: bigint; + + public constructor(milliseconds: bigint) { + this.milliseconds = milliseconds; + } + + public get type(): DuckDBTimestampMillisecondsType { + return DuckDBTimestampMillisecondsType.instance; + } +} diff --git a/api/src/values/DuckDBTimestampNanosecondsValue.ts b/api/src/values/DuckDBTimestampNanosecondsValue.ts new file mode 100644 index 00000000..1455485f --- /dev/null +++ b/api/src/values/DuckDBTimestampNanosecondsValue.ts @@ -0,0 +1,13 @@ +import { DuckDBTimestampNanosecondsType } from '../DuckDBType'; + +export class DuckDBTimestampNanosecondsValue { + public readonly nanoseconds: bigint; + + public constructor(nanoseconds: bigint) { + this.nanoseconds = nanoseconds; + } + + public get type(): DuckDBTimestampNanosecondsType { + return DuckDBTimestampNanosecondsType.instance; + } +} diff --git a/api/src/values/DuckDBTimestampSecondsValue.ts b/api/src/values/DuckDBTimestampSecondsValue.ts new file mode 100644 index 00000000..b43e4766 --- /dev/null +++ b/api/src/values/DuckDBTimestampSecondsValue.ts @@ -0,0 +1,13 @@ +import { DuckDBTimestampSecondsType } from '../DuckDBType'; + +export class DuckDBTimestampSecondsValue { + public readonly seconds: bigint; + + public constructor(seconds: bigint) { + this.seconds = seconds; + } + + public get type(): DuckDBTimestampSecondsType { + return DuckDBTimestampSecondsType.instance; + } +} diff --git a/api/src/values/DuckDBTimestampTZValue.ts b/api/src/values/DuckDBTimestampTZValue.ts new file mode 100644 index 00000000..e54a1dba --- /dev/null +++ b/api/src/values/DuckDBTimestampTZValue.ts @@ -0,0 +1,13 @@ +import { DuckDBTimestampTZType } from '../DuckDBType'; + +export class DuckDBTimestampTZValue { + public readonly micros: bigint; + + public constructor(micros: bigint) { + this.micros = micros; + } + + public get type(): DuckDBTimestampTZType { + return DuckDBTimestampTZType.instance; + } +} diff --git a/api/src/values/DuckDBTimestampValue.ts b/api/src/values/DuckDBTimestampValue.ts new file mode 100644 index 00000000..66d69454 --- /dev/null +++ b/api/src/values/DuckDBTimestampValue.ts @@ -0,0 +1,17 @@ +import { Timestamp } from '@duckdb/node-bindings'; +import { DuckDBTimestampType } from '../DuckDBType'; + +export class DuckDBTimestampValue implements Timestamp { + public readonly micros: bigint; + + public constructor(micros: bigint) { + this.micros = micros; + } + + public get type(): DuckDBTimestampType { + return DuckDBTimestampType.instance; + } +} + +export type DuckDBTimestampMicrosecondsValue = DuckDBTimestampValue; +export const DuckDBTimestampMicrosecondsValue = DuckDBTimestampValue; diff --git a/api/src/values/DuckDBUUIDValue.ts b/api/src/values/DuckDBUUIDValue.ts new file mode 100644 index 00000000..2e91db19 --- /dev/null +++ b/api/src/values/DuckDBUUIDValue.ts @@ -0,0 +1,13 @@ +import { DuckDBUUIDType } from '../DuckDBType'; + +export class DuckDBUUIDValue { + public readonly hugeint: bigint; + + public constructor(hugeint: bigint) { + this.hugeint = hugeint; + } + + public get type(): DuckDBUUIDType { + return DuckDBUUIDType.instance; + } +} diff --git a/api/src/values/DuckDBUnionValue.ts b/api/src/values/DuckDBUnionValue.ts new file mode 100644 index 00000000..b7b9f09a --- /dev/null +++ b/api/src/values/DuckDBUnionValue.ts @@ -0,0 +1,11 @@ +import { DuckDBValue } from './DuckDBValue'; + +export class DuckDBUnionValue { + public readonly tag: string; + public readonly value: TValue; + + public constructor(tag: string, value: TValue) { + this.tag = tag; + this.value = value; + } +} diff --git a/api/src/values/DuckDBValue.ts b/api/src/values/DuckDBValue.ts new file mode 100644 index 00000000..3f9edb4d --- /dev/null +++ b/api/src/values/DuckDBValue.ts @@ -0,0 +1,44 @@ +import { DuckDBArrayValue } from './DuckDBArrayValue'; +import { DuckDBBitValue } from './DuckDBBitValue'; +import { DuckDBBlobValue } from './DuckDBBlobValue'; +import { DuckDBDateValue } from './DuckDBDateValue'; +import { DuckDBDecimalValue } from './DuckDBDecimalValue'; +import { DuckDBIntervalValue } from './DuckDBIntervalValue'; +import { DuckDBListValue } from './DuckDBListValue'; +import { DuckDBMapValue } from './DuckDBMapValue'; +import { DuckDBStructValue } from './DuckDBStructValue'; +import { DuckDBTimestampMillisecondsValue } from './DuckDBTimestampMillisecondsValue'; +import { DuckDBTimestampNanosecondsValue } from './DuckDBTimestampNanosecondsValue'; +import { DuckDBTimestampSecondsValue } from './DuckDBTimestampSecondsValue'; +import { DuckDBTimestampTZValue } from './DuckDBTimestampTZValue'; +import { DuckDBTimestampValue } from './DuckDBTimestampValue'; +import { DuckDBTimeTZValue } from './DuckDBTimeTZValue'; +import { DuckDBTimeValue } from './DuckDBTimeValue'; +import { DuckDBUnionValue } from './DuckDBUnionValue'; +import { DuckDBUUIDValue } from './DuckDBUUIDValue'; + +export type DuckDBValue = + | null + | boolean + | number + | bigint + | string + | DuckDBArrayValue + | DuckDBBitValue + | DuckDBBlobValue + | DuckDBDateValue + | DuckDBDecimalValue + | DuckDBIntervalValue + | DuckDBListValue + | DuckDBMapValue + | DuckDBStructValue + | DuckDBTimestampMillisecondsValue + | DuckDBTimestampNanosecondsValue + | DuckDBTimestampSecondsValue + | DuckDBTimestampTZValue + | DuckDBTimestampValue + | DuckDBTimeTZValue + | DuckDBTimeValue + | DuckDBUnionValue + | DuckDBUUIDValue + ; diff --git a/api/src/values/index.ts b/api/src/values/index.ts new file mode 100644 index 00000000..db04bc3f --- /dev/null +++ b/api/src/values/index.ts @@ -0,0 +1,19 @@ +export * from './DuckDBArrayValue'; +export * from './DuckDBBitValue'; +export * from './DuckDBBlobValue'; +export * from './DuckDBDateValue'; +export * from './DuckDBDecimalValue'; +export * from './DuckDBIntervalValue'; +export * from './DuckDBListValue'; +export * from './DuckDBMapValue'; +export * from './DuckDBStructValue'; +export * from './DuckDBTimestampMillisecondsValue'; +export * from './DuckDBTimestampNanosecondsValue'; +export * from './DuckDBTimestampSecondsValue'; +export * from './DuckDBTimestampTZValue'; +export * from './DuckDBTimestampValue'; +export * from './DuckDBTimeTZValue'; +export * from './DuckDBTimeValue'; +export * from './DuckDBUnionValue'; +export * from './DuckDBUUIDValue'; +export * from './DuckDBValue'; diff --git a/api/test/api.test.ts b/api/test/api.test.ts index 0fad963c..0741bacd 100644 --- a/api/test/api.test.ts +++ b/api/test/api.test.ts @@ -2,6 +2,7 @@ import { assert, describe, test } from 'vitest'; import { DuckDBAnyType, DuckDBArrayType, + DuckDBArrayValue, DuckDBArrayVector, DuckDBBigIntType, DuckDBBigIntVector, @@ -9,18 +10,21 @@ import { DuckDBBitValue, DuckDBBitVector, DuckDBBlobType, + DuckDBBlobValue, DuckDBBlobVector, DuckDBBooleanType, DuckDBBooleanVector, DuckDBConnection, DuckDBDataChunk, DuckDBDateType, + DuckDBDateValue, DuckDBDateVector, DuckDBDecimal16Vector, DuckDBDecimal2Vector, DuckDBDecimal4Vector, DuckDBDecimal8Vector, DuckDBDecimalType, + DuckDBDecimalValue, DuckDBDoubleType, DuckDBDoubleVector, DuckDBEnum1Vector, @@ -34,38 +38,43 @@ import { DuckDBInstance, DuckDBIntegerType, DuckDBIntegerVector, - DuckDBInterval, DuckDBIntervalType, + DuckDBIntervalValue, DuckDBIntervalVector, - DuckDBLargeDecimal, DuckDBListType, + DuckDBListValue, DuckDBListVector, - DuckDBMapEntry, DuckDBMapType, + DuckDBMapValue, DuckDBMapVector, DuckDBPendingResultState, DuckDBResult, DuckDBSQLNullType, - DuckDBSmallDecimal, DuckDBSmallIntType, DuckDBSmallIntVector, - DuckDBStructEntry, DuckDBStructType, + DuckDBStructValue, DuckDBStructVector, DuckDBTimeTZType, DuckDBTimeTZValue, DuckDBTimeTZVector, DuckDBTimeType, + DuckDBTimeValue, DuckDBTimeVector, DuckDBTimestampMillisecondsType, + DuckDBTimestampMillisecondsValue, DuckDBTimestampMillisecondsVector, DuckDBTimestampNanosecondsType, + DuckDBTimestampNanosecondsValue, DuckDBTimestampNanosecondsVector, DuckDBTimestampSecondsType, + DuckDBTimestampSecondsValue, DuckDBTimestampSecondsVector, DuckDBTimestampTZType, + DuckDBTimestampTZValue, DuckDBTimestampTZVector, DuckDBTimestampType, + DuckDBTimestampValue, DuckDBTimestampVector, DuckDBTinyIntType, DuckDBTinyIntVector, @@ -82,9 +91,12 @@ import { DuckDBUTinyIntType, DuckDBUTinyIntVector, DuckDBUUIDType, + DuckDBUUIDValue, DuckDBUUIDVector, DuckDBUnionType, + DuckDBUnionValue, DuckDBUnionVector, + DuckDBValue, DuckDBVarCharType, DuckDBVarCharVector, DuckDBVarIntType, @@ -139,17 +151,18 @@ const MaxInt128 = BI_2_127 - BI_1; const MaxUInt128 = BI_2_128 - BI_1; const MinHugeInt = MinInt128; const MinUHugeInt = BI_0; -const MinDate = MinInt32 + 2; -const MaxDate = MaxInt32 - 1; -const DateInf = MaxInt32; -const MinTime = BI_0; -const MaxTime = BI_24 * BI_60 * BI_60 * BI_1000 * BI_1000; // 86400000000 +const MinDate = new DuckDBDateValue(MinInt32 + 2); +const MaxDate = new DuckDBDateValue(MaxInt32 - 1); +const DatePosInf = new DuckDBDateValue(MaxInt32); +const DateNegInf = new DuckDBDateValue(-MaxInt32); +const MinTime = new DuckDBTimeValue(BI_0); +const MaxTime = new DuckDBTimeValue(BI_24 * BI_60 * BI_60 * BI_1000 * BI_1000); // 86400000000 const MinTimeTZMicroseconds = 0; const MaxTimeTZMicroseconds = 24 * 60 * 60 * 1000 * 1000; // 86400000000 const MaxTimeTZOffset = 16 * 60 * 60 - 1; // from dtime_tz_t (MAX_OFFSET) const MinTimeTZOffset = -MaxTimeTZOffset; -const MinTimeTZ = new DuckDBTimeTZValue(MinTimeTZMicroseconds, MaxTimeTZOffset); -const MaxTimeTZ = new DuckDBTimeTZValue(MaxTimeTZMicroseconds, MinTimeTZOffset); +const MinTimeTZ = DuckDBTimeTZValue.fromParts(MinTimeTZMicroseconds, MaxTimeTZOffset); +const MaxTimeTZ = DuckDBTimeTZValue.fromParts(MaxTimeTZMicroseconds, MinTimeTZOffset); const MinTS_S = BigInt(-9223372022400); // from test_all_types() select epoch(timestamp_s)::bigint; const MaxTS_S = BigInt( 9223372036854); const MinTS_MS = MinTS_S * BI_1000; @@ -163,10 +176,10 @@ const MinFloat32 = Math.fround(-3.4028235e+38); const MaxFloat32 = Math.fround( 3.4028235e+38); const MinFloat64 = -Number.MAX_VALUE; const MaxFloat64 = Number.MAX_VALUE; -const MinUUID = MinInt128; -const MaxUUID = MaxInt128; -const MinVarInt = -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368n -const MaxVarInt = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368n; +const MinUUID = new DuckDBUUIDValue(MinInt128); +const MaxUUID = new DuckDBUUIDValue(MaxInt128); +const MinVarInt: bigint = -179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368n +const MaxVarInt: bigint = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368n; async function sleep(ms: number): Promise { return new Promise((resolve) => { @@ -203,14 +216,14 @@ function assertColumns(result: DuckDBResult, expectedColumns: readonly ExpectedC } } -function isVectorType>( +function isVectorType>( vector: DuckDBVector | null, vectorType: new (...args: any[]) => TVector, ): vector is TVector { return vector instanceof vectorType; } -function getColumnVector>( +function getColumnVector>( chunk: DuckDBDataChunk, columnIndex: number, vectorType: new (...args: any[]) => TVector @@ -222,27 +235,31 @@ function getColumnVector>( return column; } -function assertVectorValues(vector: DuckDBVector | null, values: readonly T[], vectorName: string) { +function assertVectorValues( + vector: DuckDBVector | null | undefined, + values: readonly TValue[], + vectorName: string, +) { if (!vector) { - assert.fail(`${vectorName} unexpectedly null`); + assert.fail(`${vectorName} unexpectedly null or undefined`); } assert.strictEqual(vector.itemCount, values.length, `expected vector ${vectorName} item count to be ${values.length} but found ${vector.itemCount}`); for (let i = 0; i < values.length; i++) { - const actual: T | null = vector.getItem(i); + const actual: TValue | null = vector.getItem(i); const expected = values[i]; assert.deepStrictEqual(actual, expected, `expected vector ${vectorName}[${i}] to be ${expected} but found ${actual}`); } } -function assertNestedVectorValues( - vector: DuckDBVector | null, - checkVectorValueFns: ((value: T | null, valueName: string) => void)[], +function assertNestedVectorValues( + vector: DuckDBVector | null | undefined, + checkVectorValueFns: ((value: TValue | null, valueName: string) => void)[], vectorName: string, ) { if (!vector) { - assert.fail(`${vectorName} unexpectedly null`); + assert.fail(`${vectorName} unexpectedly null or undefined`); } assert.strictEqual(vector.itemCount, checkVectorValueFns.length, `expected vector ${vectorName} item count to be ${checkVectorValueFns.length} but found ${vector.itemCount}`); @@ -251,7 +268,7 @@ function assertNestedVectorValues( } } -function assertValues>( +function assertValues>( chunk: DuckDBDataChunk, columnIndex: number, vectorType: new (...args: any[]) => TVector, @@ -261,7 +278,7 @@ function assertValues>( assertVectorValues(vector, values, `${columnIndex}`); } -function assertNestedValues>( +function assertNestedValues>( chunk: DuckDBDataChunk, columnIndex: number, vectorType: new (...args: any[]) => TVector, @@ -272,8 +289,8 @@ function assertNestedValues>( } const textEncoder = new TextEncoder(); -function blobFromString(str: string): Uint8Array { - return Buffer.from(textEncoder.encode(str)); +function blobFromString(str: string): DuckDBBlobValue { + return new DuckDBBlobValue(Buffer.from(textEncoder.encode(str))); } function bigints(start: bigint, end: bigint) { @@ -419,7 +436,7 @@ describe('api', () => { try { assert.strictEqual(chunk.columnCount, 1); assert.strictEqual(chunk.rowCount, 1); - assertValues(chunk, 0, DuckDBIntegerVector, [42]); + assertValues(chunk, 0, DuckDBIntegerVector, [42]); } finally { chunk.dispose(); } @@ -454,9 +471,9 @@ describe('api', () => { try { assert.strictEqual(chunk.columnCount, 4); assert.strictEqual(chunk.rowCount, 1); - assertValues(chunk, 0, DuckDBIntegerVector, [10]); - assertValues(chunk, 1, DuckDBVarCharVector, ['abc']); - assertValues(chunk, 2, DuckDBBooleanVector, [true]); + assertValues(chunk, 0, DuckDBIntegerVector, [10]); + assertValues(chunk, 1, DuckDBVarCharVector, ['abc']); + assertValues(chunk, 2, DuckDBBooleanVector, [true]); assertValues(chunk, 3, DuckDBIntegerVector, [null]); } finally { chunk.dispose(); @@ -561,6 +578,19 @@ describe('api', () => { const smallEnumValues = ['DUCK_DUCK_ENUM', 'GOOSE']; const mediumEnumValues = Array.from({ length: 300 }).map((_, i) => `enum_${i}`); const largeEnumValues = Array.from({ length: 70000 }).map((_, i) => `enum_${i}`); + const structType = new DuckDBStructType([ + { name: 'a', valueType: DuckDBIntegerType.instance }, + { name: 'b', valueType: DuckDBVarCharType.instance }, + ]); + const structOfArraysType = new DuckDBStructType([ + { name: 'a', valueType: new DuckDBListType(DuckDBIntegerType.instance) }, + { name: 'b', valueType: new DuckDBListType(DuckDBVarCharType.instance) }, + ]); + const structOfFixedArrayType = new DuckDBStructType([ + { name: 'a', valueType: new DuckDBArrayType(DuckDBIntegerType.instance, 3) }, + { name: 'b', valueType: new DuckDBArrayType(DuckDBVarCharType.instance, 3) }, + ]); + const mapType = new DuckDBMapType(DuckDBVarCharType.instance, DuckDBVarCharType.instance); assertColumns(result, [ { name: 'bool', type: DuckDBBooleanType.instance }, { name: 'tinyint', type: DuckDBTinyIntType.instance }, @@ -603,19 +633,13 @@ describe('api', () => { { name: 'timestamptz_array', type: new DuckDBListType(DuckDBTimestampTZType.instance) }, { name: 'varchar_array', type: new DuckDBListType(DuckDBVarCharType.instance) }, { name: 'nested_int_array', type: new DuckDBListType(new DuckDBListType(DuckDBIntegerType.instance)) }, - { name: 'struct', type: new DuckDBStructType([ - { name: 'a', valueType: DuckDBIntegerType.instance }, - { name: 'b', valueType: DuckDBVarCharType.instance }, - ])}, - { name: 'struct_of_arrays', type: new DuckDBStructType([ - { name: 'a', valueType: new DuckDBListType(DuckDBIntegerType.instance) }, - { name: 'b', valueType: new DuckDBListType(DuckDBVarCharType.instance) }, - ])}, + { name: 'struct', type: structType }, + { name: 'struct_of_arrays', type: structOfArraysType}, { name: 'array_of_structs', type: new DuckDBListType(new DuckDBStructType([ { name: 'a', valueType: DuckDBIntegerType.instance }, { name: 'b', valueType: DuckDBVarCharType.instance }, ]))}, - { name: 'map', type: new DuckDBMapType(DuckDBVarCharType.instance, DuckDBVarCharType.instance) }, + { name: 'map', type: mapType }, { name: 'union', type: new DuckDBUnionType([ { tag: 'name', valueType: DuckDBVarCharType.instance }, { tag: 'age', valueType: DuckDBSmallIntType.instance }, @@ -628,10 +652,7 @@ describe('api', () => { { name: 'a', valueType: DuckDBIntegerType.instance }, { name: 'b', valueType: DuckDBVarCharType.instance }, ]), 3) }, - { name: 'struct_of_fixed_array', type: new DuckDBStructType([ - { name: 'a', valueType: new DuckDBArrayType(DuckDBIntegerType.instance, 3) }, - { name: 'b', valueType: new DuckDBArrayType(DuckDBVarCharType.instance, 3) }, - ]) }, + { name: 'struct_of_fixed_array', type: structOfFixedArrayType }, { name: 'fixed_array_of_int_list', type: new DuckDBArrayType(new DuckDBListType(DuckDBIntegerType.instance), 3) }, { name: 'list_of_fixed_int_array', type: new DuckDBListType(new DuckDBArrayType(DuckDBIntegerType.instance, 3)) }, ]); @@ -655,41 +676,46 @@ describe('api', () => { assertValues(chunk, 11, DuckDBVarIntVector, [MinVarInt, MaxVarInt, null]); assertValues(chunk, 12, DuckDBDateVector, [MinDate, MaxDate, null]); assertValues(chunk, 13, DuckDBTimeVector, [MinTime, MaxTime, null]); - assertValues(chunk, 14, DuckDBTimestampVector, [MinTS_US, MaxTS_US, null]); - assertValues(chunk, 15, DuckDBTimestampSecondsVector, [MinTS_S, MaxTS_S, null]); - assertValues(chunk, 16, DuckDBTimestampMillisecondsVector, [MinTS_MS, MaxTS_MS, null]); - assertValues(chunk, 17, DuckDBTimestampNanosecondsVector, [MinTS_NS, MaxTS_NS, null]); + assertValues(chunk, 14, DuckDBTimestampVector, + [new DuckDBTimestampValue(MinTS_US), new DuckDBTimestampValue(MaxTS_US), null]); + assertValues(chunk, 15, DuckDBTimestampSecondsVector, + [new DuckDBTimestampSecondsValue(MinTS_S), new DuckDBTimestampSecondsValue(MaxTS_S), null]); + assertValues(chunk, 16, DuckDBTimestampMillisecondsVector, + [new DuckDBTimestampMillisecondsValue(MinTS_MS), new DuckDBTimestampMillisecondsValue(MaxTS_MS), null]); + assertValues(chunk, 17, DuckDBTimestampNanosecondsVector, + [new DuckDBTimestampNanosecondsValue(MinTS_NS), new DuckDBTimestampNanosecondsValue(MaxTS_NS), null]); assertValues(chunk, 18, DuckDBTimeTZVector, [MinTimeTZ, MaxTimeTZ, null]); - assertValues(chunk, 19, DuckDBTimestampTZVector, [MinTS_US, MaxTS_US, null]); + assertValues(chunk, 19, DuckDBTimestampTZVector, + [new DuckDBTimestampTZValue(MinTS_US), new DuckDBTimestampTZValue(MaxTS_US), null]); assertValues(chunk, 20, DuckDBFloatVector, [MinFloat32, MaxFloat32, null]); assertValues(chunk, 21, DuckDBDoubleVector, [MinFloat64, MaxFloat64, null]); assertValues(chunk, 22, DuckDBDecimal2Vector, [ - new DuckDBSmallDecimal(-9999, new DuckDBDecimalType(4, 1)), - new DuckDBSmallDecimal(9999, new DuckDBDecimalType(4, 1)), + new DuckDBDecimalValue(new DuckDBDecimalType(4, 1), -9999), + new DuckDBDecimalValue(new DuckDBDecimalType(4, 1), 9999), null, ]); assertValues(chunk, 23, DuckDBDecimal4Vector, [ - new DuckDBSmallDecimal(-999999999, new DuckDBDecimalType(9, 4)), - new DuckDBSmallDecimal(999999999, new DuckDBDecimalType(9, 4)), + new DuckDBDecimalValue(new DuckDBDecimalType(9, 4), -999999999), + new DuckDBDecimalValue(new DuckDBDecimalType(9, 4), 999999999), null, ]); assertValues(chunk, 24, DuckDBDecimal8Vector, [ - new DuckDBLargeDecimal(-BI_18_9s, new DuckDBDecimalType(18, 6)), - new DuckDBLargeDecimal(BI_18_9s, new DuckDBDecimalType(18, 6)), + new DuckDBDecimalValue(new DuckDBDecimalType(18, 6), -BI_18_9s), + new DuckDBDecimalValue(new DuckDBDecimalType(18, 6), BI_18_9s), null, ]); assertValues(chunk, 25, DuckDBDecimal16Vector, [ - new DuckDBLargeDecimal(-BI_38_9s, new DuckDBDecimalType(38, 10)), - new DuckDBLargeDecimal(BI_38_9s, new DuckDBDecimalType(38, 10)), + new DuckDBDecimalValue(new DuckDBDecimalType(38, 10), -BI_38_9s), + new DuckDBDecimalValue(new DuckDBDecimalType(38, 10), BI_38_9s), null, ]); - assertValues(chunk, 26, DuckDBUUIDVector, [MinUUID, MaxUUID, null]); + assertValues(chunk, 26, DuckDBUUIDVector, [MinUUID, MaxUUID, null]); assertValues(chunk, 27, DuckDBIntervalVector, [ - new DuckDBInterval(0, 0, BigInt(0)), - new DuckDBInterval(999, 999, BigInt(999999999)), + new DuckDBIntervalValue(0, 0, BigInt(0)), + new DuckDBIntervalValue(999, 999, BigInt(999999999)), null, ]); - assertValues(chunk, 28, DuckDBVarCharVector, ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null]); + assertValues(chunk, 28, DuckDBVarCharVector, ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null]); assertValues(chunk, 29, DuckDBBlobVector, [ blobFromString('thisisalongblob\x00withnullbytes'), blobFromString('\x00\x00\x00a'), @@ -716,207 +742,216 @@ describe('api', () => { null, ]); // int_array - assertNestedValues, DuckDBListVector>(chunk, 34, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), - (v, n) => assertVectorValues(v, [42, 999, null, null, -42], n), + assertNestedValues, DuckDBListVector>(chunk, 34, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), + (v, n) => assertVectorValues(v?.vector, [42, 999, null, null, -42], n), (v, n) => assert.strictEqual(v, null, n), ]); // double_array - assertNestedValues, DuckDBListVector>(chunk, 35, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), - (v, n) => assertVectorValues(v, [42.0, NaN, Infinity, -Infinity, null, -42.0], n), + assertNestedValues, DuckDBListVector>(chunk, 35, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), + (v, n) => assertVectorValues(v?.vector, [42.0, NaN, Infinity, -Infinity, null, -42.0], n), (v, n) => assert.strictEqual(v, null, n), ]); // date_array - assertNestedValues, DuckDBListVector>(chunk, 36, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), - (v, n) => assertVectorValues(v, [0, DateInf, -DateInf, null, 19124], n), + assertNestedValues, DuckDBListVector>(chunk, 36, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), + (v, n) => assertVectorValues(v?.vector, [new DuckDBDateValue(0), DatePosInf, DateNegInf, null, new DuckDBDateValue(19124)], n), (v, n) => assert.strictEqual(v, null, n), ]); // timestamp_array - assertNestedValues, DuckDBListVector>(chunk, 37, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), + assertNestedValues, DuckDBListVector>(chunk, 37, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), // 1652372625 is 2022-05-12 16:23:45 - (v, n) => assertVectorValues(v, [BI_0, TS_US_Inf, -TS_US_Inf, null, BigInt(1652372625)*BI_1000*BI_1000], n), + (v, n) => assertVectorValues(v?.vector, [ + new DuckDBTimestampValue(BI_0), + new DuckDBTimestampValue(TS_US_Inf), + new DuckDBTimestampValue(-TS_US_Inf), + null, + new DuckDBTimestampValue(BigInt(1652372625)*BI_1000*BI_1000), + ], n), (v, n) => assert.strictEqual(v, null, n), ]); // timestamptz_array - assertNestedValues, DuckDBListVector>(chunk, 38, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), + assertNestedValues, DuckDBListVector>(chunk, 38, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), // 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) - (v, n) => assertVectorValues(v, [BI_0, TS_US_Inf, -TS_US_Inf, null, BigInt(1652397825)*BI_1000*BI_1000], n), + (v, n) => assertVectorValues(v?.vector, [ + new DuckDBTimestampTZValue(BI_0), + new DuckDBTimestampTZValue(TS_US_Inf), + new DuckDBTimestampTZValue(-TS_US_Inf), + null, + new DuckDBTimestampTZValue(BigInt(1652397825)*BI_1000*BI_1000), + ], n), (v, n) => assert.strictEqual(v, null, n), ]); // varchar_array - assertNestedValues, DuckDBListVector>(chunk, 39, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), + assertNestedValues, DuckDBListVector>(chunk, 39, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), // Note that the string 'goose' in varchar_array does NOT have an embedded null character. - (v, n) => assertVectorValues(v, ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], n), + (v, n) => assertVectorValues(v?.vector, ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], n), (v, n) => assert.strictEqual(v, null, n), ]); // nested_int_array - assertNestedValues>, DuckDBListVector>>(chunk, 40, DuckDBListVector, [ + assertNestedValues>, DuckDBListVector>>(chunk, 40, DuckDBListVector, [ (v, n) => { assert.ok(v, `${n} unexpectedly null`); if (!v) return; - assert.strictEqual(v.itemCount, 0, `${n} not empty`); + assert.strictEqual(v.vector.itemCount, 0, `${n} not empty`); }, - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [], nn), - (vv, nn) => assertVectorValues(vv, [42, 999, null, null, -42], nn), + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [], nn), + (vv, nn) => assertVectorValues(vv?.vector, [42, 999, null, null, -42], nn), (vv, nn) => assert.strictEqual(vv, null, nn), - (vv, nn) => assertVectorValues(vv, [], nn), - (vv, nn) => assertVectorValues(vv, [42, 999, null, null, -42], nn), + (vv, nn) => assertVectorValues(vv?.vector, [], nn), + (vv, nn) => assertVectorValues(vv?.vector, [42, 999, null, null, -42], nn), ], n), (v, n) => assert.strictEqual(v, null, n), ]); assertValues(chunk, 41, DuckDBStructVector, [ - [{ name: 'a', value: null }, { name: 'b', value: null }], - [{ name: 'a', value: 42 }, { name: 'b', value: '🦆🦆🦆🦆🦆🦆' }], + new DuckDBStructValue(structType, [null, null]), + new DuckDBStructValue(structType, [42, '🦆🦆🦆🦆🦆🦆']), null, - ] as (readonly DuckDBStructEntry[])[]); + ]); // struct_of_arrays - assertNestedValues(chunk, 42, DuckDBStructVector, [ - (entries, n) => assert.deepStrictEqual(entries, [{ name: 'a', value: null }, { name: 'b', value: null }], n), - (entries, n) => { - assert.ok(entries, `${n} unexpectedly null`); - if (!entries) return; - assert.strictEqual(entries.length, 2, n); - assert.strictEqual(entries[0].name, 'a', n); - assert.ok(isVectorType(entries[0].value, DuckDBIntegerVector)); - assertVectorValues(entries[0].value, [42, 999, null, null, -42], n); - assert.strictEqual(entries[1].name, 'b', n); - assert.ok(isVectorType(entries[1].value, DuckDBVarCharVector)); - assertVectorValues(entries[1].value, ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], n); + assertNestedValues(chunk, 42, DuckDBStructVector, [ + (v, n) => assert.deepStrictEqual(v?.values, [null, null], n), + (v, n) => { + assert.ok(v, `${n} unexpectedly null`); + if (!v) return; + assert.deepStrictEqual(v.type, structOfArraysType); + assert.strictEqual(v.values.length, 2, n); + assert.ok(v.values[0] instanceof DuckDBListValue); + assertVectorValues((v.values[0] as DuckDBListValue).vector, [42, 999, null, null, -42], n); + assert.ok(v.values[1] instanceof DuckDBListValue); + assertVectorValues((v.values[1] as DuckDBListValue).vector, ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''], n); }, - (entries, n) => assert.strictEqual(entries, null, n), + (v, n) => assert.strictEqual(v, null, n), ]); // array_of_structs - assertNestedValues, DuckDBListVector>(chunk, 43, DuckDBListVector, [ - (v, n) => assertVectorValues(v, [], n), - (v, n) => assertVectorValues(v, [ - [{ name: 'a', value: null }, { name: 'b', value: null }], - [{ name: 'a', value: 42 }, { name: 'b', value: '🦆🦆🦆🦆🦆🦆' }], + assertNestedValues, DuckDBListVector>(chunk, 43, DuckDBListVector, [ + (v, n) => assertVectorValues(v?.vector, [], n), + (v, n) => assertVectorValues(v?.vector, [ + new DuckDBStructValue(structType, [null, null]), + new DuckDBStructValue(structType, [42, '🦆🦆🦆🦆🦆🦆']), null, ], n), (v, n) => assert.strictEqual(v, null, n), ]); assertValues(chunk, 44, DuckDBMapVector, [ - [], - [{ key: 'key1', value: '🦆🦆🦆🦆🦆🦆' }, { key: 'key2', value: 'goose' }], + new DuckDBMapValue(mapType, []), + new DuckDBMapValue(mapType, [{ key: 'key1', value: '🦆🦆🦆🦆🦆🦆' }, { key: 'key2', value: 'goose' }]), null, - ] as (readonly DuckDBMapEntry[])[]); - assertValues(chunk, 45, DuckDBUnionVector, [ - { tag: 'name', value: 'Frank' }, - { tag: 'age', value: 5 }, + ]); + assertValues(chunk, 45, DuckDBUnionVector, [ + new DuckDBUnionValue('name', 'Frank'), + new DuckDBUnionValue('age', 5), null, ]); // fixed_int_array - assertNestedValues, DuckDBArrayVector>(chunk, 46, DuckDBArrayVector, [ - (v, n) => assertVectorValues(v, [null, 2, 3], n), - (v, n) => assertVectorValues(v, [4, 5, 6], n), + assertNestedValues, DuckDBArrayVector>(chunk, 46, DuckDBArrayVector, [ + (v, n) => assertVectorValues(v?.vector, [null, 2, 3], n), + (v, n) => assertVectorValues(v?.vector, [4, 5, 6], n), (v, n) => assert.strictEqual(v, null, n), ]); // fixed_varchar_array - assertNestedValues, DuckDBArrayVector>(chunk, 47, DuckDBArrayVector, [ - (v, n) => assertVectorValues(v, ['a', null, 'c'], n), - (v, n) => assertVectorValues(v, ['d', 'e', 'f'], n), + assertNestedValues, DuckDBArrayVector>(chunk, 47, DuckDBArrayVector, [ + (v, n) => assertVectorValues(v?.vector, ['a', null, 'c'], n), + (v, n) => assertVectorValues(v?.vector, ['d', 'e', 'f'], n), (v, n) => assert.strictEqual(v, null, n), ]); // fixed_nested_int_array - assertNestedValues>, DuckDBArrayVector>(chunk, 48, DuckDBArrayVector, [ - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [null, 2, 3], nn), + assertNestedValues>, DuckDBArrayVector>>(chunk, 48, DuckDBArrayVector, [ + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [null, 2, 3], nn), (vv, nn) => assert.strictEqual(vv, null, nn), - (vv, nn) => assertVectorValues(vv, [null, 2, 3], nn), + (vv, nn) => assertVectorValues(vv?.vector, [null, 2, 3], nn), ], n), - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [4, 5, 6], nn), - (vv, nn) => assertVectorValues(vv, [null, 2, 3], nn), - (vv, nn) => assertVectorValues(vv, [4, 5, 6], nn), + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [4, 5, 6], nn), + (vv, nn) => assertVectorValues(vv?.vector, [null, 2, 3], nn), + (vv, nn) => assertVectorValues(vv?.vector, [4, 5, 6], nn), ], n), (v, n) => assert.strictEqual(v, null, n), ]); // fixed_nested_varchar_array - assertNestedValues>, DuckDBArrayVector>(chunk, 49, DuckDBArrayVector, [ - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, ['a', null, 'c'], nn), + assertNestedValues>, DuckDBArrayVector>>(chunk, 49, DuckDBArrayVector, [ + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, ['a', null, 'c'], nn), (vv, nn) => assert.strictEqual(vv, null, nn), - (vv, nn) => assertVectorValues(vv, ['a', null, 'c'], nn), + (vv, nn) => assertVectorValues(vv?.vector, ['a', null, 'c'], nn), ], n), - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, ['d', 'e', 'f'], nn), - (vv, nn) => assertVectorValues(vv, ['a', null, 'c'], nn), - (vv, nn) => assertVectorValues(vv, ['d', 'e', 'f'], nn), + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, ['d', 'e', 'f'], nn), + (vv, nn) => assertVectorValues(vv?.vector, ['a', null, 'c'], nn), + (vv, nn) => assertVectorValues(vv?.vector, ['d', 'e', 'f'], nn), ], n), (v, n) => assert.strictEqual(v, null, n), ]); // fixed_struct_array - assertNestedValues, DuckDBArrayVector>(chunk, 50, DuckDBArrayVector, [ - (v, n) => assertVectorValues(v, [ - [{ name: 'a', value: null }, { name: 'b', value: null }], - [{ name: 'a', value: 42 }, { name: 'b', value: '🦆🦆🦆🦆🦆🦆' }], - [{ name: 'a', value: null }, { name: 'b', value: null }], + assertNestedValues, DuckDBArrayVector>(chunk, 50, DuckDBArrayVector, [ + (v, n) => assertVectorValues(v?.vector, [ + new DuckDBStructValue(structType, [null, null]), + new DuckDBStructValue(structType, [42, '🦆🦆🦆🦆🦆🦆']), + new DuckDBStructValue(structType, [null, null]), ], n), - (v, n) => assertVectorValues(v, [ - [{ name: 'a', value: 42 }, { name: 'b', value: '🦆🦆🦆🦆🦆🦆' }], - [{ name: 'a', value: null }, { name: 'b', value: null }], - [{ name: 'a', value: 42 }, { name: 'b', value: '🦆🦆🦆🦆🦆🦆' }], + (v, n) => assertVectorValues(v?.vector, [ + new DuckDBStructValue(structType, [42, '🦆🦆🦆🦆🦆🦆']), + new DuckDBStructValue(structType, [null, null]), + new DuckDBStructValue(structType, [42, '🦆🦆🦆🦆🦆🦆']), ], n), (v, n) => assert.strictEqual(v, null, n), ]); // struct_of_fixed_array - assertNestedValues(chunk, 51, DuckDBStructVector, [ - (entries, n) => { - assert.ok(entries, `${n} unexpectedly null`); - if (!entries) return; - assert.strictEqual(entries.length, 2, n); - assert.strictEqual(entries[0].name, 'a', n); - assert.ok(isVectorType(entries[0].value, DuckDBIntegerVector)); - assertVectorValues(entries[0].value, [null, 2, 3], n); - assert.strictEqual(entries[1].name, 'b', n); - assert.ok(isVectorType(entries[1].value, DuckDBVarCharVector)); - assertVectorValues(entries[1].value, ['a', null, 'c'], n); + assertNestedValues(chunk, 51, DuckDBStructVector, [ + (v, n) => { + assert.ok(v, `${n} unexpectedly null`); + if (!v) return; + assert.deepStrictEqual(v.type, structOfFixedArrayType); + assert.strictEqual(v.values.length, 2, n); + assert.ok(v?.values[0] instanceof DuckDBArrayValue); + assertVectorValues((v.values[0] as DuckDBArrayValue).vector, [null, 2, 3], n); + assert.ok(v?.values[1] instanceof DuckDBArrayValue); + assertVectorValues((v.values[1] as DuckDBArrayValue).vector, ['a', null, 'c'], n); }, - (entries, n) => { - assert.ok(entries, `${n} unexpectedly null`); - if (!entries) return; - assert.strictEqual(entries.length, 2, n); - assert.strictEqual(entries[0].name, 'a', n); - assert.ok(isVectorType(entries[0].value, DuckDBIntegerVector)); - assertVectorValues(entries[0].value, [4, 5, 6], n); - assert.strictEqual(entries[1].name, 'b', n); - assert.ok(isVectorType(entries[1].value, DuckDBVarCharVector)); - assertVectorValues(entries[1].value, ['d', 'e', 'f'], n); + (v, n) => { + assert.ok(v, `${n} unexpectedly null`); + if (!v) return; + assert.deepStrictEqual(v.type, structOfFixedArrayType); + assert.strictEqual(v.values.length, 2, n); + assert.ok(v?.values[0] instanceof DuckDBArrayValue); + assertVectorValues((v.values[0] as DuckDBArrayValue).vector, [4, 5, 6], n); + assert.ok(v?.values[1] instanceof DuckDBArrayValue); + assertVectorValues((v.values[1] as DuckDBArrayValue).vector, ['d', 'e', 'f'], n); }, - (entries, n) => assert.strictEqual(entries, null, n), + (v, n) => assert.strictEqual(v, null, n), ]); // fixed_array_of_int_list - assertNestedValues>, DuckDBArrayVector>(chunk, 52, DuckDBArrayVector, [ - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [], nn), - (vv, nn) => assertVectorValues(vv, [42, 999, null, null, -42], nn), - (vv, nn) => assertVectorValues(vv, [], nn), + assertNestedValues>, DuckDBArrayVector>>(chunk, 52, DuckDBArrayVector, [ + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [], nn), + (vv, nn) => assertVectorValues(vv?.vector, [42, 999, null, null, -42], nn), + (vv, nn) => assertVectorValues(vv?.vector, [], nn), ], n), - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [42, 999, null, null, -42], nn), - (vv, nn) => assertVectorValues(vv, [], nn), - (vv, nn) => assertVectorValues(vv, [42, 999, null, null, -42], nn), + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [42, 999, null, null, -42], nn), + (vv, nn) => assertVectorValues(vv?.vector, [], nn), + (vv, nn) => assertVectorValues(vv?.vector, [42, 999, null, null, -42], nn), ], n), (v, n) => assert.strictEqual(v, null, n), ]); // list_of_fixed_int_array - assertNestedValues>, DuckDBListVector>(chunk, 53, DuckDBListVector, [ - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [null, 2, 3], nn), - (vv, nn) => assertVectorValues(vv, [4, 5, 6], nn), - (vv, nn) => assertVectorValues(vv, [null, 2, 3], nn), + assertNestedValues>, DuckDBListVector>>(chunk, 53, DuckDBListVector, [ + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [null, 2, 3], nn), + (vv, nn) => assertVectorValues(vv?.vector, [4, 5, 6], nn), + (vv, nn) => assertVectorValues(vv?.vector, [null, 2, 3], nn), ], n), - (v, n) => assertNestedVectorValues(v, [ - (vv, nn) => assertVectorValues(vv, [4, 5, 6], nn), - (vv, nn) => assertVectorValues(vv, [null, 2, 3], nn), - (vv, nn) => assertVectorValues(vv, [4, 5, 6], nn), + (v, n) => assertNestedVectorValues(v?.vector, [ + (vv, nn) => assertVectorValues(vv?.vector, [4, 5, 6], nn), + (vv, nn) => assertVectorValues(vv?.vector, [null, 2, 3], nn), + (vv, nn) => assertVectorValues(vv?.vector, [4, 5, 6], nn), ], n), (v, n) => assert.strictEqual(v, null, n), ]); diff --git a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts index b9bea920..396823f9 100644 --- a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts +++ b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts @@ -120,7 +120,7 @@ export interface QueryProgress { export interface Time { /** Microseconds since 00:00:00 */ - micros: number; + micros: bigint; } export interface TimeParts { hour: number; diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index f831802a..9551ba62 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -38,12 +38,16 @@ duckdb_date_struct GetDatePartsFromObject(Napi::Object date_parts_obj) { Napi::Object MakeTimeObject(Napi::Env env, duckdb_time time) { auto time_obj = Napi::Object::New(env); - time_obj.Set("micros", Napi::Number::New(env, time.micros)); + time_obj.Set("micros", Napi::BigInt::New(env, time.micros)); return time_obj; } -duckdb_time GetTimeFromObject(Napi::Object time_obj) { - auto micros = time_obj.Get("micros").As().Int64Value(); +duckdb_time GetTimeFromObject(Napi::Env env, Napi::Object time_obj) { + bool lossless; + auto micros = time_obj.Get("micros").As().Int64Value(&lossless); + if (!lossless) { + throw Napi::Error::New(env, "micros out of int64 range"); + } return { micros }; } @@ -1391,7 +1395,7 @@ class DuckDBNodeAddon : public Napi::Addon { Napi::Value from_time(const Napi::CallbackInfo& info) { auto env = info.Env(); auto time_obj = info[0].As(); - auto time = GetTimeFromObject(time_obj); + auto time = GetTimeFromObject(env, time_obj); auto time_parts = duckdb_from_time(time); return MakeTimePartsObject(env, time_parts); } @@ -1824,7 +1828,7 @@ class DuckDBNodeAddon : public Napi::Addon { auto env = info.Env(); auto prepared_statement = GetPreparedStatementFromExternal(env, info[0]); auto index = info[1].As().Uint32Value(); - auto value = GetTimeFromObject(info[2].As()); + auto value = GetTimeFromObject(env, info[2].As()); if (duckdb_bind_time(prepared_statement, index, value)) { throw Napi::Error::New(env, "Failed to bind time"); } @@ -2202,7 +2206,7 @@ class DuckDBNodeAddon : public Napi::Addon { // function create_time(input: Time): Value Napi::Value create_time(const Napi::CallbackInfo& info) { auto env = info.Env(); - auto input = GetTimeFromObject(info[0].As()); + auto input = GetTimeFromObject(env, info[0].As()); auto value = duckdb_create_time(input); return CreateExternalForValue(env, value); } @@ -3408,7 +3412,7 @@ class DuckDBNodeAddon : public Napi::Addon { Napi::Value append_time(const Napi::CallbackInfo& info) { auto env = info.Env(); auto appender = GetAppenderFromExternal(env, info[0]); - auto time_value = GetTimeFromObject(info[1].As()); + auto time_value = GetTimeFromObject(env, info[1].As()); if (duckdb_append_time(appender, time_value)) { throw Napi::Error::New(env, duckdb_appender_error(appender)); } diff --git a/bindings/test/appender.test.ts b/bindings/test/appender.test.ts index 2118771b..fddb2e03 100644 --- a/bindings/test/appender.test.ts +++ b/bindings/test/appender.test.ts @@ -156,7 +156,7 @@ suite('appender', () => { duckdb.append_float(appender, 3.4028234663852886e+38); duckdb.append_double(appender, 1.7976931348623157e+308); duckdb.append_date(appender, { days: 2147483646 }); - duckdb.append_time(appender, { micros: 86400000000 }); + duckdb.append_time(appender, { micros: 86400000000n }); duckdb.append_timestamp(appender, { micros: 9223372036854775806n }); duckdb.append_interval(appender, { months: 999, days: 999, micros: 999999999n }); duckdb.append_varchar(appender, '🦆🦆🦆🦆🦆🦆'); diff --git a/bindings/test/conversion.test.ts b/bindings/test/conversion.test.ts index 0f9a5620..1375d206 100644 --- a/bindings/test/conversion.test.ts +++ b/bindings/test/conversion.test.ts @@ -39,14 +39,14 @@ suite('conversion', () => { suite('from_time', () => { test('mid-range', () => { // 45296789123 = 1000000 * (60 * (60 * 12 + 34) + 56) + 789123 = 12:34:56.789123 - expect(duckdb.from_time({ micros: 45296789123 })).toStrictEqual({ hour: 12, min: 34, sec: 56, micros: 789123 }); + expect(duckdb.from_time({ micros: 45296789123n })).toStrictEqual({ hour: 12, min: 34, sec: 56, micros: 789123 }); }); test('min', () => { - expect(duckdb.from_time({ micros: 0 })).toStrictEqual({ hour: 0, min: 0, sec: 0, micros: 0 }); + expect(duckdb.from_time({ micros: 0n })).toStrictEqual({ hour: 0, min: 0, sec: 0, micros: 0 }); }); test('max', () => { // 86400000000 = 1000000 * (60 * (60 * 24 + 0) + 0) + 0 = 24:00:00.000000 - expect(duckdb.from_time({ micros: 86400000000 })).toStrictEqual({ hour: 24, min: 0, sec: 0, micros: 0 }); + expect(duckdb.from_time({ micros: 86400000000n })).toStrictEqual({ hour: 24, min: 0, sec: 0, micros: 0 }); }); }); suite('create_time_tz', () => { @@ -85,13 +85,13 @@ suite('conversion', () => { }); suite('to_time', () => { test('mid-range', () => { - expect(duckdb.to_time({ hour: 12, min: 34, sec: 56, micros: 789123 })).toStrictEqual({ micros: 45296789123 }); + expect(duckdb.to_time({ hour: 12, min: 34, sec: 56, micros: 789123 })).toStrictEqual({ micros: 45296789123n }); }); test('min', () => { - expect(duckdb.to_time({ hour: 0, min: 0, sec: 0, micros: 0 })).toStrictEqual({ micros: 0 }); + expect(duckdb.to_time({ hour: 0, min: 0, sec: 0, micros: 0 })).toStrictEqual({ micros: 0n }); }); test('max', () => { - expect(duckdb.to_time({ hour: 24, min: 0, sec: 0, micros: 0 })).toStrictEqual({ micros: 86400000000 }); + expect(duckdb.to_time({ hour: 24, min: 0, sec: 0, micros: 0 })).toStrictEqual({ micros: 86400000000n }); }); }); suite('from_timestamp', () => { diff --git a/bindings/test/prepared_statements.test.ts b/bindings/test/prepared_statements.test.ts index cb2b192e..0533bf30 100644 --- a/bindings/test/prepared_statements.test.ts +++ b/bindings/test/prepared_statements.test.ts @@ -276,7 +276,7 @@ suite('prepared statements', () => { duckdb.bind_date(prepared, 15, { days: 2147483646 }); expect(duckdb.param_type(prepared, 15)).toBe(duckdb.Type.DATE); - duckdb.bind_time(prepared, 16, { micros: 86400000000 }); + duckdb.bind_time(prepared, 16, { micros: 86400000000n }); expect(duckdb.param_type(prepared, 16)).toBe(duckdb.Type.TIME); duckdb.bind_timestamp(prepared, 17, { micros: 9223372036854775806n }); diff --git a/bindings/test/values.test.ts b/bindings/test/values.test.ts index 0ace5671..786d6001 100644 --- a/bindings/test/values.test.ts +++ b/bindings/test/values.test.ts @@ -166,7 +166,7 @@ suite('values', () => { } }); test('time', () => { - const input = { micros: 86400000000 }; + const input = { micros: 86400000000n }; const time_value = duckdb.create_time(input); try { expectLogicalType(duckdb.get_value_type(time_value), TIME);