@query-farm/vgi 0.6.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/LICENSE +134 -0
- package/README.md +494 -0
- package/dist/__dbg.test-d.d.ts +1 -0
- package/dist/arguments/argument-spec.d.ts +51 -0
- package/dist/arguments/arguments.d.ts +13 -0
- package/dist/arrow/codec/branded.d.ts +83 -0
- package/dist/arrow/codec/registry.d.ts +29 -0
- package/dist/arrow/codec/repr.d.ts +23 -0
- package/dist/arrow/codec/type-descriptors.d.ts +109 -0
- package/dist/arrow/impl-arrowjs/build.d.ts +19 -0
- package/dist/arrow/impl-arrowjs/canonical.d.ts +15 -0
- package/dist/arrow/impl-arrowjs/empty.d.ts +7 -0
- package/dist/arrow/impl-arrowjs/filter.d.ts +11 -0
- package/dist/arrow/impl-arrowjs/index.d.ts +11 -0
- package/dist/arrow/impl-arrowjs/ipc.d.ts +24 -0
- package/dist/arrow/impl-arrowjs/iterate.d.ts +39 -0
- package/dist/arrow/impl-arrowjs/project.d.ts +10 -0
- package/dist/arrow/impl-arrowjs/schema.d.ts +62 -0
- package/dist/arrow/impl-arrowjs/statistics.d.ts +13 -0
- package/dist/arrow/impl-flechette/build.d.ts +14 -0
- package/dist/arrow/impl-flechette/canonical.d.ts +18 -0
- package/dist/arrow/impl-flechette/empty.d.ts +9 -0
- package/dist/arrow/impl-flechette/filter.d.ts +10 -0
- package/dist/arrow/impl-flechette/index.d.ts +11 -0
- package/dist/arrow/impl-flechette/ipc.d.ts +33 -0
- package/dist/arrow/impl-flechette/iterate.d.ts +29 -0
- package/dist/arrow/impl-flechette/message-meta.d.ts +10 -0
- package/dist/arrow/impl-flechette/normalize-type.d.ts +11 -0
- package/dist/arrow/impl-flechette/project.d.ts +3 -0
- package/dist/arrow/impl-flechette/schema.d.ts +47 -0
- package/dist/arrow/impl-flechette/statistics.d.ts +13 -0
- package/dist/arrow/index.d.ts +11 -0
- package/dist/arrow/predicates.d.ts +47 -0
- package/dist/arrow/repr.test-d.d.ts +52 -0
- package/dist/arrow/schema-types.d.ts +71 -0
- package/dist/arrow/types.d.ts +57 -0
- package/dist/catalog/attach-option.d.ts +33 -0
- package/dist/catalog/attach-options.d.ts +30 -0
- package/dist/catalog/composite.d.ts +33 -0
- package/dist/catalog/descriptors.d.ts +154 -0
- package/dist/catalog/interface.d.ts +239 -0
- package/dist/catalog/read-only.d.ts +33 -0
- package/dist/client/client.d.ts +189 -0
- package/dist/client/errors.d.ts +26 -0
- package/dist/client/helpers.d.ts +12 -0
- package/dist/client/protocol.d.ts +16 -0
- package/dist/client/types.d.ts +125 -0
- package/dist/client-entry.d.ts +11 -0
- package/dist/client-entry.js +48 -0
- package/dist/client-entry.js.map +9 -0
- package/dist/codec/asd.d.ts +25 -0
- package/dist/crypto.d.ts +36 -0
- package/dist/errors.d.ts +24 -0
- package/dist/filter-pushdown/collector.d.ts +32 -0
- package/dist/filter-pushdown/deserialize.d.ts +16 -0
- package/dist/filter-pushdown/evaluate.d.ts +51 -0
- package/dist/filter-pushdown/index.d.ts +4 -0
- package/dist/filter-pushdown/types.d.ts +84 -0
- package/dist/functions/aggregate.d.ts +129 -0
- package/dist/functions/copy-from.d.ts +81 -0
- package/dist/functions/copy-to.d.ts +121 -0
- package/dist/functions/registry.d.ts +16 -0
- package/dist/functions/scalar.d.ts +98 -0
- package/dist/functions/storage-cf-do.d.ts +47 -0
- package/dist/functions/storage.d.ts +86 -0
- package/dist/functions/table-buffering.d.ts +99 -0
- package/dist/functions/table-in-out.d.ts +64 -0
- package/dist/functions/table.d.ts +118 -0
- package/dist/functions/types.d.ts +157 -0
- package/dist/generated/vgi-client.d.ts +466 -0
- package/dist/generated/vgi-protocol-schemas.d.ts +115 -0
- package/dist/index.core.d.ts +42 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +10888 -0
- package/dist/index.js.map +85 -0
- package/dist/metadata/resolve.d.ts +3 -0
- package/dist/metadata/serialize.d.ts +12 -0
- package/dist/metadata/types.d.ts +47 -0
- package/dist/protocol/dispatch.d.ts +23 -0
- package/dist/protocol/handlers/aggregate.d.ts +3 -0
- package/dist/protocol/handlers/catalog/admin.d.ts +3 -0
- package/dist/protocol/handlers/catalog/index.d.ts +3 -0
- package/dist/protocol/handlers/catalog/index_methods.d.ts +3 -0
- package/dist/protocol/handlers/catalog/macro.d.ts +3 -0
- package/dist/protocol/handlers/catalog/shared.d.ts +70 -0
- package/dist/protocol/handlers/catalog/table.d.ts +3 -0
- package/dist/protocol/handlers/catalog/view.d.ts +3 -0
- package/dist/protocol/handlers/function.d.ts +8 -0
- package/dist/protocol/handlers/shared.d.ts +31 -0
- package/dist/protocol/handlers/table-buffering.d.ts +3 -0
- package/dist/protocol/serialize.d.ts +4 -0
- package/dist/protocol/serializers/arguments.d.ts +3 -0
- package/dist/protocol/serializers/bind.d.ts +9 -0
- package/dist/protocol/serializers/cardinality.d.ts +7 -0
- package/dist/protocol/serializers/init.d.ts +9 -0
- package/dist/protocol/serializers/shared.d.ts +12 -0
- package/dist/protocol/state-serializer.d.ts +23 -0
- package/dist/protocol/types.d.ts +129 -0
- package/dist/secrets/helpers.d.ts +15 -0
- package/dist/types.d.ts +52 -0
- package/dist/util/arrow/index.d.ts +1 -0
- package/dist/util/bytes.d.ts +2 -0
- package/dist/util/statistics.d.ts +7 -0
- package/dist/worker-cf-entry.d.ts +30 -0
- package/dist/worker-cf-entry.js +8767 -0
- package/dist/worker-cf-entry.js.map +74 -0
- package/dist/worker.d.ts +36 -0
- package/package.json +96 -0
- package/src/arguments/argument-spec.ts +200 -0
- package/src/arguments/arguments.ts +81 -0
- package/src/arrow/codec/branded.ts +206 -0
- package/src/arrow/codec/registry.ts +674 -0
- package/src/arrow/codec/repr.ts +181 -0
- package/src/arrow/codec/type-descriptors.ts +137 -0
- package/src/arrow/impl-arrowjs/build.ts +88 -0
- package/src/arrow/impl-arrowjs/canonical.ts +509 -0
- package/src/arrow/impl-arrowjs/empty.ts +72 -0
- package/src/arrow/impl-arrowjs/filter.ts +53 -0
- package/src/arrow/impl-arrowjs/index.ts +54 -0
- package/src/arrow/impl-arrowjs/ipc.ts +67 -0
- package/src/arrow/impl-arrowjs/iterate.ts +159 -0
- package/src/arrow/impl-arrowjs/project.ts +73 -0
- package/src/arrow/impl-arrowjs/schema.ts +256 -0
- package/src/arrow/impl-arrowjs/statistics.ts +185 -0
- package/src/arrow/impl-flechette/build.ts +65 -0
- package/src/arrow/impl-flechette/canonical.ts +289 -0
- package/src/arrow/impl-flechette/empty.ts +20 -0
- package/src/arrow/impl-flechette/filter.ts +42 -0
- package/src/arrow/impl-flechette/index.ts +54 -0
- package/src/arrow/impl-flechette/ipc.ts +110 -0
- package/src/arrow/impl-flechette/iterate.ts +121 -0
- package/src/arrow/impl-flechette/message-meta.ts +175 -0
- package/src/arrow/impl-flechette/normalize-type.ts +99 -0
- package/src/arrow/impl-flechette/project.ts +64 -0
- package/src/arrow/impl-flechette/schema.ts +202 -0
- package/src/arrow/impl-flechette/statistics.ts +198 -0
- package/src/arrow/index.ts +129 -0
- package/src/arrow/predicates.ts +66 -0
- package/src/arrow/repr.test-d.ts +189 -0
- package/src/arrow/schema-types.ts +213 -0
- package/src/arrow/types.ts +74 -0
- package/src/catalog/attach-option.ts +107 -0
- package/src/catalog/attach-options.ts +79 -0
- package/src/catalog/composite.ts +180 -0
- package/src/catalog/descriptors.ts +164 -0
- package/src/catalog/interface.ts +721 -0
- package/src/catalog/read-only.ts +909 -0
- package/src/client/client.ts +1348 -0
- package/src/client/errors.ts +69 -0
- package/src/client/helpers.ts +56 -0
- package/src/client/protocol.ts +33 -0
- package/src/client/types.ts +140 -0
- package/src/client-entry.ts +112 -0
- package/src/codec/asd.ts +147 -0
- package/src/crypto.ts +143 -0
- package/src/errors.ts +61 -0
- package/src/filter-pushdown/collector.ts +147 -0
- package/src/filter-pushdown/deserialize.ts +253 -0
- package/src/filter-pushdown/evaluate.ts +591 -0
- package/src/filter-pushdown/index.ts +28 -0
- package/src/filter-pushdown/types.ts +91 -0
- package/src/functions/aggregate.ts +384 -0
- package/src/functions/copy-from.ts +282 -0
- package/src/functions/copy-to.ts +319 -0
- package/src/functions/registry.ts +258 -0
- package/src/functions/scalar.ts +356 -0
- package/src/functions/storage-cf-do.ts +303 -0
- package/src/functions/storage.ts +521 -0
- package/src/functions/table-buffering.ts +562 -0
- package/src/functions/table-in-out.ts +373 -0
- package/src/functions/table.ts +521 -0
- package/src/functions/types.ts +192 -0
- package/src/generated/vgi-client.ts +560 -0
- package/src/generated/vgi-protocol-schemas.ts +864 -0
- package/src/index.core.ts +267 -0
- package/src/index.ts +11 -0
- package/src/metadata/resolve.ts +109 -0
- package/src/metadata/serialize.ts +242 -0
- package/src/metadata/types.ts +60 -0
- package/src/protocol/dispatch.ts +52 -0
- package/src/protocol/handlers/aggregate.ts +355 -0
- package/src/protocol/handlers/catalog/admin.ts +368 -0
- package/src/protocol/handlers/catalog/index.ts +25 -0
- package/src/protocol/handlers/catalog/index_methods.ts +65 -0
- package/src/protocol/handlers/catalog/macro.ts +114 -0
- package/src/protocol/handlers/catalog/shared.ts +291 -0
- package/src/protocol/handlers/catalog/table.ts +401 -0
- package/src/protocol/handlers/catalog/view.ts +119 -0
- package/src/protocol/handlers/function.ts +302 -0
- package/src/protocol/handlers/shared.ts +80 -0
- package/src/protocol/handlers/table-buffering.ts +207 -0
- package/src/protocol/serialize.ts +23 -0
- package/src/protocol/serializers/arguments.ts +160 -0
- package/src/protocol/serializers/bind.ts +212 -0
- package/src/protocol/serializers/cardinality.ts +45 -0
- package/src/protocol/serializers/init.ts +247 -0
- package/src/protocol/serializers/shared.ts +46 -0
- package/src/protocol/state-serializer.ts +181 -0
- package/src/protocol/types.ts +153 -0
- package/src/secrets/helpers.ts +70 -0
- package/src/types.ts +71 -0
- package/src/util/arrow/index.ts +23 -0
- package/src/util/bytes.ts +14 -0
- package/src/util/statistics.ts +40 -0
- package/src/worker-cf-entry.ts +172 -0
- package/src/worker.ts +212 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Arrow IPC stream serialization/deserialization for the flechette backend.
|
|
3
|
+
//
|
|
4
|
+
// flechette's `tableFromIPC` accepts either Uint8Array or Uint8Array[] (the
|
|
5
|
+
// latter for stream-format messages produced incrementally), and
|
|
6
|
+
// `tableToIPC(table, { format: "stream" })` emits a self-contained stream.
|
|
7
|
+
// The same Schema/RecordBatch shapes are produced and consumed here as in
|
|
8
|
+
// impl-arrowjs (structurally compatible with VgiSchema / VgiBatch).
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
tableFromIPC,
|
|
12
|
+
tableToIPC,
|
|
13
|
+
columnFromArray,
|
|
14
|
+
field as f_field,
|
|
15
|
+
Table,
|
|
16
|
+
} from "@query-farm/flechette";
|
|
17
|
+
|
|
18
|
+
import type { VgiSchema, VgiBatch } from "../types.js";
|
|
19
|
+
import { readFirstRecordBatchMeta } from "./message-meta.js";
|
|
20
|
+
|
|
21
|
+
// flechette decoding options that match how vgi-typescript callers expect
|
|
22
|
+
// values to come out: 64-bit ints stay as BigInt, decimals as scaled BigInts,
|
|
23
|
+
// timestamps as BigInt nanoseconds, Maps as [k,v] arrays.
|
|
24
|
+
const EXTRACT_OPTS = {
|
|
25
|
+
useBigInt: true,
|
|
26
|
+
useBigIntTimestamp: true,
|
|
27
|
+
useDecimalInt: true,
|
|
28
|
+
useMap: false,
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Serialize a Schema to Arrow IPC bytes.
|
|
33
|
+
*
|
|
34
|
+
* flechette has no schema-only serialization helper, so we encode an empty
|
|
35
|
+
* 0-row table that carries just the schema header. Decoders see the Schema
|
|
36
|
+
* + an immediate EOS marker — same wire shape arrow-js's
|
|
37
|
+
* `RecordBatchStreamWriter.close()` produces for an empty stream.
|
|
38
|
+
*/
|
|
39
|
+
export function serializeSchema(schema: VgiSchema): Uint8Array {
|
|
40
|
+
// Build the Table directly so we preserve per-field `nullable`/`metadata`.
|
|
41
|
+
// tableFromColumns rebuilds fields with nullable=true, which round-trips
|
|
42
|
+
// wrong on the C++ side (rejects schema-conforming payloads as schema
|
|
43
|
+
// mismatches). See build.ts for the same fix on batches.
|
|
44
|
+
const cols = schema.fields.map((f) => columnFromArray([], f.type as any));
|
|
45
|
+
const fields = schema.fields.map((f, i) =>
|
|
46
|
+
f_field(f.name, cols[i].type as any, (f as any).nullable ?? true, (f as any).metadata ?? null),
|
|
47
|
+
);
|
|
48
|
+
const flechSchema = {
|
|
49
|
+
version: 5,
|
|
50
|
+
endianness: 0,
|
|
51
|
+
fields,
|
|
52
|
+
metadata: (schema as any).metadata ?? null,
|
|
53
|
+
};
|
|
54
|
+
return tableToIPC(new Table(flechSchema as any, cols), { format: "stream" }) as Uint8Array;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Deserialize an Arrow Schema from IPC bytes. Returns the schema of the
|
|
59
|
+
* decoded table (which may be 0-row if the source was a schema-only stream).
|
|
60
|
+
*/
|
|
61
|
+
export function deserializeSchema(bytes: Uint8Array): VgiSchema {
|
|
62
|
+
const table = tableFromIPC(bytes, EXTRACT_OPTS);
|
|
63
|
+
// flechette's Table.schema is the structural shape we need.
|
|
64
|
+
return table.schema as unknown as VgiSchema;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Serialize a RecordBatch to Arrow IPC bytes (stream format).
|
|
69
|
+
*
|
|
70
|
+
* The batch passed in is structurally a flechette Table (1 record batch's
|
|
71
|
+
* worth of data). flechette's `tableToIPC` round-trips it as a complete
|
|
72
|
+
* stream — schema + one batch + EOS — which is what subprocess transport
|
|
73
|
+
* consumers expect per call.
|
|
74
|
+
*/
|
|
75
|
+
export function serializeBatch(batch: VgiBatch): Uint8Array {
|
|
76
|
+
// VgiBatch is structurally a flechette Table at this point.
|
|
77
|
+
return tableToIPC(batch as any, { format: "stream" }) as Uint8Array;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Deserialize a RecordBatch from Arrow IPC bytes.
|
|
82
|
+
*
|
|
83
|
+
* We treat the decoded Table as the "batch" — flechette Tables expose
|
|
84
|
+
* numRows / schema / getChild that satisfy VgiBatch's shape. For
|
|
85
|
+
* multi-batch streams this collapses to a single logical batch, matching
|
|
86
|
+
* the subprocess transport's one-batch-per-call convention.
|
|
87
|
+
*/
|
|
88
|
+
export function deserializeBatch(bytes: Uint8Array): VgiBatch {
|
|
89
|
+
const table: any = tableFromIPC(bytes, EXTRACT_OPTS);
|
|
90
|
+
// flechette's IPC parser ignores the Message-level `custom_metadata` field
|
|
91
|
+
// and reports row counts only via column children, so a "metadata-only"
|
|
92
|
+
// batch over a 0-field schema (legal in vgi-rpc — used to ferry state
|
|
93
|
+
// tokens / cancel signals) round-trips as numRows=0/metadata=undefined.
|
|
94
|
+
// Backfill both from the wire bytes so VgiBatch matches arrow-js's shape.
|
|
95
|
+
const meta = readFirstRecordBatchMeta(bytes);
|
|
96
|
+
if (meta === null) return table as VgiBatch;
|
|
97
|
+
const wantRows = table.numRows === 0 && meta.numRows > 0;
|
|
98
|
+
const wantMeta = !table.metadata && meta.metadata.size > 0;
|
|
99
|
+
if (!wantRows && !wantMeta) return table as VgiBatch;
|
|
100
|
+
// flechette Tables freeze their properties; wrap with a Proxy so reads of
|
|
101
|
+
// numRows / metadata see the backfilled values while everything else
|
|
102
|
+
// (schema, getChild, factory, …) flows through to the underlying Table.
|
|
103
|
+
return new Proxy(table, {
|
|
104
|
+
get(target, prop, receiver) {
|
|
105
|
+
if (wantRows && prop === "numRows") return meta.numRows;
|
|
106
|
+
if (wantMeta && prop === "metadata") return meta.metadata;
|
|
107
|
+
return Reflect.get(target, prop, receiver);
|
|
108
|
+
},
|
|
109
|
+
}) as unknown as VgiBatch;
|
|
110
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Read data out of flechette tables as plain JS values.
|
|
3
|
+
|
|
4
|
+
import type { VgiBatch, VgiDataType } from "../types.js";
|
|
5
|
+
import { codecFor } from "../codec/registry.js";
|
|
6
|
+
import { readCanonicalValue } from "./canonical.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Iterate rows of a batch (flechette Table) as plain objects, in the RICH
|
|
10
|
+
* representation (Date for date32/date64; canonical otherwise). Reads via the
|
|
11
|
+
* per-backend canonical reader then maps canonical -> rich through the codec —
|
|
12
|
+
* symmetric with the build path and identical to the arrow-js backend.
|
|
13
|
+
*/
|
|
14
|
+
export function* iterRows(
|
|
15
|
+
batch: VgiBatch,
|
|
16
|
+
repr: "rich" | "raw" = "rich",
|
|
17
|
+
): Generator<Record<string, any>> {
|
|
18
|
+
const t = batch as any;
|
|
19
|
+
const fields = t.schema.fields;
|
|
20
|
+
const codecs = fields.map((f: any) => codecFor(f.type as VgiDataType));
|
|
21
|
+
for (let i = 0; i < t.numRows; i++) {
|
|
22
|
+
const row: Record<string, any> = {};
|
|
23
|
+
for (let fi = 0; fi < fields.length; fi++) {
|
|
24
|
+
const f = fields[fi];
|
|
25
|
+
const col = t.getChild(f.name);
|
|
26
|
+
if (!col) { row[f.name] = null; continue; }
|
|
27
|
+
const canonical = readCanonicalValue(f.type as VgiDataType, col, i);
|
|
28
|
+
const codec = codecs[fi];
|
|
29
|
+
row[f.name] = repr === "raw"
|
|
30
|
+
? codec.canonicalToRaw(canonical)
|
|
31
|
+
: codec.canonicalToRich(canonical);
|
|
32
|
+
}
|
|
33
|
+
yield row;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract a single-row batch as a flat dict { col: value }, in the RICH
|
|
39
|
+
* representation. Routes through the canonical reader + codec (same as iterRows)
|
|
40
|
+
* so a temporal/decimal setting is represented identically to column data and
|
|
41
|
+
* identically to the arrow-js backend.
|
|
42
|
+
*/
|
|
43
|
+
export function batchToScalarDict(
|
|
44
|
+
batch: VgiBatch | null,
|
|
45
|
+
): Record<string, any> {
|
|
46
|
+
if (!batch) return {};
|
|
47
|
+
const t = batch as any;
|
|
48
|
+
if (t.numRows === 0) return {};
|
|
49
|
+
const result: Record<string, any> = {};
|
|
50
|
+
for (const f of t.schema.fields) {
|
|
51
|
+
const col = t.getChild(f.name);
|
|
52
|
+
if (!col) continue;
|
|
53
|
+
const type = f.type as VgiDataType;
|
|
54
|
+
result[f.name] = codecFor(type).canonicalToRich(readCanonicalValue(type, col, 0));
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* No-op for flechette: dictionary columns auto-decode at extraction time.
|
|
61
|
+
* Provided for parity with impl-arrowjs (where the apache-arrow fork doesn't
|
|
62
|
+
* auto-decode in some paths).
|
|
63
|
+
*/
|
|
64
|
+
export function decodeDictValue(value: any, _index = 0): any {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Single-row batch → secret dict. Mirrors impl-arrowjs's same-named function.
|
|
70
|
+
*/
|
|
71
|
+
export function batchToSecretDict(
|
|
72
|
+
batch: VgiBatch | null,
|
|
73
|
+
): Record<string, Record<string, any>> {
|
|
74
|
+
if (!batch) return {};
|
|
75
|
+
const t = batch as any;
|
|
76
|
+
if (t.numRows === 0) return {};
|
|
77
|
+
const result: Record<string, Record<string, any>> = {};
|
|
78
|
+
for (const f of t.schema.fields) {
|
|
79
|
+
const col = t.getChild(f.name);
|
|
80
|
+
if (!col) continue;
|
|
81
|
+
// Read in RICH form via the canonical reader + codec — a secret column is a
|
|
82
|
+
// struct scalar, surfacing as a plain { field: value } object (same path as
|
|
83
|
+
// iterRows), identical to the arrow-js backend.
|
|
84
|
+
const type = f.type as VgiDataType;
|
|
85
|
+
const val = codecFor(type).canonicalToRich(readCanonicalValue(type, col, 0));
|
|
86
|
+
|
|
87
|
+
let key = f.name;
|
|
88
|
+
let scope: string | undefined;
|
|
89
|
+
if (f.name.startsWith("secret_")) {
|
|
90
|
+
const secretType = f.metadata?.get?.("secret_type");
|
|
91
|
+
if (secretType) {
|
|
92
|
+
key = secretType;
|
|
93
|
+
scope = f.metadata?.get?.("scope") ?? undefined;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (val && typeof val === "object" && !ArrayBuffer.isView(val)) {
|
|
98
|
+
const dict: Record<string, any> = { ...(val as Record<string, any>) };
|
|
99
|
+
if (key in result) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`batchToSecretDict: duplicate secret_type '${key}' (scope=${scope ?? "none"}).`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
result[key] = dict;
|
|
105
|
+
if (scope) result[`${key}:${scope}`] = dict;
|
|
106
|
+
} else if (val === null || val === undefined) {
|
|
107
|
+
// skip
|
|
108
|
+
} else {
|
|
109
|
+
result[key] = {};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* BigInt → number coercion (lossy for values past 2^53).
|
|
117
|
+
*/
|
|
118
|
+
export function safeNumber(value: any): number {
|
|
119
|
+
if (typeof value === "bigint") return Number(value);
|
|
120
|
+
return value as number;
|
|
121
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Minimal Arrow Message-envelope reader.
|
|
3
|
+
//
|
|
4
|
+
// flechette's IPC parser ignores the Message-level `custom_metadata` field
|
|
5
|
+
// (vtable offset 12 in the Message FlatBuffer) and only surfaces row count
|
|
6
|
+
// through column children — both of which are needed to round-trip vgi
|
|
7
|
+
// "params" batches that carry zero-field schemas with auth/state tokens
|
|
8
|
+
// in batch metadata. arrow-js exposes both via `RecordBatch.metadata` and
|
|
9
|
+
// `RecordBatch.numRows`.
|
|
10
|
+
//
|
|
11
|
+
// This file is a self-contained, dependency-free reader that walks an IPC
|
|
12
|
+
// stream and pulls out, for the first RecordBatch message it sees:
|
|
13
|
+
// * `numRows` — from RecordBatch.length (Message header_value, RB vtable offset 4)
|
|
14
|
+
// * `metadata` — Message.custom_metadata (Message vtable offset 12)
|
|
15
|
+
//
|
|
16
|
+
// Schema fields are 0/1/2/3/4; same for RecordBatch — see Arrow's
|
|
17
|
+
// `format/Message.fbs` and `format/Schema.fbs`.
|
|
18
|
+
|
|
19
|
+
const TYPE_SCHEMA = 1;
|
|
20
|
+
const TYPE_RECORD_BATCH = 3;
|
|
21
|
+
|
|
22
|
+
function readU32LE(b: Uint8Array, p: number): number {
|
|
23
|
+
return ((b[p] | (b[p + 1] << 8) | (b[p + 2] << 16) | (b[p + 3] << 24)) >>> 0);
|
|
24
|
+
}
|
|
25
|
+
function readI32LE(b: Uint8Array, p: number): number {
|
|
26
|
+
return readU32LE(b, p) | 0;
|
|
27
|
+
}
|
|
28
|
+
function readI16LE(b: Uint8Array, p: number): number {
|
|
29
|
+
const v = b[p] | (b[p + 1] << 8);
|
|
30
|
+
return v < 0x8000 ? v : v - 0x10000;
|
|
31
|
+
}
|
|
32
|
+
function readI64LE(b: Uint8Array, p: number): bigint {
|
|
33
|
+
const lo = BigInt(readU32LE(b, p));
|
|
34
|
+
const hi = BigInt(readU32LE(b, p + 4));
|
|
35
|
+
// Sign-extend if hi's top bit is set.
|
|
36
|
+
const top = hi & 0x80000000n;
|
|
37
|
+
const combined = (hi << 32n) | lo;
|
|
38
|
+
return top ? combined - (1n << 64n) : combined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// FlatBuffer table accessor. Holds the absolute byte offset of the table
|
|
42
|
+
// (root or sub-table) plus the parsed vtable field offsets.
|
|
43
|
+
interface FbTable {
|
|
44
|
+
pos: number;
|
|
45
|
+
fields: Int16Array;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readTable(buf: Uint8Array, tablePos: number): FbTable {
|
|
49
|
+
// First field at tablePos is the (signed) offset back to the vtable.
|
|
50
|
+
const vtableRel = readI32LE(buf, tablePos);
|
|
51
|
+
const vtablePos = tablePos - vtableRel;
|
|
52
|
+
const vtableSize = readI16LE(buf, vtablePos);
|
|
53
|
+
const fieldCount = (vtableSize - 4) >> 1;
|
|
54
|
+
const fields = new Int16Array(fieldCount);
|
|
55
|
+
for (let i = 0; i < fieldCount; i++) {
|
|
56
|
+
fields[i] = readI16LE(buf, vtablePos + 4 + i * 2);
|
|
57
|
+
}
|
|
58
|
+
return { pos: tablePos, fields };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Position of a field in the table buffer, or null if absent (offset 0). */
|
|
62
|
+
function fieldAt(t: FbTable, fieldIndex: number): number | null {
|
|
63
|
+
if (fieldIndex >= t.fields.length) return null;
|
|
64
|
+
const off = t.fields[fieldIndex];
|
|
65
|
+
return off === 0 ? null : t.pos + off;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Follow a uoffset_t at `ptrPos` to the absolute target byte position. */
|
|
69
|
+
function follow(buf: Uint8Array, ptrPos: number): number {
|
|
70
|
+
return ptrPos + readU32LE(buf, ptrPos);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readString(buf: Uint8Array, strPos: number): string {
|
|
74
|
+
const len = readU32LE(buf, strPos);
|
|
75
|
+
return new TextDecoder().decode(buf.subarray(strPos + 4, strPos + 4 + len));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface ParsedMessage {
|
|
79
|
+
/** Header type tag — 1 = Schema, 2 = DictionaryBatch, 3 = RecordBatch. */
|
|
80
|
+
headerType: number;
|
|
81
|
+
/** RecordBatch.length (rows). Zero if not a RecordBatch or absent. */
|
|
82
|
+
numRows: number;
|
|
83
|
+
/** Message-level body length (offset 10). Used to skip past the body. */
|
|
84
|
+
bodyLength: number;
|
|
85
|
+
/** Message.custom_metadata, parsed into a Map. Empty if absent. */
|
|
86
|
+
metadata: Map<string, string>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readMessageEnvelope(head: Uint8Array): ParsedMessage {
|
|
90
|
+
const rootOffset = readU32LE(head, 0);
|
|
91
|
+
const msg = readTable(head, rootOffset);
|
|
92
|
+
|
|
93
|
+
// Message field indices (Message.fbs):
|
|
94
|
+
// 0: version (int16) vtable offset 4
|
|
95
|
+
// 1: headerType (uint8) vtable offset 6
|
|
96
|
+
// 2: header (table) vtable offset 8
|
|
97
|
+
// 3: bodyLength (int64) vtable offset 10
|
|
98
|
+
// 4: custom_metadata ([KeyVal]) vtable offset 12
|
|
99
|
+
const headerTypePos = fieldAt(msg, 1);
|
|
100
|
+
const headerType = headerTypePos === null ? 0 : head[headerTypePos];
|
|
101
|
+
|
|
102
|
+
const bodyLengthPos = fieldAt(msg, 3);
|
|
103
|
+
const bodyLengthBig = bodyLengthPos === null ? 0n : readI64LE(head, bodyLengthPos);
|
|
104
|
+
// Bodies are bounded by maxDecompressedRequestBytes upstream — safe to coerce.
|
|
105
|
+
const bodyLength = Number(bodyLengthBig);
|
|
106
|
+
|
|
107
|
+
let numRows = 0;
|
|
108
|
+
if (headerType === TYPE_RECORD_BATCH) {
|
|
109
|
+
const headerValuePos = fieldAt(msg, 2);
|
|
110
|
+
if (headerValuePos !== null) {
|
|
111
|
+
const rbPos = follow(head, headerValuePos);
|
|
112
|
+
const rb = readTable(head, rbPos);
|
|
113
|
+
// RecordBatch.length is field 0 (vtable offset 4), int64.
|
|
114
|
+
const lenPos = fieldAt(rb, 0);
|
|
115
|
+
numRows = lenPos === null ? 0 : Number(readI64LE(head, lenPos));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const metadata = new Map<string, string>();
|
|
120
|
+
const metaVecPtr = fieldAt(msg, 4);
|
|
121
|
+
if (metaVecPtr !== null) {
|
|
122
|
+
const vecPos = follow(head, metaVecPtr);
|
|
123
|
+
const vecLen = readU32LE(head, vecPos);
|
|
124
|
+
for (let i = 0; i < vecLen; i++) {
|
|
125
|
+
// Vector of tables: each entry is a uoffset_t (4 bytes) pointing to the table.
|
|
126
|
+
const itemPtr = vecPos + 4 + i * 4;
|
|
127
|
+
const kvPos = follow(head, itemPtr);
|
|
128
|
+
const kv = readTable(head, kvPos);
|
|
129
|
+
const keyPtr = fieldAt(kv, 0);
|
|
130
|
+
const valPtr = fieldAt(kv, 1);
|
|
131
|
+
const key = keyPtr === null ? "" : readString(head, follow(head, keyPtr));
|
|
132
|
+
const val = valPtr === null ? "" : readString(head, follow(head, valPtr));
|
|
133
|
+
metadata.set(key, val);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { headerType, numRows, bodyLength, metadata };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Walk an Arrow IPC stream and return the first RecordBatch message's
|
|
142
|
+
* (numRows, metadata) — the bits flechette drops on the floor.
|
|
143
|
+
*
|
|
144
|
+
* Returns null if the stream contains no RecordBatch.
|
|
145
|
+
*/
|
|
146
|
+
export function readFirstRecordBatchMeta(
|
|
147
|
+
stream: Uint8Array,
|
|
148
|
+
): { numRows: number; metadata: Map<string, string> } | null {
|
|
149
|
+
let pos = 0;
|
|
150
|
+
while (pos + 4 <= stream.length) {
|
|
151
|
+
let metaLen = readI32LE(stream, pos);
|
|
152
|
+
pos += 4;
|
|
153
|
+
// Continuation-marker form (post-Arrow 0.15): the real length follows.
|
|
154
|
+
if (metaLen === -1) {
|
|
155
|
+
if (pos + 4 > stream.length) return null;
|
|
156
|
+
metaLen = readI32LE(stream, pos);
|
|
157
|
+
pos += 4;
|
|
158
|
+
}
|
|
159
|
+
if (metaLen === 0) return null; // EOS
|
|
160
|
+
if (pos + metaLen > stream.length) return null;
|
|
161
|
+
const head = stream.subarray(pos, pos + metaLen);
|
|
162
|
+
pos += metaLen;
|
|
163
|
+
const parsed = readMessageEnvelope(head);
|
|
164
|
+
if (parsed.headerType === TYPE_RECORD_BATCH) {
|
|
165
|
+
return { numRows: parsed.numRows, metadata: parsed.metadata };
|
|
166
|
+
}
|
|
167
|
+
// Skip the body (Schema messages have bodyLength = 0).
|
|
168
|
+
pos += parsed.bodyLength;
|
|
169
|
+
if (parsed.headerType !== TYPE_SCHEMA && parsed.headerType !== 2 /* Dict */) {
|
|
170
|
+
// Unknown header — stop walking.
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
//
|
|
3
|
+
// Normalize a (possibly foreign) Arrow DataType into a flechette-native type.
|
|
4
|
+
//
|
|
5
|
+
// App/worker code routinely builds schemas with concrete arrow-js DataType
|
|
6
|
+
// instances (e.g. `new Int64()` from @query-farm/apache-arrow) rather than the
|
|
7
|
+
// facade constructors. flechette's `columnFromArray` *tolerates* such foreign
|
|
8
|
+
// type objects when reading values, but the resulting `Column.type` is the
|
|
9
|
+
// foreign object — and flechette's IPC writer then serializes it incorrectly
|
|
10
|
+
// (e.g. Utf8 round-trips to ["", "xy\0\0…"], Int64 throws BigInt→number on a
|
|
11
|
+
// stale build). Reconstructing the type from its structural fields via
|
|
12
|
+
// flechette's own constructors yields a writer-safe native type.
|
|
13
|
+
//
|
|
14
|
+
// Both arrow-js and flechette encode `typeId` with the same Arrow Type enum, so
|
|
15
|
+
// we switch on it and read the shared structural props (with the one rename
|
|
16
|
+
// flechette differs on: Int uses `signed`, arrow-js uses `isSigned`).
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
Type,
|
|
20
|
+
field as f_field,
|
|
21
|
+
nullType, bool, int, float, utf8, binary, fixedSizeBinary,
|
|
22
|
+
decimal, date, time, timestamp, duration, interval,
|
|
23
|
+
list, largeList, fixedSizeList, struct, map, dictionary, union,
|
|
24
|
+
} from "@query-farm/flechette";
|
|
25
|
+
|
|
26
|
+
function fField(f: any): any {
|
|
27
|
+
return f_field(f.name, toFlechetteType(f.type), f.nullable ?? true, f.metadata ?? null);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Rebuild `type` as a flechette-native DataType. Idempotent for types that are
|
|
32
|
+
* already native (reconstructed from the same structural props). Unknown
|
|
33
|
+
* typeIds pass through unchanged as a safety net.
|
|
34
|
+
*/
|
|
35
|
+
export function toFlechetteType(type: any): any {
|
|
36
|
+
if (type == null) return nullType();
|
|
37
|
+
switch (type.typeId) {
|
|
38
|
+
case Type.Null:
|
|
39
|
+
return nullType();
|
|
40
|
+
case Type.Bool:
|
|
41
|
+
return bool();
|
|
42
|
+
case Type.Int:
|
|
43
|
+
return int(type.bitWidth, type.isSigned ?? type.signed ?? true);
|
|
44
|
+
case Type.Float:
|
|
45
|
+
return float(type.precision);
|
|
46
|
+
case Type.Binary:
|
|
47
|
+
case Type.LargeBinary:
|
|
48
|
+
return binary();
|
|
49
|
+
case Type.Utf8:
|
|
50
|
+
case Type.LargeUtf8:
|
|
51
|
+
return utf8();
|
|
52
|
+
case Type.FixedSizeBinary:
|
|
53
|
+
return fixedSizeBinary(type.byteWidth ?? type.stride);
|
|
54
|
+
case Type.Decimal:
|
|
55
|
+
return decimal(type.precision, type.scale, type.bitWidth ?? 128);
|
|
56
|
+
case Type.Date:
|
|
57
|
+
return date(type.unit);
|
|
58
|
+
case Type.Time:
|
|
59
|
+
return time(type.unit);
|
|
60
|
+
case Type.Timestamp:
|
|
61
|
+
return timestamp(type.unit, type.timezone ?? null);
|
|
62
|
+
case Type.Duration:
|
|
63
|
+
return duration(type.unit);
|
|
64
|
+
case Type.Interval:
|
|
65
|
+
return interval(type.unit);
|
|
66
|
+
case Type.List:
|
|
67
|
+
case Type.LargeList:
|
|
68
|
+
return (type.typeId === Type.LargeList ? largeList : list)(fField(type.children[0]));
|
|
69
|
+
case Type.FixedSizeList:
|
|
70
|
+
return fixedSizeList(fField(type.children[0]), type.listSize);
|
|
71
|
+
case Type.Struct:
|
|
72
|
+
return struct(type.children.map(fField));
|
|
73
|
+
case Type.Map: {
|
|
74
|
+
// children[0] is the "entries" struct field holding [key, value].
|
|
75
|
+
const entries = type.children[0];
|
|
76
|
+
const [k, v] = entries.type.children;
|
|
77
|
+
return map(fField(k), fField(v), type.keysSorted ?? false);
|
|
78
|
+
}
|
|
79
|
+
case Type.Dictionary:
|
|
80
|
+
return dictionary(
|
|
81
|
+
toFlechetteType(type.dictionary),
|
|
82
|
+
toFlechetteType(type.indices),
|
|
83
|
+
type.ordered ?? type.isOrdered ?? false,
|
|
84
|
+
type.id,
|
|
85
|
+
);
|
|
86
|
+
case Type.Union:
|
|
87
|
+
return union(type.mode, type.children.map(fField), type.typeIds);
|
|
88
|
+
default:
|
|
89
|
+
return type;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Normalize every field's type in a schema-like object to flechette-native,
|
|
95
|
+
* preserving field name / nullable / metadata.
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeSchemaFields(fields: readonly any[]): any[] {
|
|
98
|
+
return fields.map(fField);
|
|
99
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Schema/batch projection by column index for the flechette backend.
|
|
3
|
+
|
|
4
|
+
import { tableFromColumns } from "@query-farm/flechette";
|
|
5
|
+
import type { VgiSchema, VgiBatch } from "../types.js";
|
|
6
|
+
|
|
7
|
+
function validateProjectionIds(
|
|
8
|
+
caller: string,
|
|
9
|
+
ids: number[],
|
|
10
|
+
fieldCount: number,
|
|
11
|
+
): number[] {
|
|
12
|
+
for (const id of ids) {
|
|
13
|
+
if (id < -1) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`${caller}: unexpected negative projection ID ${id} (only -1 for row_id is allowed)`,
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (id >= fieldCount) {
|
|
19
|
+
console.warn(
|
|
20
|
+
`${caller}: projection ID ${id} is out of bounds (schema has ${fieldCount} fields), ignoring`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return ids.filter((i) => i >= 0 && i < fieldCount);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function projectSchema(
|
|
28
|
+
projectionIds: number[] | null,
|
|
29
|
+
schema: VgiSchema,
|
|
30
|
+
): VgiSchema {
|
|
31
|
+
if (!projectionIds) return schema;
|
|
32
|
+
const valid = validateProjectionIds(
|
|
33
|
+
"projectSchema",
|
|
34
|
+
projectionIds,
|
|
35
|
+
schema.fields.length,
|
|
36
|
+
);
|
|
37
|
+
if (valid.length === 0) return schema;
|
|
38
|
+
return {
|
|
39
|
+
fields: valid.map((i) => schema.fields[i]),
|
|
40
|
+
metadata: schema.metadata,
|
|
41
|
+
} as VgiSchema;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function projectBatch(
|
|
45
|
+
projectionIds: number[] | null,
|
|
46
|
+
batch: VgiBatch,
|
|
47
|
+
): VgiBatch {
|
|
48
|
+
if (!projectionIds) return batch;
|
|
49
|
+
const t = batch as any;
|
|
50
|
+
const valid = validateProjectionIds(
|
|
51
|
+
"projectBatch",
|
|
52
|
+
projectionIds,
|
|
53
|
+
t.schema.fields.length,
|
|
54
|
+
);
|
|
55
|
+
if (valid.length === 0) return batch;
|
|
56
|
+
|
|
57
|
+
// Rebuild a Table with only the kept columns, in the requested order.
|
|
58
|
+
const cols: Record<string, any> = {};
|
|
59
|
+
for (const i of valid) {
|
|
60
|
+
const f = t.schema.fields[i];
|
|
61
|
+
cols[f.name] = t.getChild(f.name);
|
|
62
|
+
}
|
|
63
|
+
return tableFromColumns(cols) as unknown as VgiBatch;
|
|
64
|
+
}
|