Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support binding more types #105

Merged
merged 4 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading