Skip to content

Commit b715465

Browse files
authored
Merge pull request #47 from duckdb/jray/result-convenience-functions
result convenience functions
2 parents b302dbd + b4de51c commit b715465

File tree

5 files changed

+102
-22
lines changed

5 files changed

+102
-22
lines changed

api/pkgs/@duckdb/node-api/README.md

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ This is a high-level API meant for applications. It depends on low-level binding
1616
### Roadmap
1717

1818
Some features are not yet complete:
19-
- Friendlier APIs for convering results to common JS data structures.
20-
- Friendlier APIs for converting values of specialized and complex DuckDB types to common JS types.
2119
- Appending and binding advanced data types. (Additional DuckDB C API support needed.)
2220
- Writing to data chunk vectors. (Directly writing to binary buffers is challenging to support using the Node Addon API.)
2321
- User-defined types & functions. (Support for this was added to the DuckDB C API in v1.1.0.)
@@ -94,36 +92,45 @@ const result = await prepared.run();
9492

9593
Get column names and types:
9694
```ts
97-
const columnNames = [];
98-
const columnTypes = [];
99-
const columnCount = result.columnCount;
100-
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
101-
const columnName = result.columnName(columnIndex);
102-
const columnType = result.columnType(columnIndex);
103-
columnNames.push(columnName);
104-
columnTypes.push(columnType);
105-
}
95+
const columnNames = result.columnNames();
96+
const columnTypes = result.columnTypes();
97+
```
98+
99+
Fetch all chunks:
100+
```ts
101+
const chunks = await result.fetchAllChunks();
106102
```
107103

108-
Fetch data chunks:
104+
Fetch one chunk at a time:
109105
```ts
110106
const chunks = [];
111107
while (true) {
112108
const chunk = await result.fetchChunk();
109+
// Last chunk will have zero rows.
113110
if (chunk.rowCount === 0) {
114111
break;
115112
}
116113
chunks.push(chunk);
117114
}
118115
```
119116

120-
Read column data:
117+
Read chunk data (column-major):
118+
```ts
119+
const columns = chunk.getColumns(); // array of columns, each as an array of values
120+
```
121+
122+
Read chunk data (row-major):
123+
```ts
124+
const columns = chunk.getRows(); // array of rows, each as an array of values
125+
```
126+
127+
Read chunk data (one value at a time)
121128
```ts
122129
const columns = [];
123-
const columnCount = result.columnCount;
130+
const columnCount = chunk.columnCount;
124131
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
125132
const columnValues = [];
126-
const columnVector = chunk.getColumn(columnIndex);
133+
const columnVector = chunk.getColumnVector(columnIndex);
127134
const itemCount = columnVector.itemCount;
128135
for (let itemIndex = 0; itemIndex < itemCount; itemIndex++) {
129136
const value = columnVector.getItem(itemIndex);
@@ -207,13 +214,15 @@ if (columnType.typeId === DuckDBTypeId.BLOB) {
207214
if (columnType.typeId === DuckDBTypeId.DATE) {
208215
const dateDays = columnValue.days;
209216
const dateString = columnValue.toString();
217+
const { year, month, day } = columnValue.toParts();
210218
}
211219

212220
if (columnType.typeId === DuckDBTypeId.DECIMAL) {
213221
const decimalWidth = columnValue.width;
214222
const decimalScale = columnValue.scale;
215-
const decimalValue = columnValue.value; // bigint (raw fixed-point integer; `scale` indicates number of fractional digits)
223+
const decimalValue = columnValue.value; // bigint (Scaled-up value. Represented number is value/(10^scale).)
216224
const decimalString = columnValue.toString();
225+
const decimalDouble = columnValue.toDouble();
217226
}
218227

219228
if (columnType.typeId === DuckDBTypeId.INTERVAL) {
@@ -256,22 +265,26 @@ if (columnType.typeId === DuckDBTypeId.TIMESTAMP_S) {
256265
if (columnType.typeId === DuckDBTypeId.TIMESTAMP_TZ) {
257266
const timestampTZMicros = columnValue.micros; // bigint
258267
const timestampTZString = columnValue.toString();
268+
const { date: { year, month, day }, time: { hour, min, sec, micros } } = columnValue.toParts();
259269
}
260270

261271
if (columnType.typeId === DuckDBTypeId.TIMESTAMP) {
262272
const timestampMicros = columnValue.micros; // bigint
263273
const timestampString = columnValue.toString();
274+
const { date: { year, month, day }, time: { hour, min, sec, micros } } = columnValue.toParts();
264275
}
265276

266277
if (columnType.typeId === DuckDBTypeId.TIME_TZ) {
267278
const timeTZMicros = columnValue.micros; // bigint
268279
const timeTZOffset = columnValue.offset;
269280
const timeTZString = columnValue.toString();
281+
const { time: { hour, min, sec, micros }, offset } = columnValue.toParts();
270282
}
271283

272284
if (columnType.typeId === DuckDBTypeId.TIME) {
273285
const timeMicros = columnValue.micros; // bigint
274286
const timeString = columnValue.toString();
287+
const { hour, min, sec, micros } = columnValue.toParts();
275288
}
276289

277290
if (columnType.typeId === DuckDBTypeId.UNION) {
@@ -333,7 +346,7 @@ for (let statementIndex = 0; statementIndex < statementCount; statementIndex++)
333346
}
334347
```
335348

336-
### Control Evaluation
349+
### Control Evaluation of Tasks
337350

338351
```ts
339352
import { DuckDBPendingResultState } from '@duckdb/node-api';

api/src/DuckDBDataChunk.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import duckdb from '@duckdb/node-bindings';
22
import { DuckDBVector } from './DuckDBVector';
3+
import { DuckDBValue } from './values';
34

45
export class DuckDBDataChunk {
56
public readonly chunk: duckdb.DataChunk;
@@ -15,13 +16,41 @@ export class DuckDBDataChunk {
1516
public get columnCount(): number {
1617
return duckdb.data_chunk_get_column_count(this.chunk);
1718
}
18-
public getColumn(columnIndex: number): DuckDBVector<any> {
19+
public getColumnVector(columnIndex: number): DuckDBVector {
1920
// TODO: cache vectors?
2021
return DuckDBVector.create(
2122
duckdb.data_chunk_get_vector(this.chunk, columnIndex),
2223
this.rowCount
2324
);
2425
}
26+
public getColumnValues(columnIndex: number): DuckDBValue[] {
27+
return this.getColumnVector(columnIndex).toArray();
28+
}
29+
public getColumns(): DuckDBValue[][] {
30+
const columns: DuckDBValue[][] = [];
31+
const columnCount = this.columnCount;
32+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
33+
columns.push(this.getColumnValues(columnIndex));
34+
}
35+
return columns;
36+
}
37+
public getRows(): DuckDBValue[][] {
38+
const rows: DuckDBValue[][] = [];
39+
const vectors: DuckDBVector[] = [];
40+
const columnCount = this.columnCount;
41+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
42+
vectors.push(this.getColumnVector(columnIndex));
43+
}
44+
const rowCount = this.rowCount;
45+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
46+
const row: DuckDBValue[] = [];
47+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
48+
row.push(vectors[columnIndex].getItem(rowIndex));
49+
}
50+
rows.push(row);
51+
}
52+
return rows;
53+
}
2554
public get rowCount(): number {
2655
return duckdb.data_chunk_get_size(this.chunk);
2756
}

api/src/DuckDBResult.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,14 @@ export class DuckDBResult {
2222
public columnName(columnIndex: number): string {
2323
return duckdb.column_name(this.result, columnIndex);
2424
}
25+
public columnNames(): string[] {
26+
const columnNames: string[] = [];
27+
const columnCount = this.columnCount;
28+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
29+
columnNames.push(this.columnName(columnIndex));
30+
}
31+
return columnNames;
32+
}
2533
public columnTypeId(columnIndex: number): DuckDBTypeId {
2634
return duckdb.column_type(
2735
this.result,
@@ -38,10 +46,28 @@ export class DuckDBResult {
3846
duckdb.column_logical_type(this.result, columnIndex)
3947
).asType();
4048
}
49+
public columnTypes(): DuckDBType[] {
50+
const columnTypes: DuckDBType[] = [];
51+
const columnCount = this.columnCount;
52+
for (let columnIndex = 0; columnIndex < columnCount; columnIndex++) {
53+
columnTypes.push(this.columnType(columnIndex));
54+
}
55+
return columnTypes;
56+
}
4157
public get rowsChanged(): number {
4258
return duckdb.rows_changed(this.result);
4359
}
4460
public async fetchChunk(): Promise<DuckDBDataChunk> {
4561
return new DuckDBDataChunk(await duckdb.fetch_chunk(this.result));
4662
}
63+
public async fetchAllChunks(): Promise<DuckDBDataChunk[]> {
64+
const chunks: DuckDBDataChunk[] = [];
65+
while (true) {
66+
const chunk = await this.fetchChunk();
67+
if (chunk.rowCount === 0) {
68+
return chunks;
69+
}
70+
chunks.push(chunk);
71+
}
72+
}
4773
}

api/test/api.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ function getColumnVector<TValue extends DuckDBValue, TVector extends DuckDBVecto
163163
columnIndex: number,
164164
vectorType: new (...args: any[]) => TVector
165165
): TVector {
166-
const column = chunk.getColumn(columnIndex);
167-
if (!isVectorType<TValue, TVector>(column, vectorType)) {
166+
const columnVector = chunk.getColumnVector(columnIndex);
167+
if (!isVectorType<TValue, TVector>(columnVector, vectorType)) {
168168
assert.fail(`expected column ${columnIndex} to be a ${vectorType}`);
169169
}
170-
return column;
170+
return columnVector;
171171
}
172172

173173
function assertVectorValues<TValue extends DuckDBValue>(
@@ -945,4 +945,16 @@ describe('api', () => {
945945
assert.deepEqual(DuckDBDecimalValue.fromDouble(3.14159, 6, 5), decimalValue(314159n, 6, 5));
946946
assert.deepEqual(decimalValue(314159n, 6, 5).toDouble(), 3.14159);
947947
});
948+
test('result inspection conveniences', async () => {
949+
await withConnection(async (connection) => {
950+
const result = await connection.run('select i::int as a, i::int + 10 as b from range(3) t(i)');
951+
assert.deepEqual(result.columnNames(), ['a', 'b']);
952+
assert.deepEqual(result.columnTypes(), [DuckDBIntegerType.instance, DuckDBIntegerType.instance]);
953+
const chunks = await result.fetchAllChunks();
954+
const chunkColumns = chunks.map(chunk => chunk.getColumns());
955+
assert.deepEqual(chunkColumns, [[[0, 1, 2], [10, 11, 12]]]);
956+
const chunkRows = chunks.map(chunk => chunk.getRows());
957+
assert.deepEqual(chunkRows, [[[0, 10], [1, 11], [2, 12]]]);
958+
});
959+
});
948960
});

api/test/bench/util/runSql.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export async function runSql(connection: DuckDBConnection, sql: string): Promise
66
let nullCount = 0;
77
let chunk = await result.fetchChunk();
88
while (chunk.rowCount > 0) {
9-
const col0 = chunk.getColumn(0);
9+
const col0 = chunk.getColumnVector(0);
1010
for (let i = 0; i < col0.itemCount; i++) {
1111
if (col0.getItem(i) === null) {
1212
nullCount++;

0 commit comments

Comments
 (0)