Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix empty nested values and types #129

Merged
merged 1 commit into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
});
});