Skip to content

null type: create & check #161

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
});
});