@avidian/duckdb-bun 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +286 -2
- package/package.json +96 -4
- package/src/bindings/api/appender.ts +288 -0
- package/src/bindings/api/data-chunk.ts +58 -0
- package/src/bindings/api/database.ts +232 -0
- package/src/bindings/api/datetime.ts +158 -0
- package/src/bindings/api/index.ts +13 -0
- package/src/bindings/api/logical-types.ts +227 -0
- package/src/bindings/api/query.ts +616 -0
- package/src/bindings/api/scalar-function.ts +274 -0
- package/src/bindings/api/table-function.ts +339 -0
- package/src/bindings/api/values.ts +546 -0
- package/src/bindings/api/vector.ts +266 -0
- package/src/bindings/generated/capi.ts +695 -0
- package/src/bindings/generated/contract.ts +162 -0
- package/src/bindings/index.ts +10 -0
- package/src/bindings/runtime/convert.ts +307 -0
- package/src/bindings/runtime/diagnostics.ts +45 -0
- package/src/bindings/runtime/dispatch.ts +213 -0
- package/src/bindings/runtime/exception.ts +103 -0
- package/src/bindings/runtime/flags.ts +19 -0
- package/src/bindings/runtime/handle.ts +217 -0
- package/src/bindings/runtime/kinds.ts +53 -0
- package/src/bindings/runtime/marshal.ts +190 -0
- package/src/bindings/runtime/native.ts +153 -0
- package/src/bindings/runtime/ring.ts +54 -0
- package/src/bindings/runtime/scope.ts +133 -0
- package/src/bindings/runtime/trace.ts +65 -0
- package/src/connection.ts +35 -0
- package/src/database.ts +27 -0
- package/src/errors.ts +30 -0
- package/src/file-assets.d.ts +10 -0
- package/src/index.ts +17 -0
- package/src/library.ts +83 -0
- package/src/neo.ts +16 -0
- package/src/reader.ts +47 -0
- package/src/statement.ts +61 -0
- package/src/types.ts +52 -0
- package/src/version.ts +6 -0
- package/vendor/node-api/LICENSE +7 -0
- package/vendor/node-api/PROVENANCE.md +54 -0
- package/vendor/node-api/node-bindings.d.ts +1669 -0
- package/vendor/node-api/src/DuckDBAppender.ts +237 -0
- package/vendor/node-api/src/DuckDBClientContext.ts +13 -0
- package/vendor/node-api/src/DuckDBConnection.ts +315 -0
- package/vendor/node-api/src/DuckDBDataChunk.ts +412 -0
- package/vendor/node-api/src/DuckDBDataChunkWriter.ts +144 -0
- package/vendor/node-api/src/DuckDBExtractedStatements.ts +37 -0
- package/vendor/node-api/src/DuckDBInstance.ts +35 -0
- package/vendor/node-api/src/DuckDBInstanceCache.ts +29 -0
- package/vendor/node-api/src/DuckDBLogicalType.ts +446 -0
- package/vendor/node-api/src/DuckDBMaterializedResult.ts +39 -0
- package/vendor/node-api/src/DuckDBPendingResult.ts +83 -0
- package/vendor/node-api/src/DuckDBPreparedStatement.ts +376 -0
- package/vendor/node-api/src/DuckDBPreparedStatementCollection.ts +5 -0
- package/vendor/node-api/src/DuckDBPreparedStatementWeakRefCollection.ts +31 -0
- package/vendor/node-api/src/DuckDBResult.ts +403 -0
- package/vendor/node-api/src/DuckDBResultReader.ts +283 -0
- package/vendor/node-api/src/DuckDBScalarFunction.ts +149 -0
- package/vendor/node-api/src/DuckDBScalarFunctionBindInfo.ts +34 -0
- package/vendor/node-api/src/DuckDBScalarFunctionInfo.ts +34 -0
- package/vendor/node-api/src/DuckDBScalarFunctionInitInfo.ts +35 -0
- package/vendor/node-api/src/DuckDBTableFunction.ts +149 -0
- package/vendor/node-api/src/DuckDBTableFunctionBindInfo.ts +56 -0
- package/vendor/node-api/src/DuckDBTableFunctionInfo.ts +36 -0
- package/vendor/node-api/src/DuckDBTableFunctionInitInfo.ts +52 -0
- package/vendor/node-api/src/DuckDBType.ts +1 -0
- package/vendor/node-api/src/DuckDBTypeId.ts +46 -0
- package/vendor/node-api/src/DuckDBValueConverter.ts +23 -0
- package/vendor/node-api/src/DuckDBValueConverters.ts +461 -0
- package/vendor/node-api/src/DuckDBVector.ts +1 -0
- package/vendor/node-api/src/JS.ts +11 -0
- package/vendor/node-api/src/JSDuckDBValueConverter.ts +85 -0
- package/vendor/node-api/src/JSInputTypeForTypeId.ts +101 -0
- package/vendor/node-api/src/JSToDuckDBValueConverter.ts +136 -0
- package/vendor/node-api/src/JSTypeForTypeId.ts +78 -0
- package/vendor/node-api/src/Json.ts +7 -0
- package/vendor/node-api/src/JsonDuckDBValueConverter.ts +72 -0
- package/vendor/node-api/src/JsonTypeForTypeId.ts +86 -0
- package/vendor/node-api/src/ToDuckDBValueConverter.ts +68 -0
- package/vendor/node-api/src/ToDuckDBValueConverters.ts +303 -0
- package/vendor/node-api/src/configurationOptionDescriptions.ts +13 -0
- package/vendor/node-api/src/conversion/bytesFromString.ts +5 -0
- package/vendor/node-api/src/conversion/dateTimeStringConversion.ts +343 -0
- package/vendor/node-api/src/conversion/displayStringForDuckDBValue.ts +12 -0
- package/vendor/node-api/src/conversion/stringFromBlob.ts +48 -0
- package/vendor/node-api/src/conversion/stringFromDecimal.ts +129 -0
- package/vendor/node-api/src/conversion/varintDecode.ts +42 -0
- package/vendor/node-api/src/convertColumnsFromChunks.ts +28 -0
- package/vendor/node-api/src/convertColumnsObjectFromChunks.ts +29 -0
- package/vendor/node-api/src/convertRowObjectsFromChunks.ts +21 -0
- package/vendor/node-api/src/convertRowsFromChunks.ts +16 -0
- package/vendor/node-api/src/createConfig.ts +14 -0
- package/vendor/node-api/src/createDuckDBValueConverter.ts +17 -0
- package/vendor/node-api/src/createResult.ts +15 -0
- package/vendor/node-api/src/createValue.ts +355 -0
- package/vendor/node-api/src/duckdb.ts +51 -0
- package/vendor/node-api/src/enums.ts +7 -0
- package/vendor/node-api/src/getColumnsFromChunks.ts +13 -0
- package/vendor/node-api/src/getColumnsObjectFromChunks.ts +16 -0
- package/vendor/node-api/src/getRowObjectsFromChunks.ts +13 -0
- package/vendor/node-api/src/getRowsFromChunks.ts +12 -0
- package/vendor/node-api/src/index.ts +3 -0
- package/vendor/node-api/src/readValue.ts +31 -0
- package/vendor/node-api/src/sleep.ts +5 -0
- package/vendor/node-api/src/sql.ts +7 -0
- package/vendor/node-api/src/tsconfig.json +7 -0
- package/vendor/node-api/src/typeForValue.ts +144 -0
- package/vendor/node-api/src/types/BaseDuckDBType.ts +31 -0
- package/vendor/node-api/src/types/DuckDBAnyType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBArrayType.ts +43 -0
- package/vendor/node-api/src/types/DuckDBBigIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBBigNumType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBBitType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBBlobType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBBooleanType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBDateType.ts +29 -0
- package/vendor/node-api/src/types/DuckDBDecimalType.ts +46 -0
- package/vendor/node-api/src/types/DuckDBDoubleType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBEnumType.ts +111 -0
- package/vendor/node-api/src/types/DuckDBFloatType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBGeometryType.ts +36 -0
- package/vendor/node-api/src/types/DuckDBHugeIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBIntegerLiteralType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBIntegerType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBIntervalType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBListType.ts +35 -0
- package/vendor/node-api/src/types/DuckDBMapType.ts +47 -0
- package/vendor/node-api/src/types/DuckDBSQLNullType.ts +17 -0
- package/vendor/node-api/src/types/DuckDBSmallIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBStringLiteralType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBStructType.ts +104 -0
- package/vendor/node-api/src/types/DuckDBTimeNSType.ts +20 -0
- package/vendor/node-api/src/types/DuckDBTimeTZType.ts +23 -0
- package/vendor/node-api/src/types/DuckDBTimeType.ts +20 -0
- package/vendor/node-api/src/types/DuckDBTimestampMillisecondsType.ts +29 -0
- package/vendor/node-api/src/types/DuckDBTimestampNanosecondsType.ts +29 -0
- package/vendor/node-api/src/types/DuckDBTimestampSecondsType.ts +29 -0
- package/vendor/node-api/src/types/DuckDBTimestampTZNanosecondsType.ts +32 -0
- package/vendor/node-api/src/types/DuckDBTimestampTZType.ts +32 -0
- package/vendor/node-api/src/types/DuckDBTimestampType.ts +32 -0
- package/vendor/node-api/src/types/DuckDBTinyIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBType.ts +86 -0
- package/vendor/node-api/src/types/DuckDBUBigIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBUHugeIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBUIntegerType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBUSmallIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBUTinyIntType.ts +21 -0
- package/vendor/node-api/src/types/DuckDBUUIDType.ts +20 -0
- package/vendor/node-api/src/types/DuckDBUnionType.ts +74 -0
- package/vendor/node-api/src/types/DuckDBVarCharType.ts +13 -0
- package/vendor/node-api/src/types/DuckDBVariantType.ts +13 -0
- package/vendor/node-api/src/types/index.ts +44 -0
- package/vendor/node-api/src/values/DuckDBArrayValue.ts +18 -0
- package/vendor/node-api/src/values/DuckDBBitValue.ts +117 -0
- package/vendor/node-api/src/values/DuckDBBlobValue.ts +26 -0
- package/vendor/node-api/src/values/DuckDBDateValue.ts +43 -0
- package/vendor/node-api/src/values/DuckDBDecimalValue.ts +44 -0
- package/vendor/node-api/src/values/DuckDBGeometryValue.ts +30 -0
- package/vendor/node-api/src/values/DuckDBIntervalValue.ts +22 -0
- package/vendor/node-api/src/values/DuckDBListValue.ts +18 -0
- package/vendor/node-api/src/values/DuckDBMapValue.ts +25 -0
- package/vendor/node-api/src/values/DuckDBStructValue.ts +22 -0
- package/vendor/node-api/src/values/DuckDBTimeNSValue.ts +23 -0
- package/vendor/node-api/src/values/DuckDBTimeTZValue.ts +96 -0
- package/vendor/node-api/src/values/DuckDBTimeValue.ts +34 -0
- package/vendor/node-api/src/values/DuckDBTimestampMillisecondsValue.ts +29 -0
- package/vendor/node-api/src/values/DuckDBTimestampNanosecondsValue.ts +28 -0
- package/vendor/node-api/src/values/DuckDBTimestampSecondsValue.ts +28 -0
- package/vendor/node-api/src/values/DuckDBTimestampTZNanosecondsValue.ts +44 -0
- package/vendor/node-api/src/values/DuckDBTimestampTZValue.ts +54 -0
- package/vendor/node-api/src/values/DuckDBTimestampValue.ts +45 -0
- package/vendor/node-api/src/values/DuckDBTupleValue.ts +22 -0
- package/vendor/node-api/src/values/DuckDBUUIDValue.ts +48 -0
- package/vendor/node-api/src/values/DuckDBUnionValue.ts +22 -0
- package/vendor/node-api/src/values/DuckDBValue.ts +53 -0
- package/vendor/node-api/src/values/DuckDBVariantValue.ts +34 -0
- package/vendor/node-api/src/values/index.ts +24 -0
- package/vendor/node-api/src/vectors/DuckDBArrayVector.ts +107 -0
- package/vendor/node-api/src/vectors/DuckDBBigIntVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBBigNumVector.ts +98 -0
- package/vendor/node-api/src/vectors/DuckDBBitVector.ts +102 -0
- package/vendor/node-api/src/vectors/DuckDBBlobVector.ts +112 -0
- package/vendor/node-api/src/vectors/DuckDBBooleanVector.ts +84 -0
- package/vendor/node-api/src/vectors/DuckDBDateVector.ts +80 -0
- package/vendor/node-api/src/vectors/DuckDBDecimal128Vector.ts +107 -0
- package/vendor/node-api/src/vectors/DuckDBDecimal16Vector.ts +104 -0
- package/vendor/node-api/src/vectors/DuckDBDecimal32Vector.ts +104 -0
- package/vendor/node-api/src/vectors/DuckDBDecimal64Vector.ts +104 -0
- package/vendor/node-api/src/vectors/DuckDBDoubleVector.ts +68 -0
- package/vendor/node-api/src/vectors/DuckDBEnum16Vector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBEnum32Vector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBEnum8Vector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBFloatVector.ts +68 -0
- package/vendor/node-api/src/vectors/DuckDBGeometryVector.ts +97 -0
- package/vendor/node-api/src/vectors/DuckDBHugeIntVector.ts +89 -0
- package/vendor/node-api/src/vectors/DuckDBIntegerVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBIntervalVector.ts +100 -0
- package/vendor/node-api/src/vectors/DuckDBListVector.ts +221 -0
- package/vendor/node-api/src/vectors/DuckDBMapVector.ts +88 -0
- package/vendor/node-api/src/vectors/DuckDBSQLNullVector.ts +53 -0
- package/vendor/node-api/src/vectors/DuckDBSmallIntVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBStructVector.ts +138 -0
- package/vendor/node-api/src/vectors/DuckDBTimeNSVector.ts +80 -0
- package/vendor/node-api/src/vectors/DuckDBTimeTZVector.ts +80 -0
- package/vendor/node-api/src/vectors/DuckDBTimeVector.ts +80 -0
- package/vendor/node-api/src/vectors/DuckDBTimestampMillisecondsVector.ts +88 -0
- package/vendor/node-api/src/vectors/DuckDBTimestampNanosecondsVector.ts +88 -0
- package/vendor/node-api/src/vectors/DuckDBTimestampSecondsVector.ts +88 -0
- package/vendor/node-api/src/vectors/DuckDBTimestampTZNanosecondsVector.ts +88 -0
- package/vendor/node-api/src/vectors/DuckDBTimestampTZVector.ts +86 -0
- package/vendor/node-api/src/vectors/DuckDBTimestampVector.ts +83 -0
- package/vendor/node-api/src/vectors/DuckDBTinyIntVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBTupleVector.ts +125 -0
- package/vendor/node-api/src/vectors/DuckDBUBigIntVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBUHugeIntVector.ts +89 -0
- package/vendor/node-api/src/vectors/DuckDBUIntegerVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBUSmallIntVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBUTinyIntVector.ts +75 -0
- package/vendor/node-api/src/vectors/DuckDBUUIDVector.ts +89 -0
- package/vendor/node-api/src/vectors/DuckDBUnionVector.ts +84 -0
- package/vendor/node-api/src/vectors/DuckDBValidity.ts +104 -0
- package/vendor/node-api/src/vectors/DuckDBVarCharVector.ts +100 -0
- package/vendor/node-api/src/vectors/DuckDBVariantVector.ts +609 -0
- package/vendor/node-api/src/vectors/DuckDBVector.ts +33 -0
- package/vendor/node-api/src/vectors/createVector.ts +269 -0
- package/vendor/node-api/src/vectors/dataAccessors.ts +387 -0
- package/vendor/node-api/src/vectors/index.ts +50 -0
- package/vendor/node-api/src/vectors/vectorRegistry.ts +24 -0
- package/vendor/node-api/src/version.ts +5 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// The JavaScript side of the shim's threading (docs/adr/0001-blocking-calls-run-on-native-threads.md,
|
|
2
|
+
// native/ABI.md): completes jobs that ran on native threads, runs function
|
|
3
|
+
// callbacks DuckDB makes from its own threads, and releases ids DuckDB no longer
|
|
4
|
+
// needs.
|
|
5
|
+
//
|
|
6
|
+
// The shim calls three thread-safe JSCallbacks from its threads. Bun marshals
|
|
7
|
+
// each call onto the JS thread, so everything here runs on the JS thread, and a
|
|
8
|
+
// job's completion always runs after the code that started the job has returned.
|
|
9
|
+
import { CString, JSCallback, type Pointer, read } from "bun:ffi";
|
|
10
|
+
import { isMainThread } from "node:worker_threads";
|
|
11
|
+
import type { Handle } from "./handle.ts";
|
|
12
|
+
import { shim, shimPath } from "./native.ts";
|
|
13
|
+
import { jobOp, record } from "./ring.ts";
|
|
14
|
+
import { type CallContext, traced } from "./trace.ts";
|
|
15
|
+
|
|
16
|
+
// --- keeping the event loop alive -----------------------------------------------
|
|
17
|
+
|
|
18
|
+
// A JSCallback that a native thread will call later does not keep the process
|
|
19
|
+
// alive by itself, so one timer is ref'd while any job is in flight.
|
|
20
|
+
const keepAlive = setInterval(() => {}, 2 ** 31 - 1);
|
|
21
|
+
keepAlive.unref();
|
|
22
|
+
let inFlight = 0;
|
|
23
|
+
|
|
24
|
+
function begin(): void {
|
|
25
|
+
if (inFlight++ === 0) keepAlive.ref();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function end(): void {
|
|
29
|
+
if (--inFlight === 0) keepAlive.unref();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// --- jobs -----------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
interface PendingJob {
|
|
35
|
+
handles: readonly Handle[];
|
|
36
|
+
settle(job: Pointer): void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const jobs = new Map<number, PendingJob>();
|
|
40
|
+
|
|
41
|
+
function complete(job: Pointer): void {
|
|
42
|
+
const entry = jobs.get(job);
|
|
43
|
+
if (!entry) return;
|
|
44
|
+
jobs.delete(job);
|
|
45
|
+
end();
|
|
46
|
+
for (const handle of entry.handles) handle.release();
|
|
47
|
+
try {
|
|
48
|
+
entry.settle(job);
|
|
49
|
+
} finally {
|
|
50
|
+
shim.dbs_job_free(job);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Runs one blocking C call on a native thread and settles with `read(job)`, or
|
|
56
|
+
* rejects with the job's error text. `context` names the call for tracing. Each handle in `handles` counts the job as
|
|
57
|
+
* in flight until it completes, so an explicit destroy waits for it.
|
|
58
|
+
*/
|
|
59
|
+
export function runJob<T>(
|
|
60
|
+
context: CallContext,
|
|
61
|
+
handles: readonly Handle[],
|
|
62
|
+
start: () => Pointer | null,
|
|
63
|
+
read: (job: Pointer) => T,
|
|
64
|
+
): Promise<T> {
|
|
65
|
+
return traced(context, () => startJob(context, handles, start, read));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function startJob<T>(
|
|
69
|
+
context: CallContext,
|
|
70
|
+
handles: readonly Handle[],
|
|
71
|
+
start: () => Pointer | null,
|
|
72
|
+
read: (job: Pointer) => T,
|
|
73
|
+
): Promise<T> {
|
|
74
|
+
// The shim records the job's start and end; the SQL, when kept, goes in just before.
|
|
75
|
+
if (context.sql !== undefined) record(jobOp(context.op), null, 0, context.sql);
|
|
76
|
+
for (const handle of handles) handle.acquire();
|
|
77
|
+
const job = start();
|
|
78
|
+
if (job === null) {
|
|
79
|
+
for (const handle of handles) handle.release();
|
|
80
|
+
return Promise.reject(new Error("Failed to start a native thread"));
|
|
81
|
+
}
|
|
82
|
+
begin();
|
|
83
|
+
return new Promise<T>((resolve, reject) => {
|
|
84
|
+
jobs.set(job, {
|
|
85
|
+
handles,
|
|
86
|
+
settle(done) {
|
|
87
|
+
if (shim.dbs_job_failed(done)) {
|
|
88
|
+
const error = shim.dbs_job_error(done);
|
|
89
|
+
reject(new Error(error === null ? "Unknown error" : new CString(error).toString()));
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
resolve(read(done));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
reject(error);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Native calls in flight on native threads, for diagnostics(). */
|
|
103
|
+
export function jobsInFlight(): number {
|
|
104
|
+
return inFlight;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The pointer a job produced, or null. */
|
|
108
|
+
export function jobOut0(job: Pointer): Pointer | null {
|
|
109
|
+
return shim.dbs_job_out0(job) as Pointer | null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function jobOut1(job: Pointer): number {
|
|
113
|
+
return Number(shim.dbs_job_out1(job));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// --- function callbacks -----------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
/** Call kinds from native/ABI.md. */
|
|
119
|
+
export const CallKind = {
|
|
120
|
+
scalarBind: 1,
|
|
121
|
+
scalarInit: 2,
|
|
122
|
+
scalarMain: 3,
|
|
123
|
+
tableBind: 4,
|
|
124
|
+
tableInit: 5,
|
|
125
|
+
tableLocalInit: 6,
|
|
126
|
+
tableMain: 7,
|
|
127
|
+
} as const;
|
|
128
|
+
|
|
129
|
+
export type CallHandler = (kind: number, info: Pointer, input: Pointer | null, output: Pointer | null) => void;
|
|
130
|
+
|
|
131
|
+
const handlers = new Map<number, CallHandler>();
|
|
132
|
+
const released = new Map<number, () => void>();
|
|
133
|
+
let nextId = 1;
|
|
134
|
+
|
|
135
|
+
/** Ids carry this thread's shim context above bit 40 (2^40), so DuckDB threads call back into the right Worker. */
|
|
136
|
+
const CONTEXT_SCALE = 2 ** 40;
|
|
137
|
+
let contextBase = 0;
|
|
138
|
+
|
|
139
|
+
/** Allocates a nonzero id for a function callback set, or for bind, init or state data. */
|
|
140
|
+
export function allocateId(): number {
|
|
141
|
+
return contextBase + nextId++;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function setCallHandler(id: number, handler: CallHandler): void {
|
|
145
|
+
handlers.set(id, handler);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Runs `onRelease` once DuckDB drops `id` (shim `on_release`). */
|
|
149
|
+
export function onRelease(id: number, onRelease: () => void): void {
|
|
150
|
+
released.set(id, onRelease);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function dispatch(call: Pointer): void {
|
|
154
|
+
const kind = read.u32(call, 0);
|
|
155
|
+
const id = Number(read.u64(call, 8));
|
|
156
|
+
const info = read.ptr(call, 16) as Pointer;
|
|
157
|
+
const input = (read.ptr(call, 24) as Pointer) || null;
|
|
158
|
+
const output = (read.ptr(call, 32) as Pointer) || null;
|
|
159
|
+
const handler = handlers.get(id);
|
|
160
|
+
// The handler reports a thrown error through the C API's set-error function on
|
|
161
|
+
// `info`; an id without a handler is a shim bug, never reached by user code.
|
|
162
|
+
if (handler) handler(kind, info, input, output);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const onComplete = new JSCallback((job: Pointer) => complete(job), {
|
|
166
|
+
args: ["ptr"],
|
|
167
|
+
returns: "void",
|
|
168
|
+
threadsafe: true,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const onCall = new JSCallback(
|
|
172
|
+
(call: Pointer) => {
|
|
173
|
+
try {
|
|
174
|
+
dispatch(call);
|
|
175
|
+
} finally {
|
|
176
|
+
shim.dbs_call_done(call);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{ args: ["ptr"], returns: "void", threadsafe: true },
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const onCallSync = new JSCallback((call: Pointer) => dispatch(call), { args: ["ptr"], returns: "void" });
|
|
183
|
+
|
|
184
|
+
const onReleaseCallback = new JSCallback(
|
|
185
|
+
(id: bigint) => {
|
|
186
|
+
const key = Number(id);
|
|
187
|
+
handlers.delete(key);
|
|
188
|
+
const release = released.get(key);
|
|
189
|
+
released.delete(key);
|
|
190
|
+
release?.();
|
|
191
|
+
},
|
|
192
|
+
{ args: ["u64"], returns: "void", threadsafe: true },
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// One shim context per JS thread: this one, or a Worker's (native/ABI.md, "Lifecycle").
|
|
196
|
+
const context = shim.dbs_init(onComplete.ptr, onCall.ptr, onReleaseCallback.ptr);
|
|
197
|
+
if (context < 1) throw new Error(`duckdb-bun: the native shim refused this thread (dbs_init returned ${context})`);
|
|
198
|
+
contextBase = context * CONTEXT_SCALE;
|
|
199
|
+
shim.dbs_set_sync_call(onCallSync.ptr);
|
|
200
|
+
|
|
201
|
+
if (isMainThread) {
|
|
202
|
+
// After "exit" no more script runs, so the shim drops whatever arrives for this thread.
|
|
203
|
+
process.on("exit", () => shim.dbs_context_close());
|
|
204
|
+
} else {
|
|
205
|
+
// A Worker stopped by terminate() emits no "exit" and runs no script, but Bun runs
|
|
206
|
+
// its Node-API cleanup hooks on the Worker's thread before its VM goes away. The
|
|
207
|
+
// shim, loaded as an addon here, adds one that closes this context and frees the
|
|
208
|
+
// handles it owns (docs/adr/0008-a-worker-closes-its-context-in-an-n-api-cleanup-hook.md).
|
|
209
|
+
process.dlopen({ exports: {} }, shimPath);
|
|
210
|
+
if (!shim.dbs_context_hooked()) {
|
|
211
|
+
throw new Error("duckdb-bun: the native shim could not add its Node-API cleanup hook in this Worker");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// The message of a C++ exception the shim's boundary caught (native/cpp/boundary.hpp),
|
|
2
|
+
// built as DuckDB builds an error's message from one: ErrorData(const std::exception &)
|
|
3
|
+
// and ErrorData::ConstructFinalMessage() in src/common/error_data.cpp at the pinned tag.
|
|
4
|
+
//
|
|
5
|
+
// A DuckDB exception's what() is a JSON object whose exception_type is the type's
|
|
6
|
+
// display name, such as "Conversion", and whose exception_message is the text. Any
|
|
7
|
+
// other what() is the text of an exception of type INVALID.
|
|
8
|
+
|
|
9
|
+
/** ErrorData's text after an INTERNAL error's message; the stack trace it adds is left out. */
|
|
10
|
+
const INTERNAL_SUFFIX =
|
|
11
|
+
"\nThis error signals an assertion failure within DuckDB. This usually occurs due to " +
|
|
12
|
+
"unexpected conditions or errors in the program's logic.\nFor more information, see " +
|
|
13
|
+
"https://duckdb.org/docs/current/dev/internal_errors";
|
|
14
|
+
|
|
15
|
+
/** The display names of DuckDB's ExceptionType values (EXCEPTION_MAP in src/common/exception.cpp). */
|
|
16
|
+
const EXCEPTION_TYPES = new Set([
|
|
17
|
+
"Invalid",
|
|
18
|
+
"Out of Range",
|
|
19
|
+
"Conversion",
|
|
20
|
+
"Unknown Type",
|
|
21
|
+
"Decimal",
|
|
22
|
+
"Mismatch Type",
|
|
23
|
+
"Divide by Zero",
|
|
24
|
+
"Object Size",
|
|
25
|
+
"Invalid type",
|
|
26
|
+
"Serialization",
|
|
27
|
+
"TransactionContext",
|
|
28
|
+
"Not implemented",
|
|
29
|
+
"Expression",
|
|
30
|
+
"Catalog",
|
|
31
|
+
"Parser",
|
|
32
|
+
"Binder",
|
|
33
|
+
"Planner",
|
|
34
|
+
"Scheduler",
|
|
35
|
+
"Executor",
|
|
36
|
+
"Constraint",
|
|
37
|
+
"Index",
|
|
38
|
+
"Stat",
|
|
39
|
+
"Connection",
|
|
40
|
+
"Syntax",
|
|
41
|
+
"Settings",
|
|
42
|
+
"Optimizer",
|
|
43
|
+
"NullPointer",
|
|
44
|
+
"IO",
|
|
45
|
+
"INTERRUPT",
|
|
46
|
+
"FATAL",
|
|
47
|
+
"INTERNAL",
|
|
48
|
+
"Invalid Input",
|
|
49
|
+
"Out of Memory",
|
|
50
|
+
"Permission",
|
|
51
|
+
"Parameter Not Resolved",
|
|
52
|
+
"Parameter Not Allowed",
|
|
53
|
+
"Dependency",
|
|
54
|
+
"Missing Extension",
|
|
55
|
+
"HTTP",
|
|
56
|
+
"Extension Autoloading",
|
|
57
|
+
"Sequence",
|
|
58
|
+
"Invalid Configuration",
|
|
59
|
+
"Data Corruption",
|
|
60
|
+
"Resource In Use",
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/** The what() of std::bad_alloc in libstdc++ and libc++. */
|
|
64
|
+
const BAD_ALLOC = "std::bad_alloc";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The error message DuckDB reports for an exception whose what() is `what`, or a
|
|
68
|
+
* generic one when the exception was not a std::exception (`what` null).
|
|
69
|
+
*/
|
|
70
|
+
export function exceptionMessage(what: string | null): string {
|
|
71
|
+
if (what === null) return "Error: a C++ exception that is not a std::exception";
|
|
72
|
+
let type = "Invalid";
|
|
73
|
+
let message = what;
|
|
74
|
+
if (what === BAD_ALLOC) {
|
|
75
|
+
type = "Out of Memory";
|
|
76
|
+
message = "Allocation failure";
|
|
77
|
+
} else if (what.startsWith("{")) {
|
|
78
|
+
const fields = parseFields(what);
|
|
79
|
+
if (fields) {
|
|
80
|
+
type = EXCEPTION_TYPES.has(fields.exception_type ?? "") ? (fields.exception_type as string) : "Invalid";
|
|
81
|
+
message = fields.exception_message ?? "";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
message = message.replaceAll("\0", "\\0");
|
|
85
|
+
// UNKNOWN_TYPE, whose display name is "Unknown Type", gets no prefix.
|
|
86
|
+
const prefix = type === "Unknown Type" ? "" : `${type} `;
|
|
87
|
+
return `${prefix}Error: ${message}${type === "INTERNAL" ? INTERNAL_SUFFIX : ""}`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The string fields of a JSON object, or null when `text` is not one. */
|
|
91
|
+
function parseFields(text: string): Record<string, string | undefined> | null {
|
|
92
|
+
let parsed: unknown;
|
|
93
|
+
try {
|
|
94
|
+
parsed = JSON.parse(text);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error instanceof SyntaxError) return null;
|
|
97
|
+
throw new Error("failed to parse a C++ exception message", { cause: error });
|
|
98
|
+
}
|
|
99
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
100
|
+
const fields: Record<string, string | undefined> = {};
|
|
101
|
+
for (const [key, value] of Object.entries(parsed)) if (typeof value === "string") fields[key] = value;
|
|
102
|
+
return fields;
|
|
103
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// The environment flags the bindings read once at load. README.md documents them.
|
|
2
|
+
|
|
3
|
+
function enabled(name: string): boolean {
|
|
4
|
+
return process.env[name] === "1";
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const flags = {
|
|
8
|
+
/** DUCKDB_BUN_DEBUG=1: one stderr line per native call, plus the loaded library paths. */
|
|
9
|
+
debug: enabled("DUCKDB_BUN_DEBUG"),
|
|
10
|
+
/** DUCKDB_BUN_TRACE_SQL=1: redacted SQL in the tracing channel, debug lines and crash reports. */
|
|
11
|
+
traceSql: enabled("DUCKDB_BUN_TRACE_SQL"),
|
|
12
|
+
/** DUCKDB_BUN_CRASH_HANDLER=0 leaves the fatal-signal handler uninstalled. */
|
|
13
|
+
crashHandler: process.env.DUCKDB_BUN_CRASH_HANDLER !== "0",
|
|
14
|
+
} as const;
|
|
15
|
+
|
|
16
|
+
/** Writes one diagnostic line to stderr when DUCKDB_BUN_DEBUG=1. */
|
|
17
|
+
export function debug(line: string): void {
|
|
18
|
+
if (flags.debug) process.stderr.write(`[duckdb-bun] ${line}\n`);
|
|
19
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Typed handle objects for every native pointer the bindings hand to JavaScript
|
|
2
|
+
// (docs/adr/0002-handles-are-objects-freed-by-a-finalization-registry.md).
|
|
3
|
+
//
|
|
4
|
+
// Each handle mirrors one N-API external of the official binding
|
|
5
|
+
// (bindings/src/externals.h at the vendored tag): it carries a kind that plays
|
|
6
|
+
// the role of the external's type tag, so passing the wrong handle throws the
|
|
7
|
+
// same "Invalid <kind> argument" error, and a destroy function that a
|
|
8
|
+
// FinalizationRegistry runs once the handle is unreachable.
|
|
9
|
+
//
|
|
10
|
+
// A native call that uses a handle counts itself in `inFlight` for as long as it
|
|
11
|
+
// runs. An explicit destroy (close_sync, disconnect_sync, destroy_*_sync) during
|
|
12
|
+
// that time only sets `destroyRequested`, and the last call to finish destroys
|
|
13
|
+
// the pointer. The official binding does this for results and pending results;
|
|
14
|
+
// here every handle a native thread can use follows the rule, so close_sync and
|
|
15
|
+
// disconnect_sync during a query never free memory a native thread is using.
|
|
16
|
+
|
|
17
|
+
import type { Pointer } from "bun:ffi";
|
|
18
|
+
|
|
19
|
+
export type Kind =
|
|
20
|
+
| "appender"
|
|
21
|
+
| "catalog"
|
|
22
|
+
| "catalog entry"
|
|
23
|
+
| "client context"
|
|
24
|
+
| "config"
|
|
25
|
+
| "connection"
|
|
26
|
+
| "data chunk"
|
|
27
|
+
| "database"
|
|
28
|
+
| "extracted statements"
|
|
29
|
+
| "instance cache"
|
|
30
|
+
| "logical type"
|
|
31
|
+
| "pending result"
|
|
32
|
+
| "prepared statement"
|
|
33
|
+
| "result"
|
|
34
|
+
| "scalar function"
|
|
35
|
+
| "scalar function bind info"
|
|
36
|
+
| "scalar function info"
|
|
37
|
+
| "scalar function init info"
|
|
38
|
+
| "table function"
|
|
39
|
+
| "table function bind info"
|
|
40
|
+
| "table function info"
|
|
41
|
+
| "table function init info"
|
|
42
|
+
| "value"
|
|
43
|
+
| "vector";
|
|
44
|
+
|
|
45
|
+
/** Kinds whose explicit destroy makes later use throw, as in the official binding. */
|
|
46
|
+
const DESTROYED_MESSAGE: Partial<Record<Kind, string>> = {
|
|
47
|
+
result: "Result destroyed",
|
|
48
|
+
"pending result": "Pending result destroyed",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type Destroy = (ptr: Pointer) => void;
|
|
52
|
+
|
|
53
|
+
/** The native state of one handle, shared with the FinalizationRegistry. It
|
|
54
|
+
* never references the Handle itself, so it does not keep it alive. */
|
|
55
|
+
interface Cell {
|
|
56
|
+
readonly kind: Kind;
|
|
57
|
+
ptr: Pointer | null;
|
|
58
|
+
readonly destroy: Destroy | null;
|
|
59
|
+
inFlight: number;
|
|
60
|
+
destroyRequested: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Owned handles whose pointer is not yet freed, by kind, for diagnostics(). */
|
|
64
|
+
const live = new Map<Kind, number>();
|
|
65
|
+
|
|
66
|
+
/** A snapshot of the owned handles not yet freed, by kind. */
|
|
67
|
+
export function liveHandles(): Record<string, number> {
|
|
68
|
+
return Object.fromEntries([...live].filter(([, count]) => count > 0));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function destroyCell(cell: Cell): void {
|
|
72
|
+
const { ptr, destroy } = cell;
|
|
73
|
+
cell.ptr = null;
|
|
74
|
+
if (ptr !== null && destroy) {
|
|
75
|
+
live.set(cell.kind, (live.get(cell.kind) ?? 1) - 1);
|
|
76
|
+
destroy(ptr);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- native memory pressure (docs/adr/0007-native-memory-pressure-requests-garbage-collection.md) ---
|
|
81
|
+
//
|
|
82
|
+
// The garbage collector sees only the JS heap, not the native memory a handle
|
|
83
|
+
// owns, so unreachable results and data chunks pile up between collections: a
|
|
84
|
+
// loop of small queries held about 1000 of them, against about 500 for the
|
|
85
|
+
// official binding. Every `pressureLimit` results and data chunks created and not
|
|
86
|
+
// destroyed explicitly, the binding asks for an asynchronous collection, as N-API's
|
|
87
|
+
// napi_adjust_external_memory lets V8 do. The limit grows with the live count, so
|
|
88
|
+
// a program that keeps many results alive is not collected more often.
|
|
89
|
+
|
|
90
|
+
const PRESSURE_KINDS: ReadonlySet<Kind> = new Set<Kind>(["result", "data chunk"]);
|
|
91
|
+
/** The fewest results and data chunks created between two requested collections. */
|
|
92
|
+
export const MIN_PRESSURE_LIMIT = 256;
|
|
93
|
+
let pressureCount = 0;
|
|
94
|
+
let pressureLimit = MIN_PRESSURE_LIMIT;
|
|
95
|
+
|
|
96
|
+
function addPressure(kind: Kind): void {
|
|
97
|
+
if (!PRESSURE_KINDS.has(kind) || ++pressureCount < pressureLimit) return;
|
|
98
|
+
pressureCount = 0;
|
|
99
|
+
let liveCount = 0;
|
|
100
|
+
for (const pressured of PRESSURE_KINDS) liveCount += live.get(pressured) ?? 0;
|
|
101
|
+
pressureLimit = Math.max(MIN_PRESSURE_LIMIT, liveCount);
|
|
102
|
+
Bun.gc(false);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function relievePressure(kind: Kind): void {
|
|
106
|
+
if (PRESSURE_KINDS.has(kind) && pressureCount > 0) pressureCount--;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Collections requested so far and the current limit, for tests. */
|
|
110
|
+
export function pressureState(): { count: number; limit: number } {
|
|
111
|
+
return { count: pressureCount, limit: pressureLimit };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const registry = new FinalizationRegistry<Cell>((cell) => {
|
|
115
|
+
// A native call holds a strong reference to its handles until it completes,
|
|
116
|
+
// so a collected handle has no call in flight; the check is defensive.
|
|
117
|
+
if (cell.inFlight === 0) destroyCell(cell);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
export class Handle<K extends Kind = Kind> {
|
|
121
|
+
readonly kind: K;
|
|
122
|
+
readonly #cell: Cell;
|
|
123
|
+
/** Keeps the handle this one borrows from (a data chunk for its vectors) alive. */
|
|
124
|
+
readonly #owner: object | undefined;
|
|
125
|
+
|
|
126
|
+
constructor(kind: K, ptr: Pointer | null, destroy: Destroy | null, owner?: object) {
|
|
127
|
+
this.kind = kind;
|
|
128
|
+
this.#cell = { kind, ptr, destroy, inFlight: 0, destroyRequested: false };
|
|
129
|
+
this.#owner = owner;
|
|
130
|
+
if (destroy && ptr !== null) {
|
|
131
|
+
live.set(kind, (live.get(kind) ?? 0) + 1);
|
|
132
|
+
registry.register(this, this.#cell, this.#cell);
|
|
133
|
+
addPressure(kind);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** The native pointer, or null once destroyed. */
|
|
138
|
+
get ptr(): Pointer | null {
|
|
139
|
+
return this.#cell.ptr;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
get owner(): object | undefined {
|
|
143
|
+
return this.#owner;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get destroyRequested(): boolean {
|
|
147
|
+
return this.#cell.destroyRequested;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** True for a pointer owned elsewhere, which this handle never frees. */
|
|
151
|
+
get borrowed(): boolean {
|
|
152
|
+
return this.#cell.destroy === null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Marks a native call as using this handle until `release` is called. */
|
|
156
|
+
acquire(): void {
|
|
157
|
+
this.#cell.inFlight++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
release(): void {
|
|
161
|
+
const cell = this.#cell;
|
|
162
|
+
cell.inFlight--;
|
|
163
|
+
if (cell.destroyRequested && cell.inFlight === 0) this.#finish();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Explicit destroy: now, or when the last native call using the handle completes. */
|
|
167
|
+
requestDestroy(): void {
|
|
168
|
+
const cell = this.#cell;
|
|
169
|
+
cell.destroyRequested = true;
|
|
170
|
+
if (cell.inFlight === 0) this.#finish();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Invalidates a borrowed handle, such as a callback's info once the callback returns. */
|
|
174
|
+
invalidate(): void {
|
|
175
|
+
this.#cell.ptr = null;
|
|
176
|
+
this.#cell.destroyRequested = true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#finish(): void {
|
|
180
|
+
if (this.#cell.ptr !== null && this.#cell.destroy) relievePressure(this.kind);
|
|
181
|
+
registry.unregister(this.#cell);
|
|
182
|
+
destroyCell(this.#cell);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Wraps an owned pointer, freed by `destroy` when collected or explicitly destroyed. */
|
|
187
|
+
export function owned<K extends Kind>(kind: K, ptr: Pointer, destroy: Destroy): Handle<K> {
|
|
188
|
+
return new Handle(kind, ptr, destroy);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Wraps a pointer owned elsewhere (a vector of a data chunk, a callback's info). */
|
|
192
|
+
export function borrowed<K extends Kind>(kind: K, ptr: Pointer | null, owner?: object): Handle<K> {
|
|
193
|
+
return new Handle(kind, ptr, null, owner);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* The pointer behind `value`, checked like the official binding's type tags.
|
|
198
|
+
*
|
|
199
|
+
* After an explicit destroy, a result or pending result throws the official
|
|
200
|
+
* "... destroyed" error, an invalidated borrowed handle throws the type error,
|
|
201
|
+
* and any other handle reads as null, the pointer the official binding's
|
|
202
|
+
* destroy leaves behind (so a query on it fails with "connection disconnected").
|
|
203
|
+
*/
|
|
204
|
+
export function unwrap(value: unknown, kind: Kind): Pointer | null {
|
|
205
|
+
if (!(value instanceof Handle) || value.kind !== kind) throw new Error(`Invalid ${kind} argument`);
|
|
206
|
+
if (!value.destroyRequested) return value.ptr;
|
|
207
|
+
const destroyed = DESTROYED_MESSAGE[kind];
|
|
208
|
+
if (destroyed) throw new Error(destroyed);
|
|
209
|
+
if (value.borrowed) throw new Error(`Invalid ${kind} argument`);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Like `unwrap`, but returns the Handle itself, for calls that must acquire it. */
|
|
214
|
+
export function handleOf<K extends Kind>(value: unknown, kind: K): Handle<K> {
|
|
215
|
+
unwrap(value, kind);
|
|
216
|
+
return value as Handle<K>;
|
|
217
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// One constructor per handle kind. The shim frees each owned pointer with the C
|
|
2
|
+
// function the official binding's externals.h pairs with its kind, and records it
|
|
3
|
+
// in this JS thread's context, so a Worker that ends without running script
|
|
4
|
+
// still frees it (native/ABI.md, "Handle ownership").
|
|
5
|
+
import type { Pointer } from "bun:ffi";
|
|
6
|
+
import "./dispatch.ts"; // Opens this JS thread's shim context before the first handle.
|
|
7
|
+
import { borrowed, type Handle, type Kind, owned } from "./handle.ts";
|
|
8
|
+
import { shim } from "./native.ts";
|
|
9
|
+
|
|
10
|
+
/** Handle kind codes from native/ABI.md. */
|
|
11
|
+
const KIND_CODE = {
|
|
12
|
+
appender: 1,
|
|
13
|
+
catalog: 2,
|
|
14
|
+
"catalog entry": 3,
|
|
15
|
+
"client context": 4,
|
|
16
|
+
config: 5,
|
|
17
|
+
connection: 6,
|
|
18
|
+
"data chunk": 7,
|
|
19
|
+
database: 8,
|
|
20
|
+
"extracted statements": 9,
|
|
21
|
+
"instance cache": 10,
|
|
22
|
+
"logical type": 11,
|
|
23
|
+
"pending result": 12,
|
|
24
|
+
"prepared statement": 13,
|
|
25
|
+
// A `duckdb_result *` the shim allocated with duckdb_malloc.
|
|
26
|
+
result: 14,
|
|
27
|
+
"scalar function": 15,
|
|
28
|
+
"table function": 16,
|
|
29
|
+
value: 17,
|
|
30
|
+
vector: 18,
|
|
31
|
+
} as const satisfies Partial<Record<Kind, number>>;
|
|
32
|
+
|
|
33
|
+
export type OwnedKind = keyof typeof KIND_CODE;
|
|
34
|
+
|
|
35
|
+
const DESTROY = Object.fromEntries(
|
|
36
|
+
Object.entries(KIND_CODE).map(([kind, code]) => [kind, (ptr: Pointer) => shim.dbs_destroy(code, ptr)]),
|
|
37
|
+
) as Record<OwnedKind, (ptr: Pointer) => void>;
|
|
38
|
+
|
|
39
|
+
/** Wraps a pointer the caller now owns; it is freed when collected or explicitly destroyed. */
|
|
40
|
+
export function own<K extends OwnedKind>(kind: K, ptr: Pointer): Handle<K> {
|
|
41
|
+
shim.dbs_own(KIND_CODE[kind], ptr);
|
|
42
|
+
return owned(kind, ptr, DESTROY[kind]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Wraps a pointer owned elsewhere: a data chunk's vector, a callback's info, a chunk passed to a callback. */
|
|
46
|
+
export function borrow<K extends Kind>(kind: K, ptr: Pointer | null, owner?: object): Handle<K> {
|
|
47
|
+
return borrowed(kind, ptr, owner);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The destroy function for a kind, for the explicit `*_sync` destroy entry points. */
|
|
51
|
+
export function destroyerOf(kind: OwnedKind): (ptr: Pointer) => void {
|
|
52
|
+
return DESTROY[kind];
|
|
53
|
+
}
|