Skip to content

Commit

Permalink
Merge pull request #129 from duckdb/jray/fix-empty-nested-values-and-…
Browse files Browse the repository at this point in the history
…types

fix empty nested values and types
  • Loading branch information
jraymakers authored Jan 30, 2025
2 parents 4bfbb6d + 43d5b52 commit 33d96d9
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 9 deletions.
34 changes: 25 additions & 9 deletions bindings/src/duckdb_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2584,7 +2584,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto logical_type = GetLogicalTypeFromExternal(env, info[0]);
auto values_array = info[1].As<Napi::Array>();
auto values_count = values_array.Length();
std::vector<duckdb_value> 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<duckdb_value> 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));
}
Expand All @@ -2599,7 +2601,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto logical_type = GetLogicalTypeFromExternal(env, info[0]);
auto values_array = info[1].As<Napi::Array>();
auto values_count = values_array.Length();
std::vector<duckdb_value> 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<duckdb_value> 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));
}
Expand All @@ -2614,7 +2618,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto logical_type = GetLogicalTypeFromExternal(env, info[0]);
auto values_array = info[1].As<Napi::Array>();
auto values_count = values_array.Length();
std::vector<duckdb_value> 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<duckdb_value> 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));
}
Expand Down Expand Up @@ -2722,9 +2728,12 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
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<duckdb_logical_type> member_types(member_count);
// If there are no members, we still need valid data pointers, so create single element vectors containing nulls.
std::vector<duckdb_logical_type> member_types(member_count > 0 ? member_count : 1);
std::vector<std::string> member_names_strings(member_count);
std::vector<const char *> member_names(member_count);
std::vector<const char *> 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<Napi::String>();
Expand All @@ -2743,9 +2752,12 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
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<duckdb_logical_type> member_types(member_count);
// If there are no members, we still need valid data pointers, so create single element vectors containing nulls.
std::vector<duckdb_logical_type> member_types(member_count > 0 ? member_count : 1);
std::vector<std::string> member_names_strings(member_count);
std::vector<const char *> member_names(member_count);
std::vector<const char *> 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<Napi::String>();
Expand All @@ -2762,7 +2774,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto member_names_array = info[0].As<Napi::Array>();
auto member_count = member_names_array.Length();
std::vector<std::string> member_names_strings(member_count);
std::vector<const char *> 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<const char *> 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<Napi::String>();
member_names[i] = member_names_strings[i].c_str();
Expand Down Expand Up @@ -2966,7 +2980,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
auto env = info.Env();
auto types_array = info[0].As<Napi::Array>();
auto types_count = types_array.Length();
std::vector<duckdb_logical_type> 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<duckdb_logical_type> 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));
}
Expand Down
4 changes: 4 additions & 0 deletions bindings/test/data_chunk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
19 changes: 19 additions & 0 deletions bindings/test/logical_type.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
});
});
47 changes: 47 additions & 0 deletions bindings/test/prepared_statements.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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, [])),
]
},
],
});
});
});
});
38 changes: 38 additions & 0 deletions bindings/test/values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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));
});
});

0 comments on commit 33d96d9

Please sign in to comment.