From e42f981dbef0c8cb69a62866c66ee19c2f2722ba Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 22 Feb 2025 10:33:30 -0800 Subject: [PATCH] enum type: create, get, & bind --- api/src/DuckDBPreparedStatement.ts | 5 +++- api/src/createValue.ts | 8 +++++- api/test/api.test.ts | 7 ++++- .../pkgs/@duckdb/node-bindings/duckdb.d.ts | 5 ++++ bindings/src/duckdb_node_bindings.cpp | 28 +++++++++++++++++-- bindings/test/values.test.ts | 12 ++++++-- 6 files changed, 57 insertions(+), 8 deletions(-) diff --git a/api/src/DuckDBPreparedStatement.ts b/api/src/DuckDBPreparedStatement.ts index 6ec5b7e9..2d79a74b 100644 --- a/api/src/DuckDBPreparedStatement.ts +++ b/api/src/DuckDBPreparedStatement.ts @@ -8,6 +8,7 @@ import { DuckDBResultReader } from './DuckDBResultReader'; import { BIT, DuckDBArrayType, + DuckDBEnumType, DuckDBListType, DuckDBStructType, DuckDBType, @@ -162,7 +163,9 @@ export class DuckDBPreparedStatement { public bindBlob(parameterIndex: number, value: Uint8Array) { duckdb.bind_blob(this.prepared_statement, parameterIndex, value); } - // TODO: bind ENUM + public bindEnum(parameterIndex: number, value: string, type: DuckDBEnumType) { + this.bindValue(parameterIndex, value, type); + } public bindArray( parameterIndex: number, value: DuckDBArrayValue, diff --git a/api/src/createValue.ts b/api/src/createValue.ts index 8a836fa8..3bf1bf9c 100644 --- a/api/src/createValue.ts +++ b/api/src/createValue.ts @@ -140,7 +140,13 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value { } throw new Error(`input is not a DuckDBTimestampNanosecondsValue`); case DuckDBTypeId.ENUM: - throw new Error(`not yet implemented for ENUM`); // TODO: implement when available in 1.2.0 + if (typeof input === 'string') { + return duckdb.create_enum_value( + type.toLogicalType().logical_type, + type.indexForValue(input) + ); + } + throw new Error(`input is not a string`); case DuckDBTypeId.LIST: if (input instanceof DuckDBListValue) { if (type.valueType.typeId === DuckDBTypeId.ANY) { diff --git a/api/test/api.test.ts b/api/test/api.test.ts index 85944045..6e163126 100644 --- a/api/test/api.test.ts +++ b/api/test/api.test.ts @@ -111,6 +111,7 @@ import { uuidValue, version, } from '../src'; +import { replaceSqlNullWithInteger } from './util/replaceSqlNullWithInteger'; import { ColumnNameAndType, createTestAllTypesColumnTypes, @@ -121,7 +122,6 @@ import { createTestAllTypesRowObjectsJson, createTestAllTypesRowsJson, } from './util/testAllTypes'; -import { replaceSqlNullWithInteger } from './util/replaceSqlNullWithInteger'; async function sleep(ms: number): Promise { return new Promise((resolve) => { @@ -408,6 +408,7 @@ describe('api', () => { { name: 'timestamp_s', type: TIMESTAMP_S }, { name: 'timestamp_ms', type: TIMESTAMP_MS }, { name: 'timestamp_ns', type: TIMESTAMP_NS }, + { name: 'enum', type: ENUM(['fly', 'swim', 'walk']) }, { name: 'list_int', type: LIST(INTEGER) }, { name: 'list_dec', type: LIST(DECIMAL(4, 1)) }, { name: 'list_null', type: LIST(SQLNULL) }, @@ -442,6 +443,7 @@ describe('api', () => { prepared.bindTimestampSeconds(i++, TIMESTAMP_S.max); prepared.bindTimestampMilliseconds(i++, TIMESTAMP_MS.max); prepared.bindTimestampNanoseconds(i++, TIMESTAMP_NS.max); + prepared.bindEnum(i++, 'swim', ENUM(['fly', 'swim', 'walk'])); prepared.bindList(i++, listValue([100, 200, 300]), LIST(INTEGER)); prepared.bindList( i++, @@ -527,6 +529,9 @@ describe('api', () => { assertValues(chunk, i++, DuckDBTimestampNanosecondsVector, [ TIMESTAMP_NS.max, ]); + assertValues(chunk, i++, DuckDBEnum8Vector, [ + 'swim', + ]); assertValues(chunk, i++, DuckDBListVector, [ listValue([100, 200, 300]), ]); diff --git a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts index 0792ddd1..7a283ece 100644 --- a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts +++ b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts @@ -797,8 +797,13 @@ export function create_null_value(): Value; // DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value); // DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index); + // DUCKDB_API duckdb_value duckdb_create_enum_value(duckdb_logical_type type, uint64_t value); +export function create_enum_value(logical_type: LogicalType, value: number): Value; + // DUCKDB_API uint64_t duckdb_get_enum_value(duckdb_value value); +export function get_enum_value(value: Value): number; + // DUCKDB_API duckdb_value duckdb_get_struct_child(duckdb_value value, idx_t index); // DUCKDB_API duckdb_logical_type duckdb_create_logical_type(duckdb_type type); diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 52ee92b1..a46cdb58 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -1213,6 +1213,8 @@ class DuckDBNodeAddon : public Napi::Addon { InstanceMethod("get_map_value", &DuckDBNodeAddon::get_map_value), InstanceMethod("is_null_value", &DuckDBNodeAddon::is_null_value), InstanceMethod("create_null_value", &DuckDBNodeAddon::create_null_value), + InstanceMethod("create_enum_value", &DuckDBNodeAddon::create_enum_value), + InstanceMethod("get_enum_value", &DuckDBNodeAddon::get_enum_value), InstanceMethod("create_logical_type", &DuckDBNodeAddon::create_logical_type), InstanceMethod("logical_type_get_alias", &DuckDBNodeAddon::logical_type_get_alias), @@ -3008,8 +3010,29 @@ class DuckDBNodeAddon : public Napi::Addon { // DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value); // DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index); + // DUCKDB_API duckdb_value duckdb_create_enum_value(duckdb_logical_type type, uint64_t value); + // function create_enum_value(logical_type: LogicalType, value: number): Value + Napi::Value create_enum_value(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto logical_type = GetLogicalTypeFromExternal(env, info[0]); + auto input_value = info[1].As().Uint32Value(); + auto value = duckdb_create_enum_value(logical_type, input_value); + if (!value) { + throw Napi::Error::New(env, "Failed to create enum value"); + } + return CreateExternalForValue(env, value); + } + // DUCKDB_API uint64_t duckdb_get_enum_value(duckdb_value value); + // function get_enum_value(value: Value): number + Napi::Value get_enum_value(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto value = GetValueFromExternal(env, info[0]); + auto output_value = duckdb_get_enum_value(value); + return Napi::Number::New(env, output_value); + } + // DUCKDB_API duckdb_value duckdb_get_struct_child(duckdb_value value, idx_t index); // DUCKDB_API duckdb_logical_type duckdb_create_logical_type(duckdb_type type); @@ -4121,11 +4144,10 @@ NODE_API_ADDON(DuckDBNodeAddon) --- 411 total functions - 232 instance methods + 234 instance methods 3 unimplemented instance cache functions 1 unimplemented logical type function - 1 unimplemented value creation functions - 4 unimplemented value inspection functions + 3 unimplemented value inspection functions 13 unimplemented scalar function functions 4 unimplemented scalar function set functions 12 unimplemented aggregate function functions diff --git a/bindings/test/values.test.ts b/bindings/test/values.test.ts index 3756babd..62b34ddb 100644 --- a/bindings/test/values.test.ts +++ b/bindings/test/values.test.ts @@ -21,6 +21,7 @@ import { DECIMAL, DOUBLE, ENTRY, + ENUM, FLOAT, HUGEINT, INTEGER, @@ -277,8 +278,15 @@ suite('values', () => { }); test('null', () => { const null_value = duckdb.create_null_value(); - expect(duckdb.is_null_value(null_value)).toEqual(true); + expect(duckdb.is_null_value(null_value)).toBe(true); const int32_value = duckdb.create_int32(42); - expect(duckdb.is_null_value(int32_value)).toEqual(false); + expect(duckdb.is_null_value(int32_value)).toBe(false); + }); + test('enum', () => { + const enum_members = ['fly', 'swim', 'walk']; + const enum_type = duckdb.create_enum_type(enum_members); + const enum_value = duckdb.create_enum_value(enum_type, 1); + expectLogicalType(duckdb.get_value_type(enum_value), ENUM(enum_members, duckdb.Type.UTINYINT)); + expect(duckdb.get_enum_value(enum_value)).toBe(1); }); });