Skip to content

Commit

Permalink
handle ANY type in nested values
Browse files Browse the repository at this point in the history
  • Loading branch information
jraymakers committed Jan 30, 2025
1 parent 1cac29e commit 63449eb
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 5 deletions.
19 changes: 18 additions & 1 deletion api/src/createValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
throw new Error(`not yet implemented for ENUM`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.LIST:
if (input instanceof DuckDBListValue) {
if (type.valueType.typeId === DuckDBTypeId.ANY) {
throw new Error(
'Cannot create lists with item type of ANY. Specify a specific type.'
);
}
return duckdb.create_list_value(
type.valueType.toLogicalType().logical_type,
input.items.map((item) => createValue(type.valueType, item))
Expand All @@ -132,6 +137,11 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
throw new Error(`input is not a DuckDBListValue`);
case DuckDBTypeId.STRUCT:
if (input instanceof DuckDBStructValue) {
if (type.entryTypes.find((type) => type.typeId === DuckDBTypeId.ANY)) {
throw new Error(
'Cannot create structs with an entry type of ANY. Specify a specific type.'
);
}
return duckdb.create_struct_value(
type.toLogicalType().logical_type,
Object.values(input.entries).map((value, i) =>
Expand All @@ -144,6 +154,11 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
throw new Error(`not yet implemented for MAP`); // TODO: implement when available, hopefully in 1.2.0
case DuckDBTypeId.ARRAY:
if (input instanceof DuckDBArrayValue) {
if (type.valueType.typeId === DuckDBTypeId.ANY) {
throw new Error(
'Cannot create arrays with item type of ANY. Specify a specific type.'
);
}
return duckdb.create_array_value(
type.valueType.toLogicalType().logical_type,
input.items.map((item) => createValue(type.valueType, item))
Expand All @@ -167,7 +182,9 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
}
throw new Error(`input is not a DuckDBTimestampTZValue`);
case DuckDBTypeId.ANY:
throw new Error(`cannot create values of type ANY`);
throw new Error(
`Cannot create values of type ANY. Specify a specific type.`
);
case DuckDBTypeId.VARINT:
throw new Error(`not yet implemented for VARINT`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.SQLNULL:
Expand Down
52 changes: 52 additions & 0 deletions api/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,58 @@ describe('api', () => {
}
});
});
test('should fail gracefully when binding structs contain ANY types to prepared statements', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare('select ?');
try {
prepared.bindStruct(
0,
structValue({ 'a': null }),
STRUCT({ 'a': ANY })
);
assert.fail('should throw');
} catch (err) {
assert.deepEqual(
err,
new Error(
'Cannot create structs with an entry type of ANY. Specify a specific type.'
)
);
}
});
});
test('should fail gracefully when type cannot be inferred when binding lists to prepared statements', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare('select ?');
try {
prepared.bind([listValue([])]);
assert.fail('should throw');
} catch (err) {
assert.deepEqual(
err,
new Error(
'Cannot create lists with item type of ANY. Specify a specific type.'
)
);
}
});
});
test('should fail gracefully when type cannot be inferred when binding arrays to prepared statements', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare('select ?');
try {
prepared.bind([arrayValue([])]);
assert.fail('should throw');
} catch (err) {
assert.deepEqual(
err,
new Error(
'Cannot create arrays with item type of ANY. Specify a specific type.'
)
);
}
});
});
test('should support starting prepared statements and running them incrementally', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare(
Expand Down
9 changes: 9 additions & 0 deletions bindings/src/duckdb_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2591,6 +2591,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
values_vector[i] = GetValueFromExternal(env, values_array.Get(i));
}
auto value = duckdb_create_struct_value(logical_type, values_vector.data());
if (!value) {
throw Napi::Error::New(env, "Failed to create struct value");
}
return CreateExternalForValue(env, value);
}

Expand All @@ -2608,6 +2611,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
values_vector[i] = GetValueFromExternal(env, values_array.Get(i));
}
auto value = duckdb_create_list_value(logical_type, values_vector.data(), values_count);
if (!value) {
throw Napi::Error::New(env, "Failed to create list value");
}
return CreateExternalForValue(env, value);
}

Expand All @@ -2625,6 +2631,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
values_vector[i] = GetValueFromExternal(env, values_array.Get(i));
}
auto value = duckdb_create_array_value(logical_type, values_vector.data(), values_count);
if (!value) {
throw Napi::Error::New(env, "Failed to create array value");
}
return CreateExternalForValue(env, value);
}

Expand Down
31 changes: 27 additions & 4 deletions bindings/test/values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
UINTEGER,
USMALLINT,
UTINYINT,
VARCHAR,
VARCHAR
} from './utils/expectedLogicalTypes';

suite('values', () => {
Expand Down Expand Up @@ -96,13 +96,13 @@ suite('values', () => {
expect(duckdb.get_uhugeint(uhugeint_value)).toBe(input);
});
test('float', () => {
const input = 3.4028234663852886e+38;
const input = 3.4028234663852886e38;
const float_value = duckdb.create_float(input);
expectLogicalType(duckdb.get_value_type(float_value), FLOAT);
expect(duckdb.get_float(float_value)).toBe(input);
});
test('double', () => {
const input = 1.7976931348623157e+308;
const input = 1.7976931348623157e308;
const double_value = duckdb.create_double(input);
expectLogicalType(duckdb.get_value_type(double_value), DOUBLE);
expect(duckdb.get_double(double_value)).toBe(input);
Expand Down Expand Up @@ -154,13 +154,24 @@ suite('values', () => {
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)));
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('any struct', () => {
const any_type = duckdb.create_logical_type(duckdb.Type.ANY);
const struct_type = duckdb.create_struct_type([any_type], ['a']);
const int32_value = duckdb.create_int32(42);
expect(() =>
duckdb.create_struct_value(struct_type, [int32_value])
).toThrowError('Failed to create struct value');
});
test('list', () => {
const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER);
const int32_value = duckdb.create_int32(42);
Expand All @@ -172,6 +183,12 @@ suite('values', () => {
const list_value = duckdb.create_list_value(int_type, []);
expectLogicalType(duckdb.get_value_type(list_value), LIST(INTEGER));
});
test('any list', () => {
const any_type = duckdb.create_logical_type(duckdb.Type.ANY);
expect(() => duckdb.create_list_value(any_type, [])).toThrowError(
'Failed to create list value'
);
});
test('array', () => {
const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER);
const int32_value = duckdb.create_int32(42);
Expand All @@ -183,4 +200,10 @@ suite('values', () => {
const array_value = duckdb.create_array_value(int_type, []);
expectLogicalType(duckdb.get_value_type(array_value), ARRAY(INTEGER, 0));
});
test('any array', () => {
const any_type = duckdb.create_logical_type(duckdb.Type.ANY);
expect(() => duckdb.create_array_value(any_type, [])).toThrowError(
'Failed to create array value'
);
});
});

0 comments on commit 63449eb

Please sign in to comment.