Skip to content

Commit 3280b64

Browse files
committed
null type: create & check
1 parent 25e9caa commit 3280b64

File tree

6 files changed

+114
-12
lines changed

6 files changed

+114
-12
lines changed

api/src/createValue.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
220220
}
221221
throw new Error(`input is not a bigint`);
222222
case DuckDBTypeId.SQLNULL:
223-
throw new Error(`not yet implemented for SQLNUll`); // TODO: implement when available in 1.2.0
223+
return duckdb.create_null_value();
224224
default:
225225
throw new Error(`unrecognized type id ${typeId}`);
226226
}

api/test/api.test.ts

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ import {
121121
createTestAllTypesRowObjectsJson,
122122
createTestAllTypesRowsJson,
123123
} from './util/testAllTypes';
124+
import { replaceSqlNullWithInteger } from './util/replaceSqlNullWithInteger';
124125

125126
async function sleep(ms: number): Promise<void> {
126127
return new Promise((resolve) => {
@@ -409,6 +410,7 @@ describe('api', () => {
409410
{ name: 'timestamp_ns', type: TIMESTAMP_NS },
410411
{ name: 'list_int', type: LIST(INTEGER) },
411412
{ name: 'list_dec', type: LIST(DECIMAL(4, 1)) },
413+
{ name: 'list_null', type: LIST(SQLNULL) },
412414
{ name: 'struct', type: STRUCT({ 'a': INTEGER, 'b': VARCHAR }) },
413415
{ name: 'array', type: ARRAY(INTEGER, 3) },
414416
{ name: 'uuid', type: UUID },
@@ -426,7 +428,11 @@ describe('api', () => {
426428

427429
assert.strictEqual(prepared.parameterCount, params.length);
428430
for (let i = 0; i < params.length; i++) {
429-
assert.strictEqual(prepared.parameterName(i + 1), params[i].name, `param ${i} name mismatch`);
431+
assert.strictEqual(
432+
prepared.parameterName(i + 1),
433+
params[i].name,
434+
`param ${i} name mismatch`
435+
);
430436
}
431437

432438
let i = 1;
@@ -442,6 +448,7 @@ describe('api', () => {
442448
listValue([decimalValue(9876n, 4, 1), decimalValue(5432n, 4, 1)]),
443449
LIST(DECIMAL(4, 1))
444450
);
451+
prepared.bindList(i++, listValue([null]), LIST(SQLNULL));
445452
prepared.bindStruct(
446453
i++,
447454
structValue({ 'a': 42, 'b': 'duck' }),
@@ -461,16 +468,28 @@ describe('api', () => {
461468
// VARCHAR type is reported incorrectly; see https://github.com/duckdb/duckdb/issues/16137
462469
continue;
463470
}
464-
assert.equal(prepared.parameterTypeId(i + 1), type.typeId, `param ${i} type id mismatch`);
465-
assert.deepEqual(prepared.parameterType(i + 1), type, `param ${i} type mismatch`);
471+
assert.equal(
472+
prepared.parameterTypeId(i + 1),
473+
type.typeId,
474+
`param ${i} type id mismatch`
475+
);
476+
assert.deepEqual(
477+
prepared.parameterType(i + 1),
478+
type,
479+
`param ${i} type mismatch`
480+
);
466481
}
467482

468483
const result = await prepared.run();
469484

470485
// In the result, SQLNULL params get type INTEGER.
471-
const expectedColumns = params.map((p) =>
472-
p.type.typeId === DuckDBTypeId.SQLNULL ? { ...p, type: INTEGER } : p
473-
);
486+
const expectedColumns = params.map((p) => {
487+
const replacedType = replaceSqlNullWithInteger(p.type);
488+
if (replacedType !== p.type) {
489+
return { ...p, type: replacedType };
490+
}
491+
return p;
492+
});
474493

475494
assertColumns(result, expectedColumns);
476495

@@ -514,14 +533,19 @@ describe('api', () => {
514533
assertValues(chunk, i++, DuckDBListVector, [
515534
listValue([decimalValue(9876n, 4, 1), decimalValue(5432n, 4, 1)]),
516535
]);
536+
assertValues(chunk, i++, DuckDBListVector, [listValue([null])]);
517537
assertValues(chunk, i++, DuckDBStructVector, [
518538
structValue({ 'a': 42, 'b': 'duck' }),
519539
]);
520540
assertValues(chunk, i++, DuckDBArrayVector, [
521541
arrayValue([100, 200, 300]),
522542
]);
523-
assertValues(chunk, i++, DuckDBUUIDVector, [uuidValue(0xf0e1d2c3b4a596870123456789abcdefn)]);
524-
assertValues(chunk, i++, DuckDBBitVector, [bitValue('0010001001011100010101011010111')]);
543+
assertValues(chunk, i++, DuckDBUUIDVector, [
544+
uuidValue(0xf0e1d2c3b4a596870123456789abcdefn),
545+
]);
546+
assertValues(chunk, i++, DuckDBBitVector, [
547+
bitValue('0010001001011100010101011010111'),
548+
]);
525549
assertValues(chunk, i++, DuckDBTimeTZVector, [TIMETZ.max]);
526550
assertValues(chunk, i++, DuckDBTimestampTZVector, [TIMESTAMPTZ.max]);
527551
assertValues(chunk, i++, DuckDBVarIntVector, [VARINT.max]);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {
2+
ARRAY,
3+
DuckDBIntegerType,
4+
DuckDBType,
5+
DuckDBTypeId,
6+
LIST,
7+
MAP,
8+
STRUCT,
9+
UNION,
10+
} from '../../src';
11+
12+
export function replaceSqlNullWithInteger(input: DuckDBType): DuckDBType {
13+
switch (input.typeId) {
14+
case DuckDBTypeId.SQLNULL:
15+
return DuckDBIntegerType.create(input.alias);
16+
case DuckDBTypeId.LIST:
17+
return LIST(replaceSqlNullWithInteger(input.valueType), input.alias);
18+
case DuckDBTypeId.STRUCT: {
19+
const entries: Record<string, DuckDBType> = {};
20+
for (let i = 0; i < input.entryCount; i++) {
21+
entries[input.entryNames[i]] = replaceSqlNullWithInteger(
22+
input.entryTypes[i]
23+
);
24+
}
25+
return STRUCT(entries, input.alias);
26+
}
27+
case DuckDBTypeId.ARRAY:
28+
return ARRAY(
29+
replaceSqlNullWithInteger(input.valueType),
30+
input.length,
31+
input.alias
32+
);
33+
case DuckDBTypeId.MAP:
34+
return MAP(
35+
replaceSqlNullWithInteger(input.keyType),
36+
replaceSqlNullWithInteger(input.valueType),
37+
input.alias
38+
);
39+
case DuckDBTypeId.UNION: {
40+
const members: Record<string, DuckDBType> = {};
41+
for (let i = 0; i < input.memberCount; i++) {
42+
members[input.memberTags[i]] = replaceSqlNullWithInteger(
43+
input.memberTypes[i]
44+
);
45+
}
46+
return UNION(members, input.alias);
47+
}
48+
default:
49+
return input;
50+
}
51+
}

bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,11 @@ export function get_map_key(value: Value, index: number): Value;
790790
export function get_map_value(value: Value, index: number): Value;
791791

792792
// DUCKDB_API bool duckdb_is_null_value(duckdb_value value);
793+
export function is_null_value(value: Value): boolean;
794+
793795
// DUCKDB_API duckdb_value duckdb_create_null_value();
796+
export function create_null_value(): Value;
797+
794798
// DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value);
795799
// DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index);
796800
// DUCKDB_API duckdb_value duckdb_create_enum_value(duckdb_logical_type type, uint64_t value);

bindings/src/duckdb_node_bindings.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,6 +1211,8 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
12111211
InstanceMethod("get_map_size", &DuckDBNodeAddon::get_map_size),
12121212
InstanceMethod("get_map_key", &DuckDBNodeAddon::get_map_key),
12131213
InstanceMethod("get_map_value", &DuckDBNodeAddon::get_map_value),
1214+
InstanceMethod("is_null_value", &DuckDBNodeAddon::is_null_value),
1215+
InstanceMethod("create_null_value", &DuckDBNodeAddon::create_null_value),
12141216

12151217
InstanceMethod("create_logical_type", &DuckDBNodeAddon::create_logical_type),
12161218
InstanceMethod("logical_type_get_alias", &DuckDBNodeAddon::logical_type_get_alias),
@@ -2988,7 +2990,22 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
29882990
}
29892991

29902992
// DUCKDB_API bool duckdb_is_null_value(duckdb_value value);
2993+
// function is_null_value(value: Value): boolean
2994+
Napi::Value is_null_value(const Napi::CallbackInfo& info) {
2995+
auto env = info.Env();
2996+
auto value = GetValueFromExternal(env, info[0]);
2997+
auto is_null = duckdb_is_null_value(value);
2998+
return Napi::Boolean::New(env, is_null);
2999+
}
3000+
29913001
// DUCKDB_API duckdb_value duckdb_create_null_value();
3002+
// function create_null_value(): Value
3003+
Napi::Value create_null_value(const Napi::CallbackInfo& info) {
3004+
auto env = info.Env();
3005+
auto value = duckdb_create_null_value();
3006+
return CreateExternalForValue(env, value);
3007+
}
3008+
29923009
// DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value);
29933010
// DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index);
29943011
// DUCKDB_API duckdb_value duckdb_create_enum_value(duckdb_logical_type type, uint64_t value);
@@ -4104,11 +4121,11 @@ NODE_API_ADDON(DuckDBNodeAddon)
41044121
---
41054122
411 total functions
41064123
4107-
230 instance methods
4124+
232 instance methods
41084125
3 unimplemented instance cache functions
41094126
1 unimplemented logical type function
4110-
2 unimplemented value creation functions
4111-
5 unimplemented value inspection functions
4127+
1 unimplemented value creation functions
4128+
4 unimplemented value inspection functions
41124129
13 unimplemented scalar function functions
41134130
4 unimplemented scalar function set functions
41144131
12 unimplemented aggregate function functions

bindings/test/values.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,10 @@ suite('values', () => {
275275
'Failed to create array value'
276276
);
277277
});
278+
test('null', () => {
279+
const null_value = duckdb.create_null_value();
280+
expect(duckdb.is_null_value(null_value)).toEqual(true);
281+
const int32_value = duckdb.create_int32(42);
282+
expect(duckdb.is_null_value(int32_value)).toEqual(false);
283+
});
278284
});

0 commit comments

Comments
 (0)