diff --git a/api/src/createValue.ts b/api/src/createValue.ts index 630a76ab..2e8d6f21 100644 --- a/api/src/createValue.ts +++ b/api/src/createValue.ts @@ -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)) @@ -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) => @@ -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)) @@ -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: diff --git a/api/test/api.test.ts b/api/test/api.test.ts index acfd6477..5ee9e2eb 100644 --- a/api/test/api.test.ts +++ b/api/test/api.test.ts @@ -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( diff --git a/bindings/src/duckdb_node_bindings.cpp b/bindings/src/duckdb_node_bindings.cpp index 685890f4..28cd0271 100644 --- a/bindings/src/duckdb_node_bindings.cpp +++ b/bindings/src/duckdb_node_bindings.cpp @@ -2591,6 +2591,9 @@ class DuckDBNodeAddon : public Napi::Addon { 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); } @@ -2608,6 +2611,9 @@ class DuckDBNodeAddon : public Napi::Addon { 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); } @@ -2625,6 +2631,9 @@ class DuckDBNodeAddon : public Napi::Addon { 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); } diff --git a/bindings/test/values.test.ts b/bindings/test/values.test.ts index 871c8f4f..e66bd963 100644 --- a/bindings/test/values.test.ts +++ b/bindings/test/values.test.ts @@ -25,7 +25,7 @@ import { UINTEGER, USMALLINT, UTINYINT, - VARCHAR, + VARCHAR } from './utils/expectedLogicalTypes'; suite('values', () => { @@ -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); @@ -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); @@ -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); @@ -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' + ); + }); });