diff --git a/bindings/test/prepared_statements.test.ts b/bindings/test/prepared_statements.test.ts index 2cd27aed..57cf620e 100644 --- a/bindings/test/prepared_statements.test.ts +++ b/bindings/test/prepared_statements.test.ts @@ -1,27 +1,28 @@ import duckdb from '@duckdb/node-bindings'; import { expect, suite, test } from 'vitest'; +import { data } from './utils/expectedVectors'; import { expectResult } from './utils/expectResult'; import { withConnection } from './utils/withConnection'; suite('prepared statements', () => { test('no parameters', async () => { - await withConnection(async (con) => { - const prepared = await duckdb.prepare(con, 'select 17 as seventeen'); + await withConnection(async (connection) => { + const prepared = await duckdb.prepare(connection, 'select 17 as seventeen'); try { expect(duckdb.nparams(prepared)).toBe(0); expect(duckdb.prepared_statement_type(prepared)).toBe(duckdb.StatementType.SELECT); - const res = await duckdb.execute_prepared(prepared); + const result = await duckdb.execute_prepared(prepared); try { - await expectResult(res, { + await expectResult(result, { columns: [ - { name: 'seventeen', type: duckdb.Type.INTEGER }, + { name: 'seventeen', logicalType: { typeId: duckdb.Type.INTEGER } }, ], chunks: [ - { rowCount: 1, vectors: [{ byteCount: 4, validity: [true], values: [17] }]}, + { rowCount: 1, vectors: [data(4, [true], [17])]}, ], }); } finally { - duckdb.destroy_result(res); + duckdb.destroy_result(result); } } finally { duckdb.destroy_prepare(prepared); diff --git a/bindings/test/query.test.ts b/bindings/test/query.test.ts index 6d04dbb7..1aeb178c 100644 --- a/bindings/test/query.test.ts +++ b/bindings/test/query.test.ts @@ -1,348 +1,280 @@ import duckdb from '@duckdb/node-bindings'; import { expect, suite, test } from 'vitest'; +import { + ALT, + ARRAY, + BIGINT, + BIT, + BLOB, + BOOLEAN, + DATE, + DECIMAL, + DOUBLE, + ENTRY, + ENUM, + FLOAT, + HUGEINT, + INTEGER, + INTERVAL, + LIST, + MAP, + SMALLINT, + STRUCT, + TIME, + TIME_TZ, + TIMESTAMP, + TIMESTAMP_MS, + TIMESTAMP_NS, + TIMESTAMP_S, + TIMESTAMP_TZ, + TINYINT, + UBIGINT, + UHUGEINT, + UINTEGER, + UNION, + USMALLINT, + UTINYINT, + UUID, + VARCHAR, +} from './utils/expectedLogicalTypes'; +import { array, data, list, map, struct, union } from './utils/expectedVectors'; import { expectResult } from './utils/expectResult'; -import { expectValidity } from './utils/validityTestUtils'; -import { getVarchar } from './utils/valueTestUtils'; import { withConnection } from './utils/withConnection'; +const useLargeEnum = false; + suite('query', () => { test('basic select', async () => { - await withConnection(async (con) => { - const res = await duckdb.query(con, 'select 17 as seventeen'); + await withConnection(async (connection) => { + const result = await duckdb.query(connection, 'select 17 as seventeen'); try { - await expectResult(res, { + await expectResult(result, { columns: [ - { name: 'seventeen', type: duckdb.Type.INTEGER }, + { name: 'seventeen', logicalType: { typeId: duckdb.Type.INTEGER } }, ], chunks: [ - { rowCount: 1, vectors: [{ byteCount: 4, validity: [true], values: [17] }]}, + { rowCount: 1, vectors: [data(4, [true], [17])]}, ], }); } finally { - duckdb.destroy_result(res); + duckdb.destroy_result(result); } }); }); test('basic error', async () => { - await withConnection(async (con) => { - await expect(duckdb.query(con, 'selct 1')).rejects.toThrow('Parser Error'); + await withConnection(async (connection) => { + await expect(duckdb.query(connection, 'selct 1')).rejects.toThrow('Parser Error'); }); }); test('test_all_types()', async () => { - const db = await duckdb.open(); - try { - const con = await duckdb.connect(db); + await withConnection(async (connection) => { + const result = await duckdb.query(connection, `from test_all_types(use_large_enum=${useLargeEnum})`); try { - const res = await duckdb.query(con, 'from test_all_types()'); - try { - expect(duckdb.result_statement_type(res)).toBe(duckdb.StatementType.SELECT); - expect(duckdb.result_return_type(res)).toBe(duckdb.ResultType.QUERY_RESULT); - expect(duckdb.column_count(res)).toBe(53); - - expect(duckdb.column_name(res, 0)).toBe('bool'); - expect(duckdb.column_type(res, 0)).toBe(duckdb.Type.BOOLEAN); - const col_0_logical_type = duckdb.column_logical_type(res, 0); - try { - expect(duckdb.get_type_id(col_0_logical_type)).toBe(duckdb.Type.BOOLEAN); - } finally { - duckdb.destroy_logical_type(col_0_logical_type); - } - - expect(duckdb.column_name(res, 27)).toBe('varchar'); - expect(duckdb.column_type(res, 27)).toBe(duckdb.Type.VARCHAR); - const col_27_logical_type = duckdb.column_logical_type(res, 27); - try { - expect(duckdb.get_type_id(col_27_logical_type)).toBe(duckdb.Type.VARCHAR); - } finally { - duckdb.destroy_logical_type(col_27_logical_type); - } - - expect(duckdb.column_name(res, 33)).toBe('int_array'); - expect(duckdb.column_type(res, 33)).toBe(duckdb.Type.LIST); - const col_33_logical_type = duckdb.column_logical_type(res, 33); - try { - expect(duckdb.get_type_id(col_33_logical_type)).toBe(duckdb.Type.LIST); - const child_type = duckdb.list_type_child_type(col_33_logical_type); - try { - expect(duckdb.get_type_id(child_type)).toBe(duckdb.Type.INTEGER); - } finally { - duckdb.destroy_logical_type(child_type); - } - } finally { - duckdb.destroy_logical_type(col_33_logical_type); - } - - expect(duckdb.column_name(res, 40)).toBe('struct'); - expect(duckdb.column_type(res, 40)).toBe(duckdb.Type.STRUCT); - const col_40_logical_type = duckdb.column_logical_type(res, 40); - try { - expect(duckdb.get_type_id(col_40_logical_type)).toBe(duckdb.Type.STRUCT); - expect(duckdb.struct_type_child_count(col_40_logical_type)).toBe(2); - expect(duckdb.struct_type_child_name(col_40_logical_type, 0)).toBe('a'); - expect(duckdb.struct_type_child_name(col_40_logical_type, 1)).toBe('b'); - const member_type_0 = duckdb.struct_type_child_type(col_40_logical_type, 0); - try { - expect(duckdb.get_type_id(member_type_0)).toBe(duckdb.Type.INTEGER); - } finally { - duckdb.destroy_logical_type(member_type_0); - } - const member_type_1 = duckdb.struct_type_child_type(col_40_logical_type, 1); - try { - expect(duckdb.get_type_id(member_type_1)).toBe(duckdb.Type.VARCHAR); - } finally { - duckdb.destroy_logical_type(member_type_1); - } - } finally { - duckdb.destroy_logical_type(col_40_logical_type); - } - - expect(duckdb.column_name(res, 45)).toBe('fixed_int_array'); - expect(duckdb.column_type(res, 45)).toBe(duckdb.Type.ARRAY); - const col_45_logical_type = duckdb.column_logical_type(res, 45); - try { - expect(duckdb.get_type_id(col_45_logical_type)).toBe(duckdb.Type.ARRAY); - expect(duckdb.array_type_array_size(col_45_logical_type)).toBe(3); - const child_type = duckdb.array_type_child_type(col_45_logical_type); - try { - expect(duckdb.get_type_id(child_type)).toBe(duckdb.Type.INTEGER); - } finally { - duckdb.destroy_logical_type(child_type); - } - } finally { - duckdb.destroy_logical_type(col_45_logical_type); - } - - expect(duckdb.column_name(res, 52)).toBe('list_of_fixed_int_array'); - expect(duckdb.column_type(res, 52)).toBe(duckdb.Type.LIST); - const col_52_logical_type = duckdb.column_logical_type(res, 52); - try { - expect(duckdb.get_type_id(col_52_logical_type)).toBe(duckdb.Type.LIST); - const child_type = duckdb.list_type_child_type(col_52_logical_type); - try { - expect(duckdb.get_type_id(child_type)).toBe(duckdb.Type.ARRAY); - expect(duckdb.array_type_array_size(child_type)).toBe(3); - const array_child_type = duckdb.array_type_child_type(child_type); - try { - expect(duckdb.get_type_id(array_child_type)).toBe(duckdb.Type.INTEGER); - } finally { - duckdb.destroy_logical_type(array_child_type); - } - } finally { - duckdb.destroy_logical_type(child_type); - } - } finally { - duckdb.destroy_logical_type(col_52_logical_type); - } - - const chunk = await duckdb.fetch_chunk(res); - try { - expect(duckdb.data_chunk_get_column_count(chunk)).toBe(53); - expect(duckdb.data_chunk_get_size(chunk)).toBe(3); - - // bool - const bool_vector = duckdb.data_chunk_get_vector(chunk, 0); - const bool_validity_bytes = duckdb.vector_get_validity(bool_vector, 8); - const bool_validity = new BigUint64Array(bool_validity_bytes.buffer, 0, 1); - const bool_data = duckdb.vector_get_data(bool_vector, 3); - const bool_dv = new DataView(bool_data.buffer); - - expectValidity(bool_validity_bytes, bool_validity, 0, true); - const bool_value0 = bool_dv.getUint8(0) !== 0; - expect(bool_value0).toBe(false); - - expectValidity(bool_validity_bytes, bool_validity, 1, true); - const bool_value1 = bool_dv.getUint8(1) !== 0; - expect(bool_value1).toBe(true); - - expectValidity(bool_validity_bytes, bool_validity, 2, false); - - // varchar - const varchar_vector = duckdb.data_chunk_get_vector(chunk, 27); - const varchar_validity_bytes = duckdb.vector_get_validity(varchar_vector, 8); - const varchar_validity = new BigUint64Array(varchar_validity_bytes.buffer, 0, 1); - const varchar_data = duckdb.vector_get_data(varchar_vector, 3*16); - const varchar_dv = new DataView(varchar_data.buffer); - - expectValidity(varchar_validity_bytes, varchar_validity, 0, true); - expect(getVarchar(varchar_dv, 0*16)).toBe('🦆🦆🦆🦆🦆🦆'); - - expectValidity(varchar_validity_bytes, varchar_validity, 1, true); - expect(getVarchar(varchar_dv, 1*16)).toBe('goo\0se'); - - expectValidity(varchar_validity_bytes, varchar_validity, 2, false); - - // int_array - const int_array_vector = duckdb.data_chunk_get_vector(chunk, 33); - const int_array_validity_bytes = duckdb.vector_get_validity(int_array_vector, 8); - const int_array_validity = new BigUint64Array(int_array_validity_bytes.buffer, 0, 1); - const int_array_entry_data = duckdb.vector_get_data(int_array_vector, 3 * 16); - const int_array_entry_dv = new DataView(int_array_entry_data.buffer); - const int_array_child = duckdb.list_vector_get_child(int_array_vector); - const int_array_child_size = duckdb.list_vector_get_size(int_array_vector); - const int_array_child_validity_bytes = duckdb.vector_get_validity(int_array_child, 8); - const int_array_child_validity = new BigUint64Array(int_array_child_validity_bytes.buffer, 0, 1); - const int_array_child_data = duckdb.vector_get_data(int_array_child, int_array_child_size * 4); - const int_array_child_dv = new DataView(int_array_child_data.buffer); - - expectValidity(int_array_validity_bytes, int_array_validity, 0, true); - const value0_offset = int_array_entry_dv.getBigUint64(0, true); - const value0_length = int_array_entry_dv.getBigUint64(8, true); - expect(value0_offset).toBe(0n); - expect(value0_length).toBe(0n); - - expectValidity(int_array_validity_bytes, int_array_validity, 1, true); - const value1_offset = int_array_entry_dv.getBigUint64(16, true); - const value1_length = int_array_entry_dv.getBigUint64(24, true); - expect(value1_offset).toBe(0n); - expect(value1_length).toBe(5n); - - expectValidity(int_array_child_validity_bytes, int_array_child_validity, Number(value1_offset)+0, true); - expect(int_array_child_dv.getInt32(Number(value1_offset)+0*4, true)).toBe(42); - - expectValidity(int_array_child_validity_bytes, int_array_child_validity, Number(value1_offset)+1, true); - expect(int_array_child_dv.getInt32(Number(value1_offset)+1*4, true)).toBe(999); - - expectValidity(int_array_child_validity_bytes, int_array_child_validity, Number(value1_offset)+2, false); - - expectValidity(int_array_child_validity_bytes, int_array_child_validity, Number(value1_offset)+3, false); - - expectValidity(int_array_child_validity_bytes, int_array_child_validity, Number(value1_offset)+4, true); - expect(int_array_child_dv.getInt32(Number(value1_offset)+4*4, true)).toBe(-42); - - expectValidity(int_array_validity_bytes, int_array_validity, 2, false); - const value2_offset = int_array_entry_dv.getBigUint64(32, true); - const value2_length = int_array_entry_dv.getBigUint64(40, true); - expect(value2_offset).toBe(0n); - expect(value2_length).toBe(0n); - - // struct - const struct_vector = duckdb.data_chunk_get_vector(chunk, 40); - const struct_validity_bytes = duckdb.vector_get_validity(struct_vector, 8); - const struct_validity = new BigUint64Array(struct_validity_bytes.buffer, 0, 1); - const struct_child0 = duckdb.struct_vector_get_child(struct_vector, 0); - const struct_child0_validity_bytes = duckdb.vector_get_validity(struct_child0, 8); - const struct_child0_validity = new BigUint64Array(struct_child0_validity_bytes.buffer, 0, 1); - const struct_child0_data = duckdb.vector_get_data(struct_child0, 3*4); - const struct_child0_dv = new DataView(struct_child0_data.buffer); - const struct_child1 = duckdb.struct_vector_get_child(struct_vector, 1); - const struct_child1_validity_bytes = duckdb.vector_get_validity(struct_child1, 8); - const struct_child1_validity = new BigUint64Array(struct_child1_validity_bytes.buffer, 0, 1); - const struct_child1_data = duckdb.vector_get_data(struct_child1, 3*16); - const struct_child1_dv = new DataView(struct_child1_data.buffer); - - expectValidity(struct_validity_bytes, struct_validity, 0, true); - expectValidity(struct_child0_validity_bytes, struct_child0_validity, 0, false); - expectValidity(struct_child1_validity_bytes, struct_child1_validity, 0, false); - - expectValidity(struct_validity_bytes, struct_validity, 1, true); - expectValidity(struct_child0_validity_bytes, struct_child0_validity, 1, true); - expect(struct_child0_dv.getInt32(1*4, true)).toBe(42); - expectValidity(struct_child1_validity_bytes, struct_child1_validity, 1, true); - expect(struct_child1_dv.getInt32(1*16, true)).toBe(24); - expect(getVarchar(struct_child1_dv, 1*16)).toBe('🦆🦆🦆🦆🦆🦆'); - - expectValidity(struct_validity_bytes, struct_validity, 2, false); - expectValidity(struct_child0_validity_bytes, struct_child0_validity, 2, false); - expectValidity(struct_child1_validity_bytes, struct_child1_validity, 2, false); - - // fixed_int_array - const fixed_int_array_vector = duckdb.data_chunk_get_vector(chunk, 45); - const fixed_int_array_validity_bytes = duckdb.vector_get_validity(fixed_int_array_vector, 8); - const fixed_int_array_validity = new BigUint64Array(fixed_int_array_validity_bytes.buffer, 0, 1); - const fixed_int_array_child_vector = duckdb.array_vector_get_child(fixed_int_array_vector); - const fixed_int_array_child_validity_bytes = duckdb.vector_get_validity(fixed_int_array_child_vector, 8); - const fixed_int_array_child_validity = new BigUint64Array(fixed_int_array_child_validity_bytes.buffer, 0, 1); - const fixed_int_array_child_data = duckdb.vector_get_data(fixed_int_array_child_vector, 3*3*4); - const fixed_int_array_child_dv = new DataView(fixed_int_array_child_data.buffer); - - expectValidity(fixed_int_array_validity_bytes, fixed_int_array_validity, 0, true); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 0*3+0, false); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 0*3+1, true); - expect(fixed_int_array_child_dv.getInt32((0*3+1)*4, true)).toBe(2); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 0*3+2, true); - expect(fixed_int_array_child_dv.getInt32((0*3+2)*4, true)).toBe(3); - - expectValidity(fixed_int_array_validity_bytes, fixed_int_array_validity, 1, true); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 1*3+0, true); - expect(fixed_int_array_child_dv.getInt32((1*3+0)*4, true)).toBe(4); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 1*3+1, true); - expect(fixed_int_array_child_dv.getInt32((1*3+1)*4, true)).toBe(5); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 1*3+2, true); - expect(fixed_int_array_child_dv.getInt32((1*3+2)*4, true)).toBe(6); - - expectValidity(fixed_int_array_validity_bytes, fixed_int_array_validity, 2, false); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 2*3+0, false); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 2*3+1, false); - expectValidity(fixed_int_array_child_validity_bytes, fixed_int_array_child_validity, 2*3+2, false); - - } finally { - duckdb.destroy_data_chunk(chunk); - } - } finally { - duckdb.destroy_result(res); - } + const validity = [true, true, false]; + await expectResult(result, { + columns: [ + { name: 'bool', logicalType: BOOLEAN }, + { name: 'tinyint', logicalType: TINYINT }, + { name: 'smallint', logicalType: SMALLINT }, + { name: 'int', logicalType: INTEGER }, + { name: 'bigint', logicalType: BIGINT }, + { name: 'hugeint', logicalType: HUGEINT }, + { name: 'uhugeint', logicalType: UHUGEINT }, + { name: 'utinyint', logicalType: UTINYINT }, + { name: 'usmallint', logicalType: USMALLINT }, + { name: 'uint', logicalType: UINTEGER }, + { name: 'ubigint', logicalType: UBIGINT }, + { name: 'date', logicalType: DATE }, + { name: 'time', logicalType: TIME }, + { name: 'timestamp', logicalType: TIMESTAMP }, + { name: 'timestamp_s', logicalType: TIMESTAMP_S }, + { name: 'timestamp_ms', logicalType: TIMESTAMP_MS }, + { name: 'timestamp_ns', logicalType: TIMESTAMP_NS }, + { name: 'time_tz', logicalType: TIME_TZ }, + { name: 'timestamp_tz', logicalType: TIMESTAMP_TZ }, + { name: 'float', logicalType: FLOAT }, + { name: 'double', logicalType: DOUBLE }, + { name: 'dec_4_1', logicalType: DECIMAL(4, 1, duckdb.Type.SMALLINT) }, + { name: 'dec_9_4', logicalType: DECIMAL(9, 4, duckdb.Type.INTEGER) }, + { name: 'dec_18_6', logicalType: DECIMAL(18, 6, duckdb.Type.BIGINT) }, + { name: 'dec38_10', logicalType: DECIMAL(38, 10, duckdb.Type.HUGEINT) }, + { name: 'uuid', logicalType: UUID }, + { name: 'interval', logicalType: INTERVAL }, + { name: 'varchar', logicalType: VARCHAR }, + { name: 'blob', logicalType: BLOB }, + { name: 'bit', logicalType: BIT }, + { name: 'small_enum', logicalType: ENUM(['DUCK_DUCK_ENUM', 'GOOSE'], duckdb.Type.UTINYINT) }, + { name: 'medium_enum', logicalType: ENUM(Array.from({ length: 300}).map((_, i) => `enum_${i}`), duckdb.Type.USMALLINT) }, + { name: 'large_enum', logicalType: useLargeEnum + ? ENUM(Array.from({ length: 70000}).map((_, i) => `enum_${i}`), duckdb.Type.UINTEGER) + : ENUM(['enum_0', 'enum_69999'], duckdb.Type.UTINYINT) + }, + { name: 'int_array', logicalType: LIST(INTEGER) }, + { name: 'double_array', logicalType: LIST(DOUBLE) }, + { name: 'date_array', logicalType: LIST(DATE) }, + { name: 'timestamp_array', logicalType: LIST(TIMESTAMP) }, + { name: 'timestamptz_array', logicalType: LIST(TIMESTAMP_TZ) }, + { name: 'varchar_array', logicalType: LIST(VARCHAR) }, + { name: 'nested_int_array', logicalType: LIST(LIST(INTEGER)) }, + { name: 'struct', logicalType: STRUCT(ENTRY('a', INTEGER), ENTRY('b', VARCHAR)) }, + { name: 'struct_of_arrays', logicalType: STRUCT(ENTRY('a', LIST(INTEGER)), ENTRY('b', LIST(VARCHAR))) }, + { name: 'array_of_structs', logicalType: LIST(STRUCT(ENTRY('a', INTEGER), ENTRY('b', VARCHAR))) }, + { name: 'map', logicalType: MAP(VARCHAR, VARCHAR) }, + { name: 'union', logicalType: UNION(ALT('name', VARCHAR), ALT('age', SMALLINT)) }, + { name: 'fixed_int_array', logicalType: ARRAY(INTEGER, 3) }, + { name: 'fixed_varchar_array', logicalType: ARRAY(VARCHAR, 3) }, + { name: 'fixed_nested_int_array', logicalType: ARRAY(ARRAY(INTEGER, 3), 3) }, + { name: 'fixed_nested_varchar_array', logicalType: ARRAY(ARRAY(VARCHAR, 3), 3) }, + { name: 'fixed_struct_array', logicalType: ARRAY(STRUCT(ENTRY('a', INTEGER), ENTRY('b', VARCHAR)), 3) }, + { name: 'struct_of_fixed_array', logicalType: STRUCT(ENTRY('a', ARRAY(INTEGER, 3)), ENTRY('b', ARRAY(VARCHAR, 3))) }, + { name: 'fixed_array_of_int_list', logicalType: ARRAY(LIST(INTEGER), 3) }, + { name: 'list_of_fixed_int_array', logicalType: LIST(ARRAY(INTEGER, 3)) }, + ], + chunks: [ + { + rowCount: 3, + vectors: [ + data(1, validity, [false, true, null]), // 0: bool + data(1, validity, [-128, 127, null]), // 1: tinyint + data(2, validity, [-32768, 32767, null]), // 2: smallint + data(4, validity, [-2147483648, 2147483647, null]), // 3: int + data(8, validity, [-9223372036854775808n, 9223372036854775807n, null]), // 4: bigint + data(16, validity, [-170141183460469231731687303715884105728n, 170141183460469231731687303715884105727n, null]), // 5: hugeint + data(16, validity, [0n, 340282366920938463463374607431768211455n, null]), // 6: uhugeint + data(1, validity, [0, 255, null]), // 7: utinyint + data(2, validity, [0, 65535, null]), // 8: usmallint + data(4, validity, [0, 4294967295, null]), // 9: uint + data(8, validity, [0n, 18446744073709551615n, null]), // 10: ubigint + data(4, validity, [-2147483646, 2147483646, null]), // 11: date + data(8, validity, [0n, 86400000000n, null]), // 12: time + data(8, validity, [-9223372022400000000n, 9223372036854775806n, null]), // 13: timestamp + data(8, validity, [-9223372022400n, 9223372036854n, null]), // 14: timestamp_s + data(8, validity, [-9223372022400000n, 9223372036854775n, null]), // 15: timestamp_ms + data(8, validity, [-9223372036854775808n, 9223372036854775806n, null]), // 16: timestamp_ns + data(8, validity, [0n, 1449551462400115198n, null]), // 17: time_tz + data(8, validity, [-9223372022400000000n, 9223372036854775806n, null]), // 18: timestamp_tz + data(4, validity, [-3.4028234663852886e+38, 3.4028234663852886e+38, null]), // 19: float + data(8, validity, [-1.7976931348623157e+308, 1.7976931348623157e+308, null]), // 20: double + data(2, validity, [-9999, 9999, null]), // 21: dec_4_1 + data(4, validity, [-999999999, 999999999, null]), // 22: dec_9_4 + data(8, validity, [-999999999999999999n, 999999999999999999n, null]), // 23: dec_18_6 + data(16, validity, [-99999999999999999999999999999999999999n, 99999999999999999999999999999999999999n, null]), // 24: dec38_10 + data(16, validity, [-170141183460469231731687303715884105728n, 170141183460469231731687303715884105727n, null]), // 25: uuid + data(16, validity, [{ months: 0, days: 0, micros: 0n }, { months: 999, days: 999, micros: 999999999n }, null]), // 26: interval + data(16, validity, ['🦆🦆🦆🦆🦆🦆', 'goo\0se', null]), // 27: varchar + data(16, validity, [Buffer.from('thisisalongblob\x00withnullbytes'), Buffer.from('\x00\x00\x00a'), null]), // 28: blob + data(16, validity, [Buffer.from([1, 0b10010001, 0b00101110, 0b00101010, 0b11010111]), Buffer.from([3, 0b11110101]), null]), // 29: bit (x0010001 00101110 00101010 11010111, xxx10101) + data(1, validity, [0, 1, null]), // 30: small_enum + data(2, validity, [0, 299, null]), // 31: medium_enum + data(4, validity, [0, useLargeEnum ? 69999 : 1, null]), // 32: large_enum + list(validity, [[0n, 0n], [0n, 5n], null], 5, data(4, [true, true, false, false, true], [42, 999, null, null, -42])), // 33: int_array + list(validity, [[0n, 0n], [0n, 6n], null], 6, data(8, [true, true, true, true, false, true], [42.0, NaN, Infinity, -Infinity, null, -42.0])), // 34: double_array + list(validity, [[0n, 0n], [0n, 5n], null], 5, data(4, [true, true, true, false, true], [0, 2147483647, -2147483647, null, 19124])), // 35: date_array + list(validity, [[0n, 0n], [0n, 5n], null], 5, data(8, [true, true, true, false, true], [0n, 9223372036854775807n, -9223372036854775807n, null, 1652372625000000n])), // 36: timestamp_array + list(validity, [[0n, 0n], [0n, 5n], null], 5, data(8, [true, true, true, false, true], [0n, 9223372036854775807n, -9223372036854775807n, null, 1652397825000000n])), // 37: timestamptz_array + list(validity, [[0n, 0n], [0n, 4n], null], 4, data(16, [true, true, false, true], ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''])), // 38: varchar_array + list(validity, [[0n, 0n], [0n, 5n], null], 5, + list([true, true, false, true, true], [[0n, 0n], [0n, 5n], null, [5n, 0n], [5n, 5n]], 10, + data(10, [true, true, false, false, true, true, true, false, false, true], [42, 999, null, null, -42, 42, 999, null, null, -42]))), // 39: nested_int_array + struct(3, validity, [data(4, [false, true, false], [null, 42, null]), data(16, [false, true, false], [null, '🦆🦆🦆🦆🦆🦆', null])]), // 40: struct + struct(3, validity, [ + list([false, true, false], [null, [0n, 5n], null], 5, data(4, [true, true, false, false, true], [42, 999, null, null, -42])), + list([false, true, false], [null, [0n, 4n], null], 4, data(16, [true, true, false, true], ['🦆🦆🦆🦆🦆🦆', 'goose', null, ''])), + ]), // 41: struct_of_arrays + list(validity, [[0n, 0n], [0n, 3n], null], 3, + struct(3, [true, true, false], [ + data(4, [false, true], [null, 42]), + data(16, [false, true], [null, '🦆🦆🦆🦆🦆🦆']), + ]) + ), // 42: array_of_structs + map(validity, [[0n, 0n], [0n, 2n], null], + data(16, [true, true], ['key1', 'key2']), + data(16, [true, true], ['🦆🦆🦆🦆🦆🦆', 'goose']), + ), // 43: map + union([ + data(1, validity, [0, 1, null]), // tags + data(16, [true, false, false], ['Frank', null, null]), + data(2, [false, true, false], [null, 5, null]), + ]), // 44: union + array(3, validity, data(4, [false, true, true, true, true, true], [null, 2, 3, 4, 5, 6])), // 45: fixed_int_array + array(3, validity, data(16, [true, false, true, true, true, true], ['a', null, 'c', 'd', 'e', 'f'])), // 46: fixed_varchar_array + array(3, validity, + array(3, [true, false, true, true, true, true, false, false, false], + data(4, [false, true, true, false, false, false, false, true, true, true, true, true, false, true, true, true, true, true], + [null, 2, 3, null, null, null, null, 2, 3, 4, 5, 6, null, 2, 3, 4, 5, 6]) + ) + ), // 47: fixed_nested_int_array + array(3, validity, + array(3, [true, false, true, true, true, true, false, false, false], + data(16, [true, false, true, false, false, false, true, false, true, true, true, true, true, false, true, true, true, true], + ['a', null, 'c', null, null, null, 'a', null, 'c', 'd', 'e', 'f', 'a', null, 'c', 'd', 'e', 'f']) + ) + ), // 48: fixed_nested_varchar_array + array(3, validity, + struct(9, [true, true, true, true, true, true, false, false, false], [ + data(4, [false, true, false, true, false, true, false, false, false], [null, 42, null, 42, null, 42, null, null, null]), + data(16, [false, true, false, true, false, true, false, false, false], [null, '🦆🦆🦆🦆🦆🦆', null, '🦆🦆🦆🦆🦆🦆', null, '🦆🦆🦆🦆🦆🦆', null, null, null]), + ]) + ), // 49: fixed_struct_array + struct(3, validity, [ + array(2, [true, true], data(4, [false, true, true, true, true, true], [null, 2, 3, 4, 5, 6])), + array(2, [true, true], data(16, [true, false, true, true, true, true], ['a', null, 'c', 'd', 'e', 'f'])), + ]), // 50: struct_of_fixed_array + array(3, validity, + list([true, true, true, true, true, true, false, false, false], [[0n, 0n], [0n, 5n], [5n, 0n], [5n, 5n], [10n, 0n], [10n, 5n], null, null, null], 15, + data(4, [true, true, false, false, true, true, true, false, false, true, true, true, false, false, true], + [42, 999, null, null, -42, 42, 999, null, null, -42, 42, 999, null, null, -42]) + ) + ), // 51: fixed_array_of_int_list + list(validity, [[0n, 3n], [3n, 3n]], 6, + array(6, [true, true, true, true, true, true], + data(4, [false, true, true, true, true, true, false, true, true, true, true, true, false, true, true, true, true, true], + [null, 2, 3, 4, 5, 6, null, 2, 3, 4, 5, 6, null, 2, 3, 4, 5, 6]) + ) + ), // 52: list_of_fixed_int_array + ], + }, + ], + }); } finally { - await duckdb.disconnect(con); + duckdb.destroy_result(result); } - } finally { - await duckdb.close(db); - } + }); }); test('create and insert', async () => { - const db = await duckdb.open(); - try { - const con = await duckdb.connect(db); + await withConnection(async (connection) => { + const createResult = await duckdb.query(connection, 'create table test_create_and_insert(i integer)'); try { - const res = await duckdb.query(con, 'create table test_create_and_insert(i integer)'); - try { - expect(duckdb.result_statement_type(res)).toBe(duckdb.StatementType.CREATE); - expect(duckdb.result_return_type(res)).toBe(duckdb.ResultType.NOTHING); - expect(duckdb.rows_changed(res)).toBe(0); - expect(duckdb.column_count(res)).toBe(1); - expect(duckdb.column_name(res, 0)).toBe('Count'); - expect(duckdb.column_type(res, 0)).toBe(duckdb.Type.BIGINT); - const chunk = await duckdb.fetch_chunk(res); - try { - expect(duckdb.data_chunk_get_column_count(chunk)).toBe(0); - expect(duckdb.data_chunk_get_size(chunk)).toBe(0); - } finally { - duckdb.destroy_data_chunk(chunk); - } - } finally { - duckdb.destroy_result(res); - } - const res2 = await duckdb.query(con, 'insert into test_create_and_insert from range(17)'); - try { - expect(duckdb.result_statement_type(res2)).toBe(duckdb.StatementType.INSERT); - expect(duckdb.result_return_type(res2)).toBe(duckdb.ResultType.CHANGED_ROWS); - expect(duckdb.rows_changed(res2)).toBe(17); - expect(duckdb.column_count(res2)).toBe(1); - expect(duckdb.column_name(res2, 0)).toBe('Count'); - expect(duckdb.column_type(res2, 0)).toBe(duckdb.Type.BIGINT); - const chunk = await duckdb.fetch_chunk(res2); - try { - expect(duckdb.data_chunk_get_column_count(chunk)).toBe(1); - expect(duckdb.data_chunk_get_size(chunk)).toBe(1); - const vector = duckdb.data_chunk_get_vector(chunk, 0); - const data = duckdb.vector_get_data(vector, 8); - const dv = new DataView(data.buffer); - const validity_bytes = duckdb.vector_get_validity(vector, 8); - const validity = new BigUint64Array(validity_bytes.buffer, 0, 1); - expectValidity(validity_bytes, validity, 0, true); - const value = dv.getBigInt64(0, true); - expect(value).toBe(17n); - } finally { - duckdb.destroy_data_chunk(chunk); - } - } finally { - duckdb.destroy_result(res2); - } + await expectResult(createResult, { + statementType: duckdb.StatementType.CREATE, + resultType: duckdb.ResultType.NOTHING, + columns: [ + { name: 'Count', logicalType: { typeId: duckdb.Type.BIGINT } }, + ], + chunks: [ + { columnCount: 0, rowCount: 0, vectors: [] }, + ], + }); } finally { - await duckdb.disconnect(con); + duckdb.destroy_result(createResult); } - } finally { - await duckdb.close(db); - } + const insertResult = await duckdb.query(connection, 'insert into test_create_and_insert from range(17)'); + try { + await expectResult(insertResult, { + statementType: duckdb.StatementType.INSERT, + resultType: duckdb.ResultType.CHANGED_ROWS, + rowsChanged: 17, + columns: [ + { name: 'Count', logicalType: { typeId: duckdb.Type.BIGINT } }, + ], + chunks: [ + { rowCount: 1, vectors: [data(8, [true], [17n])] }, + ], + }); + } finally { + duckdb.destroy_result(insertResult); + } + }); }); // TODO: interrupt // TODO: query_progress diff --git a/bindings/test/utils/ExpectedLogicalType.ts b/bindings/test/utils/ExpectedLogicalType.ts new file mode 100644 index 00000000..87ab95ef --- /dev/null +++ b/bindings/test/utils/ExpectedLogicalType.ts @@ -0,0 +1,74 @@ +import duckdb from '@duckdb/node-bindings'; + +export interface ExpectedSimpleLogicalType { + typeId: Exclude; +} + +export interface ExpectedArrayLogicalType { + typeId: duckdb.Type.ARRAY; + valueType: ExpectedLogicalType; + size: number; +} + +export interface ExpectedDecimalLogicalType { + typeId: duckdb.Type.DECIMAL; + width: number; + scale: number; + internalType: duckdb.Type; +} + +export interface ExpectedEnumLogicalType { + typeId: duckdb.Type.ENUM; + values: string[]; + internalType: duckdb.Type; +} + +export interface ExpectedListLogicalType { + typeId: duckdb.Type.LIST; + valueType: ExpectedLogicalType; +} + +export interface ExpectedMapLogicalType { + typeId: duckdb.Type.MAP; + keyType: ExpectedLogicalType; + valueType: ExpectedLogicalType; +} + +export interface ExpectedStructEntry { + name: string; + type: ExpectedLogicalType; +} + +export interface ExpectedStructLogicalType { + typeId: duckdb.Type.STRUCT; + entries: ExpectedStructEntry[]; +} + +export interface ExpectedUnionAlternative { + tag: string; + type: ExpectedLogicalType; +} + +export interface ExpectedUnionLogicalType { + typeId: duckdb.Type.UNION; + alternatives: ExpectedUnionAlternative[]; +} + +export type ExpectedLogicalType = + | ExpectedSimpleLogicalType + | ExpectedArrayLogicalType + | ExpectedDecimalLogicalType + | ExpectedEnumLogicalType + | ExpectedListLogicalType + | ExpectedMapLogicalType + | ExpectedStructLogicalType + | ExpectedUnionLogicalType + ; diff --git a/bindings/test/utils/ExpectedResult.ts b/bindings/test/utils/ExpectedResult.ts new file mode 100644 index 00000000..4da58d72 --- /dev/null +++ b/bindings/test/utils/ExpectedResult.ts @@ -0,0 +1,22 @@ +import duckdb from '@duckdb/node-bindings'; +import { ExpectedLogicalType } from './ExpectedLogicalType'; +import { ExpectedVector } from './ExpectedVector'; + +export interface ExpectedColumn { + name: string; + logicalType: ExpectedLogicalType; +} + +export interface ExpectedChunk { + columnCount?: number; + rowCount: number; + vectors: ExpectedVector[]; +} + +export interface ExpectedResult { + statementType?: duckdb.StatementType; + resultType?: duckdb.ResultType; + rowsChanged?: number; + columns: ExpectedColumn[]; + chunks: ExpectedChunk[]; +} diff --git a/bindings/test/utils/ExpectedVector.ts b/bindings/test/utils/ExpectedVector.ts new file mode 100644 index 00000000..4c50c7f4 --- /dev/null +++ b/bindings/test/utils/ExpectedVector.ts @@ -0,0 +1,52 @@ +export interface ExpectedArrayVector { + kind: 'array'; + itemCount: number; + validity: boolean[]; + child: ExpectedVector; +} + +export interface ExpectedDataVector { + kind: 'data'; + validity: boolean[]; + itemBytes: number; + values: any[]; +} + +export type ExpectedListEntry = [bigint, bigint] | null; + +export interface ExpectedListVector { + kind: 'list'; + validity: boolean[]; + entries: (ExpectedListEntry | null)[]; + childItemCount: number; + child: ExpectedVector; +} + +export interface ExpectedMapVector { + kind: 'map'; + validity: boolean[]; + entries: (ExpectedListEntry | null)[]; + keys: ExpectedVector; + values: ExpectedVector; +} + +export interface ExpectedStructVector { + kind: 'struct'; + itemCount: number; + validity: boolean[]; + children: ExpectedVector[]; +} + +export interface ExpectedUnionVector { + kind: 'union'; + children: ExpectedVector[]; +} + +export type ExpectedVector = + | ExpectedArrayVector + | ExpectedDataVector + | ExpectedListVector + | ExpectedMapVector + | ExpectedStructVector + | ExpectedUnionVector + ; diff --git a/bindings/test/utils/expectChunk.ts b/bindings/test/utils/expectChunk.ts new file mode 100644 index 00000000..cb55a8c8 --- /dev/null +++ b/bindings/test/utils/expectChunk.ts @@ -0,0 +1,23 @@ +import duckdb from '@duckdb/node-bindings'; +import { expect } from 'vitest'; +import { ExpectedChunk, ExpectedColumn } from './ExpectedResult'; +import { expectLogicalType } from './expectLogicalType'; +import { expectVector } from './expectVector'; +import { withLogicalType } from './withLogicalType'; + +export function expectChunk(chunk: duckdb.DataChunk, expectedChunk: ExpectedChunk, expectedColumns: ExpectedColumn[]) { + const chunkColumnCount = expectedChunk.columnCount ?? expectedColumns.length; + expect(duckdb.data_chunk_get_column_count(chunk)).toBe(chunkColumnCount); + expect(duckdb.data_chunk_get_size(chunk)).toBe(expectedChunk.rowCount); + for (let col = 0; col < expectedChunk.vectors.length; col++) { + const expectedVector = expectedChunk.vectors[col]; + const vector = duckdb.data_chunk_get_vector(chunk, col); + + const expectedLogicalType = expectedColumns[col].logicalType; + withLogicalType(duckdb.vector_get_column_type(vector), + (logical_type) => expectLogicalType(logical_type, expectedLogicalType, `col ${col}`) + ); + + expectVector(vector, expectedVector, expectedLogicalType, `col ${col}`); + } +} diff --git a/bindings/test/utils/expectLogicalType.ts b/bindings/test/utils/expectLogicalType.ts new file mode 100644 index 00000000..f97fae9e --- /dev/null +++ b/bindings/test/utils/expectLogicalType.ts @@ -0,0 +1,48 @@ +import duckdb from '@duckdb/node-bindings'; +import { expect } from 'vitest'; +import { ExpectedLogicalType } from './ExpectedLogicalType'; + +export function expectLogicalType(logical_type: duckdb.LogicalType, expectedLogicalType: ExpectedLogicalType, message?: string) { + expect(duckdb.get_type_id(logical_type), message).toBe(expectedLogicalType.typeId); + switch (expectedLogicalType.typeId) { + case duckdb.Type.ARRAY: + expectLogicalType(duckdb.array_type_child_type(logical_type), expectedLogicalType.valueType); + expect(duckdb.array_type_array_size(logical_type)).toBe(expectedLogicalType.size); + break; + case duckdb.Type.DECIMAL: + expect(duckdb.decimal_width(logical_type)).toBe(expectedLogicalType.width); + expect(duckdb.decimal_scale(logical_type)).toBe(expectedLogicalType.scale); + expect(duckdb.decimal_internal_type(logical_type)).toBe(expectedLogicalType.internalType); + break; + case duckdb.Type.ENUM: + { + expect(duckdb.enum_internal_type(logical_type)).toBe(expectedLogicalType.internalType); + expect(duckdb.enum_dictionary_size(logical_type)).toBe(expectedLogicalType.values.length); + for (let i = 0; i < expectedLogicalType.values.length; i++) { + expect(duckdb.enum_dictionary_value(logical_type, i)).toBe(expectedLogicalType.values[i]); + } + } + break; + case duckdb.Type.LIST: + expectLogicalType(duckdb.list_type_child_type(logical_type), expectedLogicalType.valueType); + break; + case duckdb.Type.MAP: + expectLogicalType(duckdb.map_type_key_type(logical_type), expectedLogicalType.keyType); + expectLogicalType(duckdb.map_type_value_type(logical_type), expectedLogicalType.valueType); + break; + case duckdb.Type.STRUCT: + expect(duckdb.struct_type_child_count(logical_type)).toBe(expectedLogicalType.entries.length); + for (let i = 0; i < expectedLogicalType.entries.length; i++) { + expect(duckdb.struct_type_child_name(logical_type, i)).toBe(expectedLogicalType.entries[i].name); + expectLogicalType(duckdb.struct_type_child_type(logical_type, i), expectedLogicalType.entries[i].type); + } + break; + case duckdb.Type.UNION: + expect(duckdb.union_type_member_count(logical_type)).toBe(expectedLogicalType.alternatives.length); + for (let i = 0; i < expectedLogicalType.alternatives.length; i++) { + expect(duckdb.union_type_member_name(logical_type, i)).toBe(expectedLogicalType.alternatives[i].tag); + expectLogicalType(duckdb.union_type_member_type(logical_type, i), expectedLogicalType.alternatives[i].type); + } + break; + } +} diff --git a/bindings/test/utils/expectResult.ts b/bindings/test/utils/expectResult.ts index f088a034..377cd970 100644 --- a/bindings/test/utils/expectResult.ts +++ b/bindings/test/utils/expectResult.ts @@ -1,74 +1,27 @@ import duckdb from '@duckdb/node-bindings'; import { expect } from 'vitest'; -import { expectValidity } from './validityTestUtils'; -import { getValue } from './valueTestUtils'; +import { expectChunk } from './expectChunk'; +import { ExpectedResult } from './ExpectedResult'; +import { expectLogicalType } from './expectLogicalType'; +import { withLogicalType } from './withLogicalType'; -export interface ExpectedResult { - statementType?: duckdb.StatementType; - resultType?: duckdb.ResultType; - rowsChanged?: number; - columns: ExpectedColumn[]; - chunks: ExpectedChunk[]; -} - -export interface ExpectedColumn { - name: string; - type: duckdb.Type; -} - -export interface ExpectedChunk { - rowCount: number; - vectors: ExpectedVector[]; -} - -export interface ExpectedVector { - byteCount: number; - validity: boolean[]; - values: any[]; -} - -export async function expectResult(res: duckdb.Result, expected: ExpectedResult) { - expect(duckdb.result_statement_type(res)).toBe(expected.statementType ?? duckdb.StatementType.SELECT); - expect(duckdb.result_return_type(res)).toBe(expected.resultType ?? duckdb.ResultType.QUERY_RESULT); - expect(duckdb.rows_changed(res)).toBe(expected.rowsChanged ?? 0); - expect(duckdb.column_count(res)).toBe(expected.columns.length); - for (let col = 0; col < expected.columns.length; col++) { - const expectedColumn = expected.columns[col]; - expect(duckdb.column_name(res, col)).toBe(expectedColumn.name); - expect(duckdb.column_type(res, col)).toBe(expectedColumn.type); - const logical_type = duckdb.column_logical_type(res, col); - try { - expect(duckdb.get_type_id(logical_type)).toBe(expectedColumn.type); - } finally { - duckdb.destroy_logical_type(logical_type); - } +export async function expectResult(result: duckdb.Result, expectedResult: ExpectedResult) { + expect(duckdb.result_statement_type(result)).toBe(expectedResult.statementType ?? duckdb.StatementType.SELECT); + expect(duckdb.result_return_type(result)).toBe(expectedResult.resultType ?? duckdb.ResultType.QUERY_RESULT); + expect(duckdb.rows_changed(result)).toBe(expectedResult.rowsChanged ?? 0); + expect(duckdb.column_count(result)).toBe(expectedResult.columns.length); + for (let col = 0; col < expectedResult.columns.length; col++) { + const expectedColumn = expectedResult.columns[col]; + expect(duckdb.column_name(result, col)).toBe(expectedColumn.name); + expect(duckdb.column_type(result, col)).toBe(expectedColumn.logicalType.typeId); + withLogicalType(duckdb.column_logical_type(result, col), + (logical_type) => expectLogicalType(logical_type, expectedColumn.logicalType, `col ${col}`) + ); } - for (const expectedChunk of expected.chunks) { - const chunk = await duckdb.fetch_chunk(res); + for (const expectedChunk of expectedResult.chunks) { + const chunk = await duckdb.fetch_chunk(result); try { - expect(duckdb.data_chunk_get_column_count(chunk)).toBe(expected.columns.length); - expect(duckdb.data_chunk_get_size(chunk)).toBe(expectedChunk.rowCount); - for (let col = 0; col < expected.columns.length; col++) { - const expectedColumn = expected.columns[col]; - const expectedVector = expectedChunk.vectors[col]; - const vector = duckdb.data_chunk_get_vector(chunk, col); - const logical_type = duckdb.vector_get_column_type(vector); - try { - expect(duckdb.get_type_id(logical_type)).toBe(expectedColumn.type); - } finally { - duckdb.destroy_logical_type(logical_type); - } - const uint64Count = Math.ceil(expectedChunk.rowCount / 64); - const byteCount = uint64Count * 8; - const validity_bytes = duckdb.vector_get_validity(vector, byteCount); - const validity = new BigUint64Array(validity_bytes.buffer, 0, uint64Count); - const data = duckdb.vector_get_data(vector, expectedVector.byteCount); - const dv = new DataView(data.buffer); - for (let row = 0; row < expectedChunk.rowCount; row++) { - expectValidity(validity_bytes, validity, row, expectedVector.validity[row]); - expect(getValue(expectedColumn.type, dv, row)).toBe(expectedVector.values[row]); - } - } + expectChunk(chunk, expectedChunk, expectedResult.columns); } finally { duckdb.destroy_data_chunk(chunk); } diff --git a/bindings/test/utils/expectValidity.ts b/bindings/test/utils/expectValidity.ts new file mode 100644 index 00000000..8c62f5f3 --- /dev/null +++ b/bindings/test/utils/expectValidity.ts @@ -0,0 +1,8 @@ +import duckdb from '@duckdb/node-bindings'; +import { expect } from 'vitest'; +import { isValid } from './isValid'; + +export function expectValidity(validity_bytes: Uint8Array, validity: BigUint64Array, bit: number, expected: boolean, vectorName: string) { + expect(duckdb.validity_row_is_valid(validity_bytes, bit), `${vectorName} validity_bytes_bit[${bit}]`).toBe(expected); + expect(isValid(validity, bit), `${vectorName} validity_bit[${bit}]`).toBe(expected); +} diff --git a/bindings/test/utils/expectVector.ts b/bindings/test/utils/expectVector.ts new file mode 100644 index 00000000..9a1be25a --- /dev/null +++ b/bindings/test/utils/expectVector.ts @@ -0,0 +1,152 @@ +import duckdb from '@duckdb/node-bindings'; +import { expect } from 'vitest'; +import { ExpectedLogicalType } from './ExpectedLogicalType'; +import { UTINYINT } from './expectedLogicalTypes'; +import { + ExpectedArrayVector, + ExpectedDataVector, + ExpectedListVector, + ExpectedMapVector, + ExpectedStructVector, + ExpectedUnionVector, + ExpectedVector, +} from './ExpectedVector'; +import { expectValidity } from './expectValidity'; +import { getListEntry, getValue } from './getValue'; + +export function expectVector(vector: duckdb.Vector, expectedVector: ExpectedVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + switch (expectedVector.kind) { + case 'array': + expectArrayVector(vector, expectedVector, expectedLogicalType, vectorName); + break; + case 'data': + expectDataVector(vector, expectedVector, expectedLogicalType, vectorName); + break; + case 'list': + expectListVector(vector, expectedVector, expectedLogicalType, vectorName); + break; + case 'map': + expectMapVector(vector, expectedVector, expectedLogicalType, vectorName); + break; + case 'struct': + expectStructVector(vector, expectedVector, expectedLogicalType, vectorName); + break; + case 'union': + expectUnionVector(vector, expectedVector, expectedLogicalType, vectorName); + break; + } +} + +function getVectorValidity(vector: duckdb.Vector, itemCount: number): { validity: BigUint64Array, validityBytes: Uint8Array } { + const validityUInt64Count = Math.ceil(itemCount / 64); + const validityByteCount = validityUInt64Count * 8; + const validityBytes = duckdb.vector_get_validity(vector, validityByteCount); + const validity = new BigUint64Array(validityBytes.buffer, 0, validityUInt64Count); + return { validity, validityBytes }; +} + +function getVectorData(vector: duckdb.Vector, itemCount: number, itemBytes: number) { + const bytes = duckdb.vector_get_data(vector, itemCount * itemBytes); + return new DataView(bytes.buffer); +} + +function expectArrayVector(vector: duckdb.Vector, expectedVector: ExpectedArrayVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + expect(expectedLogicalType.typeId).toBe(duckdb.Type.ARRAY); + if (expectedLogicalType.typeId !== duckdb.Type.ARRAY) { + return; + } + + const itemCount = expectedVector.itemCount; + const { validity, validityBytes } = getVectorValidity(vector, itemCount); + for (let row = 0; row < itemCount; row++) { + expectValidity(validityBytes, validity, row, expectedVector.validity[row], `${vectorName} row[${row}]`); + } + + const childVector = duckdb.array_vector_get_child(vector); + expectVector(childVector, expectedVector.child, expectedLogicalType.valueType, `${vectorName} array_child`); +} + +function expectDataVector(vector: duckdb.Vector, expectedVector: ExpectedDataVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + const itemCount = expectedVector.values.length; + const { validity, validityBytes } = getVectorValidity(vector, itemCount); + const dv = getVectorData(vector, itemCount, expectedVector.itemBytes); + for (let row = 0; row < itemCount; row++) { + expectValidity(validityBytes, validity, row, expectedVector.validity[row], `${vectorName} row[${row}]`); + expect(getValue(expectedLogicalType, validity, dv, row), `${vectorName} row[${row}]`).toStrictEqual(expectedVector.values[row]); + } +} + +function expectListVector(vector: duckdb.Vector, expectedVector: ExpectedListVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + expect(expectedLogicalType.typeId).toBe(duckdb.Type.LIST); + if (expectedLogicalType.typeId !== duckdb.Type.LIST) { + return; + } + + const itemCount = expectedVector.entries.length; + const { validity, validityBytes } = getVectorValidity(vector, itemCount); + const entriesDV = getVectorData(vector, itemCount, 16); + for (let row = 0; row < itemCount; row++) { + expectValidity(validityBytes, validity, row, expectedVector.validity[row], `${vectorName} row[${row}]`); + expect(getListEntry(validity, entriesDV, row)).toStrictEqual(expectedVector.entries[row]); + } + + const childItemCount = duckdb.list_vector_get_size(vector); + const childVector = duckdb.list_vector_get_child(vector); + expect(childItemCount).toBe(expectedVector.childItemCount); + expectVector(childVector, expectedVector.child, expectedLogicalType.valueType, `${vectorName} list_child`); +} + +function expectMapVector(vector: duckdb.Vector, expectedVector: ExpectedMapVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + expect(expectedLogicalType.typeId).toBe(duckdb.Type.MAP); + if (expectedLogicalType.typeId !== duckdb.Type.MAP) { + return; + } + + const itemCount = expectedVector.entries.length; + const { validity, validityBytes } = getVectorValidity(vector, itemCount); + const entriesDV = getVectorData(vector, itemCount, 16); + for (let row = 0; row < itemCount; row++) { + expectValidity(validityBytes, validity, row, expectedVector.validity[row], `${vectorName} row[${row}]`); + expect(getListEntry(validity, entriesDV, row)).toStrictEqual(expectedVector.entries[row]); + } + + const childItemCount = duckdb.list_vector_get_size(vector); + const childVector = duckdb.list_vector_get_child(vector); + expect(childItemCount).toBe(2); + const keysVector = duckdb.struct_vector_get_child(childVector, 0); + const valuesVector = duckdb.struct_vector_get_child(childVector, 1); + expectVector(keysVector, expectedVector.keys, expectedLogicalType.keyType, `${vectorName} map_keys`); + expectVector(valuesVector, expectedVector.values, expectedLogicalType.valueType, `${vectorName} map_values`); +} + +function expectStructVector(vector: duckdb.Vector, expectedVector: ExpectedStructVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + expect(expectedLogicalType.typeId).toBe(duckdb.Type.STRUCT); + if (expectedLogicalType.typeId !== duckdb.Type.STRUCT) { + return; + } + + const itemCount = expectedVector.itemCount; + const { validity, validityBytes } = getVectorValidity(vector, itemCount); + for (let row = 0; row < itemCount; row++) { + expectValidity(validityBytes, validity, row, expectedVector.validity[row], `${vectorName} row[${row}]`); + } + + for (let i = 0; i < expectedVector.children.length; i++) { + const childVector = duckdb.struct_vector_get_child(vector, i); + expectVector(childVector, expectedVector.children[i], expectedLogicalType.entries[i].type, `${vectorName} struct_child[${i}]`); + } +} + +function expectUnionVector(vector: duckdb.Vector, expectedVector: ExpectedUnionVector, expectedLogicalType: ExpectedLogicalType, vectorName: string) { + expect(expectedLogicalType.typeId).toBe(duckdb.Type.UNION); + if (expectedLogicalType.typeId !== duckdb.Type.UNION) { + return; + } + + const tagsVector = duckdb.struct_vector_get_child(vector, 0); + expectVector(tagsVector, expectedVector.children[0], UTINYINT, `${vectorName} union_tags`); + for (let i = 1; i < expectedVector.children.length; i++) { + const childVector = duckdb.struct_vector_get_child(vector, i); + expectVector(childVector, expectedVector.children[i], expectedLogicalType.alternatives[i-1].type, `${vectorName} union_child[${i}]`); + } +} diff --git a/bindings/test/utils/expectedLogicalTypes.ts b/bindings/test/utils/expectedLogicalTypes.ts new file mode 100644 index 00000000..0d3f9deb --- /dev/null +++ b/bindings/test/utils/expectedLogicalTypes.ts @@ -0,0 +1,194 @@ +import duckdb from '@duckdb/node-bindings'; +import { + ExpectedArrayLogicalType, + ExpectedDecimalLogicalType, + ExpectedEnumLogicalType, + ExpectedListLogicalType, + ExpectedLogicalType, + ExpectedMapLogicalType, + ExpectedSimpleLogicalType, + ExpectedStructEntry, + ExpectedStructLogicalType, + ExpectedUnionAlternative, + ExpectedUnionLogicalType, +} from './ExpectedLogicalType'; + +export const BOOLEAN: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.BOOLEAN, +}; + +export const TINYINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TINYINT, +}; +export const SMALLINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.SMALLINT, +}; +export const INTEGER: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.INTEGER, +}; +export const BIGINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.BIGINT, +}; + +export const UTINYINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.UTINYINT, +}; +export const USMALLINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.USMALLINT, +}; +export const UINTEGER: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.UINTEGER, +}; +export const UBIGINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.UBIGINT, +}; + +export const FLOAT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.FLOAT, +}; +export const DOUBLE: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.DOUBLE, +}; + +export const TIMESTAMP: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIMESTAMP, +}; +export const DATE: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.DATE, +}; +export const TIME: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIME, +}; +export const INTERVAL: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.INTERVAL, +}; + +export const HUGEINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.HUGEINT, +}; +export const UHUGEINT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.UHUGEINT, +}; + +export const VARCHAR: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.VARCHAR, +}; +export const BLOB: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.BLOB, +}; + +export const TIMESTAMP_S: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIMESTAMP_S, +}; +export const TIMESTAMP_MS: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIMESTAMP_MS, +}; +export const TIMESTAMP_NS: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIMESTAMP_NS, +}; + +export const UUID: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.UUID, +}; + +export const BIT: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.BIT, +}; + +export const TIME_TZ: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIME_TZ, +}; +export const TIMESTAMP_TZ: ExpectedSimpleLogicalType = { + typeId: duckdb.Type.TIMESTAMP_TZ, +}; + +export function ARRAY( + valueType: ExpectedLogicalType, + size: number +): ExpectedArrayLogicalType { + return { + typeId: duckdb.Type.ARRAY, + valueType, + size, + }; +} + +export function DECIMAL( + width: number, + scale: number, + internalType: duckdb.Type +): ExpectedDecimalLogicalType { + return { + typeId: duckdb.Type.DECIMAL, + width, + scale, + internalType, + }; +} + +export function ENUM( + values: string[], + internalType: duckdb.Type +): ExpectedEnumLogicalType { + return { + typeId: duckdb.Type.ENUM, + values, + internalType, + }; +} + +export function LIST(valueType: ExpectedLogicalType): ExpectedListLogicalType { + return { + typeId: duckdb.Type.LIST, + valueType, + }; +} + +export function MAP( + keyType: ExpectedLogicalType, + valueType: ExpectedLogicalType +): ExpectedMapLogicalType { + return { + typeId: duckdb.Type.MAP, + keyType, + valueType, + }; +} + +export function ENTRY( + name: string, + type: ExpectedLogicalType +): ExpectedStructEntry { + return { + name, + type, + }; +} + +export function STRUCT( + ...entries: ExpectedStructEntry[] +): ExpectedStructLogicalType { + return { + typeId: duckdb.Type.STRUCT, + entries, + }; +} + +export function ALT( + tag: string, + type: ExpectedLogicalType +): ExpectedUnionAlternative { + return { + tag, + type, + }; +} + +export function UNION( + ...alternatives: ExpectedUnionAlternative[] +): ExpectedUnionLogicalType { + return { + typeId: duckdb.Type.UNION, + alternatives, + }; +} diff --git a/bindings/test/utils/expectedVectors.ts b/bindings/test/utils/expectedVectors.ts new file mode 100644 index 00000000..1f61cb12 --- /dev/null +++ b/bindings/test/utils/expectedVectors.ts @@ -0,0 +1,86 @@ +import { + ExpectedArrayVector, + ExpectedDataVector, + ExpectedListEntry, + ExpectedListVector, + ExpectedMapVector, + ExpectedStructVector, + ExpectedUnionVector, + ExpectedVector, +} from './ExpectedVector'; + +export function array( + itemCount: number, + validity: boolean[], + child: ExpectedVector +): ExpectedArrayVector { + return { + kind: 'array', + itemCount, + validity, + child, + }; +} + +export function data( + itemBytes: number, + validity: boolean[], + values: any[] +): ExpectedDataVector { + return { + kind: 'data', + itemBytes, + validity, + values, + }; +} + +export function list( + validity: boolean[], + entries: (ExpectedListEntry | null)[], + childItemCount: number, + child: ExpectedVector +): ExpectedListVector { + return { + kind: 'list', + validity, + entries, + childItemCount, + child, + }; +} + +export function map( + validity: boolean[], + entries: (ExpectedListEntry | null)[], + keys: ExpectedVector, + values: ExpectedVector +): ExpectedMapVector { + return { + kind: 'map', + validity, + entries, + keys, + values, + }; +} + +export function struct( + itemCount: number, + validity: boolean[], + children: ExpectedVector[] +): ExpectedStructVector { + return { + kind: 'struct', + itemCount, + validity, + children, + }; +} + +export function union(children: ExpectedVector[]): ExpectedUnionVector { + return { + kind: 'union', + children, + }; +} diff --git a/bindings/test/utils/getValue.ts b/bindings/test/utils/getValue.ts new file mode 100644 index 00000000..aaeb7c3e --- /dev/null +++ b/bindings/test/utils/getValue.ts @@ -0,0 +1,203 @@ +import duckdb from '@duckdb/node-bindings'; +import os from 'os'; +import { ExpectedLogicalType } from './ExpectedLogicalType'; +import { isValid } from './isValid'; + +const littleEndian = os.endianness() === 'LE'; + +function getInt8(dataView: DataView, offset: number): number { + return dataView.getInt8(offset); +} + +function getUInt8(dataView: DataView, offset: number): number { + return dataView.getUint8(offset); +} + +function getInt16(dataView: DataView, offset: number): number { + return dataView.getInt16(offset, littleEndian); +} + +function getUInt16(dataView: DataView, offset: number): number { + return dataView.getUint16(offset, littleEndian); +} + +function getInt32(dataView: DataView, offset: number): number { + return dataView.getInt32(offset, littleEndian); +} + +function getUInt32(dataView: DataView, offset: number): number { + return dataView.getUint32(offset, littleEndian); +} + +function getInt64(dataView: DataView, offset: number): bigint { + return dataView.getBigInt64(offset, littleEndian); +} + +function getUInt64(dataView: DataView, offset: number): bigint { + return dataView.getBigUint64(offset, littleEndian); +} + +function getFloat32(dataView: DataView, offset: number): number { + return dataView.getFloat32(offset, littleEndian); +} + +function getFloat64(dataView: DataView, offset: number): number { + return dataView.getFloat64(offset, littleEndian); +} + +function getInt128(dataView: DataView, offset: number): bigint { + const lower = getUInt64(dataView, offset); + const upper = getInt64(dataView, offset + 8); + return (upper << BigInt(64)) + lower; +} + +function getUInt128(dataView: DataView, offset: number): bigint { + const lower = getUInt64(dataView, offset); + const upper = getUInt64(dataView, offset + 8); + return BigInt.asUintN(64, upper) << BigInt(64) | BigInt.asUintN(64, lower); +} + +/** + * Gets the bytes either in or referenced by a `duckdb_string_t` + * that is at `ofset` of the given `DataView`. + */ +function getStringBytes(dv: DataView, offset: number): Uint8Array { + const lengthInBytes = getUInt32(dv, offset); + if (lengthInBytes <= 12) { + return new Uint8Array(dv.buffer, dv.byteOffset + offset + 4, lengthInBytes); + } else { + return duckdb.get_data_from_pointer(dv.buffer, dv.byteOffset + offset + 8, lengthInBytes); + } +} + +const decoder = new TextDecoder(); + +/** + * Gets the UTF-8 string either in or referenced by a `duckdb_string_t` + * that is at `offset` of the given `DataView`. + */ +function getString(dv: DataView, offset: number): string { + return decoder.decode(getStringBytes(dv, offset)); +} + +function getBuffer(dv: DataView, offset: number): Buffer { + return Buffer.from(getStringBytes(dv, offset)); +} + +export function getValue(logicalType: ExpectedLogicalType, validity: BigUint64Array, dv: DataView, index: number): any { + if (!isValid(validity, index)) { + return null; + } + switch (logicalType.typeId) { + case duckdb.Type.BOOLEAN: + return getUInt8(dv, index) !== 0; + + case duckdb.Type.TINYINT: + return getInt8(dv, index); + case duckdb.Type.SMALLINT: + return getInt16(dv, index * 2); + case duckdb.Type.INTEGER: + return getInt32(dv, index * 4); + case duckdb.Type.BIGINT: + return getInt64(dv, index * 8); + + case duckdb.Type.UTINYINT: + return getUInt8(dv, index); + case duckdb.Type.USMALLINT: + return getUInt16(dv, index * 2); + case duckdb.Type.UINTEGER: + return getUInt32(dv, index * 4); + case duckdb.Type.UBIGINT: + return getUInt64(dv, index * 8); + + case duckdb.Type.FLOAT: + return getFloat32(dv, index * 4); + case duckdb.Type.DOUBLE: + return getFloat64(dv, index * 8); + + case duckdb.Type.TIMESTAMP: + return getInt64(dv, index * 8); + case duckdb.Type.DATE: + return getInt32(dv, index * 4); + case duckdb.Type.TIME: + return getInt64(dv, index * 8); + case duckdb.Type.INTERVAL: + return { + months: getInt32(dv, index * 16 + 0), + days: getInt32(dv, index * 16 + 4), + micros: getInt64(dv, index * 16 + 8), + }; + + case duckdb.Type.HUGEINT: + return getInt128(dv, index * 16); + case duckdb.Type.UHUGEINT: + return getUInt128(dv, index * 16); + + case duckdb.Type.VARCHAR: + return getString(dv, index * 16); + case duckdb.Type.BLOB: + return getBuffer(dv, index * 16); + + case duckdb.Type.DECIMAL: + switch (logicalType.internalType) { + case duckdb.Type.SMALLINT: + return getInt16(dv, index * 2); + case duckdb.Type.INTEGER: + return getInt32(dv, index * 4); + case duckdb.Type.BIGINT: + return getInt64(dv, index * 8); + case duckdb.Type.HUGEINT: + return getInt128(dv, index * 16); + default: + throw new Error(`unsupported DECIMAL internal type: ${duckdb.Type[logicalType.typeId]}`); + } + + case duckdb.Type.TIMESTAMP_S: + return getInt64(dv, index * 8); + case duckdb.Type.TIMESTAMP_MS: + return getInt64(dv, index * 8); + case duckdb.Type.TIMESTAMP_NS: + return getInt64(dv, index * 8); + + case duckdb.Type.ENUM: + switch (logicalType.internalType) { + case duckdb.Type.UTINYINT: + return getUInt8(dv, index); + case duckdb.Type.USMALLINT: + return getUInt16(dv, index * 2); + case duckdb.Type.UINTEGER: + return getUInt32(dv, index * 4); + default: + throw new Error(`unsupported ENUM internal type: ${duckdb.Type[logicalType.typeId]}`); + } + + // LIST + // STRUCT + // MAP + // ARRAY + + case duckdb.Type.UUID: + return getInt128(dv, index * 16); + + // UNION + + case duckdb.Type.BIT: + return getBuffer(dv, index * 16); + + case duckdb.Type.TIME_TZ: + return getInt64(dv, index * 8); + case duckdb.Type.TIMESTAMP_TZ: + return getInt64(dv, index * 8); + default: + throw new Error(`getValue not implemented for type: ${duckdb.Type[logicalType.typeId]}`); + } +} + +export function getListEntry(validity: BigUint64Array, dv: DataView, index: number): [bigint, bigint] | null { + if (!isValid(validity, index)) { + return null; + } + const offset = getUInt64(dv, index * 16); + const length = getUInt64(dv, index * 16 + 8); + return [offset, length]; +} diff --git a/bindings/test/utils/isValid.ts b/bindings/test/utils/isValid.ts new file mode 100644 index 00000000..e915775f --- /dev/null +++ b/bindings/test/utils/isValid.ts @@ -0,0 +1,3 @@ +export function isValid(validity: BigUint64Array, bit: number): boolean { + return (validity[Math.floor(bit / 64)] & (1n << BigInt(bit % 64))) !== 0n; +} diff --git a/bindings/test/utils/validityTestUtils.ts b/bindings/test/utils/validityTestUtils.ts deleted file mode 100644 index 036886ae..00000000 --- a/bindings/test/utils/validityTestUtils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import duckdb from '@duckdb/node-bindings'; -import { expect } from 'vitest'; - -export function isValid(validity: BigUint64Array, bit: number): boolean { - return (validity[Math.floor(bit / 64)] & (1n << BigInt(bit % 64))) !== 0n; -} - -export function expectValidity(validity_bytes: Uint8Array, validity: BigUint64Array, bit: number, expected: boolean) { - expect(duckdb.validity_row_is_valid(validity_bytes, bit)).toBe(expected); - expect(isValid(validity, bit)).toBe(expected); -} diff --git a/bindings/test/utils/valueTestUtils.ts b/bindings/test/utils/valueTestUtils.ts deleted file mode 100644 index 766cafe4..00000000 --- a/bindings/test/utils/valueTestUtils.ts +++ /dev/null @@ -1,33 +0,0 @@ -import duckdb from '@duckdb/node-bindings'; - -/** - * Gets the bytes either in or referenced by a `duckdb_string_t` - * that is at `string_byte_offset` of the given `DataView`. - */ -function getStringBytes(dv: DataView, string_byte_offset: number): Uint8Array { - const length_in_bytes = dv.getUint32(string_byte_offset, true); - if (length_in_bytes <= 12) { - return new Uint8Array(dv.buffer, dv.byteOffset + string_byte_offset + 4, length_in_bytes); - } else { - return duckdb.get_data_from_pointer(dv.buffer, dv.byteOffset + string_byte_offset + 8, length_in_bytes); - } -} - -const decoder = new TextDecoder(); - -/** - * Gets the UTF-8 string either in or referenced by a `duckdb_string_t` - * that is at `string_byte_offset` of the given `DataView`. - */ -export function getVarchar(dv: DataView, string_byte_offset: number): string { - return decoder.decode(getStringBytes(dv, string_byte_offset)); -} - -export function getValue(type: duckdb.Type, dv: DataView, index: number): any { - switch (type) { - case duckdb.Type.INTEGER: - return dv.getInt32(index * 4, true); - default: - throw new Error('not implemented'); - } -} diff --git a/bindings/test/utils/withConnection.ts b/bindings/test/utils/withConnection.ts index e744ec5b..d741217b 100644 --- a/bindings/test/utils/withConnection.ts +++ b/bindings/test/utils/withConnection.ts @@ -3,11 +3,11 @@ import duckdb from '@duckdb/node-bindings'; export async function withConnection(fn: (connection: duckdb.Connection) => Promise): Promise { const db = await duckdb.open(); try { - const con = await duckdb.connect(db); + const connection = await duckdb.connect(db); try { - await fn(con); + await fn(connection); } finally { - await duckdb.disconnect(con); + await duckdb.disconnect(connection); } } finally { await duckdb.close(db); diff --git a/bindings/test/utils/withLogicalType.ts b/bindings/test/utils/withLogicalType.ts new file mode 100644 index 00000000..35c83984 --- /dev/null +++ b/bindings/test/utils/withLogicalType.ts @@ -0,0 +1,9 @@ +import duckdb from '@duckdb/node-bindings'; + +export function withLogicalType(logical_type: duckdb.LogicalType, fn: (logical_type: duckdb.LogicalType) => void) { + try { + fn(logical_type); + } finally { + duckdb.destroy_logical_type(logical_type); + } +}