From c707884a12329696ae79531fa777c0868574848e Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Wed, 2 Jul 2025 19:40:46 -0700 Subject: [PATCH 1/7] core bindings for scalar functions --- .../pkgs/@duckdb/node-bindings/duckdb.d.ts | 25 +++ bindings/src/duckdb_node_bindings.cpp | 174 ++++++++++++++++++ bindings/test/scalar_functions.test.ts | 48 +++++ 3 files changed, 247 insertions(+) create mode 100644 bindings/test/scalar_functions.test.ts diff --git a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts index 991032fc..dcf7764d 100644 --- a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts +++ b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts @@ -170,6 +170,10 @@ export interface TimestampParts { time: TimeParts; } +export interface FunctionInfo { + __duckdb_type: 'duckdb_function_info'; +} + export interface Vector { __duckdb_type: 'duckdb_vector'; } @@ -228,6 +232,10 @@ export interface Result { __duckdb_type: 'duckdb_result'; } +export interface ScalarFunction { + __duckdb_type: 'duckdb_scalar_function'; +} + // export interface SelectionVector { // __duckdb_type: 'duckdb_selection_vector'; // } @@ -248,6 +256,7 @@ export interface ExtractedStatementsAndCount { statement_count: number; } +export type ScalarFunctionMainFunction = (info: FunctionInfo, input: DataChunk, output: Vector) => void; // Functions @@ -1013,23 +1022,39 @@ export function validity_set_row_invalid(validity: Uint8Array, row_index: number export function validity_set_row_valid(validity: Uint8Array, row_index: number): void; // DUCKDB_C_API duckdb_scalar_function duckdb_create_scalar_function(); +export function create_scalar_function(): ScalarFunction; + // DUCKDB_C_API void duckdb_destroy_scalar_function(duckdb_scalar_function *scalar_function); +// not exposed: destroyed in finalizer + // DUCKDB_C_API void duckdb_scalar_function_set_name(duckdb_scalar_function scalar_function, const char *name); +export function scalar_function_set_name(scalar_function: ScalarFunction, name: string): void; + // DUCKDB_C_API void duckdb_scalar_function_set_varargs(duckdb_scalar_function scalar_function, duckdb_logical_type type); // DUCKDB_C_API void duckdb_scalar_function_set_special_handling(duckdb_scalar_function scalar_function); // DUCKDB_C_API void duckdb_scalar_function_set_volatile(duckdb_scalar_function scalar_function); // DUCKDB_C_API void duckdb_scalar_function_add_parameter(duckdb_scalar_function scalar_function, duckdb_logical_type type); + // DUCKDB_C_API void duckdb_scalar_function_set_return_type(duckdb_scalar_function scalar_function, duckdb_logical_type type); +export function scalar_function_set_return_type(scalar_function: ScalarFunction, logical_type: LogicalType): void; + // DUCKDB_C_API void duckdb_scalar_function_set_extra_info(duckdb_scalar_function scalar_function, void *extra_info, duckdb_delete_callback_t destroy); // DUCKDB_C_API void duckdb_scalar_function_set_bind(duckdb_scalar_function scalar_function, duckdb_scalar_function_bind_t bind); // DUCKDB_C_API void duckdb_scalar_function_set_bind_data(duckdb_bind_info info, void *bind_data, duckdb_delete_callback_t destroy); // DUCKDB_C_API void duckdb_scalar_function_bind_set_error(duckdb_bind_info info, const char *error); + // DUCKDB_C_API void duckdb_scalar_function_set_function(duckdb_scalar_function scalar_function, duckdb_scalar_function_t function); +export function scalar_function_set_function(scalar_function: ScalarFunction, func: ScalarFunctionMainFunction): void; + // DUCKDB_C_API duckdb_state duckdb_register_scalar_function(duckdb_connection con, duckdb_scalar_function scalar_function); +export function register_scalar_function(connection: Connection, scalar_function: ScalarFunction): void; + // DUCKDB_C_API void *duckdb_scalar_function_get_extra_info(duckdb_function_info info); // DUCKDB_C_API void *duckdb_scalar_function_get_bind_data(duckdb_function_info info); // DUCKDB_C_API void duckdb_scalar_function_get_client_context(duckdb_bind_info info, duckdb_client_context *out_context); + // DUCKDB_C_API void duckdb_scalar_function_set_error(duckdb_function_info info, const char *error); +export function scalar_function_set_error(function_info: FunctionInfo, error: string): void; // DUCKDB_C_API duckdb_scalar_function_set duckdb_create_scalar_function_set(const char *name); // DUCKDB_C_API void duckdb_destroy_scalar_function_set(duckdb_scalar_function_set *scalar_function_set); diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 8a26ff5e..431ef0d6 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -462,6 +462,10 @@ Napi::External<_duckdb_data_chunk> CreateExternalForDataChunk(Napi::Env env, duc return CreateExternal<_duckdb_data_chunk>(env, DataChunkTypeTag, chunk, FinalizeDataChunk); } +Napi::External<_duckdb_data_chunk> CreateExternalForDataChunkWithoutFinalizer(Napi::Env env, duckdb_data_chunk chunk) { + return CreateExternalWithoutFinalizer<_duckdb_data_chunk>(env, DataChunkTypeTag, chunk); +} + duckdb_data_chunk GetDataChunkFromExternal(Napi::Env env, Napi::Value value) { return GetDataFromExternal<_duckdb_data_chunk>(env, DataChunkTypeTag, value, "Invalid data chunk argument"); } @@ -485,6 +489,21 @@ duckdb_extracted_statements GetExtractedStatementsFromExternal(Napi::Env env, Na return GetDataFromExternal<_duckdb_extracted_statements>(env, ExtractedStatementsTypeTag, value, "Invalid extracted statements argument"); } +static const napi_type_tag FunctionInfoTypeTag = { + 0xB0E6739D698048EA, 0x9E79734E3E137AC3 +}; + +Napi::External<_duckdb_function_info> CreateExternalForFunctionInfo(Napi::Env env, duckdb_function_info function_info) { + // FunctionInfo objects are never explicitly created; they are passed in to function callbacks. + return CreateExternalWithoutFinalizer<_duckdb_function_info>(env, FunctionInfoTypeTag, function_info); +} + +duckdb_function_info GetFunctionInfoFromExternal(Napi::Env env, Napi::Value value) { + return GetDataFromExternal<_duckdb_function_info>(env, FunctionInfoTypeTag, value, "Invalid function info argument"); +} + + + static const napi_type_tag InstanceCacheTypeTag = { 0x2F3346E30FB5457C, 0xB9201EE5112EEF9F }; @@ -592,6 +611,22 @@ duckdb_result *GetResultFromExternal(Napi::Env env, Napi::Value value) { return GetDataFromExternal(env, ResultTypeTag, value, "Invalid result argument"); } +static const napi_type_tag ScalarFunctionTypeTag = { + 0x95D48B7051D14994, 0x9F883D7DF5DEA86D +}; + +void FinalizeScalarFunction(Napi::BasicEnv, duckdb_scalar_function scalar_function) { + duckdb_destroy_scalar_function(&scalar_function); +} + +Napi::External<_duckdb_scalar_function> CreateExternalForScalarFunction(Napi::Env env, duckdb_scalar_function scalar_function) { + return CreateExternal<_duckdb_scalar_function>(env, ScalarFunctionTypeTag, scalar_function, FinalizeScalarFunction); +} + +duckdb_scalar_function GetScalarFunctionFromExternal(Napi::Env env, Napi::Value value) { + return GetDataFromExternal<_duckdb_scalar_function>(env, ScalarFunctionTypeTag, value, "Invalid scalar function argument"); +} + static const napi_type_tag ValueTypeTag = { 0xC60F36613BF14E93, 0xBAA92848936FAA25 }; @@ -624,6 +659,78 @@ duckdb_vector GetVectorFromExternal(Napi::Env env, Napi::Value value) { return GetDataFromExternal<_duckdb_vector>(env, VectorTypeTag, value, "Invalid vector argument"); } +// Scalar functions + +using ScalarFunctionMainContext = std::nullptr_t; +struct ScalarFunctionMainData { + duckdb_function_info info; + duckdb_data_chunk input; + duckdb_vector output; + std::unique_ptr cv; + std::unique_ptr cv_mutex; + bool done; +}; +void ScalarFunctionMainCallback(Napi::Env env, Napi::Function callback, ScalarFunctionMainContext *context, ScalarFunctionMainData *data) { + if (env != nullptr) { + if (callback != nullptr) { + callback.Call( + env.Undefined(), + { + CreateExternalForFunctionInfo(env, data->info), + CreateExternalForDataChunkWithoutFinalizer(env, data->input), + CreateExternalForVector(env, data->output) + } + ); + } + } + { + std::lock_guard lk(*data->cv_mutex); + data->done = true; + } + data->cv->notify_one(); +} +using ScalarFunctionMainTSFN = Napi::TypedThreadSafeFunction; + +struct ScalarFunctionMainExtraInfo { + ScalarFunctionMainTSFN tsfn; + // TODO: user extra info +}; + +ScalarFunctionMainExtraInfo *CreateScalarFunctionMainExtraInfo(Napi::Env env, Napi::Function func) { + auto extra_info = reinterpret_cast(duckdb_malloc(sizeof(ScalarFunctionMainExtraInfo))); + extra_info->tsfn = ScalarFunctionMainTSFN::New(env, func, "ScalarFunctionMain", 0, 1); + return extra_info; +} + +void DeleteScalarFunctionMainExtraInfo(ScalarFunctionMainExtraInfo *extra_info) { + extra_info->tsfn.Release(); + duckdb_free(extra_info); +} + +void ScalarFunctionMainFunction(duckdb_function_info info, duckdb_data_chunk input, duckdb_vector output) { + auto extra_info = reinterpret_cast(duckdb_scalar_function_get_extra_info(info)); + auto data = reinterpret_cast(duckdb_malloc(sizeof(ScalarFunctionMainData))); + data->info = info; + data->input = input; + data->output = output; + data->cv = std::make_unique(); + data->cv_mutex = std::make_unique(); + data->done = false; + // The "blocking" part of this call only waits for queue space, not for the JS function call to complete. + // Since we specify no limit to the queue space, it in fact never blocks. + auto status = extra_info->tsfn.BlockingCall(data); + if (status == napi_ok) { + // Wait for the JS function call to complete. + std::unique_lock lk(*data->cv_mutex); + data->cv->wait(lk, [&]{ return data->done; }); + } else { + duckdb_scalar_function_set_error(info, "BlockingCall returned not ok"); + } + data->cv.reset(); + data->cv_mutex.reset(); + duckdb_free(data); +} + // Promise workers class PromiseWorker : public Napi::AsyncWorker { @@ -1421,6 +1528,13 @@ class DuckDBNodeAddon : public Napi::Addon { InstanceMethod("validity_set_row_invalid", &DuckDBNodeAddon::validity_set_row_invalid), InstanceMethod("validity_set_row_valid", &DuckDBNodeAddon::validity_set_row_valid), + InstanceMethod("create_scalar_function", &DuckDBNodeAddon::create_scalar_function), + InstanceMethod("scalar_function_set_name", &DuckDBNodeAddon::scalar_function_set_name), + InstanceMethod("scalar_function_set_return_type", &DuckDBNodeAddon::scalar_function_set_return_type), + InstanceMethod("scalar_function_set_function", &DuckDBNodeAddon::scalar_function_set_function), + InstanceMethod("register_scalar_function", &DuckDBNodeAddon::register_scalar_function), + InstanceMethod("scalar_function_set_error", &DuckDBNodeAddon::scalar_function_set_error), + InstanceMethod("appender_create", &DuckDBNodeAddon::appender_create), InstanceMethod("appender_create_ext", &DuckDBNodeAddon::appender_create_ext), InstanceMethod("appender_column_count", &DuckDBNodeAddon::appender_column_count), @@ -3878,23 +3992,83 @@ class DuckDBNodeAddon : public Napi::Addon { } // DUCKDB_C_API duckdb_scalar_function duckdb_create_scalar_function(); + // function create_scalar_function(): ScalarFunction + Napi::Value create_scalar_function(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto scalar_function = duckdb_create_scalar_function(); + return CreateExternalForScalarFunction(env, scalar_function); + } + // DUCKDB_C_API void duckdb_destroy_scalar_function(duckdb_scalar_function *scalar_function); + // not exposed: destroyed in finalizer + // DUCKDB_C_API void duckdb_scalar_function_set_name(duckdb_scalar_function scalar_function, const char *name); + // function scalar_function_set_name(scalar_function: ScalarFunction, name: string): void + Napi::Value scalar_function_set_name(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto scalar_function = GetScalarFunctionFromExternal(env, info[0]); + std::string name = info[1].As(); + duckdb_scalar_function_set_name(scalar_function, name.c_str()); + return env.Undefined(); + } + // DUCKDB_C_API void duckdb_scalar_function_set_varargs(duckdb_scalar_function scalar_function, duckdb_logical_type type); // DUCKDB_C_API void duckdb_scalar_function_set_special_handling(duckdb_scalar_function scalar_function); // DUCKDB_C_API void duckdb_scalar_function_set_volatile(duckdb_scalar_function scalar_function); // DUCKDB_C_API void duckdb_scalar_function_add_parameter(duckdb_scalar_function scalar_function, duckdb_logical_type type); + // DUCKDB_C_API void duckdb_scalar_function_set_return_type(duckdb_scalar_function scalar_function, duckdb_logical_type type); + // function scalar_function_set_return_type(scalar_function: ScalarFunction, logical_type: LogicalType): void + Napi::Value scalar_function_set_return_type(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto scalar_function = GetScalarFunctionFromExternal(env, info[0]); + auto logical_type = GetLogicalTypeFromExternal(env, info[1]); + duckdb_scalar_function_set_return_type(scalar_function, logical_type); + return env.Undefined(); + } + // DUCKDB_C_API void duckdb_scalar_function_set_extra_info(duckdb_scalar_function scalar_function, void *extra_info, duckdb_delete_callback_t destroy); // DUCKDB_C_API void duckdb_scalar_function_set_bind(duckdb_scalar_function scalar_function, duckdb_scalar_function_bind_t bind); // DUCKDB_C_API void duckdb_scalar_function_set_bind_data(duckdb_bind_info info, void *bind_data, duckdb_delete_callback_t destroy); // DUCKDB_C_API void duckdb_scalar_function_bind_set_error(duckdb_bind_info info, const char *error); + // DUCKDB_C_API void duckdb_scalar_function_set_function(duckdb_scalar_function scalar_function, duckdb_scalar_function_t function); + // function scalar_function_set_function(scalar_function: ScalarFunction, func: ScalarFunctionMainFunction): void + Napi::Value scalar_function_set_function(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto scalar_function = GetScalarFunctionFromExternal(env, info[0]); + auto func = info[1].As(); + auto extra_info = CreateScalarFunctionMainExtraInfo(env, func); + duckdb_scalar_function_set_extra_info(scalar_function, extra_info, reinterpret_cast(DeleteScalarFunctionMainExtraInfo)); + duckdb_scalar_function_set_function(scalar_function, &ScalarFunctionMainFunction); + return env.Undefined(); + } + // DUCKDB_C_API duckdb_state duckdb_register_scalar_function(duckdb_connection con, duckdb_scalar_function scalar_function); + // function register_scalar_function(connection: Connection, scalar_function: ScalarFunction): void + Napi::Value register_scalar_function(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto connection = GetConnectionFromExternal(env, info[0]); + auto scalar_function = GetScalarFunctionFromExternal(env, info[1]); + if (duckdb_register_scalar_function(connection, scalar_function)) { + throw Napi::Error::New(env, "Failed to register scalar function"); + } + return env.Undefined(); + } + // DUCKDB_C_API void *duckdb_scalar_function_get_extra_info(duckdb_function_info info); // DUCKDB_C_API void *duckdb_scalar_function_get_bind_data(duckdb_function_info info); // DUCKDB_C_API void duckdb_scalar_function_get_client_context(duckdb_bind_info info, duckdb_client_context *out_context); + // DUCKDB_C_API void duckdb_scalar_function_set_error(duckdb_function_info info, const char *error); + // function scalar_function_set_error(function_info: FunctionInfo, error: string): void + Napi::Value scalar_function_set_error(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto function_info = GetFunctionInfoFromExternal(env, info[0]); + std::string error = info[1].As(); + duckdb_scalar_function_set_error(function_info, error.c_str()); + return env.Undefined(); + } // DUCKDB_C_API duckdb_scalar_function_set duckdb_create_scalar_function_set(const char *name); // DUCKDB_C_API void duckdb_destroy_scalar_function_set(duckdb_scalar_function_set *scalar_function_set); diff --git a/bindings/test/scalar_functions.test.ts b/bindings/test/scalar_functions.test.ts new file mode 100644 index 00000000..76c78061 --- /dev/null +++ b/bindings/test/scalar_functions.test.ts @@ -0,0 +1,48 @@ +import duckdb from '@duckdb/node-bindings'; +import { expect, suite, test } from 'vitest'; +import { data } from './utils/expectedVectors'; +import { expectResult } from './utils/expectResult'; +import { withConnection } from './utils/withConnection'; + +suite('scalar functions', () => { + test('create', () => { + const scalar_function = duckdb.create_scalar_function(); + expect(scalar_function).toBeTruthy(); + }); + test('set name', () => { + const scalar_function = duckdb.create_scalar_function(); + duckdb.scalar_function_set_name(scalar_function, 'my_func'); + }); + test('set return type', () => { + const scalar_function = duckdb.create_scalar_function(); + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + duckdb.scalar_function_set_return_type(scalar_function, int_type); + }); + test('register & run', async () => { + await withConnection(async (connection) => { + const scalar_function = duckdb.create_scalar_function(); + duckdb.scalar_function_set_name(scalar_function, 'my_func'); + const int_type = duckdb.create_logical_type(duckdb.Type.VARCHAR); + duckdb.scalar_function_set_return_type(scalar_function, int_type); + duckdb.scalar_function_set_function(scalar_function, (_info, input, output) => { + const inputSize = duckdb.data_chunk_get_size(input); + for (let i = 0; i < inputSize; i++) { + duckdb.vector_assign_string_element(output, i, `output_${i}`); + } + }); + duckdb.register_scalar_function(connection, scalar_function); + + const result = await duckdb.query(connection, "select my_func()"); + await expectResult(result, { + chunkCount: 1, + rowCount: 1, + columns: [ + { name: 'my_func()', logicalType: { typeId: duckdb.Type.VARCHAR } } + ], + chunks: [ + { rowCount: 1, vectors: [data(16, [true], ['output_0'])]} + ], + }); + }); + }); +}); From e5035d3e614de860b19c7ddb64f914b8b48ee138 Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 26 Jul 2025 10:49:08 -0700 Subject: [PATCH 2/7] clarify fn names for creation without finalizers --- bindings/src/duckdb_node_bindings.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 431ef0d6..39741dd8 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -493,7 +493,7 @@ static const napi_type_tag FunctionInfoTypeTag = { 0xB0E6739D698048EA, 0x9E79734E3E137AC3 }; -Napi::External<_duckdb_function_info> CreateExternalForFunctionInfo(Napi::Env env, duckdb_function_info function_info) { +Napi::External<_duckdb_function_info> CreateExternalForFunctionInfoWithoutFinalizer(Napi::Env env, duckdb_function_info function_info) { // FunctionInfo objects are never explicitly created; they are passed in to function callbacks. return CreateExternalWithoutFinalizer<_duckdb_function_info>(env, FunctionInfoTypeTag, function_info); } @@ -650,7 +650,7 @@ static const napi_type_tag VectorTypeTag = { 0x9FE56DE8E3124D07, 0x9ABF31145EDE1C9E }; -Napi::External<_duckdb_vector> CreateExternalForVector(Napi::Env env, duckdb_vector vector) { +Napi::External<_duckdb_vector> CreateExternalForVectorWithoutFinalizer(Napi::Env env, duckdb_vector vector) { // Vectors live as long as their containing data chunk; they cannot be explicitly destroyed. return CreateExternalWithoutFinalizer<_duckdb_vector>(env, VectorTypeTag, vector); } @@ -676,9 +676,9 @@ void ScalarFunctionMainCallback(Napi::Env env, Napi::Function callback, ScalarFu callback.Call( env.Undefined(), { - CreateExternalForFunctionInfo(env, data->info), + CreateExternalForFunctionInfoWithoutFinalizer(env, data->info), CreateExternalForDataChunkWithoutFinalizer(env, data->input), - CreateExternalForVector(env, data->output) + CreateExternalForVectorWithoutFinalizer(env, data->output) } ); } @@ -3794,7 +3794,7 @@ class DuckDBNodeAddon : public Napi::Addon { auto chunk = GetDataChunkFromExternal(env, info[0]); auto column_index = info[1].As().Uint32Value(); auto vector = duckdb_data_chunk_get_vector(chunk, column_index); - return CreateExternalForVector(env, vector); + return CreateExternalForVectorWithoutFinalizer(env, vector); } // DUCKDB_C_API idx_t duckdb_data_chunk_get_size(duckdb_data_chunk chunk); @@ -3893,7 +3893,7 @@ class DuckDBNodeAddon : public Napi::Addon { auto env = info.Env(); auto vector = GetVectorFromExternal(env, info[0]); auto child = duckdb_list_vector_get_child(vector); - return CreateExternalForVector(env, child); + return CreateExternalForVectorWithoutFinalizer(env, child); } // DUCKDB_C_API idx_t duckdb_list_vector_get_size(duckdb_vector vector); @@ -3932,7 +3932,7 @@ class DuckDBNodeAddon : public Napi::Addon { auto vector = GetVectorFromExternal(env, info[0]); auto index = info[1].As().Uint32Value(); auto child = duckdb_struct_vector_get_child(vector, index); - return CreateExternalForVector(env, child); + return CreateExternalForVectorWithoutFinalizer(env, child); } // DUCKDB_C_API duckdb_vector duckdb_array_vector_get_child(duckdb_vector vector); @@ -3941,7 +3941,7 @@ class DuckDBNodeAddon : public Napi::Addon { auto env = info.Env(); auto vector = GetVectorFromExternal(env, info[0]); auto child = duckdb_array_vector_get_child(vector); - return CreateExternalForVector(env, child); + return CreateExternalForVectorWithoutFinalizer(env, child); } // DUCKDB_C_API void duckdb_slice_vector(duckdb_vector vector, duckdb_selection_vector selection, idx_t len); From 43c1fc5a383873773945a57826b35ca27c2bb3aa Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 26 Jul 2025 10:55:27 -0700 Subject: [PATCH 3/7] add cpp headers --- bindings/src/duckdb_node_bindings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 39741dd8..60f75729 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -3,6 +3,10 @@ #define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED #include "napi.h" +#include +#include +#include +#include #include #include #include From b3974bb9aa968f684b28b821b3e448f1c2309f8b Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 26 Jul 2025 10:56:00 -0700 Subject: [PATCH 4/7] fix header order --- bindings/src/duckdb_node_bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 60f75729..48afe58d 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -3,8 +3,8 @@ #define NODE_API_NO_EXTERNAL_BUFFERS_ALLOWED #include "napi.h" -#include #include +#include #include #include #include From e974377b61ccbac45a21011acd02af4f89625d1e Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 26 Jul 2025 18:45:17 -0700 Subject: [PATCH 5/7] fix condition_variable segfault --- bindings/src/duckdb_node_bindings.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 48afe58d..59b19f84 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -670,8 +669,8 @@ struct ScalarFunctionMainData { duckdb_function_info info; duckdb_data_chunk input; duckdb_vector output; - std::unique_ptr cv; - std::unique_ptr cv_mutex; + std::condition_variable *cv; + std::mutex *cv_mutex; bool done; }; void ScalarFunctionMainCallback(Napi::Env env, Napi::Function callback, ScalarFunctionMainContext *context, ScalarFunctionMainData *data) { @@ -717,8 +716,8 @@ void ScalarFunctionMainFunction(duckdb_function_info info, duckdb_data_chunk inp data->info = info; data->input = input; data->output = output; - data->cv = std::make_unique(); - data->cv_mutex = std::make_unique(); + data->cv = new std::condition_variable; + data->cv_mutex = new std::mutex; data->done = false; // The "blocking" part of this call only waits for queue space, not for the JS function call to complete. // Since we specify no limit to the queue space, it in fact never blocks. @@ -730,8 +729,8 @@ void ScalarFunctionMainFunction(duckdb_function_info info, duckdb_data_chunk inp } else { duckdb_scalar_function_set_error(info, "BlockingCall returned not ok"); } - data->cv.reset(); - data->cv_mutex.reset(); + delete data->cv; + delete data->cv_mutex; duckdb_free(data); } From 9b0a230fc0262094269a0b000faac82d2ab3c57c Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 26 Jul 2025 19:05:08 -0700 Subject: [PATCH 6/7] destroy_scalar_function_sync --- .../pkgs/@duckdb/node-bindings/duckdb.d.ts | 2 +- bindings/src/duckdb_node_bindings.cpp | 40 +++++++++++++++---- bindings/test/scalar_functions.test.ts | 1 + 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts index dcf7764d..f15a01a5 100644 --- a/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts +++ b/bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts @@ -1025,7 +1025,7 @@ export function validity_set_row_valid(validity: Uint8Array, row_index: number): export function create_scalar_function(): ScalarFunction; // DUCKDB_C_API void duckdb_destroy_scalar_function(duckdb_scalar_function *scalar_function); -// not exposed: destroyed in finalizer +export function destroy_scalar_function_sync(scalar_function: ScalarFunction): void; // DUCKDB_C_API void duckdb_scalar_function_set_name(duckdb_scalar_function scalar_function, const char *name); export function scalar_function_set_name(scalar_function: ScalarFunction, name: string): void; diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 59b19f84..3be17c1c 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -618,16 +618,32 @@ static const napi_type_tag ScalarFunctionTypeTag = { 0x95D48B7051D14994, 0x9F883D7DF5DEA86D }; -void FinalizeScalarFunction(Napi::BasicEnv, duckdb_scalar_function scalar_function) { - duckdb_destroy_scalar_function(&scalar_function); +typedef struct { + duckdb_scalar_function scalar_function; +} duckdb_scalar_function_holder; + +duckdb_scalar_function_holder *CreateScalarFunctionHolder(duckdb_scalar_function scalar_function) { + auto scalar_function_holder_ptr = reinterpret_cast(duckdb_malloc(sizeof(duckdb_scalar_function_holder))); + scalar_function_holder_ptr->scalar_function = scalar_function; + return scalar_function_holder_ptr; +} + +void FinalizeScalarFunctionHolder(Napi::BasicEnv, duckdb_scalar_function_holder *scalar_function_holder_ptr) { + // duckdb_destroy_scalar_function is a no-op if already closed + duckdb_destroy_scalar_function(&scalar_function_holder_ptr->scalar_function); + duckdb_free(scalar_function_holder_ptr); +} + +Napi::External CreateExternalForScalarFunction(Napi::Env env, duckdb_scalar_function scalar_function) { + return CreateExternal(env, ScalarFunctionTypeTag, CreateScalarFunctionHolder(scalar_function), FinalizeScalarFunctionHolder); } -Napi::External<_duckdb_scalar_function> CreateExternalForScalarFunction(Napi::Env env, duckdb_scalar_function scalar_function) { - return CreateExternal<_duckdb_scalar_function>(env, ScalarFunctionTypeTag, scalar_function, FinalizeScalarFunction); +duckdb_scalar_function_holder *GetScalarFunctionHolderFromExternal(Napi::Env env, Napi::Value value) { + return GetDataFromExternal(env, ScalarFunctionTypeTag, value, "Invalid scalar function argument"); } duckdb_scalar_function GetScalarFunctionFromExternal(Napi::Env env, Napi::Value value) { - return GetDataFromExternal<_duckdb_scalar_function>(env, ScalarFunctionTypeTag, value, "Invalid scalar function argument"); + return GetScalarFunctionHolderFromExternal(env, value)->scalar_function; } static const napi_type_tag ValueTypeTag = { @@ -1532,6 +1548,7 @@ class DuckDBNodeAddon : public Napi::Addon { InstanceMethod("validity_set_row_valid", &DuckDBNodeAddon::validity_set_row_valid), InstanceMethod("create_scalar_function", &DuckDBNodeAddon::create_scalar_function), + InstanceMethod("destroy_scalar_function_sync", &DuckDBNodeAddon::destroy_scalar_function_sync), InstanceMethod("scalar_function_set_name", &DuckDBNodeAddon::scalar_function_set_name), InstanceMethod("scalar_function_set_return_type", &DuckDBNodeAddon::scalar_function_set_return_type), InstanceMethod("scalar_function_set_function", &DuckDBNodeAddon::scalar_function_set_function), @@ -4003,7 +4020,14 @@ class DuckDBNodeAddon : public Napi::Addon { } // DUCKDB_C_API void duckdb_destroy_scalar_function(duckdb_scalar_function *scalar_function); - // not exposed: destroyed in finalizer + // function destroy_scalar_function_sync(scalar_function: ScalarFunction): void + Napi::Value destroy_scalar_function_sync(const Napi::CallbackInfo& info) { + auto env = info.Env(); + auto scalar_function_holder_ptr = GetScalarFunctionHolderFromExternal(env, info[0]); + // duckdb_destroy_scalar_function is a no-op if already closed + duckdb_destroy_scalar_function(&scalar_function_holder_ptr->scalar_function); + return env.Undefined(); + } // DUCKDB_C_API void duckdb_scalar_function_set_name(duckdb_scalar_function scalar_function, const char *name); // function scalar_function_set_name(scalar_function: ScalarFunction, name: string): void @@ -4667,14 +4691,14 @@ NODE_API_ADDON(DuckDBNodeAddon) --- 431 total functions - 245 instance methods + 253 instance methods 3 unimplemented client context functions 1 unimplemented table names function 1 unimplemented value to string function 1 unimplemented logical type function 2 unimplemented vector creation functions 3 unimplemented vector manipulation functions - 18 unimplemented scalar function functions + 11 unimplemented scalar function functions 4 unimplemented scalar function set functions 3 unimplemented selection vector functions 12 unimplemented aggregate function functions diff --git a/bindings/test/scalar_functions.test.ts b/bindings/test/scalar_functions.test.ts index 76c78061..fe7e94fd 100644 --- a/bindings/test/scalar_functions.test.ts +++ b/bindings/test/scalar_functions.test.ts @@ -43,6 +43,7 @@ suite('scalar functions', () => { { rowCount: 1, vectors: [data(16, [true], ['output_0'])]} ], }); + duckdb.destroy_scalar_function_sync(scalar_function); }); }); }); From 1662adeb023fd6a4906bfee6d9916b078c549ffb Mon Sep 17 00:00:00 2001 From: Jeff Raymakers Date: Sat, 26 Jul 2025 19:11:06 -0700 Subject: [PATCH 7/7] typo in comment --- bindings/src/duckdb_node_bindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 3be17c1c..5f52e0d1 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -4691,7 +4691,7 @@ NODE_API_ADDON(DuckDBNodeAddon) --- 431 total functions - 253 instance methods + 252 instance methods 3 unimplemented client context functions 1 unimplemented table names function 1 unimplemented value to string function