Skip to content

Commit 63449eb

Browse files
committed
handle ANY type in nested values
1 parent 1cac29e commit 63449eb

File tree

4 files changed

+106
-5
lines changed

4 files changed

+106
-5
lines changed

api/src/createValue.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
124124
throw new Error(`not yet implemented for ENUM`); // TODO: implement when available in 1.2.0
125125
case DuckDBTypeId.LIST:
126126
if (input instanceof DuckDBListValue) {
127+
if (type.valueType.typeId === DuckDBTypeId.ANY) {
128+
throw new Error(
129+
'Cannot create lists with item type of ANY. Specify a specific type.'
130+
);
131+
}
127132
return duckdb.create_list_value(
128133
type.valueType.toLogicalType().logical_type,
129134
input.items.map((item) => createValue(type.valueType, item))
@@ -132,6 +137,11 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
132137
throw new Error(`input is not a DuckDBListValue`);
133138
case DuckDBTypeId.STRUCT:
134139
if (input instanceof DuckDBStructValue) {
140+
if (type.entryTypes.find((type) => type.typeId === DuckDBTypeId.ANY)) {
141+
throw new Error(
142+
'Cannot create structs with an entry type of ANY. Specify a specific type.'
143+
);
144+
}
135145
return duckdb.create_struct_value(
136146
type.toLogicalType().logical_type,
137147
Object.values(input.entries).map((value, i) =>
@@ -144,6 +154,11 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
144154
throw new Error(`not yet implemented for MAP`); // TODO: implement when available, hopefully in 1.2.0
145155
case DuckDBTypeId.ARRAY:
146156
if (input instanceof DuckDBArrayValue) {
157+
if (type.valueType.typeId === DuckDBTypeId.ANY) {
158+
throw new Error(
159+
'Cannot create arrays with item type of ANY. Specify a specific type.'
160+
);
161+
}
147162
return duckdb.create_array_value(
148163
type.valueType.toLogicalType().logical_type,
149164
input.items.map((item) => createValue(type.valueType, item))
@@ -167,7 +182,9 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
167182
}
168183
throw new Error(`input is not a DuckDBTimestampTZValue`);
169184
case DuckDBTypeId.ANY:
170-
throw new Error(`cannot create values of type ANY`);
185+
throw new Error(
186+
`Cannot create values of type ANY. Specify a specific type.`
187+
);
171188
case DuckDBTypeId.VARINT:
172189
throw new Error(`not yet implemented for VARINT`); // TODO: implement when available in 1.2.0
173190
case DuckDBTypeId.SQLNULL:

api/test/api.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,58 @@ describe('api', () => {
561561
}
562562
});
563563
});
564+
test('should fail gracefully when binding structs contain ANY types to prepared statements', async () => {
565+
await withConnection(async (connection) => {
566+
const prepared = await connection.prepare('select ?');
567+
try {
568+
prepared.bindStruct(
569+
0,
570+
structValue({ 'a': null }),
571+
STRUCT({ 'a': ANY })
572+
);
573+
assert.fail('should throw');
574+
} catch (err) {
575+
assert.deepEqual(
576+
err,
577+
new Error(
578+
'Cannot create structs with an entry type of ANY. Specify a specific type.'
579+
)
580+
);
581+
}
582+
});
583+
});
584+
test('should fail gracefully when type cannot be inferred when binding lists to prepared statements', async () => {
585+
await withConnection(async (connection) => {
586+
const prepared = await connection.prepare('select ?');
587+
try {
588+
prepared.bind([listValue([])]);
589+
assert.fail('should throw');
590+
} catch (err) {
591+
assert.deepEqual(
592+
err,
593+
new Error(
594+
'Cannot create lists with item type of ANY. Specify a specific type.'
595+
)
596+
);
597+
}
598+
});
599+
});
600+
test('should fail gracefully when type cannot be inferred when binding arrays to prepared statements', async () => {
601+
await withConnection(async (connection) => {
602+
const prepared = await connection.prepare('select ?');
603+
try {
604+
prepared.bind([arrayValue([])]);
605+
assert.fail('should throw');
606+
} catch (err) {
607+
assert.deepEqual(
608+
err,
609+
new Error(
610+
'Cannot create arrays with item type of ANY. Specify a specific type.'
611+
)
612+
);
613+
}
614+
});
615+
});
564616
test('should support starting prepared statements and running them incrementally', async () => {
565617
await withConnection(async (connection) => {
566618
const prepared = await connection.prepare(

bindings/src/duckdb_node_bindings.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2591,6 +2591,9 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
25912591
values_vector[i] = GetValueFromExternal(env, values_array.Get(i));
25922592
}
25932593
auto value = duckdb_create_struct_value(logical_type, values_vector.data());
2594+
if (!value) {
2595+
throw Napi::Error::New(env, "Failed to create struct value");
2596+
}
25942597
return CreateExternalForValue(env, value);
25952598
}
25962599

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

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

bindings/test/values.test.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
UINTEGER,
2626
USMALLINT,
2727
UTINYINT,
28-
VARCHAR,
28+
VARCHAR
2929
} from './utils/expectedLogicalTypes';
3030

3131
suite('values', () => {
@@ -96,13 +96,13 @@ suite('values', () => {
9696
expect(duckdb.get_uhugeint(uhugeint_value)).toBe(input);
9797
});
9898
test('float', () => {
99-
const input = 3.4028234663852886e+38;
99+
const input = 3.4028234663852886e38;
100100
const float_value = duckdb.create_float(input);
101101
expectLogicalType(duckdb.get_value_type(float_value), FLOAT);
102102
expect(duckdb.get_float(float_value)).toBe(input);
103103
});
104104
test('double', () => {
105-
const input = 1.7976931348623157e+308;
105+
const input = 1.7976931348623157e308;
106106
const double_value = duckdb.create_double(input);
107107
expectLogicalType(duckdb.get_value_type(double_value), DOUBLE);
108108
expect(duckdb.get_double(double_value)).toBe(input);
@@ -154,13 +154,24 @@ suite('values', () => {
154154
const struct_type = duckdb.create_struct_type([int_type], ['a']);
155155
const int32_value = duckdb.create_int32(42);
156156
const struct_value = duckdb.create_struct_value(struct_type, [int32_value]);
157-
expectLogicalType(duckdb.get_value_type(struct_value), STRUCT(ENTRY('a', INTEGER)));
157+
expectLogicalType(
158+
duckdb.get_value_type(struct_value),
159+
STRUCT(ENTRY('a', INTEGER))
160+
);
158161
});
159162
test('empty struct', () => {
160163
const struct_type = duckdb.create_struct_type([], []);
161164
const struct_value = duckdb.create_struct_value(struct_type, []);
162165
expectLogicalType(duckdb.get_value_type(struct_value), STRUCT());
163166
});
167+
test('any struct', () => {
168+
const any_type = duckdb.create_logical_type(duckdb.Type.ANY);
169+
const struct_type = duckdb.create_struct_type([any_type], ['a']);
170+
const int32_value = duckdb.create_int32(42);
171+
expect(() =>
172+
duckdb.create_struct_value(struct_type, [int32_value])
173+
).toThrowError('Failed to create struct value');
174+
});
164175
test('list', () => {
165176
const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER);
166177
const int32_value = duckdb.create_int32(42);
@@ -172,6 +183,12 @@ suite('values', () => {
172183
const list_value = duckdb.create_list_value(int_type, []);
173184
expectLogicalType(duckdb.get_value_type(list_value), LIST(INTEGER));
174185
});
186+
test('any list', () => {
187+
const any_type = duckdb.create_logical_type(duckdb.Type.ANY);
188+
expect(() => duckdb.create_list_value(any_type, [])).toThrowError(
189+
'Failed to create list value'
190+
);
191+
});
175192
test('array', () => {
176193
const int_type = duckdb.create_logical_type(duckdb.Type.INTEGER);
177194
const int32_value = duckdb.create_int32(42);
@@ -183,4 +200,10 @@ suite('values', () => {
183200
const array_value = duckdb.create_array_value(int_type, []);
184201
expectLogicalType(duckdb.get_value_type(array_value), ARRAY(INTEGER, 0));
185202
});
203+
test('any array', () => {
204+
const any_type = duckdb.create_logical_type(duckdb.Type.ANY);
205+
expect(() => duckdb.create_array_value(any_type, [])).toThrowError(
206+
'Failed to create array value'
207+
);
208+
});
186209
});

0 commit comments

Comments
 (0)