Skip to content

Commit

Permalink
bind LIST, STRUCT, ARRAY, plus tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jraymakers committed Jan 14, 2025
1 parent e67c944 commit 48daee4
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 24 deletions.
80 changes: 67 additions & 13 deletions api/src/DuckDBPreparedStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,28 @@ import { DuckDBMaterializedResult } from './DuckDBMaterializedResult';
import { DuckDBPendingResult } from './DuckDBPendingResult';
import { DuckDBResult } from './DuckDBResult';
import { DuckDBResultReader } from './DuckDBResultReader';
import { DuckDBTimestampTZType, DuckDBTimeTZType } from './DuckDBType';
import {
DuckDBArrayType,
DuckDBListType,
DuckDBStructType,
DuckDBType,
TIMESTAMPTZ,
TIMETZ,
} from './DuckDBType';
import { DuckDBTypeId } from './DuckDBTypeId';
import { StatementType } from './enums';
import {
DuckDBArrayValue,
DuckDBDateValue,
DuckDBDecimalValue,
DuckDBIntervalValue,
DuckDBListValue,
DuckDBStructValue,
DuckDBTimestampTZValue,
DuckDBTimestampValue,
DuckDBTimeTZValue,
DuckDBTimeValue,
DuckDBValue,
} from './values';

export class DuckDBPreparedStatement {
Expand Down Expand Up @@ -92,15 +103,18 @@ export class DuckDBPreparedStatement {
duckdb.bind_time(this.prepared_statement, parameterIndex, value);
}
public bindTimeTZ(parameterIndex: number, value: DuckDBTimeTZValue) {
duckdb.bind_value(this.prepared_statement, parameterIndex, createValue(DuckDBTimeTZType.instance, value));
this.bindValue(parameterIndex, value, TIMETZ);
}
public bindTimestamp(parameterIndex: number, value: DuckDBTimestampValue) {
duckdb.bind_timestamp(this.prepared_statement, parameterIndex, value);
}
public bindTimestampTZ(parameterIndex: number, value: DuckDBTimestampTZValue) {
duckdb.bind_value(this.prepared_statement, parameterIndex, createValue(DuckDBTimestampTZType.instance, value));
public bindTimestampTZ(
parameterIndex: number,
value: DuckDBTimestampTZValue
) {
this.bindValue(parameterIndex, value, TIMESTAMPTZ);
}
// TODO: bind TIMESTAMPS_S/_MS/_NS?
// TODO: bind TIMESTAMPS_S/_MS/_NS
public bindInterval(parameterIndex: number, value: DuckDBIntervalValue) {
duckdb.bind_interval(this.prepared_statement, parameterIndex, value);
}
Expand All @@ -110,15 +124,49 @@ export class DuckDBPreparedStatement {
public bindBlob(parameterIndex: number, value: Uint8Array) {
duckdb.bind_blob(this.prepared_statement, parameterIndex, value);
}
// TODO: bind ENUM?
// TODO: bind nested types? (ARRAY, LIST, STRUCT, MAP, UNION) (using bindValue?)
// TODO: bind UUID?
// TODO: bind BIT?
// TODO: bind ENUM
public bindArray(
parameterIndex: number,
value: DuckDBArrayValue,
type: DuckDBArrayType
) {
this.bindValue(parameterIndex, value, type);
}
public bindList(
parameterIndex: number,
value: DuckDBListValue,
type: DuckDBListType
) {
this.bindValue(parameterIndex, value, type);
}
public bindStruct(
parameterIndex: number,
value: DuckDBStructValue,
type: DuckDBStructType
) {
this.bindValue(parameterIndex, value, type);
}
// TODO: bind MAP, UNION
// TODO: bind UUID
// TODO: bind BIT
public bindNull(parameterIndex: number) {
duckdb.bind_null(this.prepared_statement, parameterIndex);
}
public bindValue(
parameterIndex: number,
value: DuckDBValue,
type: DuckDBType
) {
duckdb.bind_value(
this.prepared_statement,
parameterIndex,
createValue(type, value)
);
}
public async run(): Promise<DuckDBMaterializedResult> {
return new DuckDBMaterializedResult(await duckdb.execute_prepared(this.prepared_statement));
return new DuckDBMaterializedResult(
await duckdb.execute_prepared(this.prepared_statement)
);
}
public async runAndRead(): Promise<DuckDBResultReader> {
return new DuckDBResultReader(await this.run());
Expand All @@ -128,13 +176,17 @@ export class DuckDBPreparedStatement {
await reader.readAll();
return reader;
}
public async runAndReadUntil(targetRowCount: number): Promise<DuckDBResultReader> {
public async runAndReadUntil(
targetRowCount: number
): Promise<DuckDBResultReader> {
const reader = new DuckDBResultReader(await this.run());
await reader.readUntil(targetRowCount);
return reader;
}
public async stream(): Promise<DuckDBResult> {
return new DuckDBResult(await duckdb.execute_prepared_streaming(this.prepared_statement));
return new DuckDBResult(
await duckdb.execute_prepared_streaming(this.prepared_statement)
);
}
public async streamAndRead(): Promise<DuckDBResultReader> {
return new DuckDBResultReader(await this.stream());
Expand All @@ -144,7 +196,9 @@ export class DuckDBPreparedStatement {
await reader.readAll();
return reader;
}
public async streamAndReadUntil(targetRowCount: number): Promise<DuckDBResultReader> {
public async streamAndReadUntil(
targetRowCount: number
): Promise<DuckDBResultReader> {
const reader = new DuckDBResultReader(await this.stream());
await reader.readUntil(targetRowCount);
return reader;
Expand Down
29 changes: 26 additions & 3 deletions api/src/createValue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import duckdb, { Value } from '@duckdb/node-bindings';
import { DuckDBType } from './DuckDBType';
import { DuckDBTypeId } from './DuckDBTypeId';
import {
DuckDBArrayValue,
DuckDBBlobValue,
DuckDBDateValue,
DuckDBIntervalValue,
DuckDBListValue,
DuckDBStructValue,
DuckDBTimestampTZValue,
DuckDBTimestampValue,
DuckDBTimeTZValue,
Expand Down Expand Up @@ -120,13 +123,33 @@ export function createValue(type: DuckDBType, input: DuckDBValue): Value {
case DuckDBTypeId.ENUM:
throw new Error(`not yet implemented for ENUM`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.LIST:
throw new Error(`not yet implemented for LIST`); // TODO (need toLogicalType)
if (input instanceof DuckDBListValue) {
return duckdb.create_list_value(
type.valueType.toLogicalType().logical_type,
input.items.map((item) => createValue(type.valueType, item))
);
}
throw new Error(`input is not a DuckDBListValue`);
case DuckDBTypeId.STRUCT:
throw new Error(`not yet implemented for STRUCT`); // TODO (need toLogicalType)
if (input instanceof DuckDBStructValue) {
return duckdb.create_struct_value(
type.toLogicalType().logical_type,
Object.values(input.entries).map((value, i) =>
createValue(type.entryTypes[i], value)
)
);
}
throw new Error(`input is not a DuckDBStructValue`);
case DuckDBTypeId.MAP:
throw new Error(`not yet implemented for MAP`); // TODO: implement when available, hopefully in 1.2.0
case DuckDBTypeId.ARRAY:
throw new Error(`not yet implemented for ARRAY`); // TODO (need toLogicalType)
if (input instanceof DuckDBArrayValue) {
return duckdb.create_array_value(
type.valueType.toLogicalType().logical_type,
input.items.map((item) => createValue(type.valueType, item))
);
}
throw new Error(`input is not a DuckDBArrayValue`);
case DuckDBTypeId.UUID:
throw new Error(`not yet implemented for UUID`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.UNION:
Expand Down
52 changes: 44 additions & 8 deletions api/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,31 +388,43 @@ describe('api', () => {
// ensure double-disconnect doesn't break anything
connection.disconnect();
});
test('should support running prepared statements', async () => {
test.only('should support running prepared statements', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare(
'select $num as a, $str as b, $bool as c, $null as d'
'select $num as a, $str as b, $bool as c, $timetz as d, $list as e, $struct as f, $array as g, $null as h'
);
assert.strictEqual(prepared.parameterCount, 4);
assert.strictEqual(prepared.parameterCount, 8);
assert.strictEqual(prepared.parameterName(1), 'num');
assert.strictEqual(prepared.parameterName(2), 'str');
assert.strictEqual(prepared.parameterName(3), 'bool');
assert.strictEqual(prepared.parameterName(4), 'null');
assert.strictEqual(prepared.parameterName(4), 'timetz');
assert.strictEqual(prepared.parameterName(5), 'list');
assert.strictEqual(prepared.parameterName(6), 'struct');
assert.strictEqual(prepared.parameterName(7), 'array');
assert.strictEqual(prepared.parameterName(8), 'null');
prepared.bindInteger(1, 10);
prepared.bindVarchar(2, 'abc');
prepared.bindBoolean(3, true);
prepared.bindNull(4);
prepared.bindTimeTZ(4, TIMETZ.max);
prepared.bindList(5, listValue([100, 200, 300]), LIST(INTEGER));
prepared.bindStruct(6, structValue({ 'a': 42, 'b': 'duck' }), STRUCT({ 'a': INTEGER, 'b': VARCHAR }));
prepared.bindArray(7, arrayValue([100, 200, 300]), ARRAY(INTEGER, 3));
prepared.bindNull(8);
const result = await prepared.run();
assertColumns(result, [
{ name: 'a', type: INTEGER },
{ name: 'b', type: VARCHAR },
{ name: 'c', type: BOOLEAN },
{ name: 'd', type: INTEGER },
{ name: 'd', type: TIMETZ },
{ name: 'e', type: LIST(INTEGER) },
{ name: 'f', type: STRUCT({ 'a': INTEGER, 'b': VARCHAR }) },
{ name: 'g', type: ARRAY(INTEGER, 3) },
{ name: 'h', type: INTEGER },
]);
const chunk = await result.fetchChunk();
assert.isDefined(chunk);
if (chunk) {
assert.strictEqual(chunk.columnCount, 4);
assert.strictEqual(chunk.columnCount, 8);
assert.strictEqual(chunk.rowCount, 1);
assertValues<number, DuckDBIntegerVector>(
chunk,
Expand All @@ -432,9 +444,33 @@ describe('api', () => {
DuckDBBooleanVector,
[true]
);
assertValues<number, DuckDBIntegerVector>(
assertValues(
chunk,
3,
DuckDBTimeTZVector,
[TIMETZ.max]
);
assertValues(
chunk,
4,
DuckDBListVector,
[listValue([100, 200, 300])]
);
assertValues(
chunk,
5,
DuckDBStructVector,
[structValue({ 'a': 42, 'b': 'duck' })]
);
assertValues(
chunk,
6,
DuckDBArrayVector,
[arrayValue([100, 200, 300])]
);
assertValues<number, DuckDBIntegerVector>(
chunk,
7,
DuckDBIntegerVector,
[null]
);
Expand Down

0 comments on commit 48daee4

Please sign in to comment.