Skip to content

Commit 90a4ff6

Browse files
committed
improve bindings test infra
1 parent ece0b44 commit 90a4ff6

18 files changed

+1152
-436
lines changed

bindings/test/prepared_statements.test.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import duckdb from '@duckdb/node-bindings';
22
import { expect, suite, test } from 'vitest';
3+
import { data } from './utils/expectedVectors';
34
import { expectResult } from './utils/expectResult';
45
import { withConnection } from './utils/withConnection';
56

67
suite('prepared statements', () => {
78
test('no parameters', async () => {
8-
await withConnection(async (con) => {
9-
const prepared = await duckdb.prepare(con, 'select 17 as seventeen');
9+
await withConnection(async (connection) => {
10+
const prepared = await duckdb.prepare(connection, 'select 17 as seventeen');
1011
try {
1112
expect(duckdb.nparams(prepared)).toBe(0);
1213
expect(duckdb.prepared_statement_type(prepared)).toBe(duckdb.StatementType.SELECT);
13-
const res = await duckdb.execute_prepared(prepared);
14+
const result = await duckdb.execute_prepared(prepared);
1415
try {
15-
await expectResult(res, {
16+
await expectResult(result, {
1617
columns: [
17-
{ name: 'seventeen', type: duckdb.Type.INTEGER },
18+
{ name: 'seventeen', logicalType: { typeId: duckdb.Type.INTEGER } },
1819
],
1920
chunks: [
20-
{ rowCount: 1, vectors: [{ byteCount: 4, validity: [true], values: [17] }]},
21+
{ rowCount: 1, vectors: [data(4, [true], [17])]},
2122
],
2223
});
2324
} finally {
24-
duckdb.destroy_result(res);
25+
duckdb.destroy_result(result);
2526
}
2627
} finally {
2728
duckdb.destroy_prepare(prepared);

bindings/test/query.test.ts

Lines changed: 248 additions & 316 deletions
Large diffs are not rendered by default.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
3+
export interface ExpectedSimpleLogicalType {
4+
typeId: Exclude<duckdb.Type,
5+
| duckdb.Type.ARRAY
6+
| duckdb.Type.DECIMAL
7+
| duckdb.Type.ENUM
8+
| duckdb.Type.LIST
9+
| duckdb.Type.MAP
10+
| duckdb.Type.STRUCT
11+
| duckdb.Type.UNION
12+
>;
13+
}
14+
15+
export interface ExpectedArrayLogicalType {
16+
typeId: duckdb.Type.ARRAY;
17+
valueType: ExpectedLogicalType;
18+
size: number;
19+
}
20+
21+
export interface ExpectedDecimalLogicalType {
22+
typeId: duckdb.Type.DECIMAL;
23+
width: number;
24+
scale: number;
25+
internalType: duckdb.Type;
26+
}
27+
28+
export interface ExpectedEnumLogicalType {
29+
typeId: duckdb.Type.ENUM;
30+
values: string[];
31+
internalType: duckdb.Type;
32+
}
33+
34+
export interface ExpectedListLogicalType {
35+
typeId: duckdb.Type.LIST;
36+
valueType: ExpectedLogicalType;
37+
}
38+
39+
export interface ExpectedMapLogicalType {
40+
typeId: duckdb.Type.MAP;
41+
keyType: ExpectedLogicalType;
42+
valueType: ExpectedLogicalType;
43+
}
44+
45+
export interface ExpectedStructEntry {
46+
name: string;
47+
type: ExpectedLogicalType;
48+
}
49+
50+
export interface ExpectedStructLogicalType {
51+
typeId: duckdb.Type.STRUCT;
52+
entries: ExpectedStructEntry[];
53+
}
54+
55+
export interface ExpectedUnionAlternative {
56+
tag: string;
57+
type: ExpectedLogicalType;
58+
}
59+
60+
export interface ExpectedUnionLogicalType {
61+
typeId: duckdb.Type.UNION;
62+
alternatives: ExpectedUnionAlternative[];
63+
}
64+
65+
export type ExpectedLogicalType =
66+
| ExpectedSimpleLogicalType
67+
| ExpectedArrayLogicalType
68+
| ExpectedDecimalLogicalType
69+
| ExpectedEnumLogicalType
70+
| ExpectedListLogicalType
71+
| ExpectedMapLogicalType
72+
| ExpectedStructLogicalType
73+
| ExpectedUnionLogicalType
74+
;

bindings/test/utils/ExpectedResult.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { ExpectedLogicalType } from './ExpectedLogicalType';
3+
import { ExpectedVector } from './ExpectedVector';
4+
5+
export interface ExpectedColumn {
6+
name: string;
7+
logicalType: ExpectedLogicalType;
8+
}
9+
10+
export interface ExpectedChunk {
11+
columnCount?: number;
12+
rowCount: number;
13+
vectors: ExpectedVector[];
14+
}
15+
16+
export interface ExpectedResult {
17+
statementType?: duckdb.StatementType;
18+
resultType?: duckdb.ResultType;
19+
rowsChanged?: number;
20+
columns: ExpectedColumn[];
21+
chunks: ExpectedChunk[];
22+
}

bindings/test/utils/ExpectedVector.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
export interface ExpectedArrayVector {
2+
kind: 'array';
3+
itemCount: number;
4+
validity: boolean[];
5+
child: ExpectedVector;
6+
}
7+
8+
export interface ExpectedDataVector {
9+
kind: 'data';
10+
validity: boolean[];
11+
itemBytes: number;
12+
values: any[];
13+
}
14+
15+
export type ExpectedListEntry = [bigint, bigint] | null;
16+
17+
export interface ExpectedListVector {
18+
kind: 'list';
19+
validity: boolean[];
20+
entries: (ExpectedListEntry | null)[];
21+
childItemCount: number;
22+
child: ExpectedVector;
23+
}
24+
25+
export interface ExpectedMapVector {
26+
kind: 'map';
27+
validity: boolean[];
28+
entries: (ExpectedListEntry | null)[];
29+
keys: ExpectedVector;
30+
values: ExpectedVector;
31+
}
32+
33+
export interface ExpectedStructVector {
34+
kind: 'struct';
35+
itemCount: number;
36+
validity: boolean[];
37+
children: ExpectedVector[];
38+
}
39+
40+
export interface ExpectedUnionVector {
41+
kind: 'union';
42+
children: ExpectedVector[];
43+
}
44+
45+
export type ExpectedVector =
46+
| ExpectedArrayVector
47+
| ExpectedDataVector
48+
| ExpectedListVector
49+
| ExpectedMapVector
50+
| ExpectedStructVector
51+
| ExpectedUnionVector
52+
;

bindings/test/utils/expectChunk.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { expect } from 'vitest';
3+
import { ExpectedChunk, ExpectedColumn } from './ExpectedResult';
4+
import { expectLogicalType } from './expectLogicalType';
5+
import { expectVector } from './expectVector';
6+
import { withLogicalType } from './withLogicalType';
7+
8+
export function expectChunk(chunk: duckdb.DataChunk, expectedChunk: ExpectedChunk, expectedColumns: ExpectedColumn[]) {
9+
const chunkColumnCount = expectedChunk.columnCount ?? expectedColumns.length;
10+
expect(duckdb.data_chunk_get_column_count(chunk)).toBe(chunkColumnCount);
11+
expect(duckdb.data_chunk_get_size(chunk)).toBe(expectedChunk.rowCount);
12+
for (let col = 0; col < expectedChunk.vectors.length; col++) {
13+
const expectedVector = expectedChunk.vectors[col];
14+
const vector = duckdb.data_chunk_get_vector(chunk, col);
15+
16+
const expectedLogicalType = expectedColumns[col].logicalType;
17+
withLogicalType(duckdb.vector_get_column_type(vector),
18+
(logical_type) => expectLogicalType(logical_type, expectedLogicalType, `col ${col}`)
19+
);
20+
21+
expectVector(vector, expectedVector, expectedLogicalType, `col ${col}`);
22+
}
23+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { expect } from 'vitest';
3+
import { ExpectedLogicalType } from './ExpectedLogicalType';
4+
5+
export function expectLogicalType(logical_type: duckdb.LogicalType, expectedLogicalType: ExpectedLogicalType, message?: string) {
6+
expect(duckdb.get_type_id(logical_type), message).toBe(expectedLogicalType.typeId);
7+
switch (expectedLogicalType.typeId) {
8+
case duckdb.Type.ARRAY:
9+
expectLogicalType(duckdb.array_type_child_type(logical_type), expectedLogicalType.valueType);
10+
expect(duckdb.array_type_array_size(logical_type)).toBe(expectedLogicalType.size);
11+
break;
12+
case duckdb.Type.DECIMAL:
13+
expect(duckdb.decimal_width(logical_type)).toBe(expectedLogicalType.width);
14+
expect(duckdb.decimal_scale(logical_type)).toBe(expectedLogicalType.scale);
15+
expect(duckdb.decimal_internal_type(logical_type)).toBe(expectedLogicalType.internalType);
16+
break;
17+
case duckdb.Type.ENUM:
18+
{
19+
expect(duckdb.enum_internal_type(logical_type)).toBe(expectedLogicalType.internalType);
20+
expect(duckdb.enum_dictionary_size(logical_type)).toBe(expectedLogicalType.values.length);
21+
for (let i = 0; i < expectedLogicalType.values.length; i++) {
22+
expect(duckdb.enum_dictionary_value(logical_type, i)).toBe(expectedLogicalType.values[i]);
23+
}
24+
}
25+
break;
26+
case duckdb.Type.LIST:
27+
expectLogicalType(duckdb.list_type_child_type(logical_type), expectedLogicalType.valueType);
28+
break;
29+
case duckdb.Type.MAP:
30+
expectLogicalType(duckdb.map_type_key_type(logical_type), expectedLogicalType.keyType);
31+
expectLogicalType(duckdb.map_type_value_type(logical_type), expectedLogicalType.valueType);
32+
break;
33+
case duckdb.Type.STRUCT:
34+
expect(duckdb.struct_type_child_count(logical_type)).toBe(expectedLogicalType.entries.length);
35+
for (let i = 0; i < expectedLogicalType.entries.length; i++) {
36+
expect(duckdb.struct_type_child_name(logical_type, i)).toBe(expectedLogicalType.entries[i].name);
37+
expectLogicalType(duckdb.struct_type_child_type(logical_type, i), expectedLogicalType.entries[i].type);
38+
}
39+
break;
40+
case duckdb.Type.UNION:
41+
expect(duckdb.union_type_member_count(logical_type)).toBe(expectedLogicalType.alternatives.length);
42+
for (let i = 0; i < expectedLogicalType.alternatives.length; i++) {
43+
expect(duckdb.union_type_member_name(logical_type, i)).toBe(expectedLogicalType.alternatives[i].tag);
44+
expectLogicalType(duckdb.union_type_member_type(logical_type, i), expectedLogicalType.alternatives[i].type);
45+
}
46+
break;
47+
}
48+
}

bindings/test/utils/expectResult.ts

Lines changed: 19 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,27 @@
11
import duckdb from '@duckdb/node-bindings';
22
import { expect } from 'vitest';
3-
import { expectValidity } from './validityTestUtils';
4-
import { getValue } from './valueTestUtils';
3+
import { expectChunk } from './expectChunk';
4+
import { ExpectedResult } from './ExpectedResult';
5+
import { expectLogicalType } from './expectLogicalType';
6+
import { withLogicalType } from './withLogicalType';
57

6-
export interface ExpectedResult {
7-
statementType?: duckdb.StatementType;
8-
resultType?: duckdb.ResultType;
9-
rowsChanged?: number;
10-
columns: ExpectedColumn[];
11-
chunks: ExpectedChunk[];
12-
}
13-
14-
export interface ExpectedColumn {
15-
name: string;
16-
type: duckdb.Type;
17-
}
18-
19-
export interface ExpectedChunk {
20-
rowCount: number;
21-
vectors: ExpectedVector[];
22-
}
23-
24-
export interface ExpectedVector {
25-
byteCount: number;
26-
validity: boolean[];
27-
values: any[];
28-
}
29-
30-
export async function expectResult(res: duckdb.Result, expected: ExpectedResult) {
31-
expect(duckdb.result_statement_type(res)).toBe(expected.statementType ?? duckdb.StatementType.SELECT);
32-
expect(duckdb.result_return_type(res)).toBe(expected.resultType ?? duckdb.ResultType.QUERY_RESULT);
33-
expect(duckdb.rows_changed(res)).toBe(expected.rowsChanged ?? 0);
34-
expect(duckdb.column_count(res)).toBe(expected.columns.length);
35-
for (let col = 0; col < expected.columns.length; col++) {
36-
const expectedColumn = expected.columns[col];
37-
expect(duckdb.column_name(res, col)).toBe(expectedColumn.name);
38-
expect(duckdb.column_type(res, col)).toBe(expectedColumn.type);
39-
const logical_type = duckdb.column_logical_type(res, col);
40-
try {
41-
expect(duckdb.get_type_id(logical_type)).toBe(expectedColumn.type);
42-
} finally {
43-
duckdb.destroy_logical_type(logical_type);
44-
}
8+
export async function expectResult(result: duckdb.Result, expectedResult: ExpectedResult) {
9+
expect(duckdb.result_statement_type(result)).toBe(expectedResult.statementType ?? duckdb.StatementType.SELECT);
10+
expect(duckdb.result_return_type(result)).toBe(expectedResult.resultType ?? duckdb.ResultType.QUERY_RESULT);
11+
expect(duckdb.rows_changed(result)).toBe(expectedResult.rowsChanged ?? 0);
12+
expect(duckdb.column_count(result)).toBe(expectedResult.columns.length);
13+
for (let col = 0; col < expectedResult.columns.length; col++) {
14+
const expectedColumn = expectedResult.columns[col];
15+
expect(duckdb.column_name(result, col)).toBe(expectedColumn.name);
16+
expect(duckdb.column_type(result, col)).toBe(expectedColumn.logicalType.typeId);
17+
withLogicalType(duckdb.column_logical_type(result, col),
18+
(logical_type) => expectLogicalType(logical_type, expectedColumn.logicalType, `col ${col}`)
19+
);
4520
}
46-
for (const expectedChunk of expected.chunks) {
47-
const chunk = await duckdb.fetch_chunk(res);
21+
for (const expectedChunk of expectedResult.chunks) {
22+
const chunk = await duckdb.fetch_chunk(result);
4823
try {
49-
expect(duckdb.data_chunk_get_column_count(chunk)).toBe(expected.columns.length);
50-
expect(duckdb.data_chunk_get_size(chunk)).toBe(expectedChunk.rowCount);
51-
for (let col = 0; col < expected.columns.length; col++) {
52-
const expectedColumn = expected.columns[col];
53-
const expectedVector = expectedChunk.vectors[col];
54-
const vector = duckdb.data_chunk_get_vector(chunk, col);
55-
const logical_type = duckdb.vector_get_column_type(vector);
56-
try {
57-
expect(duckdb.get_type_id(logical_type)).toBe(expectedColumn.type);
58-
} finally {
59-
duckdb.destroy_logical_type(logical_type);
60-
}
61-
const uint64Count = Math.ceil(expectedChunk.rowCount / 64);
62-
const byteCount = uint64Count * 8;
63-
const validity_bytes = duckdb.vector_get_validity(vector, byteCount);
64-
const validity = new BigUint64Array(validity_bytes.buffer, 0, uint64Count);
65-
const data = duckdb.vector_get_data(vector, expectedVector.byteCount);
66-
const dv = new DataView(data.buffer);
67-
for (let row = 0; row < expectedChunk.rowCount; row++) {
68-
expectValidity(validity_bytes, validity, row, expectedVector.validity[row]);
69-
expect(getValue(expectedColumn.type, dv, row)).toBe(expectedVector.values[row]);
70-
}
71-
}
24+
expectChunk(chunk, expectedChunk, expectedResult.columns);
7225
} finally {
7326
duckdb.destroy_data_chunk(chunk);
7427
}

bindings/test/utils/expectValidity.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { expect } from 'vitest';
3+
import { isValid } from './isValid';
4+
5+
export function expectValidity(validity_bytes: Uint8Array, validity: BigUint64Array, bit: number, expected: boolean, vectorName: string) {
6+
expect(duckdb.validity_row_is_valid(validity_bytes, bit), `${vectorName} validity_bytes_bit[${bit}]`).toBe(expected);
7+
expect(isValid(validity, bit), `${vectorName} validity_bit[${bit}]`).toBe(expected);
8+
}

0 commit comments

Comments
 (0)