From 05450d554d49d54d12a4a6b3be588251eebfbc47 Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 15 Feb 2025 18:16:19 -0800 Subject: [PATCH 1/2] create & get decimal --- api/src/createValue.ts | 6 ++- api/test/api.test.ts | 47 ++++++++++++------- .../pkgs/@duckdb/node-bindings/duckdb.d.ts | 2 + bindings/src/duckdb_node_bindings.cpp | 17 +++++++ bindings/test/values.test.ts | 33 +++++++++---- 5 files changed, 79 insertions(+), 26 deletions(-) diff --git a/api/src/createValue.ts b/api/src/createValue.ts index f26717f4..66b58e66 100644 --- a/api/src/createValue.ts +++ b/api/src/createValue.ts @@ -5,6 +5,7 @@ import { DuckDBArrayValue, DuckDBBlobValue, DuckDBDateValue, + DuckDBDecimalValue, DuckDBIntervalValue, DuckDBListValue, DuckDBStructValue, @@ -113,7 +114,10 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value { } throw new Error(`input is not a DuckDBBlobValue`); case DuckDBTypeId.DECIMAL: - throw new Error(`not yet implemented for DECIMAL`); // TODO: implement when available in 1.2.0 + if (input instanceof DuckDBDecimalValue) { + return duckdb.create_decimal(input); + } + throw new Error(`input is not a DuckDBDecimalValue`); case DuckDBTypeId.TIMESTAMP_S: throw new Error(`not yet implemented for TIMESTAMP_S`); // TODO: implement when available in 1.2.0 case DuckDBTypeId.TIMESTAMP_MS: diff --git a/api/test/api.test.ts b/api/test/api.test.ts index ea3ffce1..2012495e 100644 --- a/api/test/api.test.ts +++ b/api/test/api.test.ts @@ -407,33 +407,40 @@ describe('api', () => { $timetz as timetz, \ $varint as varint, \ $list as list, \ + $list_dec as list_dec, \ $struct as struct, \ $array as array, \ $null as null_value' ); - assert.strictEqual(prepared.parameterCount, 9); + assert.strictEqual(prepared.parameterCount, 10); assert.strictEqual(prepared.parameterName(1), 'num'); assert.strictEqual(prepared.parameterName(2), 'str'); assert.strictEqual(prepared.parameterName(3), 'bool'); assert.strictEqual(prepared.parameterName(4), 'timetz'); assert.strictEqual(prepared.parameterName(5), 'varint'); assert.strictEqual(prepared.parameterName(6), 'list'); - assert.strictEqual(prepared.parameterName(7), 'struct'); - assert.strictEqual(prepared.parameterName(8), 'array'); - assert.strictEqual(prepared.parameterName(9), 'null'); + assert.strictEqual(prepared.parameterName(7), 'list_dec'); + assert.strictEqual(prepared.parameterName(8), 'struct'); + assert.strictEqual(prepared.parameterName(9), 'array'); + assert.strictEqual(prepared.parameterName(10), 'null'); prepared.bindInteger(1, 10); prepared.bindVarchar(2, 'abc'); prepared.bindBoolean(3, true); prepared.bindTimeTZ(4, TIMETZ.max); prepared.bindVarInt(5, VARINT.max); prepared.bindList(6, listValue([100, 200, 300]), LIST(INTEGER)); - prepared.bindStruct( + prepared.bindList( 7, + listValue([decimalValue(9876n, 4, 1), decimalValue(5432n, 4, 1)]), + LIST(DECIMAL(4, 1)) + ); + prepared.bindStruct( + 8, structValue({ 'a': 42, 'b': 'duck' }), STRUCT({ 'a': INTEGER, 'b': VARCHAR }) ); - prepared.bindArray(8, arrayValue([100, 200, 300]), ARRAY(INTEGER, 3)); - prepared.bindNull(9); + prepared.bindArray(9, arrayValue([100, 200, 300]), ARRAY(INTEGER, 3)); + prepared.bindNull(10); assert.equal(prepared.parameterTypeId(1), DuckDBTypeId.INTEGER); assert.deepEqual(prepared.parameterType(1), INTEGER); // See https://github.com/duckdb/duckdb/issues/16137 @@ -447,12 +454,14 @@ describe('api', () => { assert.deepEqual(prepared.parameterType(5), VARINT); assert.equal(prepared.parameterTypeId(6), DuckDBTypeId.LIST); assert.deepEqual(prepared.parameterType(6), LIST(INTEGER)); - assert.equal(prepared.parameterTypeId(7), DuckDBTypeId.STRUCT); - assert.deepEqual(prepared.parameterType(7), STRUCT({ 'a': INTEGER, 'b': VARCHAR })); - assert.equal(prepared.parameterTypeId(8), DuckDBTypeId.ARRAY); - assert.deepEqual(prepared.parameterType(8), ARRAY(INTEGER, 3)); - assert.equal(prepared.parameterTypeId(9), DuckDBTypeId.SQLNULL); - assert.deepEqual(prepared.parameterType(9), SQLNULL); + assert.equal(prepared.parameterTypeId(7), DuckDBTypeId.LIST); + assert.deepEqual(prepared.parameterType(7), LIST(DECIMAL(4, 1))); + assert.equal(prepared.parameterTypeId(8), DuckDBTypeId.STRUCT); + assert.deepEqual(prepared.parameterType(8), STRUCT({ 'a': INTEGER, 'b': VARCHAR })); + assert.equal(prepared.parameterTypeId(9), DuckDBTypeId.ARRAY); + assert.deepEqual(prepared.parameterType(9), ARRAY(INTEGER, 3)); + assert.equal(prepared.parameterTypeId(10), DuckDBTypeId.SQLNULL); + assert.deepEqual(prepared.parameterType(10), SQLNULL); const result = await prepared.run(); assertColumns(result, [ { name: 'num', type: INTEGER }, @@ -461,6 +470,7 @@ describe('api', () => { { name: 'timetz', type: TIMETZ }, { name: 'varint', type: VARINT }, { name: 'list', type: LIST(INTEGER) }, + { name: 'list_dec', type: LIST(DECIMAL(4, 1)) }, { name: 'struct', type: STRUCT({ 'a': INTEGER, 'b': VARCHAR }) }, { name: 'array', type: ARRAY(INTEGER, 3) }, { name: 'null_value', type: INTEGER }, @@ -468,7 +478,7 @@ describe('api', () => { const chunk = await result.fetchChunk(); assert.isDefined(chunk); if (chunk) { - assert.strictEqual(chunk.columnCount, 9); + assert.strictEqual(chunk.columnCount, 10); assert.strictEqual(chunk.rowCount, 1); assertValues( chunk, @@ -491,15 +501,18 @@ describe('api', () => { assertValues(chunk, 3, DuckDBTimeTZVector, [TIMETZ.max]); assertValues(chunk, 4, DuckDBVarIntVector, [VARINT.max]); assertValues(chunk, 5, DuckDBListVector, [listValue([100, 200, 300])]); - assertValues(chunk, 6, DuckDBStructVector, [ + assertValues(chunk, 6, DuckDBListVector, [ + listValue([decimalValue(9876n, 4, 1), decimalValue(5432n, 4, 1)]), + ]); + assertValues(chunk, 7, DuckDBStructVector, [ structValue({ 'a': 42, 'b': 'duck' }), ]); - assertValues(chunk, 7, DuckDBArrayVector, [ + assertValues(chunk, 8, DuckDBArrayVector, [ arrayValue([100, 200, 300]), ]); assertValues( chunk, - 8, + 9, DuckDBIntegerVector, [null] ); diff --git a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts index 892bc2b7..498381a9 100644 --- a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts +++ b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts @@ -640,6 +640,7 @@ export function create_uhugeint(input: bigint): Value; export function create_varint(input: bigint): Value; // DUCKDB_API duckdb_value duckdb_create_decimal(duckdb_decimal input); +export function create_decimal(input: Decimal): Value; // DUCKDB_API duckdb_value duckdb_create_float(float input); export function create_float(input: number): Value; @@ -710,6 +711,7 @@ export function get_uhugeint(value: Value): bigint; export function get_varint(value: Value): bigint; // DUCKDB_API duckdb_decimal duckdb_get_decimal(duckdb_value val); +export function get_decimal(value: Value): Decimal; // DUCKDB_API float duckdb_get_float(duckdb_value val); export function get_float(value: Value): number; diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 546c1af3..30067b8d 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -1143,6 +1143,7 @@ class DuckDBNodeAddon : public Napi::Addon { InstanceMethod("create_hugeint", &DuckDBNodeAddon::create_hugeint), InstanceMethod("create_uhugeint", &DuckDBNodeAddon::create_uhugeint), InstanceMethod("create_varint", &DuckDBNodeAddon::create_varint), + InstanceMethod("create_decimal", &DuckDBNodeAddon::create_decimal), InstanceMethod("create_float", &DuckDBNodeAddon::create_float), InstanceMethod("create_double", &DuckDBNodeAddon::create_double), InstanceMethod("create_date", &DuckDBNodeAddon::create_date), @@ -1163,6 +1164,7 @@ class DuckDBNodeAddon : public Napi::Addon { InstanceMethod("get_hugeint", &DuckDBNodeAddon::get_hugeint), InstanceMethod("get_uhugeint", &DuckDBNodeAddon::get_uhugeint), InstanceMethod("get_varint", &DuckDBNodeAddon::get_varint), + InstanceMethod("get_decimal", &DuckDBNodeAddon::get_decimal), InstanceMethod("get_float", &DuckDBNodeAddon::get_float), InstanceMethod("get_double", &DuckDBNodeAddon::get_double), InstanceMethod("get_date", &DuckDBNodeAddon::get_date), @@ -2461,6 +2463,14 @@ class DuckDBNodeAddon : public Napi::Addon { } // DUCKDB_API duckdb_value duckdb_create_decimal(duckdb_decimal input); + // function create_decimal(input: Decimal): Value + Napi::Value create_decimal(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto decimal_obj = info[0].As(); + auto decimal = GetDecimalFromObject(env, decimal_obj); + auto value = duckdb_create_decimal(decimal); + return CreateExternalForValue(env, value); + } // DUCKDB_API duckdb_value duckdb_create_float(float input); // function create_float(input: number): Value @@ -2655,6 +2665,13 @@ class DuckDBNodeAddon : public Napi::Addon { } // DUCKDB_API duckdb_decimal duckdb_get_decimal(duckdb_value val); + // function get_decimal(value: Value): Decimal + Napi::Value get_decimal(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto value = GetValueFromExternal(env, info[0]); + auto decimal = duckdb_get_decimal(value); + return MakeDecimalObject(env, decimal); + } // DUCKDB_API float duckdb_get_float(duckdb_value val); // function get_float(value: Value): number diff --git a/bindings/test/values.test.ts b/bindings/test/values.test.ts index e9b00334..9b145728 100644 --- a/bindings/test/values.test.ts +++ b/bindings/test/values.test.ts @@ -1,4 +1,11 @@ -import duckdb from '@duckdb/node-bindings'; +import duckdb, { + Date_, + Decimal, + Interval, + Time, + Timestamp, + TimeTZ, +} from '@duckdb/node-bindings'; import { expect, suite, test } from 'vitest'; import { expectLogicalType } from './utils/expectLogicalType'; import { @@ -7,6 +14,7 @@ import { BLOB, BOOLEAN, DATE, + DECIMAL, DOUBLE, ENTRY, FLOAT, @@ -26,7 +34,7 @@ import { USMALLINT, UTINYINT, VARCHAR, - VARINT + VARINT, } from './utils/expectedLogicalTypes'; suite('values', () => { @@ -97,11 +105,20 @@ suite('values', () => { expect(duckdb.get_uhugeint(uhugeint_value)).toBe(input); }); test('varint', () => { - const input = -((((2n ** 10n + 11n) * (2n ** 64n) + (2n ** 9n + 7n)) * (2n ** 64n)) + (2n ** 8n + 5n)); + const input = -( + ((2n ** 10n + 11n) * 2n ** 64n + (2n ** 9n + 7n)) * 2n ** 64n + + (2n ** 8n + 5n) + ); const varint_value = duckdb.create_varint(input); expectLogicalType(duckdb.get_value_type(varint_value), VARINT); expect(duckdb.get_varint(varint_value)).toBe(input); }); + test('decimal', () => { + const input: Decimal = { width: 9, scale: 4, value: 987654321n }; + const decimal_value = duckdb.create_decimal(input); + expectLogicalType(duckdb.get_value_type(decimal_value), DECIMAL(9, 4, duckdb.Type.INTEGER)); + expect(duckdb.get_decimal(decimal_value)).toStrictEqual(input); + }); test('float', () => { const input = 3.4028234663852886e38; const float_value = duckdb.create_float(input); @@ -115,31 +132,31 @@ suite('values', () => { expect(duckdb.get_double(double_value)).toBe(input); }); test('date', () => { - const input = { days: 2147483646 }; + const input: Date_ = { days: 2147483646 }; const date_value = duckdb.create_date(input); expectLogicalType(duckdb.get_value_type(date_value), DATE); expect(duckdb.get_date(date_value)).toStrictEqual(input); }); test('time', () => { - const input = { micros: 86400000000n }; + const input: Time = { micros: 86400000000n }; const time_value = duckdb.create_time(input); expectLogicalType(duckdb.get_value_type(time_value), TIME); expect(duckdb.get_time(time_value)).toStrictEqual(input); }); test('time_tz', () => { - const input = { bits: 1449551462400115198n }; + const input: TimeTZ = { bits: 1449551462400115198n }; const time_tz_value = duckdb.create_time_tz_value(input); expectLogicalType(duckdb.get_value_type(time_tz_value), TIME_TZ); expect(duckdb.get_time_tz(time_tz_value)).toStrictEqual(input); }); test('timestamp', () => { - const input = { micros: 9223372036854775806n }; + const input: Timestamp = { micros: 9223372036854775806n }; const timestamp_value = duckdb.create_timestamp(input); expectLogicalType(duckdb.get_value_type(timestamp_value), TIMESTAMP); expect(duckdb.get_timestamp(timestamp_value)).toStrictEqual(input); }); test('interval', () => { - const input = { months: 999, days: 999, micros: 999999999n }; + const input: Interval = { months: 999, days: 999, micros: 999999999n }; const interval_value = duckdb.create_interval(input); expectLogicalType(duckdb.get_value_type(interval_value), INTERVAL); expect(duckdb.get_interval(interval_value)).toStrictEqual(input); From 05099238b64fd09c43b90dc67b4b1b1a1154b9d4 Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 15 Feb 2025 18:16:55 -0800 Subject: [PATCH 2/2] update accounting comment --- bindings/src/duckdb_node_bindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 30067b8d..a019fd03 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -3979,11 +3979,11 @@ NODE_API_ADDON(DuckDBNodeAddon) --- 411 total functions - 216 instance methods + 218 instance methods 3 unimplemented instance cache functions 1 unimplemented logical type function - 9 unimplemented value creation functions - 12 unimplemented value inspection functions + 8 unimplemented value creation functions + 11 unimplemented value inspection functions 13 unimplemented scalar function functions 4 unimplemented scalar function set functions 12 unimplemented aggregate function functions