Skip to content

Commit

Permalink
bind helper
Browse files Browse the repository at this point in the history
  • Loading branch information
jraymakers committed Jan 14, 2025
1 parent 54f4939 commit 173ab39
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 1 deletion.
16 changes: 16 additions & 0 deletions api/pkgs/@duckdb/node-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,22 @@ prepared.bindList(3, listValue([10, 11, 12]), LIST(INTEGER));
const result = await prepared.run();
```

or:

```ts
const prepared = await connection.prepare('select $a, $b, $c');
prepared.bind({
'a': 'duck',
'b': 42,
'c': listValue([10, 11, 12]),
}, {
'a': VARCHAR,
'b': INTEGER,
'c': LIST(INTEGER),
});
const result = await prepared.run();
```

### Stream Results

Streaming results evaluate lazily when rows are read.
Expand Down
15 changes: 15 additions & 0 deletions api/src/DuckDBPreparedStatement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from './DuckDBType';
import { DuckDBTypeId } from './DuckDBTypeId';
import { StatementType } from './enums';
import { typeForValue } from './typeForValue';
import {
DuckDBArrayValue,
DuckDBDateValue,
Expand Down Expand Up @@ -163,6 +164,20 @@ export class DuckDBPreparedStatement {
createValue(type, value)
);
}
public bind(values: DuckDBValue[] | Record<string, DuckDBValue>, types?: DuckDBType[] | Record<string, DuckDBType>) {
if (Array.isArray(values)) {
const typesIsArray = Array.isArray(types);
for (let i = 0; i < values.length; i++) {
this.bindValue(i + 1, values[i], typesIsArray ? types[i] : typeForValue(values[i]));
}
} else {
const typesIsRecord = types && !Array.isArray(types);
for (const key in values) {
const index = this.parameterIndex(key);
this.bindValue(index, values[key], typesIsRecord ? types[key] : typeForValue(values[key]));
}
}
}
public async run(): Promise<DuckDBMaterializedResult> {
return new DuckDBMaterializedResult(
await duckdb.execute_prepared(this.prepared_statement)
Expand Down
1 change: 0 additions & 1 deletion api/src/DuckDBVector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3283,7 +3283,6 @@ export class DuckDBUnionVector extends DuckDBVector<DuckDBUnionValue> {
public override setItem(itemIndex: number, value: DuckDBUnionValue | null) {
if (value != null) {
const memberIndex = this.unionType.memberIndexForTag(value.tag);
console.log({ value, memberIndex });
this.structVector.setItemValue(itemIndex, 0, memberIndex);
const entryIndex = memberIndex + 1;
this.structVector.setItemValue(itemIndex, entryIndex, value.value);
Expand Down
112 changes: 112 additions & 0 deletions api/src/typeForValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {
ANY,
ARRAY,
BIT,
BLOB,
BOOLEAN,
DATE,
DECIMAL,
DOUBLE,
DuckDBType,
HUGEINT,
INTERVAL,
LIST,
MAP,
SQLNULL,
STRUCT,
TIME,
TIMESTAMP,
TIMESTAMP_MS,
TIMESTAMP_NS,
TIMESTAMP_S,
TIMESTAMPTZ,
TIMETZ,
UNION,
UUID,
VARCHAR,
} from './DuckDBType';
import {
DuckDBArrayValue,
DuckDBBitValue,
DuckDBBlobValue,
DuckDBDateValue,
DuckDBDecimalValue,
DuckDBIntervalValue,
DuckDBListValue,
DuckDBMapValue,
DuckDBStructValue,
DuckDBTimestampMillisecondsValue,
DuckDBTimestampNanosecondsValue,
DuckDBTimestampSecondsValue,
DuckDBTimestampTZValue,
DuckDBTimestampValue,
DuckDBTimeTZValue,
DuckDBTimeValue,
DuckDBUnionValue,
DuckDBUUIDValue,
DuckDBValue,
} from './values';

export function typeForValue(value: DuckDBValue): DuckDBType {
if (value === null) {
return SQLNULL;
} else {
switch (typeof value) {
case 'boolean':
return BOOLEAN;
case 'number':
return DOUBLE;
case 'bigint':
return HUGEINT;
case 'string':
return VARCHAR;
case 'object':
if (value instanceof DuckDBArrayValue) {
return ARRAY(typeForValue(value.items[0]), value.items.length);
} else if (value instanceof DuckDBBitValue) {
return BIT;
} else if (value instanceof DuckDBBlobValue) {
return BLOB;
} else if (value instanceof DuckDBDateValue) {
return DATE;
} else if (value instanceof DuckDBDecimalValue) {
return DECIMAL(value.width, value.scale);
} else if (value instanceof DuckDBIntervalValue) {
return INTERVAL;
} else if (value instanceof DuckDBListValue) {
return LIST(typeForValue(value.items[0]));
} else if (value instanceof DuckDBMapValue) {
return MAP(
typeForValue(value.entries[0].key),
typeForValue(value.entries[0].value)
);
} else if (value instanceof DuckDBStructValue) {
const entryTypes: Record<string, DuckDBType> = {};
for (const key in value.entries) {
entryTypes[key] = typeForValue(value.entries[key]);
}
return STRUCT(entryTypes);
} else if (value instanceof DuckDBTimestampMillisecondsValue) {
return TIMESTAMP_MS;
} else if (value instanceof DuckDBTimestampNanosecondsValue) {
return TIMESTAMP_NS;
} else if (value instanceof DuckDBTimestampSecondsValue) {
return TIMESTAMP_S;
} else if (value instanceof DuckDBTimestampTZValue) {
return TIMESTAMPTZ;
} else if (value instanceof DuckDBTimestampValue) {
return TIMESTAMP;
} else if (value instanceof DuckDBTimeTZValue) {
return TIMETZ;
} else if (value instanceof DuckDBTimeValue) {
return TIME;
} else if (value instanceof DuckDBUnionValue) {
return UNION({ [value.tag]: typeForValue(value.value) });
} else if (value instanceof DuckDBUUIDValue) {
return UUID;
}
break;
}
}
return ANY;
}
80 changes: 80 additions & 0 deletions api/test/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,86 @@ describe('api', () => {
}
});
});
test('should support prepare statement bind with list', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare(
'select $1 as a, $2 as b, $3 as c'
);
prepared.bind([42, 'duck', listValue([10, 11, 12])]);
const result = await prepared.run();
assertColumns(result, [
{ name: 'a', type: DOUBLE },
{ name: 'b', type: VARCHAR },
{ name: 'c', type: LIST(DOUBLE) },
]);
const chunk = await result.fetchChunk();
assert.isDefined(chunk);
if (chunk) {
assert.strictEqual(chunk.columnCount, 3);
assert.strictEqual(chunk.rowCount, 1);
assertValues<number, DuckDBDoubleVector>(
chunk,
0,
DuckDBDoubleVector,
[42]
);
assertValues<string, DuckDBVarCharVector>(
chunk,
1,
DuckDBVarCharVector,
['duck']
);
assertValues(
chunk,
2,
DuckDBListVector,
[listValue([10, 11, 12])]
);
}
});
});
test('should support prepare statement bind with object', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare(
'select $a as a, $b as b, $c as c'
);
prepared.bind({
a: 42,
b: 'duck',
c: listValue([10, 11, 12]),
});
const result = await prepared.run();
assertColumns(result, [
{ name: 'a', type: DOUBLE },
{ name: 'b', type: VARCHAR },
{ name: 'c', type: LIST(DOUBLE) },
]);
const chunk = await result.fetchChunk();
assert.isDefined(chunk);
if (chunk) {
assert.strictEqual(chunk.columnCount, 3);
assert.strictEqual(chunk.rowCount, 1);
assertValues<number, DuckDBDoubleVector>(
chunk,
0,
DuckDBDoubleVector,
[42]
);
assertValues<string, DuckDBVarCharVector>(
chunk,
1,
DuckDBVarCharVector,
['duck']
);
assertValues(
chunk,
2,
DuckDBListVector,
[listValue([10, 11, 12])]
);
}
});
});
test('should support starting prepared statements and running them incrementally', async () => {
await withConnection(async (connection) => {
const prepared = await connection.prepare(
Expand Down

0 comments on commit 173ab39

Please sign in to comment.