@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,119 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Catalog view handlers: view_get/create/drop/rename/comment_set.
|
|
3
|
+
|
|
4
|
+
import { type VgiSchema, schema, type VgiField, field, type VgiDataType, binary, utf8 } from "../../../arrow/index.js";
|
|
5
|
+
import { Protocol } from "@query-farm/vgi-rpc";
|
|
6
|
+
import { encodeViewInfo } from "../../../generated/vgi-client.js";
|
|
7
|
+
import { CatalogViewGetResultSchema } from "../../../generated/vgi-protocol-schemas.js";
|
|
8
|
+
import { toUint8Array } from "../../../util/bytes.js";
|
|
9
|
+
import {
|
|
10
|
+
RESULT_BINARY_SCHEMA,
|
|
11
|
+
wrapResult,
|
|
12
|
+
} from "../shared.js";
|
|
13
|
+
import {
|
|
14
|
+
type GetCatalog,
|
|
15
|
+
emptyResultSchema,
|
|
16
|
+
attachOpaqueDataSchemaNameTxnParams,
|
|
17
|
+
schemaNameIgnoreNotFoundTxnParams,
|
|
18
|
+
schemaNameRenameParams,
|
|
19
|
+
schemaNameCommentParams,
|
|
20
|
+
catalogUnary,
|
|
21
|
+
} from "./shared.js";
|
|
22
|
+
|
|
23
|
+
export function registerCatalogViewMethods(protocol: Protocol, getCatalog: GetCatalog, signingKey?: Uint8Array): void {
|
|
24
|
+
// catalog_view_get
|
|
25
|
+
catalogUnary(protocol, signingKey, "catalog_view_get", {
|
|
26
|
+
params: attachOpaqueDataSchemaNameTxnParams,
|
|
27
|
+
result: RESULT_BINARY_SCHEMA,
|
|
28
|
+
handler: async (params) => {
|
|
29
|
+
const cat = getCatalog();
|
|
30
|
+
const info = await cat.viewGet(
|
|
31
|
+
toUint8Array(params.attach_opaque_data),
|
|
32
|
+
params.schema_name,
|
|
33
|
+
params.name,
|
|
34
|
+
params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
|
|
35
|
+
);
|
|
36
|
+
return wrapResult({
|
|
37
|
+
items: info ? [encodeViewInfo(info)] : [],
|
|
38
|
+
}, CatalogViewGetResultSchema);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// catalog_view_create
|
|
43
|
+
catalogUnary(protocol, signingKey, "catalog_view_create", {
|
|
44
|
+
params: schema([
|
|
45
|
+
field("attach_opaque_data", binary(), true),
|
|
46
|
+
field("schema_name", utf8(), false),
|
|
47
|
+
field("name", utf8(), false),
|
|
48
|
+
field("definition", utf8(), false),
|
|
49
|
+
field("on_conflict", utf8(), false),
|
|
50
|
+
field("transaction_opaque_data", binary(), true),
|
|
51
|
+
]),
|
|
52
|
+
result: emptyResultSchema,
|
|
53
|
+
handler: async (params) => {
|
|
54
|
+
const cat = getCatalog();
|
|
55
|
+
await cat.viewCreate(
|
|
56
|
+
toUint8Array(params.attach_opaque_data),
|
|
57
|
+
params.schema_name,
|
|
58
|
+
params.name,
|
|
59
|
+
params.definition,
|
|
60
|
+
params.on_conflict,
|
|
61
|
+
params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
|
|
62
|
+
);
|
|
63
|
+
return {};
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// catalog_view_drop
|
|
68
|
+
catalogUnary(protocol, signingKey, "catalog_view_drop", {
|
|
69
|
+
params: schemaNameIgnoreNotFoundTxnParams,
|
|
70
|
+
result: emptyResultSchema,
|
|
71
|
+
handler: async (params) => {
|
|
72
|
+
const cat = getCatalog();
|
|
73
|
+
await cat.viewDrop(
|
|
74
|
+
toUint8Array(params.attach_opaque_data),
|
|
75
|
+
params.schema_name,
|
|
76
|
+
params.name,
|
|
77
|
+
params.ignore_not_found,
|
|
78
|
+
params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
|
|
79
|
+
);
|
|
80
|
+
return {};
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// catalog_view_rename
|
|
85
|
+
catalogUnary(protocol, signingKey, "catalog_view_rename", {
|
|
86
|
+
params: schemaNameRenameParams,
|
|
87
|
+
result: emptyResultSchema,
|
|
88
|
+
handler: async (params) => {
|
|
89
|
+
const cat = getCatalog();
|
|
90
|
+
await cat.viewRename(
|
|
91
|
+
toUint8Array(params.attach_opaque_data),
|
|
92
|
+
params.schema_name,
|
|
93
|
+
params.name,
|
|
94
|
+
params.new_name,
|
|
95
|
+
params.ignore_not_found,
|
|
96
|
+
params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
|
|
97
|
+
);
|
|
98
|
+
return {};
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// catalog_view_comment_set
|
|
103
|
+
catalogUnary(protocol, signingKey, "catalog_view_comment_set", {
|
|
104
|
+
params: schemaNameCommentParams,
|
|
105
|
+
result: emptyResultSchema,
|
|
106
|
+
handler: async (params) => {
|
|
107
|
+
const cat = getCatalog();
|
|
108
|
+
await cat.viewCommentSet(
|
|
109
|
+
toUint8Array(params.attach_opaque_data),
|
|
110
|
+
params.schema_name,
|
|
111
|
+
params.name,
|
|
112
|
+
params.comment,
|
|
113
|
+
params.ignore_not_found,
|
|
114
|
+
params.transaction_opaque_data ? toUint8Array(params.transaction_opaque_data) : undefined
|
|
115
|
+
);
|
|
116
|
+
return {};
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Handlers for the per-function lifecycle: bind, init (exchange), cardinality,
|
|
3
|
+
// statistics. Generic over scalar/table/table-in-out — each function kind
|
|
4
|
+
// implements VgiFunction.bind/globalInit/createStreamHandlers.
|
|
5
|
+
|
|
6
|
+
import { type VgiSchema, schema, type VgiField, field, type VgiDataType, binary, int64 } from "../../arrow/index.js";
|
|
7
|
+
import { Protocol } from "@query-farm/vgi-rpc";
|
|
8
|
+
import type { FunctionRegistry } from "../../functions/registry.js";
|
|
9
|
+
import type { StreamHandlers, HandlerState } from "../../functions/types.js";
|
|
10
|
+
import {
|
|
11
|
+
deserializeBindRequest,
|
|
12
|
+
serializeBindResponse,
|
|
13
|
+
deserializeInitRequest,
|
|
14
|
+
serializeGlobalInitResponse,
|
|
15
|
+
deserializeCardinalityRequest,
|
|
16
|
+
serializeTableCardinality,
|
|
17
|
+
} from "../serialize.js";
|
|
18
|
+
import type { GlobalInitResponse } from "../types.js";
|
|
19
|
+
import { batchToScalarDict, deserializeBatch } from "../../util/arrow/index.js";
|
|
20
|
+
import { toUint8Array } from "../../util/bytes.js";
|
|
21
|
+
import { serializeColumnStatistics } from "../../util/statistics.js";
|
|
22
|
+
import { BindResultSchema, TableFunctionCardinalityResultSchema, TableFunctionDynamicToStringResultSchema } from "../../generated/vgi-protocol-schemas.js";
|
|
23
|
+
import {
|
|
24
|
+
REQUEST_PARAMS_SCHEMA,
|
|
25
|
+
RESULT_BINARY_SCHEMA,
|
|
26
|
+
RESULT_BINARY_NULLABLE_SCHEMA,
|
|
27
|
+
unwrapRequest,
|
|
28
|
+
wrapResult,
|
|
29
|
+
overloadContext,
|
|
30
|
+
recoverFinalizeState,
|
|
31
|
+
} from "./shared.js";
|
|
32
|
+
import { openAttach } from "./catalog/shared.js";
|
|
33
|
+
|
|
34
|
+
export interface FunctionHandlerConfig {
|
|
35
|
+
registry: FunctionRegistry;
|
|
36
|
+
recoverExchangeState?: (opaqueData: Uint8Array) => any;
|
|
37
|
+
signingKey?: Uint8Array;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function registerFunctionMethods(protocol: Protocol, config: FunctionHandlerConfig): void {
|
|
41
|
+
const { registry, signingKey } = config;
|
|
42
|
+
|
|
43
|
+
// The framework mints every attach as uuid(16) || catalog_bytes (sealed on
|
|
44
|
+
// HTTP, plaintext on subprocess). Function bodies — like catalog bodies —
|
|
45
|
+
// must see the catalog's own bytes, so unseal (when keyed) and strip the
|
|
46
|
+
// framework UUID prefix before the user function reads attach_opaque_data.
|
|
47
|
+
async function stripAttach(attach: any, ctx: any): Promise<Uint8Array | null> {
|
|
48
|
+
if (attach == null) return null;
|
|
49
|
+
const env = toUint8Array(attach);
|
|
50
|
+
if (env.length === 0) return env;
|
|
51
|
+
return openAttach(env, ctx?.auth, signingKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const initHeaderSchema = schema([
|
|
55
|
+
field("execution_id", binary(), false),
|
|
56
|
+
field("opaque_data", binary(), true),
|
|
57
|
+
field("max_workers", int64(), false),
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const emptySchema = schema([]);
|
|
61
|
+
|
|
62
|
+
// Dummy non-empty schema for the init exchange registration.
|
|
63
|
+
// The dispatch determines producer vs exchange based on inputSchema emptiness.
|
|
64
|
+
// We need exchange mode so that input batches are passed to our callback.
|
|
65
|
+
// The actual input schema comes from the IPC stream, not this registration.
|
|
66
|
+
const dummyInputSchema = schema([
|
|
67
|
+
field("_tick", binary(), true),
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
// --------------------------------------------------------------------------
|
|
71
|
+
// bind (unary)
|
|
72
|
+
// --------------------------------------------------------------------------
|
|
73
|
+
protocol.unary("bind", {
|
|
74
|
+
params: REQUEST_PARAMS_SCHEMA,
|
|
75
|
+
result: RESULT_BINARY_SCHEMA,
|
|
76
|
+
handler: async (params, ctx) => {
|
|
77
|
+
const innerParams = unwrapRequest(params.request);
|
|
78
|
+
const request = deserializeBindRequest(innerParams);
|
|
79
|
+
request.attach_opaque_data = await stripAttach(request.attach_opaque_data, ctx);
|
|
80
|
+
const func = registry.get(request.function_name, overloadContext(request));
|
|
81
|
+
const response = await func.bind(request);
|
|
82
|
+
const serialized = serializeBindResponse(response);
|
|
83
|
+
return wrapResult(serialized, BindResultSchema);
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// --------------------------------------------------------------------------
|
|
88
|
+
// init (streaming) - dynamically produces either producer or exchange streams
|
|
89
|
+
// --------------------------------------------------------------------------
|
|
90
|
+
protocol.exchange("init", {
|
|
91
|
+
params: REQUEST_PARAMS_SCHEMA,
|
|
92
|
+
inputSchema: dummyInputSchema,
|
|
93
|
+
outputSchema: emptySchema,
|
|
94
|
+
init: async (params) => {
|
|
95
|
+
// Preserve raw request IPC bytes for exchange reconstruction
|
|
96
|
+
const requestIpcBytes = toUint8Array(params.request);
|
|
97
|
+
const innerParams = unwrapRequest(params.request);
|
|
98
|
+
const request = deserializeInitRequest(innerParams);
|
|
99
|
+
// Exchange init has no ctx; on subprocess (no signing key) openAttach is a
|
|
100
|
+
// pass-through strip that needs no auth. HTTP carries auth via the bind path.
|
|
101
|
+
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));
|
|
103
|
+
|
|
104
|
+
// globalInit is async — table function onInit may touch HTTP-backed
|
|
105
|
+
// FunctionStorage (e.g. Cloudflare DO).
|
|
106
|
+
const initResponse = await func.globalInit(request);
|
|
107
|
+
|
|
108
|
+
// For FINALIZE over HTTP, recover accumulated INPUT state from init_opaque_data.
|
|
109
|
+
// The C++ extension passes the last exchange state token as init_opaque_data.
|
|
110
|
+
const accumulatedState = recoverFinalizeState(request, config.recoverExchangeState);
|
|
111
|
+
|
|
112
|
+
const handlers = func.createStreamHandlers(request, initResponse, accumulatedState);
|
|
113
|
+
|
|
114
|
+
// Initialize the appropriate handler
|
|
115
|
+
let handlerState: HandlerState | undefined;
|
|
116
|
+
if (handlers.producerInit) {
|
|
117
|
+
handlerState = handlers.producerInit();
|
|
118
|
+
} else if (handlers.exchangeInit) {
|
|
119
|
+
handlerState = handlers.exchangeInit();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const isProducer = !!handlers.producerFn;
|
|
123
|
+
|
|
124
|
+
// Extract mutable user state from handler (e.g. { remaining, currentIndex })
|
|
125
|
+
// for serialization across HTTP exchanges. The processParams/infrastructure
|
|
126
|
+
// is reconstructed fresh; only user state needs to persist.
|
|
127
|
+
const userState = handlerState?.state ?? null;
|
|
128
|
+
|
|
129
|
+
// Build state object with raw binary data (no base64/hex encoding).
|
|
130
|
+
// The Arrow state serializer picks fields by name; live objects
|
|
131
|
+
// (_handlers, _handlerState, _initResponse, __outputSchema) are ignored.
|
|
132
|
+
const state: any = {
|
|
133
|
+
functionName: request.bind_call.function_name,
|
|
134
|
+
initRequestIpc: requestIpcBytes,
|
|
135
|
+
executionId: initResponse.execution_id,
|
|
136
|
+
maxWorkers: Number(initResponse.max_workers),
|
|
137
|
+
opaqueData: initResponse.opaque_data ?? null,
|
|
138
|
+
isProducer,
|
|
139
|
+
userState,
|
|
140
|
+
__isProducer: isProducer,
|
|
141
|
+
// Live Schemas for vgi-rpc to read during init (not serializable).
|
|
142
|
+
// __inputSchema overrides dispatchStream's method.inputSchema per call
|
|
143
|
+
// — the TS worker registers `init` as exchange with the permissive
|
|
144
|
+
// `dummyInputSchema` sentinel; the real per-function input shape comes
|
|
145
|
+
// from the bound handlers here.
|
|
146
|
+
__outputSchema: handlers.outputSchema ?? emptySchema,
|
|
147
|
+
__inputSchema: handlers.inputSchema ?? emptySchema,
|
|
148
|
+
// Live objects for immediate use during init (producer mode).
|
|
149
|
+
_handlers: handlers,
|
|
150
|
+
_handlerState: handlerState,
|
|
151
|
+
_initResponse: initResponse,
|
|
152
|
+
};
|
|
153
|
+
return state;
|
|
154
|
+
},
|
|
155
|
+
exchange: async (state, input, out) => {
|
|
156
|
+
// Reconstruct live objects from serializable state.
|
|
157
|
+
// This handles both immediate use (producer during init, where _handlers
|
|
158
|
+
// is still present) and deserialized exchange (where we reconstruct).
|
|
159
|
+
let handlers: StreamHandlers;
|
|
160
|
+
let handlerState: HandlerState | undefined;
|
|
161
|
+
|
|
162
|
+
if (state._handlers) {
|
|
163
|
+
// Immediate use (same request, state still in memory)
|
|
164
|
+
handlers = state._handlers;
|
|
165
|
+
handlerState = state._handlerState;
|
|
166
|
+
} else {
|
|
167
|
+
// Deserialized from token — reconstruct from serializable refs.
|
|
168
|
+
// Infrastructure (processParams, BoundStorage) is recreated fresh.
|
|
169
|
+
// Mutable user state is merged from state.userState.
|
|
170
|
+
const initRequestBatch = deserializeBatch(state.initRequestIpc);
|
|
171
|
+
const initRequestDict = batchToScalarDict(initRequestBatch);
|
|
172
|
+
const request = deserializeInitRequest(initRequestDict);
|
|
173
|
+
const func = registry.get(state.functionName, overloadContext(request.bind_call));
|
|
174
|
+
const executionId = state.executionId;
|
|
175
|
+
const opaqueData = state.opaqueData ?? null;
|
|
176
|
+
const initResponse: GlobalInitResponse = {
|
|
177
|
+
execution_id: executionId,
|
|
178
|
+
max_workers: Number(state.maxWorkers ?? 1),
|
|
179
|
+
opaque_data: opaqueData,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Recover accumulated state for FINALIZE phase from initOpaqueData
|
|
183
|
+
const recoveredState = recoverFinalizeState(request, config.recoverExchangeState);
|
|
184
|
+
|
|
185
|
+
handlers = func.createStreamHandlers(request, initResponse, recoveredState);
|
|
186
|
+
if (handlers.producerInit) {
|
|
187
|
+
handlerState = handlers.producerInit();
|
|
188
|
+
} else if (handlers.exchangeInit) {
|
|
189
|
+
handlerState = handlers.exchangeInit();
|
|
190
|
+
}
|
|
191
|
+
// Merge preserved user state (e.g. { remaining, currentIndex })
|
|
192
|
+
if (state.userState != null && handlerState?.state !== undefined) {
|
|
193
|
+
handlerState!.state = state.userState;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (state.isProducer && handlers.producerFn) {
|
|
198
|
+
// Producer mode inside an exchange dispatch: patch finish() to bypass
|
|
199
|
+
// OutputCollector's producerMode check (since dispatch created it
|
|
200
|
+
// in exchange mode but we're actually producing).
|
|
201
|
+
out.finish = () => { (out as any)._finished = true; };
|
|
202
|
+
// Let the handler observe tick-batch metadata before producing. Used
|
|
203
|
+
// by table functions to apply dynamic-filter updates DuckDB attaches
|
|
204
|
+
// as `vgi_pushdown_filters` on each tick (Top-N heap tightening).
|
|
205
|
+
if (handlers.onTick) {
|
|
206
|
+
await handlers.onTick(handlerState, (input as any)?.metadata);
|
|
207
|
+
}
|
|
208
|
+
await handlers.producerFn(handlerState, out);
|
|
209
|
+
} else if (handlers.exchangeFn) {
|
|
210
|
+
await handlers.exchangeFn(handlerState, input, out);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Save mutated user state for the next exchange round
|
|
214
|
+
if (handlerState?.state !== undefined) {
|
|
215
|
+
state.userState = handlerState!.state;
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
headerSchema: initHeaderSchema,
|
|
219
|
+
headerInit: (params: any, state: any, ctx: any) => {
|
|
220
|
+
// During init, _initResponse is still in memory.
|
|
221
|
+
// For exchange, this is never called (headers are only in init response).
|
|
222
|
+
if (!state._initResponse) {
|
|
223
|
+
throw new Error("headerInit called on deserialized state: _initResponse not available");
|
|
224
|
+
}
|
|
225
|
+
return serializeGlobalInitResponse(state._initResponse);
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// --------------------------------------------------------------------------
|
|
230
|
+
// table_function_cardinality (unary)
|
|
231
|
+
// --------------------------------------------------------------------------
|
|
232
|
+
protocol.unary("table_function_cardinality", {
|
|
233
|
+
params: REQUEST_PARAMS_SCHEMA,
|
|
234
|
+
result: RESULT_BINARY_SCHEMA,
|
|
235
|
+
handler: async (params) => {
|
|
236
|
+
const innerParams = unwrapRequest(params.request);
|
|
237
|
+
const request = deserializeCardinalityRequest(innerParams);
|
|
238
|
+
const func = registry.get(request.bind_call.function_name, overloadContext(request.bind_call));
|
|
239
|
+
let cardResult: Record<string, any>;
|
|
240
|
+
if (func.cardinality) {
|
|
241
|
+
cardResult = serializeTableCardinality(await func.cardinality(request));
|
|
242
|
+
} else {
|
|
243
|
+
cardResult = { estimate: null, max: null };
|
|
244
|
+
}
|
|
245
|
+
return wrapResult(cardResult, TableFunctionCardinalityResultSchema);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// --------------------------------------------------------------------------
|
|
250
|
+
// table_function_statistics (unary)
|
|
251
|
+
// --------------------------------------------------------------------------
|
|
252
|
+
// Returns bytes-or-null result: serialized ColumnStatistics RecordBatch when
|
|
253
|
+
// the function declared a statistics() hook and it produced a non-empty
|
|
254
|
+
// list, else null. DuckDB uses the bounds for plan-time filter elimination
|
|
255
|
+
// (folds impossible filters to EMPTY_RESULT).
|
|
256
|
+
protocol.unary("table_function_statistics", {
|
|
257
|
+
params: REQUEST_PARAMS_SCHEMA,
|
|
258
|
+
result: RESULT_BINARY_NULLABLE_SCHEMA,
|
|
259
|
+
handler: (params) => {
|
|
260
|
+
const innerParams = unwrapRequest(params.request);
|
|
261
|
+
const request = deserializeCardinalityRequest(innerParams);
|
|
262
|
+
const func = registry.get(request.bind_call.function_name, overloadContext(request.bind_call));
|
|
263
|
+
if (!func.statistics) return { result: null };
|
|
264
|
+
const stats = func.statistics(request);
|
|
265
|
+
if (!stats || stats.length === 0) return { result: null };
|
|
266
|
+
return { result: serializeColumnStatistics(stats) };
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// --------------------------------------------------------------------------
|
|
271
|
+
// table_function_dynamic_to_string (unary)
|
|
272
|
+
// --------------------------------------------------------------------------
|
|
273
|
+
// DuckDB calls this once per parallel scan thread at FinishSource. The
|
|
274
|
+
// result is a List<Utf8>/List<Utf8> pair carrying ordered key→value
|
|
275
|
+
// diagnostics that surface under EXPLAIN ANALYZE alongside the
|
|
276
|
+
// intrinsics (Function, Rows Read, Threads). When the function doesn't
|
|
277
|
+
// declare dynamicToString, return empty maps so the C++ side falls back
|
|
278
|
+
// to intrinsics only.
|
|
279
|
+
protocol.unary("table_function_dynamic_to_string", {
|
|
280
|
+
params: REQUEST_PARAMS_SCHEMA,
|
|
281
|
+
result: RESULT_BINARY_SCHEMA,
|
|
282
|
+
handler: async (params) => {
|
|
283
|
+
const innerParams = unwrapRequest(params.request);
|
|
284
|
+
const bindCallBytes = toUint8Array(innerParams.bind_call);
|
|
285
|
+
const bindCallBatch = deserializeBatch(bindCallBytes);
|
|
286
|
+
// Single-row bind_call batch -> dict via the codec/canonical path.
|
|
287
|
+
const bindParams = batchToScalarDict(bindCallBatch);
|
|
288
|
+
const bindCall = deserializeBindRequest(bindParams);
|
|
289
|
+
const globalExecutionId = toUint8Array(innerParams.global_execution_id);
|
|
290
|
+
const bindOpaqueData = innerParams.bind_opaque_data
|
|
291
|
+
? toUint8Array(innerParams.bind_opaque_data)
|
|
292
|
+
: null;
|
|
293
|
+
const func = registry.get(bindCall.function_name, overloadContext(bindCall));
|
|
294
|
+
const map = func.dynamicToString
|
|
295
|
+
? await func.dynamicToString({ bindCall, bindOpaqueData, globalExecutionId })
|
|
296
|
+
: {};
|
|
297
|
+
const keys = Object.keys(map);
|
|
298
|
+
const values = keys.map((k) => map[k] ?? "");
|
|
299
|
+
return wrapResult({ keys, values }, TableFunctionDynamicToStringResultSchema);
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Shared schemas and helpers used by every protocol handler.
|
|
3
|
+
|
|
4
|
+
import { type VgiSchema, schema, type VgiField, field, type VgiDataType, binary } from "../../arrow/index.js";
|
|
5
|
+
import type { OverloadContext } from "../../functions/registry.js";
|
|
6
|
+
import { batchToScalarDict, deserializeBatch, serializeBatch, batchFromColumns } from "../../util/arrow/index.js";
|
|
7
|
+
import { toUint8Array } from "../../util/bytes.js";
|
|
8
|
+
import { TableInOutPhase } from "../../types.js";
|
|
9
|
+
import type { InitRequest } from "../types.js";
|
|
10
|
+
|
|
11
|
+
// The Python vgi-rpc framework wraps ALL non-void unary results in a single
|
|
12
|
+
// "result" column. For ArrowSerializableDataclass types, the result is serialized
|
|
13
|
+
// as Arrow IPC bytes in a Binary column. DuckDB's VGI extension expects this format.
|
|
14
|
+
export const RESULT_BINARY_SCHEMA = schema([
|
|
15
|
+
field("result", binary(), false),
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
// DuckDB wraps ArrowSerializableDataclass parameters in a single "request" Binary column.
|
|
19
|
+
export const REQUEST_PARAMS_SCHEMA = schema([
|
|
20
|
+
field("request", binary(), false),
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
export const RESULT_BINARY_NULLABLE_SCHEMA = schema([
|
|
24
|
+
field("result", binary(), true),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Unwrap a "request" Binary column: deserialize the inner Arrow IPC batch
|
|
29
|
+
* and return flat columns as a dict (row 0).
|
|
30
|
+
*/
|
|
31
|
+
export function unwrapRequest(requestBytes: any): Record<string, any> {
|
|
32
|
+
const bytes = toUint8Array(requestBytes);
|
|
33
|
+
const innerBatch = deserializeBatch(bytes);
|
|
34
|
+
return batchToScalarDict(innerBatch);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Wrap a dict of values into a single "result" Binary column.
|
|
39
|
+
* Builds a 1-row batch from the values using the given schema,
|
|
40
|
+
* serializes it to IPC bytes, and returns { result: bytes }.
|
|
41
|
+
*/
|
|
42
|
+
export function wrapResult(
|
|
43
|
+
values: Record<string, any>,
|
|
44
|
+
innerSchema: VgiSchema | import("../../arrow/index.js").VgiSchema,
|
|
45
|
+
): { result: Uint8Array } {
|
|
46
|
+
const a = innerSchema as VgiSchema;
|
|
47
|
+
const batch = batchFromColumns(
|
|
48
|
+
Object.fromEntries(a.fields.map(f => [f.name, [values[f.name] ?? null]])),
|
|
49
|
+
a,
|
|
50
|
+
);
|
|
51
|
+
return { result: serializeBatch(batch) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function overloadContext(req: { function_name: string; arguments: any; input_schema: any; function_type: any }): OverloadContext {
|
|
55
|
+
return {
|
|
56
|
+
arguments: req.arguments,
|
|
57
|
+
inputSchema: req.input_schema,
|
|
58
|
+
isScalar: String(req.function_type).toLowerCase() === "scalar",
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Recover accumulated exchange state from a FINALIZE init request.
|
|
64
|
+
* For HTTP transport, this unpacks the state token that the C++ extension
|
|
65
|
+
* passes from the last INPUT exchange to the FINALIZE init request.
|
|
66
|
+
*/
|
|
67
|
+
export function recoverFinalizeState(
|
|
68
|
+
request: InitRequest,
|
|
69
|
+
recoverExchangeState: ((opaqueData: Uint8Array) => any) | undefined,
|
|
70
|
+
): any {
|
|
71
|
+
if (request.phase === TableInOutPhase.FINALIZE && request.init_opaque_data && recoverExchangeState) {
|
|
72
|
+
try {
|
|
73
|
+
const recovered = recoverExchangeState(request.init_opaque_data);
|
|
74
|
+
return recovered?.userState;
|
|
75
|
+
} catch (e: any) {
|
|
76
|
+
throw new Error(`Failed to recover FINALIZE state from init_opaque_data: ${e.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|