Skip to content

Commit

Permalink
null type: create & check
Browse files Browse the repository at this point in the history
  • Loading branch information
jraymakers committed Feb 22, 2025
1 parent 25e9caa commit 3280b64
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 12 deletions.
2 changes: 1 addition & 1 deletion api/src/createValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
}
throw new Error(`input is not a bigint`);
case DuckDBTypeId.SQLNULL:
throw new Error(`not yet implemented for SQLNUll`); // TODO: implement when available in 1.2.0
return duckdb.create_null_value();
default:
throw new Error(`unrecognized type id ${typeId}`);
}
Expand Down
40 changes: 32 additions & 8 deletions api/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import {
createTestAllTypesRowObjectsJson,
createTestAllTypesRowsJson,
} from './util/testAllTypes';
import { replaceSqlNullWithInteger } from './util/replaceSqlNullWithInteger';

async function sleep(ms: number): Promise<void> {
return new Promise((resolve) => {
Expand Down Expand Up @@ -409,6 +410,7 @@ describe('api', () => {
{ name: 'timestamp_ns', type: TIMESTAMP_NS },
{ name: 'list_int', type: LIST(INTEGER) },
{ name: 'list_dec', type: LIST(DECIMAL(4, 1)) },
{ name: 'list_null', type: LIST(SQLNULL) },
{ name: 'struct', type: STRUCT({ 'a': INTEGER, 'b': VARCHAR }) },
{ name: 'array', type: ARRAY(INTEGER, 3) },
{ name: 'uuid', type: UUID },
Expand All @@ -426,7 +428,11 @@ describe('api', () => {

assert.strictEqual(prepared.parameterCount, params.length);
for (let i = 0; i < params.length; i++) {
assert.strictEqual(prepared.parameterName(i + 1), params[i].name, `param ${i} name mismatch`);
assert.strictEqual(
prepared.parameterName(i + 1),
params[i].name,
`param ${i} name mismatch`
);
}

let i = 1;
Expand All @@ -442,6 +448,7 @@ describe('api', () => {
listValue([decimalValue(9876n, 4, 1), decimalValue(5432n, 4, 1)]),
LIST(DECIMAL(4, 1))
);
prepared.bindList(i++, listValue([null]), LIST(SQLNULL));
prepared.bindStruct(
i++,
structValue({ 'a': 42, 'b': 'duck' }),
Expand All @@ -461,16 +468,28 @@ describe('api', () => {
// VARCHAR type is reported incorrectly; see https://github.com/duckdb/duckdb/issues/16137
continue;
}
assert.equal(prepared.parameterTypeId(i + 1), type.typeId, `param ${i} type id mismatch`);
assert.deepEqual(prepared.parameterType(i + 1), type, `param ${i} type mismatch`);
assert.equal(
prepared.parameterTypeId(i + 1),
type.typeId,
`param ${i} type id mismatch`
);
assert.deepEqual(
prepared.parameterType(i + 1),
type,
`param ${i} type mismatch`
);
}

const result = await prepared.run();

// In the result, SQLNULL params get type INTEGER.
const expectedColumns = params.map((p) =>
p.type.typeId === DuckDBTypeId.SQLNULL ? { ...p, type: INTEGER } : p
);
const expectedColumns = params.map((p) => {
const replacedType = replaceSqlNullWithInteger(p.type);
if (replacedType !== p.type) {
return { ...p, type: replacedType };
}
return p;
});

assertColumns(result, expectedColumns);

Expand Down Expand Up @@ -514,14 +533,19 @@ describe('api', () => {
assertValues(chunk, i++, DuckDBListVector, [
listValue([decimalValue(9876n, 4, 1), decimalValue(5432n, 4, 1)]),
]);
assertValues(chunk, i++, DuckDBListVector, [listValue([null])]);
assertValues(chunk, i++, DuckDBStructVector, [
structValue({ 'a': 42, 'b': 'duck' }),
]);
assertValues(chunk, i++, DuckDBArrayVector, [
arrayValue([100, 200, 300]),
]);
assertValues(chunk, i++, DuckDBUUIDVector, [uuidValue(0xf0e1d2c3b4a596870123456789abcdefn)]);
assertValues(chunk, i++, DuckDBBitVector, [bitValue('0010001001011100010101011010111')]);
assertValues(chunk, i++, DuckDBUUIDVector, [
uuidValue(0xf0e1d2c3b4a596870123456789abcdefn),
]);
assertValues(chunk, i++, DuckDBBitVector, [
bitValue('0010001001011100010101011010111'),
]);
assertValues(chunk, i++, DuckDBTimeTZVector, [TIMETZ.max]);
assertValues(chunk, i++, DuckDBTimestampTZVector, [TIMESTAMPTZ.max]);
assertValues(chunk, i++, DuckDBVarIntVector, [VARINT.max]);
Expand Down
51 changes: 51 additions & 0 deletions api/test/util/replaceSqlNullWithInteger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
ARRAY,
DuckDBIntegerType,
DuckDBType,
DuckDBTypeId,
LIST,
MAP,
STRUCT,
UNION,
} from '../../src';

export function replaceSqlNullWithInteger(input: DuckDBType): DuckDBType {
switch (input.typeId) {
case DuckDBTypeId.SQLNULL:
return DuckDBIntegerType.create(input.alias);
case DuckDBTypeId.LIST:
return LIST(replaceSqlNullWithInteger(input.valueType), input.alias);
case DuckDBTypeId.STRUCT: {
const entries: Record<string, DuckDBType> = {};
for (let i = 0; i < input.entryCount; i++) {
entries[input.entryNames[i]] = replaceSqlNullWithInteger(
input.entryTypes[i]
);
}
return STRUCT(entries, input.alias);
}
case DuckDBTypeId.ARRAY:
return ARRAY(
replaceSqlNullWithInteger(input.valueType),
input.length,
input.alias
);
case DuckDBTypeId.MAP:
return MAP(
replaceSqlNullWithInteger(input.keyType),
replaceSqlNullWithInteger(input.valueType),
input.alias
);
case DuckDBTypeId.UNION: {
const members: Record<string, DuckDBType> = {};
for (let i = 0; i < input.memberCount; i++) {
members[input.memberTags[i]] = replaceSqlNullWithInteger(
input.memberTypes[i]
);
}
return UNION(members, input.alias);
}
default:
return input;
}
}
4 changes: 4 additions & 0 deletions bindings/pkgs/@duckdb/node-bindings/duckdb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,11 @@ export function get_map_key(value: Value, index: number): Value;
export function get_map_value(value: Value, index: number): Value;

// DUCKDB_API bool duckdb_is_null_value(duckdb_value value);
export function is_null_value(value: Value): boolean;

// DUCKDB_API duckdb_value duckdb_create_null_value();
export function create_null_value(): Value;

// DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value);
// DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index);
// DUCKDB_API duckdb_value duckdb_create_enum_value(duckdb_logical_type type, uint64_t value);
Expand Down
23 changes: 20 additions & 3 deletions bindings/src/duckdb_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1211,6 +1211,8 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
InstanceMethod("get_map_size", &DuckDBNodeAddon::get_map_size),
InstanceMethod("get_map_key", &DuckDBNodeAddon::get_map_key),
InstanceMethod("get_map_value", &DuckDBNodeAddon::get_map_value),
InstanceMethod("is_null_value", &DuckDBNodeAddon::is_null_value),
InstanceMethod("create_null_value", &DuckDBNodeAddon::create_null_value),

InstanceMethod("create_logical_type", &DuckDBNodeAddon::create_logical_type),
InstanceMethod("logical_type_get_alias", &DuckDBNodeAddon::logical_type_get_alias),
Expand Down Expand Up @@ -2988,7 +2990,22 @@ class DuckDBNodeAddon : public Napi::Addon<DuckDBNodeAddon> {
}

// DUCKDB_API bool duckdb_is_null_value(duckdb_value value);
// function is_null_value(value: Value): boolean
Napi::Value is_null_value(const Napi::CallbackInfo& info) {
auto env = info.Env();
auto value = GetValueFromExternal(env, info[0]);
auto is_null = duckdb_is_null_value(value);
return Napi::Boolean::New(env, is_null);
}

// DUCKDB_API duckdb_value duckdb_create_null_value();
// function create_null_value(): Value
Napi::Value create_null_value(const Napi::CallbackInfo& info) {
auto env = info.Env();
auto value = duckdb_create_null_value();
return CreateExternalForValue(env, value);
}

// DUCKDB_API idx_t duckdb_get_list_size(duckdb_value value);
// DUCKDB_API duckdb_value duckdb_get_list_child(duckdb_value value, idx_t index);
// DUCKDB_API duckdb_value duckdb_create_enum_value(duckdb_logical_type type, uint64_t value);
Expand Down Expand Up @@ -4104,11 +4121,11 @@ NODE_API_ADDON(DuckDBNodeAddon)
---
411 total functions
230 instance methods
232 instance methods
3 unimplemented instance cache functions
1 unimplemented logical type function
2 unimplemented value creation functions
5 unimplemented value inspection functions
1 unimplemented value creation functions
4 unimplemented value inspection functions
13 unimplemented scalar function functions
4 unimplemented scalar function set functions
12 unimplemented aggregate function functions
Expand Down
6 changes: 6 additions & 0 deletions bindings/test/values.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,4 +275,10 @@ suite('values', () => {
'Failed to create array value'
);
});
test('null', () => {
const null_value = duckdb.create_null_value();
expect(duckdb.is_null_value(null_value)).toEqual(true);
const int32_value = duckdb.create_int32(42);
expect(duckdb.is_null_value(int32_value)).toEqual(false);
});
});

0 comments on commit 3280b64

Please sign in to comment.