From 59e06b0df5c1e039dc96fcbe54e2dacbcdc1bea2 Mon Sep 17 00:00:00 2001 From: 190n Date: Tue, 17 Dec 2024 17:38:12 -0800 Subject: [PATCH] fix(napi): set lossless parameter in napi_get_value_bigint_{int64,uint64}, and trim leading zeroes in napi_create_bigint_words (#15804) --- src/bun.js/bindings/napi.cpp | 117 +- src/napi/napi.zig | 22 +- test/bun.lockb | Bin 424434 -> 427354 bytes .../@duckdb/node-api/duckdb.test.ts | 1038 +++++++++++++++++ test/napi/napi-app/main.cpp | 112 ++ test/napi/napi-app/module.js | 4 + test/napi/napi.test.ts | 28 + test/package-json-lint.test.ts | 13 +- test/package.json | 5 +- 9 files changed, 1297 insertions(+), 42 deletions(-) create mode 100644 test/js/third_party/@duckdb/node-api/duckdb.test.ts diff --git a/src/bun.js/bindings/napi.cpp b/src/bun.js/bindings/napi.cpp index c06ac65170..c32a3ef234 100644 --- a/src/bun.js/bindings/napi.cpp +++ b/src/bun.js/bindings/napi.cpp @@ -2519,6 +2519,78 @@ extern "C" napi_status napi_typeof(napi_env env, napi_value val, return napi_generic_failure; } +static_assert(std::is_same_v, "All NAPI bigint functions assume that bigint words are 64 bits"); +#if USE(BIGINT32) +#error All NAPI bigint functions assume that BIGINT32 is disabled +#endif + +extern "C" napi_status napi_get_value_bigint_int64(napi_env env, napi_value value, int64_t* result, bool* lossless) +{ + NAPI_PREMABLE + JSValue jsValue = toJS(value); + if (!env || jsValue.isEmpty() || !result || !lossless) { + return napi_invalid_arg; + } + if (!jsValue.isHeapBigInt()) { + return napi_bigint_expected; + } + + auto* globalObject = toJS(env); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + *result = jsValue.toBigInt64(toJS(env)); + // toBigInt64 can throw if the value is not a bigint. we have already checked, so we shouldn't + // hit an exception here, but we should check just in case + scope.assertNoException(); + + JSBigInt* bigint = jsValue.asHeapBigInt(); + uint64_t digit = bigint->length() > 0 ? bigint->digit(0) : 0; + + if (bigint->length() > 1) { + *lossless = false; + } else if (bigint->sign()) { + // negative + // lossless if numeric value is >= -2^63, + // for which digit will be <= 2^63 + *lossless = (digit <= (1ull << 63)); + } else { + // positive + // lossless if numeric value is <= 2^63 - 1 + *lossless = (digit <= static_cast(INT64_MAX)); + } + + return napi_ok; +} + +extern "C" napi_status napi_get_value_bigint_uint64(napi_env env, napi_value value, uint64_t* result, bool* lossless) +{ + NAPI_PREMABLE + JSValue jsValue = toJS(value); + if (!env || jsValue.isEmpty() || !result || !lossless) { + return napi_invalid_arg; + } + if (!jsValue.isHeapBigInt()) { + return napi_bigint_expected; + } + + auto* globalObject = toJS(env); + auto& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(vm); + + *result = jsValue.toBigUInt64(toJS(env)); + // toBigUInt64 can throw if the value is not a bigint. we have already checked, so we shouldn't + // hit an exception here, but we should check just in case + scope.assertNoException(); + + // bigint to uint64 conversion is lossless if and only if there aren't multiple digits and the + // value is positive + JSBigInt* bigint = jsValue.asHeapBigInt(); + *lossless = (bigint->length() <= 1 && bigint->sign() == false); + + return napi_ok; +} + extern "C" napi_status napi_get_value_bigint_words(napi_env env, napi_value value, int* sign_bit, @@ -2662,32 +2734,45 @@ extern "C" napi_status napi_create_bigint_words(napi_env env, const uint64_t* words, napi_value* result) { - NAPI_PREMABLE - - if (UNLIKELY(!result)) { + NAPI_PREMABLE; + // JSBigInt::createWithLength's size argument is unsigned int + if (!env || !result || !words || word_count > UINT_MAX) { return napi_invalid_arg; } Zig::GlobalObject* globalObject = toJS(env); - JSC::VM& vm = globalObject->vm(); - auto* bigint = JSC::JSBigInt::tryCreateWithLength(vm, word_count); - if (UNLIKELY(!bigint)) { - return napi_generic_failure; + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + RETURN_IF_EXCEPTION(scope, napi_pending_exception); + + if (word_count == 0) { + auto* bigint = JSBigInt::createZero(globalObject); + scope.assertNoException(); + *result = toNapi(bigint, globalObject); + return napi_ok; } - // TODO: verify sign bit is consistent - bigint->setSign(sign_bit); + // JSBigInt requires there are no leading zeroes in the words array, but native modules may have + // passed an array containing leading zeroes. so we have to cut those off. + while (word_count > 0 && words[word_count - 1] == 0) { + word_count--; + } - if (words != nullptr) { - const uint64_t* word = words; - // TODO: add fast path that uses memcpy here instead of setDigit - // we need to add this to JSC. V8 has this optimization - for (size_t i = 0; i < word_count; i++) { - bigint->setDigit(i, *word++); - } + // throws RangeError if size is larger than JSC's limit + auto* bigint = JSBigInt::createWithLength(globalObject, word_count); + RETURN_IF_EXCEPTION(scope, napi_pending_exception); + ASSERT(bigint); + + bigint->setSign(sign_bit != 0); + + const uint64_t* current_word = words; + // TODO: add fast path that uses memcpy here instead of setDigit + // we need to add this to JSC. V8 has this optimization + for (size_t i = 0; i < word_count; i++) { + bigint->setDigit(i, *current_word++); } *result = toNapi(bigint, globalObject); + scope.assertNoException(); return napi_ok; } diff --git a/src/napi/napi.zig b/src/napi/napi.zig index c8d735d0b2..ab089d9b6d 100644 --- a/src/napi/napi.zig +++ b/src/napi/napi.zig @@ -1032,26 +1032,8 @@ pub export fn napi_create_bigint_uint64(env: napi_env, value: u64, result_: ?*na return .ok; } pub extern fn napi_create_bigint_words(env: napi_env, sign_bit: c_int, word_count: usize, words: [*c]const u64, result: *napi_value) napi_status; -// TODO: lossless -pub export fn napi_get_value_bigint_int64(_: napi_env, value_: napi_value, result_: ?*i64, _: *bool) napi_status { - log("napi_get_value_bigint_int64", .{}); - const result = result_ orelse { - return invalidArg(); - }; - const value = value_.get(); - result.* = value.toInt64(); - return .ok; -} -// TODO: lossless -pub export fn napi_get_value_bigint_uint64(_: napi_env, value_: napi_value, result_: ?*u64, _: *bool) napi_status { - log("napi_get_value_bigint_uint64", .{}); - const result = result_ orelse { - return invalidArg(); - }; - const value = value_.get(); - result.* = value.toUInt64NoTruncate(); - return .ok; -} +pub extern fn napi_get_value_bigint_int64(env: napi_env, value: napi_value, result: ?*i64, lossless: ?*bool) napi_status; +pub extern fn napi_get_value_bigint_uint64(env: napi_env, value: napi_value, result: ?*u64, lossless: ?*bool) napi_status; pub extern fn napi_get_value_bigint_words(env: napi_env, value: napi_value, sign_bit: [*c]c_int, word_count: [*c]usize, words: [*c]u64) napi_status; pub extern fn napi_get_all_property_names(env: napi_env, object: napi_value, key_mode: napi_key_collection_mode, key_filter: napi_key_filter, key_conversion: napi_key_conversion, result: *napi_value) napi_status; diff --git a/test/bun.lockb b/test/bun.lockb index fbf0da7978c74fb43117143efa38953611026ec6..3ebb88c5a69b5a0d1719913fe3d8be9aa2b7faa3 100755 GIT binary patch delta 66784 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 F{tw1UwfX=6 delta 65050 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 CkPWo} diff --git a/test/js/third_party/@duckdb/node-api/duckdb.test.ts b/test/js/third_party/@duckdb/node-api/duckdb.test.ts new file mode 100644 index 0000000000..4fb3c1e012 --- /dev/null +++ b/test/js/third_party/@duckdb/node-api/duckdb.test.ts @@ -0,0 +1,1038 @@ +// Copyright 2018-2023 Stichting DuckDB Foundation +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +// Copied from https://github.com/duckdb/duckdb-node-neo/blob/3f85023c6b42d6b288a2e0f92dd7c7b40cf2a63c/api/test/api.test.ts, +// with minor modifications to work as a Bun test + +import { libcFamily } from "harness"; +if (libcFamily == "musl") { + // @duckdb/node-bindings does not distribute musl binaries, so we skip this test on musl to avoid CI noise + process.exit(0); +} + +import { describe, test } from "bun:test"; +import assert from "node:assert"; +// Must be CJS require so that the above code can exit before we attempt to import DuckDB +const { + DateParts, + DuckDBAnyType, + DuckDBArrayType, + DuckDBArrayVector, + DuckDBBigIntType, + DuckDBBigIntVector, + DuckDBBitType, + DuckDBBitVector, + DuckDBBlobType, + DuckDBBlobValue, + DuckDBBlobVector, + DuckDBBooleanType, + DuckDBBooleanVector, + DuckDBConnection, + DuckDBDataChunk, + DuckDBDateType, + DuckDBDateValue, + DuckDBDateVector, + DuckDBDecimal16Vector, + DuckDBDecimal2Vector, + DuckDBDecimal4Vector, + DuckDBDecimal8Vector, + DuckDBDecimalType, + DuckDBDecimalValue, + DuckDBDoubleType, + DuckDBDoubleVector, + DuckDBEnum1Vector, + DuckDBEnum2Vector, + DuckDBEnum4Vector, + DuckDBEnumType, + DuckDBFloatType, + DuckDBFloatVector, + DuckDBHugeIntType, + DuckDBHugeIntVector, + DuckDBInstance, + DuckDBIntegerType, + DuckDBIntegerVector, + DuckDBIntervalType, + DuckDBIntervalVector, + DuckDBListType, + DuckDBListVector, + DuckDBMapType, + DuckDBMapVector, + DuckDBPendingResultState, + DuckDBResult, + DuckDBSQLNullType, + DuckDBSmallIntType, + DuckDBSmallIntVector, + DuckDBStructType, + DuckDBStructVector, + DuckDBTimeTZType, + DuckDBTimeTZValue, + DuckDBTimeTZVector, + DuckDBTimeType, + DuckDBTimeValue, + DuckDBTimeVector, + DuckDBTimestampMillisecondsType, + DuckDBTimestampMillisecondsValue, + DuckDBTimestampMillisecondsVector, + DuckDBTimestampNanosecondsType, + DuckDBTimestampNanosecondsValue, + DuckDBTimestampNanosecondsVector, + DuckDBTimestampSecondsType, + DuckDBTimestampSecondsValue, + DuckDBTimestampSecondsVector, + DuckDBTimestampTZType, + DuckDBTimestampTZValue, + DuckDBTimestampTZVector, + DuckDBTimestampType, + DuckDBTimestampValue, + DuckDBTimestampVector, + DuckDBTinyIntType, + DuckDBTinyIntVector, + DuckDBType, + DuckDBTypeId, + DuckDBUBigIntType, + DuckDBUBigIntVector, + DuckDBUHugeIntType, + DuckDBUHugeIntVector, + DuckDBUIntegerType, + DuckDBUIntegerVector, + DuckDBUSmallIntType, + DuckDBUSmallIntVector, + DuckDBUTinyIntType, + DuckDBUTinyIntVector, + DuckDBUUIDType, + DuckDBUUIDValue, + DuckDBUUIDVector, + DuckDBUnionType, + DuckDBUnionVector, + DuckDBValue, + DuckDBVarCharType, + DuckDBVarCharVector, + DuckDBVarIntType, + DuckDBVarIntVector, + DuckDBVector, + ResultReturnType, + StatementType, + TimeParts, + TimeTZParts, + TimestampParts, + arrayValue, + bitValue, + configurationOptionDescriptions, + dateValue, + decimalValue, + intervalValue, + listValue, + mapValue, + structValue, + timeTZValue, + timeValue, + timestampTZValue, + timestampValue, + unionValue, + version, +} = require("@duckdb/node-api"); + +const BI_10_8 = 100000000n; +const BI_10_10 = 10000000000n; +const BI_18_9s = BI_10_8 * BI_10_10 - 1n; +const BI_38_9s = BI_10_8 * BI_10_10 * BI_10_10 * BI_10_10 - 1n; + +async function sleep(ms: number): Promise { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +async function withConnection(fn: (connection: DuckDBConnection) => Promise) { + const instance = await DuckDBInstance.create(); + const connection = await instance.connect(); + await fn(connection); +} + +interface ExpectedColumn { + readonly name: string; + readonly type: DuckDBType; +} + +function assertColumns(result: DuckDBResult, expectedColumns: readonly ExpectedColumn[]) { + assert.strictEqual(result.columnCount, expectedColumns.length, "column count"); + for (let i = 0; i < expectedColumns.length; i++) { + const { name, type } = expectedColumns[i]; + assert.strictEqual(result.columnName(i), name, "column name"); + assert.strictEqual(result.columnTypeId(i), type.typeId, `column type id (column: ${name})`); + assert.deepStrictEqual(result.columnType(i), type, `column type (column: ${name})`); + } +} + +function isVectorType>( + vector: DuckDBVector | null, + vectorType: new (...args: any[]) => TVector, +): vector is TVector { + return vector instanceof vectorType; +} + +function getColumnVector>( + chunk: DuckDBDataChunk, + columnIndex: number, + vectorType: new (...args: any[]) => TVector, +): TVector { + const columnVector = chunk.getColumnVector(columnIndex); + if (!isVectorType(columnVector, vectorType)) { + assert.fail(`expected column ${columnIndex} to be a ${vectorType}`); + } + return columnVector; +} + +function assertVectorValues( + vector: DuckDBVector | null | undefined, + values: readonly TValue[], + vectorName: string, +) { + if (!vector) { + 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: TValue | null = vector.getItem(i); + const expected = values[i]; + assert.deepStrictEqual( + actual, + expected, + `expected vector ${vectorName}[${i}] to be ${expected} but found ${actual}`, + ); + } +} + +function assertValues>( + chunk: DuckDBDataChunk, + columnIndex: number, + vectorType: new (...args: any[]) => TVector, + values: readonly (TValue | null)[], +) { + const vector = getColumnVector(chunk, columnIndex, vectorType); + assertVectorValues(vector, values, `${columnIndex}`); +} + +function bigints(start: bigint, end: bigint) { + return Array.from({ length: Number(end - start) + 1 }).map((_, i) => start + BigInt(i)); +} + +describe("api", () => { + test("should expose version", () => { + const ver = version(); + assert.ok(ver.startsWith("v"), `version starts with 'v'`); + }); + test("should expose configuration option descriptions", () => { + const descriptions = configurationOptionDescriptions(); + assert.ok(descriptions["memory_limit"], `descriptions has 'memory_limit'`); + }); + test("ReturnResultType enum", () => { + assert.equal(ResultReturnType.INVALID, 0); + assert.equal(ResultReturnType.CHANGED_ROWS, 1); + assert.equal(ResultReturnType.NOTHING, 2); + assert.equal(ResultReturnType.QUERY_RESULT, 3); + + assert.equal(ResultReturnType[ResultReturnType.INVALID], "INVALID"); + assert.equal(ResultReturnType[ResultReturnType.CHANGED_ROWS], "CHANGED_ROWS"); + assert.equal(ResultReturnType[ResultReturnType.NOTHING], "NOTHING"); + assert.equal(ResultReturnType[ResultReturnType.QUERY_RESULT], "QUERY_RESULT"); + }); + test("StatementType enum", () => { + assert.equal(StatementType.INVALID, 0); + assert.equal(StatementType.SELECT, 1); + assert.equal(StatementType.INSERT, 2); + assert.equal(StatementType.UPDATE, 3); + assert.equal(StatementType.EXPLAIN, 4); + assert.equal(StatementType.DELETE, 5); + assert.equal(StatementType.PREPARE, 6); + assert.equal(StatementType.CREATE, 7); + assert.equal(StatementType.EXECUTE, 8); + assert.equal(StatementType.ALTER, 9); + assert.equal(StatementType.TRANSACTION, 10); + assert.equal(StatementType.COPY, 11); + assert.equal(StatementType.ANALYZE, 12); + assert.equal(StatementType.VARIABLE_SET, 13); + assert.equal(StatementType.CREATE_FUNC, 14); + assert.equal(StatementType.DROP, 15); + assert.equal(StatementType.EXPORT, 16); + assert.equal(StatementType.PRAGMA, 17); + assert.equal(StatementType.VACUUM, 18); + assert.equal(StatementType.CALL, 19); + assert.equal(StatementType.SET, 20); + assert.equal(StatementType.LOAD, 21); + assert.equal(StatementType.RELATION, 22); + assert.equal(StatementType.EXTENSION, 23); + assert.equal(StatementType.LOGICAL_PLAN, 24); + assert.equal(StatementType.ATTACH, 25); + assert.equal(StatementType.DETACH, 26); + assert.equal(StatementType.MULTI, 27); + + assert.equal(StatementType[StatementType.INVALID], "INVALID"); + assert.equal(StatementType[StatementType.SELECT], "SELECT"); + assert.equal(StatementType[StatementType.INSERT], "INSERT"); + assert.equal(StatementType[StatementType.UPDATE], "UPDATE"); + assert.equal(StatementType[StatementType.EXPLAIN], "EXPLAIN"); + assert.equal(StatementType[StatementType.DELETE], "DELETE"); + assert.equal(StatementType[StatementType.PREPARE], "PREPARE"); + assert.equal(StatementType[StatementType.CREATE], "CREATE"); + assert.equal(StatementType[StatementType.EXECUTE], "EXECUTE"); + assert.equal(StatementType[StatementType.ALTER], "ALTER"); + assert.equal(StatementType[StatementType.TRANSACTION], "TRANSACTION"); + assert.equal(StatementType[StatementType.COPY], "COPY"); + assert.equal(StatementType[StatementType.ANALYZE], "ANALYZE"); + assert.equal(StatementType[StatementType.VARIABLE_SET], "VARIABLE_SET"); + assert.equal(StatementType[StatementType.CREATE_FUNC], "CREATE_FUNC"); + assert.equal(StatementType[StatementType.DROP], "DROP"); + assert.equal(StatementType[StatementType.EXPORT], "EXPORT"); + assert.equal(StatementType[StatementType.PRAGMA], "PRAGMA"); + assert.equal(StatementType[StatementType.VACUUM], "VACUUM"); + assert.equal(StatementType[StatementType.CALL], "CALL"); + assert.equal(StatementType[StatementType.SET], "SET"); + assert.equal(StatementType[StatementType.LOAD], "LOAD"); + assert.equal(StatementType[StatementType.RELATION], "RELATION"); + assert.equal(StatementType[StatementType.EXTENSION], "EXTENSION"); + assert.equal(StatementType[StatementType.LOGICAL_PLAN], "LOGICAL_PLAN"); + assert.equal(StatementType[StatementType.ATTACH], "ATTACH"); + assert.equal(StatementType[StatementType.DETACH], "DETACH"); + assert.equal(StatementType[StatementType.MULTI], "MULTI"); + }); + test("DuckDBType toString", () => { + assert.equal(DuckDBBooleanType.instance.toString(), "BOOLEAN"); + assert.equal(DuckDBTinyIntType.instance.toString(), "TINYINT"); + assert.equal(DuckDBSmallIntType.instance.toString(), "SMALLINT"); + assert.equal(DuckDBIntegerType.instance.toString(), "INTEGER"); + assert.equal(DuckDBBigIntType.instance.toString(), "BIGINT"); + assert.equal(DuckDBUTinyIntType.instance.toString(), "UTINYINT"); + assert.equal(DuckDBUSmallIntType.instance.toString(), "USMALLINT"); + assert.equal(DuckDBUIntegerType.instance.toString(), "UINTEGER"); + assert.equal(DuckDBUBigIntType.instance.toString(), "UBIGINT"); + assert.equal(DuckDBFloatType.instance.toString(), "FLOAT"); + assert.equal(DuckDBDoubleType.instance.toString(), "DOUBLE"); + assert.equal(DuckDBTimestampType.instance.toString(), "TIMESTAMP"); + assert.equal(DuckDBDateType.instance.toString(), "DATE"); + assert.equal(DuckDBTimeType.instance.toString(), "TIME"); + assert.equal(DuckDBIntervalType.instance.toString(), "INTERVAL"); + assert.equal(DuckDBHugeIntType.instance.toString(), "HUGEINT"); + assert.equal(DuckDBUHugeIntType.instance.toString(), "UHUGEINT"); + assert.equal(DuckDBVarCharType.instance.toString(), "VARCHAR"); + assert.equal(DuckDBBlobType.instance.toString(), "BLOB"); + assert.equal(new DuckDBDecimalType(17, 5).toString(), "DECIMAL(17,5)"); + assert.equal(DuckDBTimestampSecondsType.instance.toString(), "TIMESTAMP_S"); + assert.equal(DuckDBTimestampMillisecondsType.instance.toString(), "TIMESTAMP_MS"); + assert.equal(DuckDBTimestampNanosecondsType.instance.toString(), "TIMESTAMP_NS"); + assert.equal( + new DuckDBEnumType(["fly", "swim", "walk"], DuckDBTypeId.UTINYINT).toString(), + `ENUM('fly', 'swim', 'walk')`, + ); + assert.equal(new DuckDBListType(DuckDBIntegerType.instance).toString(), "INTEGER[]"); + assert.equal( + new DuckDBStructType(["id", "ts"], [DuckDBVarCharType.instance, DuckDBTimestampType.instance]).toString(), + 'STRUCT("id" VARCHAR, "ts" TIMESTAMP)', + ); + assert.equal( + new DuckDBMapType(DuckDBIntegerType.instance, DuckDBVarCharType.instance).toString(), + "MAP(INTEGER, VARCHAR)", + ); + assert.equal(new DuckDBArrayType(DuckDBIntegerType.instance, 3).toString(), "INTEGER[3]"); + assert.equal(DuckDBUUIDType.instance.toString(), "UUID"); + assert.equal( + new DuckDBUnionType(["str", "num"], [DuckDBVarCharType.instance, DuckDBIntegerType.instance]).toString(), + 'UNION("str" VARCHAR, "num" INTEGER)', + ); + assert.equal(DuckDBBitType.instance.toString(), "BIT"); + assert.equal(DuckDBTimeTZType.instance.toString(), "TIME WITH TIME ZONE"); + assert.equal(DuckDBTimestampTZType.instance.toString(), "TIMESTAMP WITH TIME ZONE"); + assert.equal(DuckDBAnyType.instance.toString(), "ANY"); + assert.equal(DuckDBVarIntType.instance.toString(), "VARINT"); + assert.equal(DuckDBSQLNullType.instance.toString(), "SQLNULL"); + }); + test("should support creating, connecting, running a basic query, and reading results", async () => { + const instance = await DuckDBInstance.create(); + const connection = await instance.connect(); + const result = await connection.run("select 42 as num"); + assertColumns(result, [{ name: "num", type: DuckDBIntegerType.instance }]); + const chunk = await result.fetchChunk(); + assert.strictEqual(chunk.columnCount, 1); + assert.strictEqual(chunk.rowCount, 1); + assertValues(chunk, 0, DuckDBIntegerVector, [42]); + }); + test("should support running prepared statements", async () => { + await withConnection(async connection => { + const prepared = await connection.prepare("select $num as a, $str as b, $bool as c, $null as d"); + assert.strictEqual(prepared.parameterCount, 4); + assert.strictEqual(prepared.parameterName(1), "num"); + assert.strictEqual(prepared.parameterName(2), "str"); + assert.strictEqual(prepared.parameterName(3), "bool"); + assert.strictEqual(prepared.parameterName(4), "null"); + prepared.bindInteger(1, 10); + prepared.bindVarchar(2, "abc"); + prepared.bindBoolean(3, true); + prepared.bindNull(4); + const result = await prepared.run(); + assertColumns(result, [ + { name: "a", type: DuckDBIntegerType.instance }, + { name: "b", type: DuckDBVarCharType.instance }, + { name: "c", type: DuckDBBooleanType.instance }, + { name: "d", type: DuckDBIntegerType.instance }, + ]); + const chunk = await result.fetchChunk(); + 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, 3, DuckDBIntegerVector, [null]); + }); + }); + test("should support starting prepared statements and running them incrementally", async () => { + await withConnection(async connection => { + const prepared = await connection.prepare("select int from test_all_types()"); + const pending = prepared.start(); + let taskCount = 0; + while (pending.runTask() !== DuckDBPendingResultState.RESULT_READY) { + taskCount++; + if (taskCount > 100) { + // arbitrary upper bound on the number of tasks expected for this simple query + assert.fail("Unexpectedly large number of tasks"); + } + await sleep(1); + } + // console.debug('task count: ', taskCount); + const result = await pending.getResult(); + assertColumns(result, [{ name: "int", type: DuckDBIntegerType.instance }]); + const chunk = await result.fetchChunk(); + assert.strictEqual(chunk.columnCount, 1); + assert.strictEqual(chunk.rowCount, 3); + assertValues(chunk, 0, DuckDBIntegerVector, [DuckDBIntegerType.Min, DuckDBIntegerType.Max, null]); + }); + }); + test("should support streaming results from prepared statements", async () => { + await withConnection(async connection => { + const prepared = await connection.prepare("from range(10000)"); + const pending = prepared.start(); + const result = await pending.getResult(); + assertColumns(result, [{ name: "range", type: DuckDBBigIntType.instance }]); + const chunks: DuckDBDataChunk[] = []; + let currentChunk: DuckDBDataChunk | null = null; + currentChunk = await result.fetchChunk(); + while (currentChunk.rowCount > 0) { + chunks.push(currentChunk); + currentChunk = await result.fetchChunk(); + } + currentChunk = null; + assert.strictEqual(chunks.length, 5); // ceil(10000 / 2048) = 5 + assertValues(chunks[0], 0, DuckDBBigIntVector, bigints(0n, 2048n - 1n)); + assertValues(chunks[1], 0, DuckDBBigIntVector, bigints(2048n, 2048n * 2n - 1n)); + assertValues(chunks[2], 0, DuckDBBigIntVector, bigints(2048n * 2n, 2048n * 3n - 1n)); + assertValues(chunks[3], 0, DuckDBBigIntVector, bigints(2048n * 3n, 2048n * 4n - 1n)); + assertValues(chunks[4], 0, DuckDBBigIntVector, bigints(2048n * 4n, 9999n)); + }); + }); + test("should support all data types", async () => { + await withConnection(async connection => { + const result = await connection.run("from test_all_types(use_large_enum=true)"); + 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}`); + assertColumns(result, [ + { name: "bool", type: DuckDBBooleanType.instance }, + { name: "tinyint", type: DuckDBTinyIntType.instance }, + { name: "smallint", type: DuckDBSmallIntType.instance }, + { name: "int", type: DuckDBIntegerType.instance }, + { name: "bigint", type: DuckDBBigIntType.instance }, + { name: "hugeint", type: DuckDBHugeIntType.instance }, + { name: "uhugeint", type: DuckDBUHugeIntType.instance }, + { name: "utinyint", type: DuckDBUTinyIntType.instance }, + { name: "usmallint", type: DuckDBUSmallIntType.instance }, + { name: "uint", type: DuckDBUIntegerType.instance }, + { name: "ubigint", type: DuckDBUBigIntType.instance }, + { name: "varint", type: DuckDBVarIntType.instance }, + { name: "date", type: DuckDBDateType.instance }, + { name: "time", type: DuckDBTimeType.instance }, + { name: "timestamp", type: DuckDBTimestampType.instance }, + { name: "timestamp_s", type: DuckDBTimestampSecondsType.instance }, + { name: "timestamp_ms", type: DuckDBTimestampMillisecondsType.instance }, + { name: "timestamp_ns", type: DuckDBTimestampNanosecondsType.instance }, + { name: "time_tz", type: DuckDBTimeTZType.instance }, + { name: "timestamp_tz", type: DuckDBTimestampTZType.instance }, + { name: "float", type: DuckDBFloatType.instance }, + { name: "double", type: DuckDBDoubleType.instance }, + { name: "dec_4_1", type: new DuckDBDecimalType(4, 1) }, + { name: "dec_9_4", type: new DuckDBDecimalType(9, 4) }, + { name: "dec_18_6", type: new DuckDBDecimalType(18, 6) }, + { name: "dec38_10", type: new DuckDBDecimalType(38, 10) }, + { name: "uuid", type: DuckDBUUIDType.instance }, + { name: "interval", type: DuckDBIntervalType.instance }, + { name: "varchar", type: DuckDBVarCharType.instance }, + { name: "blob", type: DuckDBBlobType.instance }, + { name: "bit", type: DuckDBBitType.instance }, + { name: "small_enum", type: new DuckDBEnumType(smallEnumValues, DuckDBTypeId.UTINYINT) }, + { name: "medium_enum", type: new DuckDBEnumType(mediumEnumValues, DuckDBTypeId.USMALLINT) }, + { name: "large_enum", type: new DuckDBEnumType(largeEnumValues, DuckDBTypeId.UINTEGER) }, + { name: "int_array", type: new DuckDBListType(DuckDBIntegerType.instance) }, + { name: "double_array", type: new DuckDBListType(DuckDBDoubleType.instance) }, + { name: "date_array", type: new DuckDBListType(DuckDBDateType.instance) }, + { name: "timestamp_array", type: new DuckDBListType(DuckDBTimestampType.instance) }, + { 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(["a", "b"], [DuckDBIntegerType.instance, DuckDBVarCharType.instance]), + }, + { + name: "struct_of_arrays", + type: new DuckDBStructType( + ["a", "b"], + [new DuckDBListType(DuckDBIntegerType.instance), new DuckDBListType(DuckDBVarCharType.instance)], + ), + }, + { + name: "array_of_structs", + type: new DuckDBListType( + new DuckDBStructType(["a", "b"], [DuckDBIntegerType.instance, DuckDBVarCharType.instance]), + ), + }, + { name: "map", type: new DuckDBMapType(DuckDBVarCharType.instance, DuckDBVarCharType.instance) }, + { + name: "union", + type: new DuckDBUnionType(["name", "age"], [DuckDBVarCharType.instance, DuckDBSmallIntType.instance]), + }, + { name: "fixed_int_array", type: new DuckDBArrayType(DuckDBIntegerType.instance, 3) }, + { name: "fixed_varchar_array", type: new DuckDBArrayType(DuckDBVarCharType.instance, 3) }, + { + name: "fixed_nested_int_array", + type: new DuckDBArrayType(new DuckDBArrayType(DuckDBIntegerType.instance, 3), 3), + }, + { + name: "fixed_nested_varchar_array", + type: new DuckDBArrayType(new DuckDBArrayType(DuckDBVarCharType.instance, 3), 3), + }, + { + name: "fixed_struct_array", + type: new DuckDBArrayType( + new DuckDBStructType(["a", "b"], [DuckDBIntegerType.instance, DuckDBVarCharType.instance]), + 3, + ), + }, + { + name: "struct_of_fixed_array", + type: new DuckDBStructType( + ["a", "b"], + [new DuckDBArrayType(DuckDBIntegerType.instance, 3), new DuckDBArrayType(DuckDBVarCharType.instance, 3)], + ), + }, + { + 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)), + }, + ]); + + const chunk = await result.fetchChunk(); + assert.strictEqual(chunk.columnCount, 54); + assert.strictEqual(chunk.rowCount, 3); + + assertValues(chunk, 0, DuckDBBooleanVector, [false, true, null]); + assertValues(chunk, 1, DuckDBTinyIntVector, [DuckDBTinyIntType.Min, DuckDBTinyIntType.Max, null]); + assertValues(chunk, 2, DuckDBSmallIntVector, [DuckDBSmallIntType.Min, DuckDBSmallIntType.Max, null]); + assertValues(chunk, 3, DuckDBIntegerVector, [DuckDBIntegerType.Min, DuckDBIntegerType.Max, null]); + assertValues(chunk, 4, DuckDBBigIntVector, [DuckDBBigIntType.Min, DuckDBBigIntType.Max, null]); + assertValues(chunk, 5, DuckDBHugeIntVector, [DuckDBHugeIntType.Min, DuckDBHugeIntType.Max, null]); + assertValues(chunk, 6, DuckDBUHugeIntVector, [DuckDBUHugeIntType.Min, DuckDBUHugeIntType.Max, null]); + assertValues(chunk, 7, DuckDBUTinyIntVector, [DuckDBUTinyIntType.Min, DuckDBUTinyIntType.Max, null]); + assertValues(chunk, 8, DuckDBUSmallIntVector, [DuckDBUSmallIntType.Min, DuckDBUSmallIntType.Max, null]); + assertValues(chunk, 9, DuckDBUIntegerVector, [DuckDBUIntegerType.Min, DuckDBUIntegerType.Max, null]); + assertValues(chunk, 10, DuckDBUBigIntVector, [DuckDBUBigIntType.Min, DuckDBUBigIntType.Max, null]); + assertValues(chunk, 11, DuckDBVarIntVector, [DuckDBVarIntType.Min, DuckDBVarIntType.Max, null]); + assertValues(chunk, 12, DuckDBDateVector, [DuckDBDateValue.Min, DuckDBDateValue.Max, null]); + assertValues(chunk, 13, DuckDBTimeVector, [DuckDBTimeValue.Min, DuckDBTimeValue.Max, null]); + assertValues(chunk, 14, DuckDBTimestampVector, [DuckDBTimestampValue.Min, DuckDBTimestampValue.Max, null]); + assertValues(chunk, 15, DuckDBTimestampSecondsVector, [ + DuckDBTimestampSecondsValue.Min, + DuckDBTimestampSecondsValue.Max, + null, + ]); + assertValues(chunk, 16, DuckDBTimestampMillisecondsVector, [ + DuckDBTimestampMillisecondsValue.Min, + DuckDBTimestampMillisecondsValue.Max, + null, + ]); + assertValues(chunk, 17, DuckDBTimestampNanosecondsVector, [ + DuckDBTimestampNanosecondsValue.Min, + DuckDBTimestampNanosecondsValue.Max, + null, + ]); + assertValues(chunk, 18, DuckDBTimeTZVector, [DuckDBTimeTZValue.Min, DuckDBTimeTZValue.Max, null]); + assertValues(chunk, 19, DuckDBTimestampTZVector, [DuckDBTimestampTZValue.Min, DuckDBTimestampTZValue.Max, null]); + assertValues(chunk, 20, DuckDBFloatVector, [DuckDBFloatType.Min, DuckDBFloatType.Max, null]); + assertValues(chunk, 21, DuckDBDoubleVector, [DuckDBDoubleType.Min, DuckDBDoubleType.Max, null]); + assertValues(chunk, 22, DuckDBDecimal2Vector, [decimalValue(-9999n, 4, 1), decimalValue(9999n, 4, 1), null]); + assertValues(chunk, 23, DuckDBDecimal4Vector, [ + decimalValue(-999999999n, 9, 4), + decimalValue(999999999n, 9, 4), + null, + ]); + assertValues(chunk, 24, DuckDBDecimal8Vector, [ + decimalValue(-BI_18_9s, 18, 6), + decimalValue(BI_18_9s, 18, 6), + null, + ]); + assertValues(chunk, 25, DuckDBDecimal16Vector, [ + decimalValue(-BI_38_9s, 38, 10), + decimalValue(BI_38_9s, 38, 10), + null, + ]); + assertValues(chunk, 26, DuckDBUUIDVector, [DuckDBUUIDValue.Min, DuckDBUUIDValue.Max, null]); + assertValues(chunk, 27, DuckDBIntervalVector, [ + intervalValue(0, 0, 0n), + intervalValue(999, 999, 999999999n), + null, + ]); + assertValues(chunk, 28, DuckDBVarCharVector, ["🦆🦆🦆🦆🦆🦆", "goo\0se", null]); + assertValues(chunk, 29, DuckDBBlobVector, [ + DuckDBBlobValue.fromString("thisisalongblob\x00withnullbytes"), + DuckDBBlobValue.fromString("\x00\x00\x00a"), + null, + ]); + assertValues(chunk, 30, DuckDBBitVector, [bitValue("0010001001011100010101011010111"), bitValue("10101"), null]); + assertValues(chunk, 31, DuckDBEnum1Vector, [ + smallEnumValues[0], + smallEnumValues[smallEnumValues.length - 1], + null, + ]); + assertValues(chunk, 32, DuckDBEnum2Vector, [ + mediumEnumValues[0], + mediumEnumValues[mediumEnumValues.length - 1], + null, + ]); + assertValues(chunk, 33, DuckDBEnum4Vector, [ + largeEnumValues[0], + largeEnumValues[largeEnumValues.length - 1], + null, + ]); + // int_array + assertValues(chunk, 34, DuckDBListVector, [listValue([]), listValue([42, 999, null, null, -42]), null]); + // double_array + assertValues(chunk, 35, DuckDBListVector, [ + listValue([]), + listValue([42.0, NaN, Infinity, -Infinity, null, -42.0]), + null, + ]); + // date_array + assertValues(chunk, 36, DuckDBListVector, [ + listValue([]), + listValue([dateValue(0), DuckDBDateValue.PosInf, DuckDBDateValue.NegInf, null, dateValue(19124)]), + null, + ]); + // timestamp_array + assertValues(chunk, 37, DuckDBListVector, [ + listValue([]), + listValue([ + DuckDBTimestampValue.Epoch, + DuckDBTimestampValue.PosInf, + DuckDBTimestampValue.NegInf, + null, + // 1652372625 is 2022-05-12 16:23:45 + timestampValue(1652372625n * 1000n * 1000n), + ]), + null, + ]); + // timestamptz_array + assertValues(chunk, 38, DuckDBListVector, [ + listValue([]), + listValue([ + DuckDBTimestampTZValue.Epoch, + DuckDBTimestampTZValue.PosInf, + DuckDBTimestampTZValue.NegInf, + null, + // 1652397825 = 1652372625 + 25200, 25200 = 7 * 60 * 60 = 7 hours in seconds + // This 7 hour difference is hard coded into test_all_types (value is 2022-05-12 16:23:45-07) + timestampTZValue(1652397825n * 1000n * 1000n), + ]), + null, + ]); + // varchar_array + assertValues(chunk, 39, DuckDBListVector, [ + listValue([]), + // Note that the string 'goose' in varchar_array does NOT have an embedded null character. + listValue(["🦆🦆🦆🦆🦆🦆", "goose", null, ""]), + null, + ]); + // nested_int_array + assertValues(chunk, 40, DuckDBListVector, [ + listValue([]), + listValue([ + listValue([]), + listValue([42, 999, null, null, -42]), + null, + listValue([]), + listValue([42, 999, null, null, -42]), + ]), + null, + ]); + assertValues(chunk, 41, DuckDBStructVector, [ + structValue({ "a": null, "b": null }), + structValue({ "a": 42, "b": "🦆🦆🦆🦆🦆🦆" }), + null, + ]); + // struct_of_arrays + assertValues(chunk, 42, DuckDBStructVector, [ + structValue({ "a": null, "b": null }), + structValue({ + "a": listValue([42, 999, null, null, -42]), + "b": listValue(["🦆🦆🦆🦆🦆🦆", "goose", null, ""]), + }), + null, + ]); + // array_of_structs + assertValues(chunk, 43, DuckDBListVector, [ + listValue([]), + listValue([structValue({ "a": null, "b": null }), structValue({ "a": 42, "b": "🦆🦆🦆🦆🦆🦆" }), null]), + null, + ]); + assertValues(chunk, 44, DuckDBMapVector, [ + mapValue([]), + mapValue([ + { key: "key1", value: "🦆🦆🦆🦆🦆🦆" }, + { key: "key2", value: "goose" }, + ]), + null, + ]); + assertValues(chunk, 45, DuckDBUnionVector, [ + unionValue("name", "Frank"), + unionValue("age", 5), + null, + ]); + // fixed_int_array + assertValues(chunk, 46, DuckDBArrayVector, [arrayValue([null, 2, 3]), arrayValue([4, 5, 6]), null]); + // fixed_varchar_array + assertValues(chunk, 47, DuckDBArrayVector, [arrayValue(["a", null, "c"]), arrayValue(["d", "e", "f"]), null]); + // fixed_nested_int_array + assertValues(chunk, 48, DuckDBArrayVector, [ + arrayValue([arrayValue([null, 2, 3]), null, arrayValue([null, 2, 3])]), + arrayValue([arrayValue([4, 5, 6]), arrayValue([null, 2, 3]), arrayValue([4, 5, 6])]), + null, + ]); + // fixed_nested_varchar_array + assertValues(chunk, 49, DuckDBArrayVector, [ + arrayValue([arrayValue(["a", null, "c"]), null, arrayValue(["a", null, "c"])]), + arrayValue([arrayValue(["d", "e", "f"]), arrayValue(["a", null, "c"]), arrayValue(["d", "e", "f"])]), + null, + ]); + // fixed_struct_array + assertValues(chunk, 50, DuckDBArrayVector, [ + arrayValue([ + structValue({ "a": null, "b": null }), + structValue({ "a": 42, "b": "🦆🦆🦆🦆🦆🦆" }), + structValue({ "a": null, "b": null }), + ]), + arrayValue([ + structValue({ "a": 42, "b": "🦆🦆🦆🦆🦆🦆" }), + structValue({ "a": null, "b": null }), + structValue({ "a": 42, "b": "🦆🦆🦆🦆🦆🦆" }), + ]), + null, + ]); + // struct_of_fixed_array + assertValues(chunk, 51, DuckDBStructVector, [ + structValue({ + "a": arrayValue([null, 2, 3]), + "b": arrayValue(["a", null, "c"]), + }), + structValue({ + "a": arrayValue([4, 5, 6]), + "b": arrayValue(["d", "e", "f"]), + }), + null, + ]); + // fixed_array_of_int_list + assertValues(chunk, 52, DuckDBArrayVector, [ + arrayValue([listValue([]), listValue([42, 999, null, null, -42]), listValue([])]), + arrayValue([listValue([42, 999, null, null, -42]), listValue([]), listValue([42, 999, null, null, -42])]), + null, + ]); + // list_of_fixed_int_array + assertValues(chunk, 53, DuckDBListVector, [ + listValue([arrayValue([null, 2, 3]), arrayValue([4, 5, 6]), arrayValue([null, 2, 3])]), + listValue([arrayValue([4, 5, 6]), arrayValue([null, 2, 3]), arrayValue([4, 5, 6])]), + null, + ]); + }); + }); + test("values toString", () => { + // array + assert.equal(arrayValue([]).toString(), "[]"); + assert.equal(arrayValue([1, 2, 3]).toString(), "[1, 2, 3]"); + assert.equal(arrayValue(["a", "b", "c"]).toString(), `['a', 'b', 'c']`); + + // bit + assert.equal(bitValue("").toString(), ""); + assert.equal(bitValue("10101").toString(), "10101"); + assert.equal(bitValue("0010001001011100010101011010111").toString(), "0010001001011100010101011010111"); + + // blob + assert.equal(DuckDBBlobValue.fromString("").toString(), ""); + assert.equal( + DuckDBBlobValue.fromString("thisisalongblob\x00withnullbytes").toString(), + "thisisalongblob\\x00withnullbytes", + ); + assert.equal(DuckDBBlobValue.fromString("\x00\x00\x00a").toString(), "\\x00\\x00\\x00a"); + + // date + assert.equal(DuckDBDateValue.Epoch.toString(), "1970-01-01"); + assert.equal(DuckDBDateValue.Max.toString(), "5881580-07-10"); + assert.equal(DuckDBDateValue.Min.toString(), "5877642-06-25 (BC)"); + + // decimal + assert.equal(decimalValue(0n, 4, 1).toString(), "0.0"); + assert.equal(decimalValue(9876n, 4, 1).toString(), "987.6"); + assert.equal(decimalValue(-9876n, 4, 1).toString(), "-987.6"); + + assert.equal(decimalValue(0n, 9, 4).toString(), "0.0000"); + assert.equal(decimalValue(987654321n, 9, 4).toString(), "98765.4321"); + assert.equal(decimalValue(-987654321n, 9, 4).toString(), "-98765.4321"); + + assert.equal(decimalValue(0n, 18, 6).toString(), "0.000000"); + assert.equal(decimalValue(987654321098765432n, 18, 6).toString(), "987654321098.765432"); + assert.equal(decimalValue(-987654321098765432n, 18, 6).toString(), "-987654321098.765432"); + + assert.equal(decimalValue(0n, 38, 10).toString(), "0.0000000000"); + assert.equal( + decimalValue(98765432109876543210987654321098765432n, 38, 10).toString(), + "9876543210987654321098765432.1098765432", + ); + assert.equal( + decimalValue(-98765432109876543210987654321098765432n, 38, 10).toString(), + "-9876543210987654321098765432.1098765432", + ); + + // interval + assert.equal(intervalValue(0, 0, 0n).toString(), "00:00:00"); + + assert.equal(intervalValue(1, 0, 0n).toString(), "1 month"); + assert.equal(intervalValue(-1, 0, 0n).toString(), "-1 month"); + assert.equal(intervalValue(2, 0, 0n).toString(), "2 months"); + assert.equal(intervalValue(-2, 0, 0n).toString(), "-2 months"); + assert.equal(intervalValue(12, 0, 0n).toString(), "1 year"); + assert.equal(intervalValue(-12, 0, 0n).toString(), "-1 year"); + assert.equal(intervalValue(24, 0, 0n).toString(), "2 years"); + assert.equal(intervalValue(-24, 0, 0n).toString(), "-2 years"); + assert.equal(intervalValue(25, 0, 0n).toString(), "2 years 1 month"); + assert.equal(intervalValue(-25, 0, 0n).toString(), "-2 years -1 month"); + + assert.equal(intervalValue(0, 1, 0n).toString(), "1 day"); + assert.equal(intervalValue(0, -1, 0n).toString(), "-1 day"); + assert.equal(intervalValue(0, 2, 0n).toString(), "2 days"); + assert.equal(intervalValue(0, -2, 0n).toString(), "-2 days"); + assert.equal(intervalValue(0, 30, 0n).toString(), "30 days"); + assert.equal(intervalValue(0, 365, 0n).toString(), "365 days"); + + assert.equal(intervalValue(0, 0, 1n).toString(), "00:00:00.000001"); + assert.equal(intervalValue(0, 0, -1n).toString(), "-00:00:00.000001"); + assert.equal(intervalValue(0, 0, 987654n).toString(), "00:00:00.987654"); + assert.equal(intervalValue(0, 0, -987654n).toString(), "-00:00:00.987654"); + assert.equal(intervalValue(0, 0, 1000000n).toString(), "00:00:01"); + assert.equal(intervalValue(0, 0, -1000000n).toString(), "-00:00:01"); + assert.equal(intervalValue(0, 0, 59n * 1000000n).toString(), "00:00:59"); + assert.equal(intervalValue(0, 0, -59n * 1000000n).toString(), "-00:00:59"); + assert.equal(intervalValue(0, 0, 60n * 1000000n).toString(), "00:01:00"); + assert.equal(intervalValue(0, 0, -60n * 1000000n).toString(), "-00:01:00"); + assert.equal(intervalValue(0, 0, 59n * 60n * 1000000n).toString(), "00:59:00"); + assert.equal(intervalValue(0, 0, -59n * 60n * 1000000n).toString(), "-00:59:00"); + assert.equal(intervalValue(0, 0, 60n * 60n * 1000000n).toString(), "01:00:00"); + assert.equal(intervalValue(0, 0, -60n * 60n * 1000000n).toString(), "-01:00:00"); + assert.equal(intervalValue(0, 0, 24n * 60n * 60n * 1000000n).toString(), "24:00:00"); + assert.equal(intervalValue(0, 0, -24n * 60n * 60n * 1000000n).toString(), "-24:00:00"); + assert.equal(intervalValue(0, 0, 2147483647n * 60n * 60n * 1000000n).toString(), "2147483647:00:00"); + assert.equal(intervalValue(0, 0, -2147483647n * 60n * 60n * 1000000n).toString(), "-2147483647:00:00"); + assert.equal(intervalValue(0, 0, 2147483647n * 60n * 60n * 1000000n + 1n).toString(), "2147483647:00:00.000001"); + assert.equal( + intervalValue(0, 0, -(2147483647n * 60n * 60n * 1000000n + 1n)).toString(), + "-2147483647:00:00.000001", + ); + + assert.equal( + intervalValue(2 * 12 + 3, 5, (7n * 60n * 60n + 11n * 60n + 13n) * 1000000n + 17n).toString(), + "2 years 3 months 5 days 07:11:13.000017", + ); + assert.equal( + intervalValue(-(2 * 12 + 3), -5, -((7n * 60n * 60n + 11n * 60n + 13n) * 1000000n + 17n)).toString(), + "-2 years -3 months -5 days -07:11:13.000017", + ); + + // list + assert.equal(listValue([]).toString(), "[]"); + assert.equal(listValue([1, 2, 3]).toString(), "[1, 2, 3]"); + assert.equal(listValue(["a", "b", "c"]).toString(), `['a', 'b', 'c']`); + + // map + assert.equal(mapValue([]).toString(), "{}"); + assert.equal( + mapValue([ + { key: 1, value: "a" }, + { key: 2, value: "b" }, + ]).toString(), + `{1: 'a', 2: 'b'}`, + ); + + // struct + assert.equal(structValue({}).toString(), "{}"); + assert.equal(structValue({ a: 1, b: 2 }).toString(), `{'a': 1, 'b': 2}`); + + // timestamp milliseconds + assert.equal(DuckDBTimestampMillisecondsValue.Epoch.toString(), "1970-01-01 00:00:00"); + assert.equal(DuckDBTimestampMillisecondsValue.Max.toString(), "294247-01-10 04:00:54.775"); + assert.equal(DuckDBTimestampMillisecondsValue.Min.toString(), "290309-12-22 (BC) 00:00:00"); + + // timestamp nanoseconds + assert.equal(DuckDBTimestampNanosecondsValue.Epoch.toString(), "1970-01-01 00:00:00"); + assert.equal(DuckDBTimestampNanosecondsValue.Max.toString(), "2262-04-11 23:47:16.854775806"); + assert.equal(DuckDBTimestampNanosecondsValue.Min.toString(), "1677-09-22 00:00:00"); + + // timestamp seconds + assert.equal(DuckDBTimestampSecondsValue.Epoch.toString(), "1970-01-01 00:00:00"); + assert.equal(DuckDBTimestampSecondsValue.Max.toString(), "294247-01-10 04:00:54"); + assert.equal(DuckDBTimestampSecondsValue.Min.toString(), "290309-12-22 (BC) 00:00:00"); + + // timestamp tz + assert.equal(DuckDBTimestampTZValue.Epoch.toString(), "1970-01-01 00:00:00"); + // assert.equal(DuckDBTimestampTZValue.Max.toString(), '294247-01-09 20:00:54.775806-08'); // in PST + assert.equal(DuckDBTimestampTZValue.Max.toString(), "294247-01-10 04:00:54.775806"); // TODO TZ + // assert.equal(DuckDBTimestampTZValue.Min.toString(), '290309-12-21 (BC) 16:00:00-08'); // in PST + assert.equal(DuckDBTimestampTZValue.Min.toString(), "290309-12-22 (BC) 00:00:00"); // TODO TZ + assert.equal(DuckDBTimestampTZValue.PosInf.toString(), "infinity"); + assert.equal(DuckDBTimestampTZValue.NegInf.toString(), "-infinity"); + + // timestamp + assert.equal(DuckDBTimestampValue.Epoch.toString(), "1970-01-01 00:00:00"); + assert.equal(DuckDBTimestampValue.Max.toString(), "294247-01-10 04:00:54.775806"); + assert.equal(DuckDBTimestampValue.Min.toString(), "290309-12-22 (BC) 00:00:00"); + assert.equal(DuckDBTimestampValue.PosInf.toString(), "infinity"); + assert.equal(DuckDBTimestampValue.NegInf.toString(), "-infinity"); + + // time tz + assert.equal(timeTZValue(0n, 0).toString(), "00:00:00"); + // assert.equal(DuckDBTimeTZValue.Max.toString(), '24:00:00-15:59:59'); + assert.equal(DuckDBTimeTZValue.Max.toString(), "24:00:00"); // TODO TZ + // assert.equal(DuckDBTimeTZValue.Max.toString(), '00:00:00+15:59:59'); + assert.equal(DuckDBTimeTZValue.Min.toString(), "00:00:00"); // TODO TZ + + // time + assert.equal(DuckDBTimeValue.Max.toString(), "24:00:00"); + assert.equal(DuckDBTimeValue.Min.toString(), "00:00:00"); + assert.equal(timeValue((12n * 60n * 60n + 34n * 60n + 56n) * 1000000n + 987654n).toString(), "12:34:56.987654"); + + // union + assert.equal(unionValue("a", 42).toString(), "42"); + assert.equal(unionValue("b", "duck").toString(), "duck"); + + // uuid + assert.equal(DuckDBUUIDValue.Min.toString(), "00000000-0000-0000-0000-000000000000"); + assert.equal(DuckDBUUIDValue.Max.toString(), "ffffffff-ffff-ffff-ffff-ffffffffffff"); + }); + test("date isFinite", () => { + assert(DuckDBDateValue.Epoch.isFinite); + assert(DuckDBDateValue.Max.isFinite); + assert(DuckDBDateValue.Min.isFinite); + assert(!DuckDBDateValue.PosInf.isFinite); + assert(!DuckDBDateValue.NegInf.isFinite); + }); + test("value conversion", () => { + const dateParts: DateParts = { year: 2024, month: 6, day: 3 }; + const timeParts: TimeParts = { hour: 12, min: 34, sec: 56, micros: 789123 }; + const timeTZParts: TimeTZParts = { time: timeParts, offset: DuckDBTimeTZValue.MinOffset }; + const timestampParts: TimestampParts = { date: dateParts, time: timeParts }; + + assert.deepEqual(DuckDBDateValue.fromParts(dateParts).toParts(), dateParts); + assert.deepEqual(DuckDBTimeValue.fromParts(timeParts).toParts(), timeParts); + assert.deepEqual(DuckDBTimeTZValue.fromParts(timeTZParts).toParts(), timeTZParts); + assert.deepEqual(DuckDBTimestampValue.fromParts(timestampParts).toParts(), timestampParts); + assert.deepEqual(DuckDBTimestampTZValue.fromParts(timestampParts).toParts(), timestampParts); + + assert.deepEqual(DuckDBDecimalValue.fromDouble(3.14159, 6, 5), decimalValue(314159n, 6, 5)); + assert.deepEqual(decimalValue(314159n, 6, 5).toDouble(), 3.14159); + }); + test("result inspection conveniences", async () => { + await withConnection(async connection => { + const result = await connection.run("select i::int as a, i::int + 10 as b from range(3) t(i)"); + assert.deepEqual(result.columnNames(), ["a", "b"]); + assert.deepEqual(result.columnTypes(), [DuckDBIntegerType.instance, DuckDBIntegerType.instance]); + const chunks = await result.fetchAllChunks(); + const chunkColumns = chunks.map(chunk => chunk.getColumns()); + assert.deepEqual(chunkColumns, [ + [ + [0, 1, 2], + [10, 11, 12], + ], + ]); + const chunkRows = chunks.map(chunk => chunk.getRows()); + assert.deepEqual(chunkRows, [ + [ + [0, 10], + [1, 11], + [2, 12], + ], + ]); + }); + }); + test("result reader", async () => { + await withConnection(async connection => { + const reader = await connection.runAndReadAll("select i::int as a, i::int + 10000 as b from range(5000) t(i)"); + assert.deepEqual(reader.columnNames(), ["a", "b"]); + assert.deepEqual(reader.columnTypes(), [DuckDBIntegerType.instance, DuckDBIntegerType.instance]); + const columns = reader.getColumns(); + assert.equal(columns.length, 2); + assert.equal(columns[0][0], 0); + assert.equal(columns[0][4999], 4999); + assert.equal(columns[1][0], 10000); + assert.equal(columns[1][4999], 14999); + const rows = reader.getRows(); + assert.equal(rows.length, 5000); + assert.deepEqual(rows[0], [0, 10000]); + assert.deepEqual(rows[4999], [4999, 14999]); + }); + }); + test("default duckdb_api without explicit options", async () => { + const instance = await DuckDBInstance.create(); + const connection = await instance.connect(); + const result = await connection.run(`select current_setting('duckdb_api') as duckdb_api`); + assertColumns(result, [{ name: "duckdb_api", type: DuckDBVarCharType.instance }]); + const chunk = await result.fetchChunk(); + assert.strictEqual(chunk.columnCount, 1); + assert.strictEqual(chunk.rowCount, 1); + assertValues(chunk, 0, DuckDBVarCharVector, ["node-neo-api"]); + }); + test("default duckdb_api with explicit options", async () => { + const instance = await DuckDBInstance.create(undefined, {}); + const connection = await instance.connect(); + const result = await connection.run(`select current_setting('duckdb_api') as duckdb_api`); + assertColumns(result, [{ name: "duckdb_api", type: DuckDBVarCharType.instance }]); + const chunk = await result.fetchChunk(); + assert.strictEqual(chunk.columnCount, 1); + assert.strictEqual(chunk.rowCount, 1); + assertValues(chunk, 0, DuckDBVarCharVector, ["node-neo-api"]); + }); + test("overriding duckdb_api", async () => { + const instance = await DuckDBInstance.create(undefined, { "duckdb_api": "custom-duckdb-api" }); + const connection = await instance.connect(); + const result = await connection.run(`select current_setting('duckdb_api') as duckdb_api`); + assertColumns(result, [{ name: "duckdb_api", type: DuckDBVarCharType.instance }]); + const chunk = await result.fetchChunk(); + assert.strictEqual(chunk.columnCount, 1); + assert.strictEqual(chunk.rowCount, 1); + assertValues(chunk, 0, DuckDBVarCharVector, ["custom-duckdb-api"]); + }); +}); diff --git a/test/napi/napi-app/main.cpp b/test/napi/napi-app/main.cpp index 334ebafaca..64b4450c3d 100644 --- a/test/napi/napi-app/main.cpp +++ b/test/napi/napi-app/main.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -969,6 +970,112 @@ static napi_value try_add_tag(const Napi::CallbackInfo &info) { return Napi::Boolean::New(env, status == napi_ok); } +static napi_value bigint_to_i64(const Napi::CallbackInfo &info) { + napi_env env = info.Env(); + // start at 1 is intentional, since argument 0 is the callback to run GC + // passed to every function + // perform test on all arguments + for (size_t i = 1; i < info.Length(); i++) { + napi_value bigint = info[i]; + + napi_valuetype type; + NODE_API_CALL(env, napi_typeof(env, bigint, &type)); + + int64_t result = 0; + bool lossless = false; + + if (type != napi_bigint) { + printf("napi_get_value_bigint_int64 return for non-bigint: %d\n", + napi_get_value_bigint_int64(env, bigint, &result, &lossless)); + } else { + NODE_API_CALL( + env, napi_get_value_bigint_int64(env, bigint, &result, &lossless)); + printf("napi_get_value_bigint_int64 result: %" PRId64 "\n", result); + printf("lossless: %s\n", lossless ? "true" : "false"); + } + } + + return ok(env); +} + +static napi_value bigint_to_u64(const Napi::CallbackInfo &info) { + napi_env env = info.Env(); + // start at 1 is intentional, since argument 0 is the callback to run GC + // passed to every function + // perform test on all arguments + for (size_t i = 1; i < info.Length(); i++) { + napi_value bigint = info[i]; + + napi_valuetype type; + NODE_API_CALL(env, napi_typeof(env, bigint, &type)); + + uint64_t result; + bool lossless; + + if (type != napi_bigint) { + printf("napi_get_value_bigint_uint64 return for non-bigint: %d\n", + napi_get_value_bigint_uint64(env, bigint, &result, &lossless)); + } else { + NODE_API_CALL( + env, napi_get_value_bigint_uint64(env, bigint, &result, &lossless)); + printf("napi_get_value_bigint_uint64 result: %" PRIu64 "\n", result); + printf("lossless: %s\n", lossless ? "true" : "false"); + } + } + + return ok(env); +} + +static napi_value bigint_to_64_null(const Napi::CallbackInfo &info) { + napi_env env = info.Env(); + + napi_value bigint; + NODE_API_CALL(env, napi_create_bigint_int64(env, 5, &bigint)); + + int64_t result_signed; + uint64_t result_unsigned; + bool lossless; + + printf("status (int64, null result) = %d\n", + napi_get_value_bigint_int64(env, bigint, nullptr, &lossless)); + printf("status (int64, null lossless) = %d\n", + napi_get_value_bigint_int64(env, bigint, &result_signed, nullptr)); + printf("status (uint64, null result) = %d\n", + napi_get_value_bigint_uint64(env, bigint, nullptr, &lossless)); + printf("status (uint64, null lossless) = %d\n", + napi_get_value_bigint_uint64(env, bigint, &result_unsigned, nullptr)); + + return ok(env); +} + +static napi_value create_weird_bigints(const Napi::CallbackInfo &info) { + // create bigints by passing weird parameters to napi_create_bigint_words + napi_env env = info.Env(); + + std::array bigints; + std::array words{{123, 0, 0, 0}}; + + NODE_API_CALL(env, napi_create_bigint_int64(env, 0, &bigints[0])); + NODE_API_CALL(env, napi_create_bigint_uint64(env, 0, &bigints[1])); + // sign is not 0 or 1 (should be interpreted as negative) + NODE_API_CALL(env, + napi_create_bigint_words(env, 2, 1, words.data(), &bigints[2])); + // leading zeroes in word representation + NODE_API_CALL(env, + napi_create_bigint_words(env, 0, 4, words.data(), &bigints[3])); + // zero + NODE_API_CALL(env, + napi_create_bigint_words(env, 1, 0, words.data(), &bigints[4])); + + napi_value array; + NODE_API_CALL(env, + napi_create_array_with_length(env, bigints.size(), &array)); + for (size_t i = 0; i < bigints.size(); i++) { + NODE_API_CALL(env, napi_set_element(env, array, (uint32_t)i, bigints[i])); + } + return array; +} + Napi::Value RunCallback(const Napi::CallbackInfo &info) { Napi::Env env = info.Env(); // this function is invoked without the GC callback @@ -1035,6 +1142,11 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) { exports.Set("add_tag", Napi::Function::New(env, add_tag)); exports.Set("try_add_tag", Napi::Function::New(env, try_add_tag)); exports.Set("check_tag", Napi::Function::New(env, check_tag)); + exports.Set("bigint_to_i64", Napi::Function::New(env, bigint_to_i64)); + exports.Set("bigint_to_u64", Napi::Function::New(env, bigint_to_u64)); + exports.Set("bigint_to_64_null", Napi::Function::New(env, bigint_to_64_null)); + exports.Set("create_weird_bigints", + Napi::Function::New(env, create_weird_bigints)); napitests::register_wrap_tests(env, exports); diff --git a/test/napi/napi-app/module.js b/test/napi/napi-app/module.js index 307e9ee9f6..8516abb5b6 100644 --- a/test/napi/napi-app/module.js +++ b/test/napi/napi-app/module.js @@ -490,4 +490,8 @@ nativeTests.test_remove_wrap_lifetime_with_strong_ref = async () => { await gcUntil(() => nativeTests.get_object_from_ref() === undefined); }; +nativeTests.test_create_bigint_words = () => { + console.log(nativeTests.create_weird_bigints()); +}; + module.exports = nativeTests; diff --git a/test/napi/napi.test.ts b/test/napi/napi.test.ts index 18a53af2a7..7c79e53de0 100644 --- a/test/napi/napi.test.ts +++ b/test/napi/napi.test.ts @@ -349,6 +349,34 @@ describe("napi", () => { checkSameOutput("test_remove_wrap_lifetime_with_strong_ref", []); }); }); + + describe("bigint conversion to int64/uint64", () => { + it("works", () => { + const tests = [-1n, 0n, 1n]; + for (const power of [63, 64, 65]) { + for (const sign of [-1, 1]) { + const boundary = BigInt(sign) * 2n ** BigInt(power); + tests.push(boundary, boundary - 1n, boundary + 1n); + } + } + + const testsString = "[" + tests.map(bigint => bigint.toString() + "n").join(",") + "]"; + checkSameOutput("bigint_to_i64", testsString); + checkSameOutput("bigint_to_u64", testsString); + }); + it("returns the right error code", () => { + const badTypes = '[null, undefined, 5, "123", "abc"]'; + checkSameOutput("bigint_to_i64", badTypes); + checkSameOutput("bigint_to_u64", badTypes); + checkSameOutput("bigint_to_64_null", []); + }); + }); + + describe("create_bigint_words", () => { + it("works", () => { + checkSameOutput("test_create_bigint_words", []); + }); + }); }); function checkSameOutput(test: string, args: any[] | string) { diff --git a/test/package-json-lint.test.ts b/test/package-json-lint.test.ts index fb3d9a55c3..aa253f17e3 100644 --- a/test/package-json-lint.test.ts +++ b/test/package-json-lint.test.ts @@ -22,20 +22,25 @@ describe("package.json dependencies must be exact versions", async () => { optionalDependencies = {}, } = await Bun.file(join(dir, "./package.json")).json(); + // Hyphen is necessary to accept prerelease versions like "1.1.3-alpha.7" + // This regex still forbids semver ranges like "1.0.0 - 1.2.0", as those must have spaces + // around the hyphen. + const okRegex = /^([a-zA-Z0-9\.\-])+$/; + for (const [name, dep] of Object.entries(dependencies)) { - expect(dep).toMatch(/^([a-zA-Z0-9\.])+$/); + expect(dep, `dependency ${name} specifies non-exact version "${dep}"`).toMatch(okRegex); } for (const [name, dep] of Object.entries(devDependencies)) { - expect(dep).toMatch(/^([a-zA-Z0-9\.])+$/); + expect(dep, `dev dependency ${name} specifies non-exact version "${dep}"`).toMatch(okRegex); } for (const [name, dep] of Object.entries(peerDependencies)) { - expect(dep).toMatch(/^([a-zA-Z0-9\.])+$/); + expect(dep, `peer dependency ${name} specifies non-exact version "${dep}"`).toMatch(okRegex); } for (const [name, dep] of Object.entries(optionalDependencies)) { - expect(dep).toMatch(/^([a-zA-Z0-9\.])+$/); + expect(dep, `optional dependency ${name} specifies non-exact version "${dep}"`).toMatch(okRegex); } }); } diff --git a/test/package.json b/test/package.json index 56bcb98c72..95a6074447 100644 --- a/test/package.json +++ b/test/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@azure/service-bus": "7.9.4", + "@duckdb/node-api": "1.1.3-alpha.7", "@grpc/grpc-js": "1.12.0", "@grpc/proto-loader": "0.7.10", "@napi-rs/canvas": "0.1.65", @@ -33,6 +34,7 @@ "iconv-lite": "0.6.3", "isbot": "5.1.13", "jest-extended": "4.0.0", + "jimp": "1.6.0", "jsonwebtoken": "9.0.2", "jws": "4.0.0", "lodash": "4.17.21", @@ -69,8 +71,7 @@ "webpack": "5.88.0", "webpack-cli": "4.7.2", "xml2js": "0.6.2", - "yargs": "17.7.2", - "jimp": "1.6.0" + "yargs": "17.7.2" }, "private": true, "scripts": {