Skip to content

Commit

Permalink
Merge pull request #105 from duckdb/jray/support-more-binds
Browse files Browse the repository at this point in the history
support binding more types
  • Loading branch information
jraymakers authored Jan 14, 2025
2 parents 60d2299 + 67eb792 commit 54f4939
Show file tree
Hide file tree
Showing 4 changed files with 296 additions and 22 deletions.
3 changes: 2 additions & 1 deletion api/pkgs/@duckdb/node-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,10 @@ const result = await connection.run('from test_all_types()');
### Parameterize SQL

```ts
const prepared = await connection.prepare('select $1, $2');
const prepared = await connection.prepare('select $1, $2, $3');
prepared.bindVarchar(1, 'duck');
prepared.bindInteger(2, 42);
prepared.bindList(3, listValue([10, 11, 12]), LIST(INTEGER));
const result = await prepared.run();
```

Expand Down
87 changes: 73 additions & 14 deletions api/src/DuckDBPreparedStatement.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
import duckdb from '@duckdb/node-bindings';
import { createValue } from './createValue';
import { DuckDBMaterializedResult } from './DuckDBMaterializedResult';
import { DuckDBPendingResult } from './DuckDBPendingResult';
import { DuckDBResult } from './DuckDBResult';
import { DuckDBResultReader } from './DuckDBResultReader';
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 @@ -87,11 +102,19 @@ export class DuckDBPreparedStatement {
public bindTime(parameterIndex: number, value: DuckDBTimeValue) {
duckdb.bind_time(this.prepared_statement, parameterIndex, value);
}
public bindTimeTZ(parameterIndex: number, value: DuckDBTimeTZValue) {
this.bindValue(parameterIndex, value, TIMETZ);
}
public bindTimestamp(parameterIndex: number, value: DuckDBTimestampValue) {
duckdb.bind_timestamp(this.prepared_statement, parameterIndex, value);
}
// TODO: bind TIMESTAMPS_S/_MS/_NS?
// TODO: bind TIME_TZ/TIMESTAMP_TZ?
public bindTimestampTZ(
parameterIndex: number,
value: DuckDBTimestampTZValue
) {
this.bindValue(parameterIndex, value, TIMESTAMPTZ);
}
// TODO: bind TIMESTAMPS_S/_MS/_NS
public bindInterval(parameterIndex: number, value: DuckDBIntervalValue) {
duckdb.bind_interval(this.prepared_statement, parameterIndex, value);
}
Expand All @@ -101,19 +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);
}
// TODO: expose bindValue, or implement bindList, bindStruct, etc.?
// public bindValue(parameterIndex: number, value: Value) {
// duckdb.bind_value(this.prepared_statement, parameterIndex, value);
// }
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 @@ -123,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 @@ -139,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
178 changes: 178 additions & 0 deletions api/src/createValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
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,
DuckDBTimeValue,
DuckDBValue,
} from './values';

export function createValue(type: DuckDBType, input: DuckDBValue): Value {
switch (type.typeId) {
case DuckDBTypeId.BOOLEAN:
if (typeof input === 'boolean') {
return duckdb.create_bool(input);
}
throw new Error(`input is not a boolean`);
case DuckDBTypeId.TINYINT:
if (typeof input === 'number') {
return duckdb.create_int8(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.SMALLINT:
if (typeof input === 'number') {
return duckdb.create_int16(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.INTEGER:
if (typeof input === 'number') {
return duckdb.create_int32(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.BIGINT:
if (typeof input === 'bigint') {
return duckdb.create_int64(input);
}
throw new Error(`input is not a bigint`);
case DuckDBTypeId.UTINYINT:
if (typeof input === 'number') {
return duckdb.create_uint8(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.USMALLINT:
if (typeof input === 'number') {
return duckdb.create_uint16(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.UINTEGER:
if (typeof input === 'number') {
return duckdb.create_uint32(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.UBIGINT:
if (typeof input === 'bigint') {
return duckdb.create_uint64(input);
}
throw new Error(`input is not a bigint`);
case DuckDBTypeId.FLOAT:
if (typeof input === 'number') {
return duckdb.create_float(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.DOUBLE:
if (typeof input === 'number') {
return duckdb.create_double(input);
}
throw new Error(`input is not a number`);
case DuckDBTypeId.TIMESTAMP:
if (input instanceof DuckDBTimestampValue) {
return duckdb.create_timestamp(input);
}
throw new Error(`input is not a DuckDBTimestampValue`);
case DuckDBTypeId.DATE:
if (input instanceof DuckDBDateValue) {
return duckdb.create_date(input);
}
throw new Error(`input is not a DuckDBDateValue`);
case DuckDBTypeId.TIME:
if (input instanceof DuckDBTimeValue) {
return duckdb.create_time(input);
}
throw new Error(`input is not a DuckDBTimeValue`);
case DuckDBTypeId.INTERVAL:
if (input instanceof DuckDBIntervalValue) {
return duckdb.create_interval(input);
}
throw new Error(`input is not a DuckDBIntervalValue`);
case DuckDBTypeId.HUGEINT:
if (typeof input === 'bigint') {
return duckdb.create_hugeint(input);
}
throw new Error(`input is not a bigint`);
case DuckDBTypeId.UHUGEINT:
if (typeof input === 'bigint') {
return duckdb.create_uhugeint(input);
}
throw new Error(`input is not a bigint`);
case DuckDBTypeId.VARCHAR:
if (typeof input === 'string') {
return duckdb.create_varchar(input);
}
throw new Error(`input is not a string`);
case DuckDBTypeId.BLOB:
if (input instanceof DuckDBBlobValue) {
return duckdb.create_blob(input.bytes);
}
throw new Error(`input is not a DuckDBBlobValue`);
case DuckDBTypeId.DECIMAL:
throw new Error(`not yet implemented for DECIMAL`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.TIMESTAMP_S:
throw new Error(`not yet implemented for TIMESTAMP_S`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.TIMESTAMP_MS:
throw new Error(`not yet implemented for TIMESTAMP_MS`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.TIMESTAMP_NS:
throw new Error(`not yet implemented for TIMESTAMP_NS`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.ENUM:
throw new Error(`not yet implemented for ENUM`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.LIST:
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:
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:
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:
throw new Error(`not yet implemented for UNION`); // TODO: implement when available, hopefully in 1.2.0
case DuckDBTypeId.UNION:
throw new Error(`not yet implemented for BIT`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.TIME_TZ:
if (input instanceof DuckDBTimeTZValue) {
return duckdb.create_time_tz_value(input);
}
throw new Error(`input is not a DuckDBTimeTZValue`);
case DuckDBTypeId.TIMESTAMP_TZ:
if (input instanceof DuckDBTimestampTZValue) {
return duckdb.create_timestamp(input); // TODO: change to create_timestamp_tz when available in 1.2.0
}
throw new Error(`input is not a DuckDBTimestampTZValue`);
case DuckDBTypeId.ANY:
throw new Error(`cannot create values of type ANY`);
case DuckDBTypeId.VARINT:
throw new Error(`not yet implemented for VARINT`); // TODO: implement when available in 1.2.0
case DuckDBTypeId.SQLNULL:
throw new Error(`not yet implemented for SQLNUll`); // TODO: implement when available in 1.2.0
default:
throw new Error(`unrecognized type id ${type.typeId}`);
}
}
Loading

0 comments on commit 54f4939

Please sign in to comment.