@query-farm/vgi 0.20.0 → 0.22.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/dist/arrow/impl-arrowjs/index.d.ts +7 -0
- package/dist/arrow/impl-flechette/arrowjs-shape.d.ts +16 -0
- package/dist/arrow/impl-flechette/compat.d.ts +22 -0
- package/dist/arrow/impl-flechette/index.d.ts +2 -0
- package/dist/arrow/impl-flechette/ipc.d.ts +1 -1
- package/dist/arrow/index.d.ts +2 -2
- package/dist/arrow/predicates.d.ts +16 -0
- package/dist/cache-control.d.ts +33 -0
- package/dist/catalog/composite.d.ts +2 -0
- package/dist/catalog/interface.d.ts +11 -0
- package/dist/client-entry.js +9 -3
- package/dist/client-entry.js.map +7 -7
- package/dist/functions/registry.d.ts +31 -0
- package/dist/functions/scalar.d.ts +7 -3
- package/dist/generated/vgi-client.d.ts +16 -0
- package/dist/index.core.d.ts +2 -2
- package/dist/index.js +175 -19
- package/dist/index.js.map +19 -19
- package/dist/protocol/handlers/function.d.ts +8 -0
- package/dist/protocol/handlers/shared.d.ts +4 -0
- package/dist/protocol/types.d.ts +8 -0
- package/dist/serve-entry.js +40 -13
- package/dist/serve-entry.js.map +10 -10
- package/dist/util/arrow/index.d.ts +1 -1
- package/dist/worker-cf-entry.js +167 -17
- package/dist/worker-cf-entry.js.map +17 -17
- package/dist/worker.d.ts +9 -0
- package/package.json +5 -5
- package/src/arrow/impl-arrowjs/index.ts +10 -0
- package/src/arrow/impl-flechette/arrowjs-shape.ts +78 -0
- package/src/arrow/impl-flechette/canonical.ts +19 -0
- package/src/arrow/impl-flechette/compat.ts +254 -0
- package/src/arrow/impl-flechette/empty.ts +5 -1
- package/src/arrow/impl-flechette/index.ts +8 -0
- package/src/arrow/impl-flechette/ipc.ts +72 -6
- package/src/arrow/impl-flechette/normalize-type.ts +10 -2
- package/src/arrow/impl-flechette/schema.ts +13 -9
- package/src/arrow/index.ts +3 -0
- package/src/arrow/predicates.ts +59 -0
- package/src/cache-control.ts +35 -0
- package/src/catalog/composite.ts +9 -0
- package/src/catalog/interface.ts +15 -0
- package/src/catalog/read-only.ts +13 -0
- package/src/functions/registry.ts +127 -5
- package/src/functions/scalar.ts +10 -5
- package/src/generated/vgi-client.ts +17 -1
- package/src/index.core.ts +4 -0
- package/src/protocol/dispatch.ts +1 -0
- package/src/protocol/handlers/function.ts +38 -9
- package/src/protocol/handlers/shared.ts +25 -1
- package/src/protocol/serializers/bind.ts +7 -0
- package/src/protocol/types.ts +8 -0
- package/src/util/arrow/index.ts +1 -0
- package/src/worker.ts +15 -1
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
serializeTableCardinality,
|
|
17
17
|
} from "../serialize.js";
|
|
18
18
|
import type { GlobalInitResponse } from "../types.js";
|
|
19
|
-
import { batchToScalarDict, deserializeBatch } from "../../util/arrow/index.js";
|
|
19
|
+
import { batchToScalarDict, deserializeBatch, adoptArrowJsShape } from "../../util/arrow/index.js";
|
|
20
20
|
import { toUint8Array } from "../../util/bytes.js";
|
|
21
21
|
import { serializeColumnStatistics } from "../../util/statistics.js";
|
|
22
22
|
import { BindResultSchema, TableFunctionCardinalityResultSchema, TableFunctionDynamicToStringResultSchema } from "../../generated/vgi-protocol-schemas.js";
|
|
@@ -35,10 +35,16 @@ export interface FunctionHandlerConfig {
|
|
|
35
35
|
registry: FunctionRegistry;
|
|
36
36
|
recoverExchangeState?: (opaqueData: Uint8Array) => any;
|
|
37
37
|
signingKey?: Uint8Array;
|
|
38
|
+
/**
|
|
39
|
+
* Used to map a bind's `attach_opaque_data` to its catalog, so resolution can
|
|
40
|
+
* be scoped when two catalogs declare the same schema and function name.
|
|
41
|
+
* Optional — without it, resolution falls back to (schema, name).
|
|
42
|
+
*/
|
|
43
|
+
catalogInterface?: { catalogNameForAttach(a: Uint8Array): string | null };
|
|
38
44
|
}
|
|
39
45
|
|
|
40
46
|
export function registerFunctionMethods(protocol: Protocol, config: FunctionHandlerConfig): void {
|
|
41
|
-
const { registry, signingKey } = config;
|
|
47
|
+
const { registry, signingKey, catalogInterface } = config;
|
|
42
48
|
|
|
43
49
|
// The framework mints every attach as uuid(16) || catalog_bytes (sealed on
|
|
44
50
|
// HTTP, plaintext on subprocess). Function bodies — like catalog bodies —
|
|
@@ -51,6 +57,21 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
51
57
|
return openAttach(env, ctx?.auth, signingKey);
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
// Build an overload context whose attach has been unsealed, so catalog
|
|
61
|
+
// resolution sees the real route byte. The bind/init handlers strip their
|
|
62
|
+
// request in place and can call overloadContext directly; every other site
|
|
63
|
+
// still holds the sealed envelope (73 bytes on HTTP vs 16 plaintext), whose
|
|
64
|
+
// leading byte would otherwise route to an arbitrary catalog.
|
|
65
|
+
async function strippedContext(bindCall: any, ctx?: any) {
|
|
66
|
+
let attach: Uint8Array | null = null;
|
|
67
|
+
try {
|
|
68
|
+
attach = await stripAttach(bindCall.attach_opaque_data, ctx);
|
|
69
|
+
} catch {
|
|
70
|
+
attach = null; // Unopenable (wrong key / not sealed) — resolve unscoped.
|
|
71
|
+
}
|
|
72
|
+
return overloadContext({ ...bindCall, attach_opaque_data: attach }, catalogInterface);
|
|
73
|
+
}
|
|
74
|
+
|
|
54
75
|
const initHeaderSchema = schema([
|
|
55
76
|
field("execution_id", binary(), false),
|
|
56
77
|
field("opaque_data", binary(), true),
|
|
@@ -77,7 +98,7 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
77
98
|
const innerParams = unwrapRequest(params.request);
|
|
78
99
|
const request = deserializeBindRequest(innerParams);
|
|
79
100
|
request.attach_opaque_data = await stripAttach(request.attach_opaque_data, ctx);
|
|
80
|
-
const func = registry.get(request.function_name, overloadContext(request));
|
|
101
|
+
const func = registry.get(request.function_name, overloadContext(request, catalogInterface));
|
|
81
102
|
const response = await func.bind(request);
|
|
82
103
|
const serialized = serializeBindResponse(response);
|
|
83
104
|
return wrapResult(serialized, BindResultSchema);
|
|
@@ -99,7 +120,7 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
99
120
|
// Exchange init has no ctx; on subprocess (no signing key) openAttach is a
|
|
100
121
|
// pass-through strip that needs no auth. HTTP carries auth via the bind path.
|
|
101
122
|
request.bind_call.attach_opaque_data = await stripAttach(request.bind_call.attach_opaque_data, undefined);
|
|
102
|
-
const func = registry.get(request.bind_call.function_name, overloadContext(request.bind_call));
|
|
123
|
+
const func = registry.get(request.bind_call.function_name, overloadContext(request.bind_call, catalogInterface));
|
|
103
124
|
|
|
104
125
|
// globalInit is async — table function onInit may touch HTTP-backed
|
|
105
126
|
// FunctionStorage (e.g. Cloudflare DO).
|
|
@@ -153,6 +174,14 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
153
174
|
return state;
|
|
154
175
|
},
|
|
155
176
|
exchange: async (state, input, out) => {
|
|
177
|
+
// `input` is the one batch worker code sees that this package did not
|
|
178
|
+
// decode — vgi-rpc read it off the wire with its own Arrow backend. If
|
|
179
|
+
// that resolved to a second copy of @query-farm/flechette (nested under
|
|
180
|
+
// vgi-rpc whenever the version ranges disagree, even transiently), the
|
|
181
|
+
// facade's prototype patches never touched its classes and user code
|
|
182
|
+
// gets `col.isValid is not a function` on the first exchange round.
|
|
183
|
+
// Adopting here is a WeakSet probe per call and a no-op on arrow-js.
|
|
184
|
+
input = adoptArrowJsShape(input);
|
|
156
185
|
// Reconstruct live objects from serializable state.
|
|
157
186
|
// This handles both immediate use (producer during init, where _handlers
|
|
158
187
|
// is still present) and deserialized exchange (where we reconstruct).
|
|
@@ -170,7 +199,7 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
170
199
|
const initRequestBatch = deserializeBatch(state.initRequestIpc);
|
|
171
200
|
const initRequestDict = batchToScalarDict(initRequestBatch);
|
|
172
201
|
const request = deserializeInitRequest(initRequestDict);
|
|
173
|
-
const func = registry.get(state.functionName,
|
|
202
|
+
const func = registry.get(state.functionName, await strippedContext(request.bind_call));
|
|
174
203
|
const executionId = state.executionId;
|
|
175
204
|
const opaqueData = state.opaqueData ?? null;
|
|
176
205
|
const initResponse: GlobalInitResponse = {
|
|
@@ -235,7 +264,7 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
235
264
|
handler: async (params) => {
|
|
236
265
|
const innerParams = unwrapRequest(params.request);
|
|
237
266
|
const request = deserializeCardinalityRequest(innerParams);
|
|
238
|
-
const func = registry.get(request.bind_call.function_name,
|
|
267
|
+
const func = registry.get(request.bind_call.function_name, await strippedContext(request.bind_call));
|
|
239
268
|
let cardResult: Record<string, any>;
|
|
240
269
|
if (func.cardinality) {
|
|
241
270
|
cardResult = serializeTableCardinality(await func.cardinality(request));
|
|
@@ -256,10 +285,10 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
256
285
|
protocol.unary("table_function_statistics", {
|
|
257
286
|
params: REQUEST_PARAMS_SCHEMA,
|
|
258
287
|
result: RESULT_BINARY_NULLABLE_SCHEMA,
|
|
259
|
-
handler: (params) => {
|
|
288
|
+
handler: async (params) => {
|
|
260
289
|
const innerParams = unwrapRequest(params.request);
|
|
261
290
|
const request = deserializeCardinalityRequest(innerParams);
|
|
262
|
-
const func = registry.get(request.bind_call.function_name,
|
|
291
|
+
const func = registry.get(request.bind_call.function_name, await strippedContext(request.bind_call));
|
|
263
292
|
if (!func.statistics) return { result: null };
|
|
264
293
|
const stats = func.statistics(request);
|
|
265
294
|
if (!stats || stats.length === 0) return { result: null };
|
|
@@ -290,7 +319,7 @@ export function registerFunctionMethods(protocol: Protocol, config: FunctionHand
|
|
|
290
319
|
const bindOpaqueData = innerParams.bind_opaque_data
|
|
291
320
|
? toUint8Array(innerParams.bind_opaque_data)
|
|
292
321
|
: null;
|
|
293
|
-
const func = registry.get(bindCall.function_name,
|
|
322
|
+
const func = registry.get(bindCall.function_name, await strippedContext(bindCall));
|
|
294
323
|
const map = func.dynamicToString
|
|
295
324
|
? await func.dynamicToString({ bindCall, bindOpaqueData, globalExecutionId })
|
|
296
325
|
: {};
|
|
@@ -51,11 +51,35 @@ export function wrapResult(
|
|
|
51
51
|
return { result: serializeBatch(batch) };
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
export function overloadContext(
|
|
54
|
+
export function overloadContext(
|
|
55
|
+
req: {
|
|
56
|
+
function_name: string;
|
|
57
|
+
arguments: any;
|
|
58
|
+
input_schema: any;
|
|
59
|
+
function_type: any;
|
|
60
|
+
schema_name?: string | null;
|
|
61
|
+
attach_opaque_data?: Uint8Array | null;
|
|
62
|
+
},
|
|
63
|
+
catalogInterface?: { catalogNameForAttach(a: Uint8Array): string | null },
|
|
64
|
+
): OverloadContext {
|
|
65
|
+
// The attachment identifies the catalog, which is the only key separating two
|
|
66
|
+
// catalogs that declare the same schema AND function name.
|
|
67
|
+
let catalogName: string | null = null;
|
|
68
|
+
if (catalogInterface && req.attach_opaque_data) {
|
|
69
|
+
try {
|
|
70
|
+
catalogName = catalogInterface.catalogNameForAttach(req.attach_opaque_data);
|
|
71
|
+
} catch {
|
|
72
|
+
catalogName = null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
55
75
|
return {
|
|
56
76
|
arguments: req.arguments,
|
|
57
77
|
inputSchema: req.input_schema,
|
|
58
78
|
isScalar: String(req.function_type).toLowerCase() === "scalar",
|
|
79
|
+
// Scopes resolution when the caller named a schema; a name registered in
|
|
80
|
+
// several schemas is otherwise ambiguous.
|
|
81
|
+
schemaName: req.schema_name ?? null,
|
|
82
|
+
catalogName,
|
|
59
83
|
};
|
|
60
84
|
}
|
|
61
85
|
|
|
@@ -30,6 +30,10 @@ const BIND_REQUEST_SCHEMA = schema([
|
|
|
30
30
|
// Both null when the scan has no AT clause. See BindRequest.at_unit.
|
|
31
31
|
field("at_unit", utf8(), true),
|
|
32
32
|
field("at_value", utf8(), true),
|
|
33
|
+
// Catalog schema owning the function. A worker may register one name in
|
|
34
|
+
// several schemas, so the bare name is not a unique key — resolution is by
|
|
35
|
+
// (schema_name, function_name). Null for callers with no catalog context.
|
|
36
|
+
field("schema_name", utf8(), true),
|
|
33
37
|
]);
|
|
34
38
|
|
|
35
39
|
// COPY ... FROM context — a nullable nested struct<format, file_path,
|
|
@@ -76,6 +80,7 @@ export function serializeBindRequest(req: BindRequest): VgiBatch {
|
|
|
76
80
|
resolved_secrets_provided: req.resolved_secrets_provided ?? false,
|
|
77
81
|
at_unit: req.at_unit ?? null,
|
|
78
82
|
at_value: req.at_value ?? null,
|
|
83
|
+
schema_name: req.schema_name ?? null,
|
|
79
84
|
};
|
|
80
85
|
// Append the copy_from / copy_to struct columns only for COPY scans/sinks, so
|
|
81
86
|
// non-COPY binds serialize to the exact legacy shape (fields matched by name
|
|
@@ -172,6 +177,8 @@ export function deserializeBindRequest(
|
|
|
172
177
|
// Empty string -> null (matches the C++ BuildBindRequest convention).
|
|
173
178
|
at_unit: params.at_unit ? String(params.at_unit) : null,
|
|
174
179
|
at_value: params.at_value ? String(params.at_value) : null,
|
|
180
|
+
// Empty string -> null, same convention as at_unit/at_value.
|
|
181
|
+
schema_name: params.schema_name ? String(params.schema_name) : null,
|
|
175
182
|
// COPY ... FROM context — absent for ordinary scans (params.copy_from
|
|
176
183
|
// undefined -> null). The struct column decodes to a plain object.
|
|
177
184
|
copy_from: parseCopyFromContext(params.copy_from),
|
package/src/protocol/types.ts
CHANGED
|
@@ -88,6 +88,14 @@ export interface BindRequest {
|
|
|
88
88
|
*/
|
|
89
89
|
at_unit?: string | null;
|
|
90
90
|
at_value?: string | null;
|
|
91
|
+
/**
|
|
92
|
+
* Catalog schema that declares the function being bound. A worker may
|
|
93
|
+
* register the same name in more than one schema, so the bare name does not
|
|
94
|
+
* identify an implementation — resolution is by `(schema_name,
|
|
95
|
+
* function_name)`. Null for callers with no catalog context (COPY handler
|
|
96
|
+
* binds). Mirrors vgi-python's `BindRequest.schema_name`.
|
|
97
|
+
*/
|
|
98
|
+
schema_name?: string | null;
|
|
91
99
|
}
|
|
92
100
|
|
|
93
101
|
export interface BindResponse {
|
package/src/util/arrow/index.ts
CHANGED
package/src/worker.ts
CHANGED
|
@@ -65,6 +65,14 @@ export interface WorkerConfig {
|
|
|
65
65
|
/** Factory that receives the built ReadOnlyCatalogInterface and returns a custom catalog. */
|
|
66
66
|
catalogInterfaceFactory?: (base: ReadOnlyCatalogInterface) => CatalogInterface;
|
|
67
67
|
catalogName?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Registry to dispatch through. Pass the same instance used to build a
|
|
70
|
+
* pre-constructed `catalogInterface` — catalogs record which schema (and
|
|
71
|
+
* catalog) declares each function on the registry they are given, and
|
|
72
|
+
* schema-qualified resolution only works if that is the registry the worker
|
|
73
|
+
* dispatches on. Omit to have the worker create its own.
|
|
74
|
+
*/
|
|
75
|
+
registry?: FunctionRegistry;
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
export class Worker {
|
|
@@ -73,7 +81,10 @@ export class Worker {
|
|
|
73
81
|
private _catalogName?: string;
|
|
74
82
|
|
|
75
83
|
constructor(config: WorkerConfig) {
|
|
76
|
-
|
|
84
|
+
// Reuse the caller's registry when given: a catalogInterface built
|
|
85
|
+
// elsewhere recorded its per-schema function ownership there, and that
|
|
86
|
+
// index is what makes a schema-qualified bind resolve correctly.
|
|
87
|
+
this._registry = config.registry ?? new FunctionRegistry();
|
|
77
88
|
this._catalogName = config.catalogName;
|
|
78
89
|
|
|
79
90
|
// Register explicit functions
|
|
@@ -89,6 +100,9 @@ export class Worker {
|
|
|
89
100
|
if (schema.functions) {
|
|
90
101
|
for (const func of schema.functions) {
|
|
91
102
|
this._registry.register(func);
|
|
103
|
+
// Also index by owning schema + catalog so a schema-qualified bind
|
|
104
|
+
// resolves when one name is declared in more than one schema.
|
|
105
|
+
this._registry.registerInSchema(func, schema.name, config.catalog.name);
|
|
92
106
|
}
|
|
93
107
|
}
|
|
94
108
|
}
|