Skip to content

Commit b302dbd

Browse files
authored
Merge pull request #46 from duckdb/jray/value-conversion
value conversion in api
2 parents 221141d + 06af714 commit b302dbd

File tree

10 files changed

+196
-95
lines changed

10 files changed

+196
-95
lines changed

api/pkgs/@duckdb/node-api/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,13 +264,13 @@ if (columnType.typeId === DuckDBTypeId.TIMESTAMP) {
264264
}
265265

266266
if (columnType.typeId === DuckDBTypeId.TIME_TZ) {
267-
const timeTZMicros = columnValue.microseconds;
267+
const timeTZMicros = columnValue.micros; // bigint
268268
const timeTZOffset = columnValue.offset;
269269
const timeTZString = columnValue.toString();
270270
}
271271

272272
if (columnType.typeId === DuckDBTypeId.TIME) {
273-
const timeMicros = columnValue.microseconds; // bigint
273+
const timeMicros = columnValue.micros; // bigint
274274
const timeString = columnValue.toString();
275275
}
276276

api/src/DuckDBVector.ts

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -190,24 +190,24 @@ function makeGetBoolean(): (dataView: DataView, offset: number) => boolean {
190190

191191
const getBoolean = makeGetBoolean();
192192

193-
function getDecimal2(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue<number> {
194-
const scaledValue = getInt16(dataView, offset);
195-
return new DuckDBDecimalValue(type.width, type.scale, scaledValue);
193+
function getDecimal2(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue {
194+
const value = getInt16(dataView, offset);
195+
return new DuckDBDecimalValue(BigInt(value), type.width, type.scale);
196196
}
197197

198-
function getDecimal4(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue<number> {
199-
const scaledValue = getInt32(dataView, offset);
200-
return new DuckDBDecimalValue(type.width, type.scale, scaledValue);
198+
function getDecimal4(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue {
199+
const value = getInt32(dataView, offset);
200+
return new DuckDBDecimalValue(BigInt(value), type.width, type.scale);
201201
}
202202

203-
function getDecimal8(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue<bigint> {
204-
const scaledValue = getInt64(dataView, offset);
205-
return new DuckDBDecimalValue(type.width, type.scale, scaledValue);
203+
function getDecimal8(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue {
204+
const value = getInt64(dataView, offset);
205+
return new DuckDBDecimalValue(value, type.width, type.scale);
206206
}
207207

208-
function getDecimal16(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue<bigint> {
209-
const scaledValue = getInt128(dataView, offset);
210-
return new DuckDBDecimalValue(type.width, type.scale, scaledValue);
208+
function getDecimal16(dataView: DataView, offset: number, type: DuckDBDecimalType): DuckDBDecimalValue {
209+
const value = getInt128(dataView, offset);
210+
return new DuckDBDecimalValue(value, type.width, type.scale);
211211
}
212212

213213
function vectorData(vector: duckdb.Vector, byteCount: number): Uint8Array {
@@ -863,6 +863,11 @@ export class DuckDBHugeIntVector extends DuckDBVector<bigint> {
863863
public override getItem(itemIndex: number): bigint | null {
864864
return this.validity.itemValid(itemIndex) ? getInt128(this.dataView, itemIndex * 16) : null;
865865
}
866+
public getDouble(itemIndex: number): number | null {
867+
return this.validity.itemValid(itemIndex)
868+
? duckdb.hugeint_to_double(getInt128(this.dataView, itemIndex * 16))
869+
: null;
870+
}
866871
public override slice(offset: number, length: number): DuckDBHugeIntVector {
867872
return new DuckDBHugeIntVector(
868873
new DataView(this.dataView.buffer, this.dataView.byteOffset + offset * 16, length * 16),
@@ -897,6 +902,11 @@ export class DuckDBUHugeIntVector extends DuckDBVector<bigint> {
897902
public override getItem(itemIndex: number): bigint | null {
898903
return this.validity.itemValid(itemIndex) ? getUInt128(this.dataView, itemIndex * 16) : null;
899904
}
905+
public getDouble(itemIndex: number): number | null {
906+
return this.validity.itemValid(itemIndex)
907+
? duckdb.uhugeint_to_double(getUInt128(this.dataView, itemIndex * 16))
908+
: null;
909+
}
900910
public override slice(offset: number, length: number): DuckDBUHugeIntVector {
901911
return new DuckDBUHugeIntVector(
902912
new DataView(this.dataView.buffer, this.dataView.byteOffset + offset * 16, length * 16),
@@ -974,7 +984,7 @@ export class DuckDBBlobVector extends DuckDBVector<DuckDBBlobValue> {
974984
}
975985
}
976986

977-
export class DuckDBDecimal2Vector extends DuckDBVector<DuckDBDecimalValue<number>> {
987+
export class DuckDBDecimal2Vector extends DuckDBVector<DuckDBDecimalValue> {
978988
private readonly decimalType: DuckDBDecimalType;
979989
private readonly dataView: DataView;
980990
private readonly validity: DuckDBValidity;
@@ -998,7 +1008,7 @@ export class DuckDBDecimal2Vector extends DuckDBVector<DuckDBDecimalValue<number
9981008
public override get itemCount(): number {
9991009
return this._itemCount;
10001010
}
1001-
public override getItem(itemIndex: number): DuckDBDecimalValue<number> | null {
1011+
public override getItem(itemIndex: number): DuckDBDecimalValue | null {
10021012
return this.validity.itemValid(itemIndex) ? getDecimal2(this.dataView, itemIndex * 2, this.decimalType) : null;
10031013
}
10041014
public getScaledValue(itemIndex: number): number | null {
@@ -1014,7 +1024,7 @@ export class DuckDBDecimal2Vector extends DuckDBVector<DuckDBDecimalValue<number
10141024
}
10151025
}
10161026

1017-
export class DuckDBDecimal4Vector extends DuckDBVector<DuckDBDecimalValue<number>> {
1027+
export class DuckDBDecimal4Vector extends DuckDBVector<DuckDBDecimalValue> {
10181028
private readonly decimalType: DuckDBDecimalType;
10191029
private readonly dataView: DataView;
10201030
private readonly validity: DuckDBValidity;
@@ -1038,7 +1048,7 @@ export class DuckDBDecimal4Vector extends DuckDBVector<DuckDBDecimalValue<number
10381048
public override get itemCount(): number {
10391049
return this._itemCount;
10401050
}
1041-
public override getItem(itemIndex: number): DuckDBDecimalValue<number> | null {
1051+
public override getItem(itemIndex: number): DuckDBDecimalValue | null {
10421052
return this.validity.itemValid(itemIndex) ? getDecimal4(this.dataView, itemIndex * 4, this.decimalType) : null;
10431053
}
10441054
public getScaledValue(itemIndex: number): number | null {
@@ -1054,7 +1064,7 @@ export class DuckDBDecimal4Vector extends DuckDBVector<DuckDBDecimalValue<number
10541064
}
10551065
}
10561066

1057-
export class DuckDBDecimal8Vector extends DuckDBVector<DuckDBDecimalValue<bigint>> {
1067+
export class DuckDBDecimal8Vector extends DuckDBVector<DuckDBDecimalValue> {
10581068
private readonly decimalType: DuckDBDecimalType;
10591069
private readonly dataView: DataView;
10601070
private readonly validity: DuckDBValidity;
@@ -1078,7 +1088,7 @@ export class DuckDBDecimal8Vector extends DuckDBVector<DuckDBDecimalValue<bigint
10781088
public override get itemCount(): number {
10791089
return this._itemCount;
10801090
}
1081-
public override getItem(itemIndex: number): DuckDBDecimalValue<bigint> | null {
1091+
public override getItem(itemIndex: number): DuckDBDecimalValue | null {
10821092
return this.validity.itemValid(itemIndex) ? getDecimal8(this.dataView, itemIndex * 8, this.decimalType) : null;
10831093
}
10841094
public getScaledValue(itemIndex: number): bigint | null {
@@ -1094,7 +1104,7 @@ export class DuckDBDecimal8Vector extends DuckDBVector<DuckDBDecimalValue<bigint
10941104
}
10951105
}
10961106

1097-
export class DuckDBDecimal16Vector extends DuckDBVector<DuckDBDecimalValue<bigint>> {
1107+
export class DuckDBDecimal16Vector extends DuckDBVector<DuckDBDecimalValue> {
10981108
private readonly decimalType: DuckDBDecimalType;
10991109
private readonly dataView: DataView;
11001110
private readonly validity: DuckDBValidity;
@@ -1118,7 +1128,7 @@ export class DuckDBDecimal16Vector extends DuckDBVector<DuckDBDecimalValue<bigin
11181128
public override get itemCount(): number {
11191129
return this._itemCount;
11201130
}
1121-
public override getItem(itemIndex: number): DuckDBDecimalValue<bigint> | null {
1131+
public override getItem(itemIndex: number): DuckDBDecimalValue | null {
11221132
return this.validity.itemValid(itemIndex) ? getDecimal16(this.dataView, itemIndex * 16, this.decimalType) : null;
11231133
}
11241134
public getScaledValue(itemIndex: number): bigint | null {

api/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
export {
2+
hugeint_to_double,
3+
double_to_hugeint,
4+
uhugeint_to_double,
5+
double_to_uhugeint,
6+
} from '@duckdb/node-bindings';
17
export * from './DuckDBAppender';
28
export * from './DuckDBConnection';
39
export * from './DuckDBDataChunk';

api/src/values/DuckDBDateValue.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,31 @@
1-
import { Date_ } from '@duckdb/node-bindings';
1+
import duckdb, { Date_, DateParts } from '@duckdb/node-bindings';
22
import { getDuckDBDateStringFromDays } from '../conversion/dateTimeStringConversion';
33

4+
export type { DateParts };
5+
46
export class DuckDBDateValue implements Date_ {
57
public readonly days: number;
68

79
public constructor(days: number) {
810
this.days = days;
911
}
1012

13+
public get isFinite(): boolean {
14+
return duckdb.is_finite_date(this);
15+
}
16+
1117
public toString(): string {
1218
return getDuckDBDateStringFromDays(this.days);
1319
}
1420

21+
public toParts(): DateParts {
22+
return duckdb.from_date(this);
23+
}
24+
25+
public static fromParts(parts: DateParts): DuckDBDateValue {
26+
return new DuckDBDateValue(duckdb.to_date(parts).days);
27+
}
28+
1529
public static readonly Epoch = new DuckDBDateValue(0);
1630

1731
public static readonly Max = new DuckDBDateValue(2 ** 31 - 2);

api/src/values/DuckDBDecimalValue.ts

Lines changed: 24 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
1-
import { Decimal } from '@duckdb/node-bindings';
1+
import duckdb, { Decimal } from '@duckdb/node-bindings';
22
import { stringFromDecimal } from '../conversion/stringFromDecimal';
33

4-
export class DuckDBDecimalValue<T extends number | bigint = number | bigint> implements Decimal {
4+
export class DuckDBDecimalValue implements Decimal {
5+
/** Total number of decimal digits (including fractional digits) in represented number. */
56
public readonly width: number;
7+
8+
/** Number of fractional digits in represented number. */
69
public readonly scale: number;
7-
public readonly scaledValue: T;
810

9-
public constructor(width: number, scale: number, scaledValue: T) {
11+
/** Scaled-up value. Represented number is value/(10^scale). */
12+
public readonly value: bigint;
13+
14+
/**
15+
* @param value Scaled-up value. Represented number is value/(10^scale).
16+
* @param width Total number of decimal digits (including fractional digits) in represented number.
17+
* @param scale Number of fractional digits in represented number.
18+
*/
19+
public constructor(value: bigint, width: number, scale: number) {
1020
this.width = width;
1121
this.scale = scale;
12-
this.scaledValue = scaledValue;
13-
}
14-
15-
get value(): bigint {
16-
return BigInt(this.scaledValue);
22+
this.value = value;
1723
}
1824

1925
public toString(): string {
2026
return stringFromDecimal(this.value, this.scale);
2127
}
22-
}
2328

24-
export function decimalValue<T extends number | bigint = number | bigint>(
25-
width: number,
26-
scale: number,
27-
scaledValue: T
28-
): DuckDBDecimalValue<T> {
29-
return new DuckDBDecimalValue(width, scale, scaledValue);
30-
}
29+
public toDouble(): number {
30+
return duckdb.decimal_to_double(this);
31+
}
3132

32-
export function decimalNumber(
33-
width: number,
34-
scale: number,
35-
scaledValue: number
36-
): DuckDBDecimalValue<number> {
37-
return new DuckDBDecimalValue(width, scale, scaledValue);
33+
public static fromDouble(double: number, width: number, scale: number): DuckDBDecimalValue {
34+
const decimal = duckdb.double_to_decimal(double, width, scale);
35+
return new DuckDBDecimalValue(decimal.value, decimal.width, decimal.scale);
36+
}
3837
}
3938

40-
export function decimalBigint(
41-
width: number,
42-
scale: number,
43-
scaledValue: bigint
44-
): DuckDBDecimalValue<bigint> {
45-
return new DuckDBDecimalValue(width, scale, scaledValue);
39+
export function decimalValue(value: bigint, width: number, scale: number): DuckDBDecimalValue {
40+
return new DuckDBDecimalValue(value, width, scale);
4641
}

api/src/values/DuckDBTimeTZValue.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,70 @@
1-
import { TimeTZ } from '@duckdb/node-bindings';
1+
import duckdb, { TimeTZ, TimeTZParts } from '@duckdb/node-bindings';
22
import { getDuckDBTimeStringFromMicrosecondsInDay } from '../conversion/dateTimeStringConversion';
33

4+
export type { TimeTZParts };
5+
46
export class DuckDBTimeTZValue implements TimeTZ {
7+
/**
8+
* 40 bits for micros, then 24 bits for encoded offset in seconds.
9+
*
10+
* Max absolute unencoded offset = 15:59:59 = 60 * (60 * 15 + 59) + 59 = 57599.
11+
*
12+
* Encoded offset is unencoded offset inverted then shifted (by +57599) to unsigned.
13+
*
14+
* Max unencoded offset = 57599 -> -57599 -> 0 encoded.
15+
*
16+
* Min unencoded offset = -57599 -> 57599 -> 115198 encoded.
17+
*/
518
public readonly bits: bigint;
619

720
/** Ranges from 0 to 86400000000 (= 24 * 60 * 60 * 1000 * 1000) */
8-
public readonly microseconds: number;
21+
public readonly micros: bigint;
922

1023
/** In seconds, ranges from -57599 to 57599 (= 16 * 60 * 60 - 1) */
1124
public readonly offset: number;
1225

13-
public constructor(bits: bigint, microseconds: number, offset: number) {
26+
public constructor(bits: bigint, micros: bigint, offset: number) {
1427
this.bits = bits;
15-
this.microseconds = microseconds;
28+
this.micros = micros;
1629
this.offset = offset;
1730
}
1831

1932
public toString(): string {
2033
// TODO: display offset
21-
return getDuckDBTimeStringFromMicrosecondsInDay(BigInt(this.microseconds));
34+
return getDuckDBTimeStringFromMicrosecondsInDay(this.micros);
35+
}
36+
37+
public toParts(): TimeTZParts {
38+
return duckdb.from_time_tz(this);
2239
}
2340

2441
public static TimeBits = 40;
2542
public static OffsetBits = 24;
2643
public static MaxOffset = 16 * 60 * 60 - 1; // ±15:59:59 = 57599 seconds
2744
public static MinOffset = -DuckDBTimeTZValue.MaxOffset;
28-
public static MaxMicroseconds = 24 * 60 * 60 * 1000 * 1000; // 86400000000
29-
public static MinMicroseconds = 0;
45+
public static MaxMicros = 24n * 60n * 60n * 1000n * 1000n; // 86400000000
46+
public static MinMicros = 0n;
3047

3148
public static fromBits(bits: bigint): DuckDBTimeTZValue {
32-
const microseconds = Number(BigInt.asUintN(DuckDBTimeTZValue.TimeBits, bits >> BigInt(DuckDBTimeTZValue.OffsetBits)));
49+
const micros = BigInt.asUintN(DuckDBTimeTZValue.TimeBits, bits >> BigInt(DuckDBTimeTZValue.OffsetBits));
3350
const offset = DuckDBTimeTZValue.MaxOffset - Number(BigInt.asUintN(DuckDBTimeTZValue.OffsetBits, bits));
34-
return new DuckDBTimeTZValue(bits, microseconds, offset);
51+
return new DuckDBTimeTZValue(bits, micros, offset);
3552
}
3653

37-
public static fromParts(microseconds: number, offset: number): DuckDBTimeTZValue {
38-
const bits = BigInt.asUintN(DuckDBTimeTZValue.TimeBits, BigInt(microseconds)) << BigInt(DuckDBTimeTZValue.OffsetBits)
54+
public static fromMicrosAndOffset(micros: bigint, offset: number): DuckDBTimeTZValue {
55+
const bits = BigInt.asUintN(DuckDBTimeTZValue.TimeBits, micros) << BigInt(DuckDBTimeTZValue.OffsetBits)
3956
| BigInt.asUintN(DuckDBTimeTZValue.OffsetBits, BigInt(DuckDBTimeTZValue.MaxOffset - offset));
40-
return new DuckDBTimeTZValue(bits, microseconds, offset);
57+
return new DuckDBTimeTZValue(bits, micros, offset);
58+
}
59+
60+
public static fromParts(parts: TimeTZParts): DuckDBTimeTZValue {
61+
return DuckDBTimeTZValue.fromMicrosAndOffset(duckdb.to_time(parts.time).micros, parts.offset);
4162
}
4263

43-
public static readonly Max = DuckDBTimeTZValue.fromParts(DuckDBTimeTZValue.MaxMicroseconds, DuckDBTimeTZValue.MinOffset);
44-
public static readonly Min = DuckDBTimeTZValue.fromParts(DuckDBTimeTZValue.MinMicroseconds, DuckDBTimeTZValue.MaxOffset);
64+
public static readonly Max = DuckDBTimeTZValue.fromMicrosAndOffset(DuckDBTimeTZValue.MaxMicros, DuckDBTimeTZValue.MinOffset);
65+
public static readonly Min = DuckDBTimeTZValue.fromMicrosAndOffset(DuckDBTimeTZValue.MinMicros, DuckDBTimeTZValue.MaxOffset);
4566
}
4667

47-
export function timeTZValue(microseconds: number, offset: number): DuckDBTimeTZValue {
48-
return DuckDBTimeTZValue.fromParts(microseconds, offset);
68+
export function timeTZValue(micros: bigint, offset: number): DuckDBTimeTZValue {
69+
return DuckDBTimeTZValue.fromMicrosAndOffset(micros, offset);
4970
}

api/src/values/DuckDBTimeValue.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { Time } from '@duckdb/node-bindings';
1+
import duckdb, { Time, TimeParts } from '@duckdb/node-bindings';
22
import { getDuckDBTimeStringFromMicrosecondsInDay } from '../conversion/dateTimeStringConversion';
33

4+
export type { TimeParts };
5+
46
export class DuckDBTimeValue implements Time {
57
public readonly micros: bigint;
68

@@ -12,6 +14,14 @@ export class DuckDBTimeValue implements Time {
1214
return getDuckDBTimeStringFromMicrosecondsInDay(this.micros);
1315
}
1416

17+
public toParts(): TimeParts {
18+
return duckdb.from_time(this);
19+
}
20+
21+
public static fromParts(parts: TimeParts): DuckDBTimeValue {
22+
return new DuckDBTimeValue(duckdb.to_time(parts).micros);
23+
}
24+
1525
public static readonly Max = new DuckDBTimeValue(24n * 60n * 60n * 1000n * 1000n);
1626
public static readonly Min = new DuckDBTimeValue(0n);
1727
}

api/src/values/DuckDBTimestampTZValue.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import duckdb, { Timestamp, TimestampParts } from '@duckdb/node-bindings';
12
import { getDuckDBTimestampStringFromMicroseconds } from '../conversion/dateTimeStringConversion';
23
import { DuckDBTimestampValue } from './DuckDBTimestampValue';
34

4-
export class DuckDBTimestampTZValue {
5+
export class DuckDBTimestampTZValue implements Timestamp {
56
public readonly micros: bigint;
67

78
public constructor(micros: bigint) {
@@ -13,6 +14,14 @@ export class DuckDBTimestampTZValue {
1314
return getDuckDBTimestampStringFromMicroseconds(this.micros);
1415
}
1516

17+
public toParts(): TimestampParts {
18+
return duckdb.from_timestamp(this);
19+
}
20+
21+
public static fromParts(parts: TimestampParts): DuckDBTimestampTZValue {
22+
return new DuckDBTimestampTZValue(duckdb.to_timestamp(parts).micros);
23+
}
24+
1625
public static readonly Epoch = new DuckDBTimestampTZValue(0n);
1726
public static readonly Max = new DuckDBTimestampTZValue(DuckDBTimestampValue.Max.micros);
1827
public static readonly Min = new DuckDBTimestampTZValue(DuckDBTimestampValue.Min.micros);

0 commit comments

Comments
 (0)