@query-farm/vgi 0.32.0 → 0.34.0
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/README.md +1 -1
- package/dist/client-entry.js +45 -1
- package/dist/client-entry.js.map +4 -4
- package/dist/index.js +45 -1
- package/dist/index.js.map +4 -4
- package/dist/serve-entry.js +45 -1
- package/dist/serve-entry.js.map +4 -4
- package/dist/worker-cf-entry.js +45 -1
- package/dist/worker-cf-entry.js.map +4 -4
- package/package.json +3 -3
- package/src/arguments/arguments.ts +9 -0
- package/src/arrow/impl-arrowjs/canonical.ts +50 -0
- package/src/arrow/impl-flechette/canonical.ts +21 -4
- package/src/arrow/impl-flechette/metadata.ts +12 -1
- package/src/arrow/impl-flechette/normalize-type.ts +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@query-farm/vgi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.34.0",
|
|
4
4
|
"description": "TypeScript framework for extending DuckDB with user-defined functions via the Vector Gateway Interface (VGI) over Arrow IPC.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Query Farm LLC (https://query.farm)",
|
|
@@ -91,12 +91,12 @@
|
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
93
|
"@query-farm/apache-arrow": "^21.1.1",
|
|
94
|
-
"@query-farm/vgi-rpc": ">=0.
|
|
94
|
+
"@query-farm/vgi-rpc": ">=0.25.2 <0.26.0"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@duckdb/node-api": "^1.5.5-r.4",
|
|
98
98
|
"@query-farm/apache-arrow": "^21.1.1",
|
|
99
|
-
"@query-farm/vgi-rpc": "^0.
|
|
99
|
+
"@query-farm/vgi-rpc": "^0.25.2",
|
|
100
100
|
"@types/bun": "latest",
|
|
101
101
|
"typescript": "^7.0.2"
|
|
102
102
|
},
|
|
@@ -53,11 +53,20 @@ export class Arguments {
|
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Unwrap an Arrow Scalar to a plain JS value.
|
|
56
|
+
*
|
|
57
|
+
* A `Date` is returned as-is. Arguments are decoded to their RICH values
|
|
58
|
+
* (deserializeArguments runs every value through the codec), and a JS `Date`
|
|
59
|
+
* is the rich value of date32/date64 — `valueOf()` would turn it into epoch
|
|
60
|
+
* MILLISECONDS, while a bare number in a date32 column means DAYS. So a DATE
|
|
61
|
+
* argument read through `get()` (every typed `params.args.x`, every scalar
|
|
62
|
+
* const) and written back out as a DATE came back 86,400,000x too far out:
|
|
63
|
+
* '2024-01-15' as 480510-12-09 on arrow-js, and an invalid Date on flechette.
|
|
56
64
|
*/
|
|
57
65
|
function unwrapScalar(val: any): any {
|
|
58
66
|
if (val === null || val === undefined) return null;
|
|
59
67
|
// If it's already a primitive, return it
|
|
60
68
|
if (typeof val !== "object") return val;
|
|
69
|
+
if (val instanceof Date) return val;
|
|
61
70
|
// Arrow Scalar objects have a valueOf() method
|
|
62
71
|
if (typeof val.valueOf === "function") {
|
|
63
72
|
let v: any;
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
import {
|
|
28
28
|
Struct,
|
|
29
29
|
List,
|
|
30
|
+
FixedSizeList,
|
|
30
31
|
DataType,
|
|
31
32
|
DateUnit,
|
|
32
33
|
makeData,
|
|
@@ -54,6 +55,7 @@ export function writeCanonicalColumn(
|
|
|
54
55
|
|
|
55
56
|
function buildColumnData(values: unknown[], type: DataType): any {
|
|
56
57
|
if (DataType.isList(type)) return buildListData(values, type as List);
|
|
58
|
+
if (DataType.isFixedSizeList(type)) return buildFixedSizeListData(values, type as FixedSizeList);
|
|
57
59
|
if (DataType.isMap(type)) return buildMapData(values, type as Map_);
|
|
58
60
|
if (DataType.isStruct(type)) return buildStructData(values, type as Struct);
|
|
59
61
|
if (DataType.isDecimal(type)) return buildDecimalData(values, type);
|
|
@@ -200,6 +202,48 @@ function buildListData(values: unknown[], listType: List): any {
|
|
|
200
202
|
});
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
/**
|
|
206
|
+
* FixedSizeList (DuckDB's ARRAY) is built by hand, like List, so its items go
|
|
207
|
+
* through buildColumnData. Handing it to vectorFromArray fed arrow-js's own
|
|
208
|
+
* child builders CANONICAL values they read in other units: a date32
|
|
209
|
+
* day-number as epoch-ms (DATE[2], STRUCT(d DATE)[1], MAP(DATE, DATE)[1] and
|
|
210
|
+
* DATE[][1] all came back as 1970-01-01), and a bigint timestamp or unscaled
|
|
211
|
+
* decimal threw.
|
|
212
|
+
*/
|
|
213
|
+
function buildFixedSizeListData(values: unknown[], type: FixedSizeList): any {
|
|
214
|
+
const childField = type.children[0];
|
|
215
|
+
const listSize = type.listSize;
|
|
216
|
+
const numRows = values.length;
|
|
217
|
+
const allItems: unknown[] = [];
|
|
218
|
+
const nullBitmap = new Uint8Array(Math.ceil(numRows / 8));
|
|
219
|
+
let nullCount = 0;
|
|
220
|
+
for (let i = 0; i < numRows; i++) {
|
|
221
|
+
const row = values[i];
|
|
222
|
+
if (row == null) {
|
|
223
|
+
// A null row still occupies listSize child slots.
|
|
224
|
+
nullCount++;
|
|
225
|
+
for (let j = 0; j < listSize; j++) allItems.push(null);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
nullBitmap[i >> 3] |= 1 << (i & 7);
|
|
229
|
+
const items = Array.isArray(row) ? row : Array.from(row as Iterable<unknown>);
|
|
230
|
+
if (items.length !== listSize) {
|
|
231
|
+
throw new TypeError(
|
|
232
|
+
`fixed_size_list[${listSize}]: row ${i} has ${items.length} items`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
for (const item of items) allItems.push(item);
|
|
236
|
+
}
|
|
237
|
+
const childData = allItems.length > 0
|
|
238
|
+
? buildColumnData(allItems, childField.type)
|
|
239
|
+
: emptyDataForType(childField.type);
|
|
240
|
+
return makeData({
|
|
241
|
+
type, length: numRows, nullCount,
|
|
242
|
+
nullBitmap: nullCount > 0 ? nullBitmap : undefined,
|
|
243
|
+
child: childData,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
203
247
|
function buildMapData(values: unknown[], mapType: Map_): any {
|
|
204
248
|
const numRows = values.length;
|
|
205
249
|
const entriesField = mapType.children[0];
|
|
@@ -285,6 +329,12 @@ function emptyDataForType(type: DataType): any {
|
|
|
285
329
|
valueOffsets: new Int32Array([0]),
|
|
286
330
|
});
|
|
287
331
|
}
|
|
332
|
+
if (DataType.isFixedSizeList(type)) {
|
|
333
|
+
return makeData({
|
|
334
|
+
type, length: 0, nullCount: 0,
|
|
335
|
+
child: emptyDataForType((type as FixedSizeList).children[0].type),
|
|
336
|
+
});
|
|
337
|
+
}
|
|
288
338
|
if (DataType.isStruct(type)) {
|
|
289
339
|
const children = (type as any).children.map((cf: Field) => emptyDataForType(cf.type));
|
|
290
340
|
return makeData({ type, length: 0, nullCount: 0, children });
|
|
@@ -118,12 +118,29 @@ export function prepareForFlechette(type: VgiDataType, values: unknown[]): unkno
|
|
|
118
118
|
});
|
|
119
119
|
}
|
|
120
120
|
if (tid === 17) {
|
|
121
|
+
// The keys and values need the same preparation as a column of their
|
|
122
|
+
// type: a date32 key/value is a canonical day-number, which flechette's
|
|
123
|
+
// builder reads as epoch-ms, so MAP {DATE: DATE} came back as
|
|
124
|
+
// {1970-01-01=1970-01-01} unless it is turned into a JS Date first.
|
|
125
|
+
const entries = (type as any).children?.[0]?.type?.children;
|
|
126
|
+
const keyType = entries?.[0]?.type as VgiDataType | undefined;
|
|
127
|
+
const valueType = entries?.[1]?.type as VgiDataType | undefined;
|
|
128
|
+
const prepKey = keyType !== undefined && needsPrepare(keyType);
|
|
129
|
+
const prepValue = valueType !== undefined && needsPrepare(valueType);
|
|
121
130
|
return values.map((v) => {
|
|
122
131
|
if (v == null) return null;
|
|
123
|
-
|
|
124
|
-
if (
|
|
125
|
-
if (
|
|
126
|
-
|
|
132
|
+
let pairs: Array<[unknown, unknown]>;
|
|
133
|
+
if (v instanceof Map) pairs = Array.from(v.entries());
|
|
134
|
+
else if (Array.isArray(v)) pairs = v as Array<[unknown, unknown]>;
|
|
135
|
+
else if (typeof v === "object") pairs = Object.entries(v as any);
|
|
136
|
+
else return v;
|
|
137
|
+
if (prepKey || prepValue) {
|
|
138
|
+
pairs = pairs.map(([k, x]) => [
|
|
139
|
+
prepKey ? prepareForFlechette(keyType!, [k])[0] : k,
|
|
140
|
+
prepValue ? prepareForFlechette(valueType!, [x])[0] : x,
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
return new Map(pairs);
|
|
127
144
|
});
|
|
128
145
|
}
|
|
129
146
|
if (tid === 13) {
|
|
@@ -16,7 +16,18 @@ import type { VgiBatch } from "../types.js";
|
|
|
16
16
|
*/
|
|
17
17
|
export function withBatchMetadata(batch: VgiBatch, metadata: Map<string, string>): VgiBatch {
|
|
18
18
|
const t = batch as any;
|
|
19
|
-
|
|
19
|
+
// Copy the own properties as descriptors, not by assignment. vgi-rpc's reader
|
|
20
|
+
// pins a zero-column batch's row count as an own `numRows` over
|
|
21
|
+
// `Table.prototype.numRows`, a getter-only accessor, and `Object.assign`
|
|
22
|
+
// *assigns* each own property -- which throws in strict mode ("Attempted to
|
|
23
|
+
// assign to readonly property") over an inherited getter-only accessor.
|
|
24
|
+
const clone = Object.create(Object.getPrototypeOf(t), Object.getOwnPropertyDescriptors(t));
|
|
25
|
+
// This backend's own reader supplies that row count through a Proxy instead
|
|
26
|
+
// (see `deserializeBatch`), which no property copy carries: without this the
|
|
27
|
+
// clone would derive 0 rows from its absent columns.
|
|
28
|
+
if (clone.numRows !== t.numRows) {
|
|
29
|
+
Object.defineProperty(clone, "numRows", { value: t.numRows, configurable: true, enumerable: true });
|
|
30
|
+
}
|
|
20
31
|
if (metadata && metadata.size > 0) {
|
|
21
32
|
clone._vgiRecordMetadata = metadata;
|
|
22
33
|
clone.metadata = metadata;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import {
|
|
19
19
|
Type,
|
|
20
20
|
field as f_field,
|
|
21
|
-
nullType, bool, int, float, utf8, binary, fixedSizeBinary,
|
|
21
|
+
nullType, bool, int, float, utf8, largeUtf8, binary, largeBinary, fixedSizeBinary,
|
|
22
22
|
decimal, date, time, timestamp, duration, interval,
|
|
23
23
|
list, largeList, fixedSizeList, struct, map, dictionary, union,
|
|
24
24
|
} from "@query-farm/flechette";
|
|
@@ -47,12 +47,19 @@ export function toFlechetteType(type: any): any {
|
|
|
47
47
|
return aliasIntSigned(int(type.bitWidth, type.isSigned ?? type.signed ?? true));
|
|
48
48
|
case Type.Float:
|
|
49
49
|
return float(type.precision);
|
|
50
|
+
// Large variants stay large: flechette builds and encodes them, and
|
|
51
|
+
// folding them into Binary/Utf8 put a different type on the wire than the
|
|
52
|
+
// one declared (arrow-js writes LargeBinary/LargeUtf8). InitRequest
|
|
53
|
+
// declares `pushdown_filters` large_binary, so a flechette worker or
|
|
54
|
+
// client wrote that field as binary.
|
|
50
55
|
case Type.Binary:
|
|
51
|
-
case Type.LargeBinary:
|
|
52
56
|
return binary();
|
|
57
|
+
case Type.LargeBinary:
|
|
58
|
+
return largeBinary();
|
|
53
59
|
case Type.Utf8:
|
|
54
|
-
case Type.LargeUtf8:
|
|
55
60
|
return utf8();
|
|
61
|
+
case Type.LargeUtf8:
|
|
62
|
+
return largeUtf8();
|
|
56
63
|
case Type.FixedSizeBinary:
|
|
57
64
|
return fixedSizeBinary(type.byteWidth ?? type.stride);
|
|
58
65
|
case Type.Decimal:
|