Skip to content

Commit 978fc97

Browse files
committed
basic prepared test and test helpers
1 parent 2593ab7 commit 978fc97

File tree

6 files changed

+187
-84
lines changed

6 files changed

+187
-84
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { expect, suite, test } from 'vitest';
3+
import { expectResult } from './utils/expectResult';
4+
import { withConnection } from './utils/withConnection';
5+
6+
suite('prepared statements', () => {
7+
test('no parameters', async () => {
8+
await withConnection(async (con) => {
9+
const prepared = await duckdb.prepare(con, 'select 17 as seventeen');
10+
try {
11+
expect(duckdb.nparams(prepared)).toBe(0);
12+
expect(duckdb.prepared_statement_type(prepared)).toBe(duckdb.StatementType.SELECT);
13+
const res = await duckdb.execute_prepared(prepared);
14+
try {
15+
await expectResult(res, {
16+
columns: [
17+
{ name: 'seventeen', type: duckdb.Type.INTEGER },
18+
],
19+
chunks: [
20+
{ rowCount: 1, vectors: [{ byteCount: 4, validity: [true], values: [17] }]},
21+
],
22+
});
23+
} finally {
24+
duckdb.destroy_result(res);
25+
}
26+
} finally {
27+
duckdb.destroy_prepare(prepared);
28+
}
29+
});
30+
});
31+
});

bindings/test/query.test.ts

Lines changed: 21 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,97 +1,32 @@
11
import duckdb from '@duckdb/node-bindings';
22
import { expect, suite, test } from 'vitest';
3-
4-
function isValid(validity: BigUint64Array, bit: number): boolean {
5-
return (validity[Math.floor(bit / 64)] & (1n << BigInt(bit % 64))) !== 0n;
6-
}
7-
8-
function expectValidity(validity_bytes: Uint8Array, validity: BigUint64Array, bit: number, expected: boolean) {
9-
expect(duckdb.validity_row_is_valid(validity_bytes, bit)).toBe(expected);
10-
expect(isValid(validity, bit)).toBe(expected);
11-
}
12-
13-
/**
14-
* Gets the bytes either in or referenced by a `duckdb_string_t`
15-
* that is at `string_byte_offset` of the given `DataView`.
16-
*/
17-
function getStringBytes(dv: DataView, string_byte_offset: number): Uint8Array {
18-
const length_in_bytes = dv.getUint32(string_byte_offset, true);
19-
if (length_in_bytes <= 12) {
20-
return new Uint8Array(dv.buffer, dv.byteOffset + string_byte_offset + 4, length_in_bytes);
21-
} else {
22-
return duckdb.get_data_from_pointer(dv.buffer, dv.byteOffset + string_byte_offset + 8, length_in_bytes);
23-
}
24-
}
25-
26-
const decoder = new TextDecoder();
27-
28-
/**
29-
* Gets the UTF-8 string either in or referenced by a `duckdb_string_t`
30-
* that is at `string_byte_offset` of the given `DataView`.
31-
*/
32-
function getVarchar(dv: DataView, string_byte_offset: number): string {
33-
return decoder.decode(getStringBytes(dv, string_byte_offset));
34-
}
3+
import { expectResult } from './utils/expectResult';
4+
import { expectValidity } from './utils/validityTestUtils';
5+
import { getVarchar } from './utils/valueTestUtils';
6+
import { withConnection } from './utils/withConnection';
357

368
suite('query', () => {
379
test('basic select', async () => {
38-
const db = await duckdb.open();
39-
try {
40-
const con = await duckdb.connect(db);
10+
await withConnection(async (con) => {
11+
const res = await duckdb.query(con, 'select 17 as seventeen');
4112
try {
42-
const res = await duckdb.query(con, 'select 17 as seventeen');
43-
try {
44-
expect(duckdb.result_statement_type(res)).toBe(duckdb.StatementType.SELECT);
45-
expect(duckdb.result_return_type(res)).toBe(duckdb.ResultType.QUERY_RESULT);
46-
expect(duckdb.rows_changed(res)).toBe(0);
47-
expect(duckdb.column_count(res)).toBe(1);
48-
expect(duckdb.column_name(res, 0)).toBe('seventeen');
49-
expect(duckdb.column_type(res, 0)).toBe(duckdb.Type.INTEGER);
50-
const col_0_logical_type = duckdb.column_logical_type(res, 0);
51-
try {
52-
expect(duckdb.get_type_id(col_0_logical_type)).toBe(duckdb.Type.INTEGER);
53-
} finally {
54-
duckdb.destroy_logical_type(col_0_logical_type);
55-
}
56-
const chunk = await duckdb.fetch_chunk(res);
57-
try {
58-
expect(duckdb.data_chunk_get_column_count(chunk)).toBe(1);
59-
expect(duckdb.data_chunk_get_size(chunk)).toBe(1);
60-
const vector = duckdb.data_chunk_get_vector(chunk, 0);
61-
const logical_type = duckdb.vector_get_column_type(vector);
62-
expect(duckdb.get_type_id(logical_type)).toBe(duckdb.Type.INTEGER);
63-
const validityBytes = duckdb.vector_get_validity(vector, 8);
64-
const validity = new BigUint64Array(validityBytes.buffer, 0, 1);
65-
const data = duckdb.vector_get_data(vector, 4);
66-
const dv = new DataView(data.buffer);
67-
expect(isValid(validity, 0)).toBe(true);
68-
const value = dv.getInt32(0, true);
69-
expect(value).toBe(17);
70-
} finally {
71-
duckdb.destroy_data_chunk(chunk);
72-
}
73-
} finally {
74-
duckdb.destroy_result(res);
75-
}
13+
await expectResult(res, {
14+
columns: [
15+
{ name: 'seventeen', type: duckdb.Type.INTEGER },
16+
],
17+
chunks: [
18+
{ rowCount: 1, vectors: [{ byteCount: 4, validity: [true], values: [17] }]},
19+
],
20+
});
7621
} finally {
77-
await duckdb.disconnect(con);
22+
duckdb.destroy_result(res);
7823
}
79-
} finally {
80-
await duckdb.close(db);
81-
}
24+
});
8225
});
8326
test('basic error', async () => {
84-
const db = await duckdb.open();
85-
try {
86-
const con = await duckdb.connect(db);
87-
try {
88-
await expect(duckdb.query(con, 'selct 1')).rejects.toThrow('Parser Error');
89-
} finally {
90-
await duckdb.disconnect(con);
91-
}
92-
} finally {
93-
await duckdb.close(db);
94-
}
27+
await withConnection(async (con) => {
28+
await expect(duckdb.query(con, 'selct 1')).rejects.toThrow('Parser Error');
29+
});
9530
});
9631
test('test_all_types()', async () => {
9732
const db = await duckdb.open();
@@ -409,4 +344,6 @@ suite('query', () => {
409344
await duckdb.close(db);
410345
}
411346
});
347+
// TODO: interrupt
348+
// TODO: query_progress
412349
});

bindings/test/utils/expectResult.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { expect } from 'vitest';
3+
import { expectValidity } from './validityTestUtils';
4+
import { getValue } from './valueTestUtils';
5+
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+
}
45+
}
46+
for (const expectedChunk of expected.chunks) {
47+
const chunk = await duckdb.fetch_chunk(res);
48+
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+
}
72+
} finally {
73+
duckdb.destroy_data_chunk(chunk);
74+
}
75+
}
76+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
import { expect } from 'vitest';
3+
4+
export function isValid(validity: BigUint64Array, bit: number): boolean {
5+
return (validity[Math.floor(bit / 64)] & (1n << BigInt(bit % 64))) !== 0n;
6+
}
7+
8+
export function expectValidity(validity_bytes: Uint8Array, validity: BigUint64Array, bit: number, expected: boolean) {
9+
expect(duckdb.validity_row_is_valid(validity_bytes, bit)).toBe(expected);
10+
expect(isValid(validity, bit)).toBe(expected);
11+
}

bindings/test/utils/valueTestUtils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
3+
/**
4+
* Gets the bytes either in or referenced by a `duckdb_string_t`
5+
* that is at `string_byte_offset` of the given `DataView`.
6+
*/
7+
function getStringBytes(dv: DataView, string_byte_offset: number): Uint8Array {
8+
const length_in_bytes = dv.getUint32(string_byte_offset, true);
9+
if (length_in_bytes <= 12) {
10+
return new Uint8Array(dv.buffer, dv.byteOffset + string_byte_offset + 4, length_in_bytes);
11+
} else {
12+
return duckdb.get_data_from_pointer(dv.buffer, dv.byteOffset + string_byte_offset + 8, length_in_bytes);
13+
}
14+
}
15+
16+
const decoder = new TextDecoder();
17+
18+
/**
19+
* Gets the UTF-8 string either in or referenced by a `duckdb_string_t`
20+
* that is at `string_byte_offset` of the given `DataView`.
21+
*/
22+
export function getVarchar(dv: DataView, string_byte_offset: number): string {
23+
return decoder.decode(getStringBytes(dv, string_byte_offset));
24+
}
25+
26+
export function getValue(type: duckdb.Type, dv: DataView, index: number): any {
27+
switch (type) {
28+
case duckdb.Type.INTEGER:
29+
return dv.getInt32(index * 4, true);
30+
default:
31+
throw new Error('not implemented');
32+
}
33+
}

bindings/test/utils/withConnection.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import duckdb from '@duckdb/node-bindings';
2+
3+
export async function withConnection(fn: (connection: duckdb.Connection) => Promise<void>): Promise<void> {
4+
const db = await duckdb.open();
5+
try {
6+
const con = await duckdb.connect(db);
7+
try {
8+
await fn(con);
9+
} finally {
10+
await duckdb.disconnect(con);
11+
}
12+
} finally {
13+
await duckdb.close(db);
14+
}
15+
}

0 commit comments

Comments
 (0)