diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 580d4055..685890f4 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -2584,7 +2584,9 @@ class DuckDBNodeAddon : public Napi::Addon { auto logical_type = GetLogicalTypeFromExternal(env, info[0]); auto values_array = info[1].As(); auto values_count = values_array.Length(); - std::vector values_vector(values_count); + // If there are no values, we still need a valid data pointer, so create a single element vector containing a null. + std::vector values_vector(values_count > 0 ? values_count : 1); + values_vector[0] = nullptr; for (uint32_t i = 0; i < values_count; i++) { values_vector[i] = GetValueFromExternal(env, values_array.Get(i)); } @@ -2599,7 +2601,9 @@ class DuckDBNodeAddon : public Napi::Addon { auto logical_type = GetLogicalTypeFromExternal(env, info[0]); auto values_array = info[1].As(); auto values_count = values_array.Length(); - std::vector values_vector(values_count); + // If there are no values, we still need a valid data pointer, so create a single element vector containing a null. + std::vector values_vector(values_count > 0 ? values_count : 1); + values_vector[0] = nullptr; for (uint32_t i = 0; i < values_count; i++) { values_vector[i] = GetValueFromExternal(env, values_array.Get(i)); } @@ -2614,7 +2618,9 @@ class DuckDBNodeAddon : public Napi::Addon { auto logical_type = GetLogicalTypeFromExternal(env, info[0]); auto values_array = info[1].As(); auto values_count = values_array.Length(); - std::vector values_vector(values_count); + // If there are no values, we still need a valid data pointer, so create a single element vector containing a null. + std::vector values_vector(values_count > 0 ? values_count : 1); + values_vector[0] = nullptr; for (uint32_t i = 0; i < values_count; i++) { values_vector[i] = GetValueFromExternal(env, values_array.Get(i)); } @@ -2722,9 +2728,12 @@ class DuckDBNodeAddon : public Napi::Addon { auto member_types_count = member_types_array.Length(); auto member_names_count = member_names_array.Length(); auto member_count = member_types_count < member_names_count ? member_types_count : member_names_count; - std::vector member_types(member_count); + // If there are no members, we still need valid data pointers, so create single element vectors containing nulls. + std::vector member_types(member_count > 0 ? member_count : 1); std::vector member_names_strings(member_count); - std::vector member_names(member_count); + std::vector member_names(member_count > 0 ? member_count : 1); + member_types[0] = nullptr; + member_names[0] = nullptr; for (uint32_t i = 0; i < member_count; i++) { member_types[i] = GetLogicalTypeFromExternal(env, member_types_array.Get(i)); member_names_strings[i] = member_names_array.Get(i).As(); @@ -2743,9 +2752,12 @@ class DuckDBNodeAddon : public Napi::Addon { auto member_types_count = member_types_array.Length(); auto member_names_count = member_names_array.Length(); auto member_count = member_types_count < member_names_count ? member_types_count : member_names_count; - std::vector member_types(member_count); + // If there are no members, we still need valid data pointers, so create single element vectors containing nulls. + std::vector member_types(member_count > 0 ? member_count : 1); std::vector member_names_strings(member_count); - std::vector member_names(member_count); + std::vector member_names(member_count > 0 ? member_count : 1); + member_types[0] = nullptr; + member_names[0] = nullptr; for (uint32_t i = 0; i < member_count; i++) { member_types[i] = GetLogicalTypeFromExternal(env, member_types_array.Get(i)); member_names_strings[i] = member_names_array.Get(i).As(); @@ -2762,7 +2774,9 @@ class DuckDBNodeAddon : public Napi::Addon { auto member_names_array = info[0].As(); auto member_count = member_names_array.Length(); std::vector member_names_strings(member_count); - std::vector member_names(member_count); + // If there are no members, we still need a valid data pointer, so create a single element vector containing a null. + std::vector member_names(member_count > 0 ? member_count : 1); + member_names[0] = nullptr; for (uint32_t i = 0; i < member_count; i++) { member_names_strings[i] = member_names_array.Get(i).As(); member_names[i] = member_names_strings[i].c_str(); @@ -2966,7 +2980,9 @@ class DuckDBNodeAddon : public Napi::Addon { auto env = info.Env(); auto types_array = info[0].As(); auto types_count = types_array.Length(); - std::vector types(types_count); + // If there are no types, we still need a valid data pointer, so create a single element vector containing a null. + std::vector types(types_count > 0 ? types_count : 1); + types[0] = nullptr; for (uint32_t i = 0; i < types_count; i++) { types[i] = GetLogicalTypeFromExternal(env, types_array.Get(i)); } diff --git a/bindings/test/data_chunk.test.ts b/bindings/test/data_chunk.test.ts index 4e3ea376..a05c5818 100644 --- a/bindings/test/data_chunk.test.ts +++ b/bindings/test/data_chunk.test.ts @@ -133,4 +133,8 @@ suite('data chunk', () => { duckdb.list_vector_set_size(vector, 5); expect(duckdb.list_vector_get_size(vector)).toBe(5); }); + test('create no types', () => { + const chunk = duckdb.create_data_chunk([]); + expect(duckdb.data_chunk_get_column_count(chunk)).toBe(0); + }); }); diff --git a/bindings/test/logical_type.test.ts b/bindings/test/logical_type.test.ts index 2bbeb6b7..2fed89c4 100644 --- a/bindings/test/logical_type.test.ts +++ b/bindings/test/logical_type.test.ts @@ -77,6 +77,13 @@ suite('logical_type', () => { expect(duckdb.enum_dictionary_value(enum_type, 0)).toBe('enum_0'); expect(duckdb.enum_dictionary_value(enum_type, 69999)).toBe('enum_69999'); }); + test('empty enum', () => { + const enum_type = duckdb.create_enum_type([]); + expect(duckdb.get_type_id(enum_type)).toBe(duckdb.Type.ENUM); + expect(duckdb.logical_type_get_alias(enum_type)).toBeNull(); + expect(duckdb.enum_internal_type(enum_type)).toBe(duckdb.Type.UTINYINT); + expect(duckdb.enum_dictionary_size(enum_type)).toBe(0); + }); test('list', () => { const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); const list_type = duckdb.create_list_type(int_type); @@ -110,6 +117,12 @@ suite('logical_type', () => { const member_type_1 = duckdb.struct_type_child_type(struct_type, 1); expect(duckdb.get_type_id(member_type_1)).toBe(duckdb.Type.VARCHAR); }); + test('empty struct', () => { + const struct_type = duckdb.create_struct_type([], []); + expect(duckdb.get_type_id(struct_type)).toBe(duckdb.Type.STRUCT); + expect(duckdb.logical_type_get_alias(struct_type)).toBeNull(); + expect(duckdb.struct_type_child_count(struct_type)).toBe(0); + }); test('union', () => { const varchar_type = duckdb.create_logical_type(duckdb.Type.VARCHAR); const smallint_type = duckdb.create_logical_type(duckdb.Type.SMALLINT); @@ -124,4 +137,10 @@ suite('logical_type', () => { const member_type_1 = duckdb.union_type_member_type(union_type, 1); expect(duckdb.get_type_id(member_type_1)).toBe(duckdb.Type.SMALLINT); }); + test('empty union', () => { + const union_type = duckdb.create_union_type([], []); + expect(duckdb.get_type_id(union_type)).toBe(duckdb.Type.UNION); + expect(duckdb.logical_type_get_alias(union_type)).toBeNull(); + expect(duckdb.union_type_member_count(union_type)).toBe(0); + }); }); diff --git a/bindings/test/prepared_statements.test.ts b/bindings/test/prepared_statements.test.ts index a2648669..0ba2540a 100644 --- a/bindings/test/prepared_statements.test.ts +++ b/bindings/test/prepared_statements.test.ts @@ -395,4 +395,51 @@ suite('prepared statements', () => { }); }); }); + test('bind empty nested types', async () => { + await withConnection(async (connection) => { + const prepared = await duckdb.prepare(connection, + 'select \ + ? as struct, \ + ? as list, \ + ? as array' + ); + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + const struct_type = duckdb.create_struct_type([], []); + + const struct_value = duckdb.create_struct_value(struct_type, []); + duckdb.bind_value(prepared, 1, struct_value); + expect(duckdb.param_type(prepared, 1)).toBe(duckdb.Type.STRUCT); + + const list_value = duckdb.create_list_value(int_type, []); + duckdb.bind_value(prepared, 2, list_value); + expect(duckdb.param_type(prepared, 2)).toBe(duckdb.Type.LIST); + + const array_value = duckdb.create_array_value(int_type, []); + duckdb.bind_value(prepared, 3, array_value); + expect(duckdb.param_type(prepared, 3)).toBe(duckdb.Type.ARRAY); + + // TODO: map value? + + const result = await duckdb.execute_prepared(prepared); + await expectResult(result, { + chunkCount: 1, + rowCount: 1, + columns: [ + { name: 'struct', logicalType: STRUCT() }, + { name: 'list', logicalType: LIST(INTEGER) }, + { name: 'array', logicalType: ARRAY(INTEGER, 0) }, + ], + chunks: [ + { + rowCount: 1, + vectors: [ + struct(1, [true], []), + list([true], [[0n, 0n]], 0, data(0, null, [])), + array(1, [true], data(0, null, [])), + ] + }, + ], + }); + }); + }); }); diff --git a/bindings/test/values.test.ts b/bindings/test/values.test.ts index ac52d0d6..871c8f4f 100644 --- a/bindings/test/values.test.ts +++ b/bindings/test/values.test.ts @@ -2,16 +2,20 @@ import duckdb from '@duckdb/node-bindings'; import { expect, suite, test } from 'vitest'; import { expectLogicalType } from './utils/expectLogicalType'; import { + ARRAY, BIGINT, BLOB, BOOLEAN, DATE, DOUBLE, + ENTRY, FLOAT, HUGEINT, INTEGER, INTERVAL, + LIST, SMALLINT, + STRUCT, TIME, TIME_TZ, TIMESTAMP, @@ -145,4 +149,38 @@ suite('values', () => { expectLogicalType(duckdb.get_value_type(varchar_value), VARCHAR); expect(duckdb.get_varchar(varchar_value)).toBe(input); }); + test('struct', () => { + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + const struct_type = duckdb.create_struct_type([int_type], ['a']); + const int32_value = duckdb.create_int32(42); + const struct_value = duckdb.create_struct_value(struct_type, [int32_value]); + expectLogicalType(duckdb.get_value_type(struct_value), STRUCT(ENTRY('a', INTEGER))); + }); + test('empty struct', () => { + const struct_type = duckdb.create_struct_type([], []); + const struct_value = duckdb.create_struct_value(struct_type, []); + expectLogicalType(duckdb.get_value_type(struct_value), STRUCT()); + }); + test('list', () => { + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + const int32_value = duckdb.create_int32(42); + const list_value = duckdb.create_list_value(int_type, [int32_value]); + expectLogicalType(duckdb.get_value_type(list_value), LIST(INTEGER)); + }); + test('empty list', () => { + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + const list_value = duckdb.create_list_value(int_type, []); + expectLogicalType(duckdb.get_value_type(list_value), LIST(INTEGER)); + }); + test('array', () => { + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + const int32_value = duckdb.create_int32(42); + const array_value = duckdb.create_array_value(int_type, [int32_value]); + expectLogicalType(duckdb.get_value_type(array_value), ARRAY(INTEGER, 1)); + }); + test('empty array', () => { + const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER); + const array_value = duckdb.create_array_value(int_type, []); + expectLogicalType(duckdb.get_value_type(array_value), ARRAY(INTEGER, 0)); + }); });