@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,373 @@
|
|
|
1
|
+
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
|
+
// Table In-Out function implementation.
|
|
3
|
+
// Two-phase: INPUT phase receives and transforms batches,
|
|
4
|
+
// FINALIZE phase emits final results.
|
|
5
|
+
|
|
6
|
+
import { type VgiSchema, schema, type VgiField, type VgiDataType, type VgiBatch, nullType } from "../arrow/index.js";
|
|
7
|
+
import type { OutputCollector } from "@query-farm/vgi-rpc";
|
|
8
|
+
import { DEFAULT_MAX_WORKERS, TableInOutPhase } from "../types.js";
|
|
9
|
+
import type {
|
|
10
|
+
BindRequest,
|
|
11
|
+
BindResponse,
|
|
12
|
+
InitRequest,
|
|
13
|
+
GlobalInitResponse,
|
|
14
|
+
} from "../protocol/types.js";
|
|
15
|
+
import type {
|
|
16
|
+
VgiFunction,
|
|
17
|
+
FunctionMeta,
|
|
18
|
+
StreamHandlers,
|
|
19
|
+
FunctionExample,
|
|
20
|
+
} from "./types.js";
|
|
21
|
+
import type { ArgumentSpec } from "../arguments/argument-spec.js";
|
|
22
|
+
import { batchToScalarDict, batchToSecretDict, projectSchema, projectBatch, emptyBatch } from "../util/arrow/index.js";
|
|
23
|
+
import {
|
|
24
|
+
buildJoinKeysLookup,
|
|
25
|
+
deserializeFilters,
|
|
26
|
+
FilteringOutputCollector,
|
|
27
|
+
type PushdownFilters,
|
|
28
|
+
} from "../filter-pushdown/index.js";
|
|
29
|
+
import { FunctionStability } from "../types.js";
|
|
30
|
+
import { BoundStorage, storage as defaultStorage } from "./storage.js";
|
|
31
|
+
import { serializeUserState, deserializeUserState } from "../protocol/state-serializer.js";
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Table In-Out parameter bundles (reuse table function's)
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
export interface TableInOutBindParams<TArgs = Record<string, any>> {
|
|
38
|
+
args: TArgs;
|
|
39
|
+
bindCall: BindRequest;
|
|
40
|
+
settings: Record<string, any>;
|
|
41
|
+
secrets: Record<string, Record<string, any>>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface TableInOutProcessParams<TArgs = Record<string, any>> {
|
|
45
|
+
args: TArgs;
|
|
46
|
+
initCall: InitRequest;
|
|
47
|
+
initResponse: GlobalInitResponse;
|
|
48
|
+
outputSchema: VgiSchema;
|
|
49
|
+
settings: Record<string, any>;
|
|
50
|
+
secrets: Record<string, Record<string, any>>;
|
|
51
|
+
pushdownFilters?: PushdownFilters;
|
|
52
|
+
/** Shared storage for cross-phase and cross-worker data (SQLite-backed). */
|
|
53
|
+
storage: BoundStorage;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Functional API
|
|
58
|
+
// ============================================================================
|
|
59
|
+
|
|
60
|
+
export interface TableInOutConfig<
|
|
61
|
+
TArgs = Record<string, any>,
|
|
62
|
+
TState = null,
|
|
63
|
+
> {
|
|
64
|
+
name: string;
|
|
65
|
+
description?: string;
|
|
66
|
+
args?: Record<string, VgiDataType>;
|
|
67
|
+
/** Named arguments (optional, DuckDB passes by name) */
|
|
68
|
+
namedArgs?: Record<string, VgiDataType>;
|
|
69
|
+
/** Argument defaults */
|
|
70
|
+
argDefaults?: Record<string, any>;
|
|
71
|
+
/** Bind: default passes through input schema. May be async. */
|
|
72
|
+
onBind?: (params: TableInOutBindParams<TArgs>) =>
|
|
73
|
+
| { outputSchema: VgiSchema; opaqueData?: Uint8Array }
|
|
74
|
+
| Promise<{ outputSchema: VgiSchema; opaqueData?: Uint8Array }>;
|
|
75
|
+
onInit?: (params: {
|
|
76
|
+
args: TArgs;
|
|
77
|
+
initCall: InitRequest;
|
|
78
|
+
outputSchema: VgiSchema;
|
|
79
|
+
executionId: Uint8Array;
|
|
80
|
+
}) => GlobalInitResponse | Promise<GlobalInitResponse>;
|
|
81
|
+
initialState?: (params: TableInOutProcessParams<TArgs>) => TState;
|
|
82
|
+
/** Process: transform input batch, emit output via out */
|
|
83
|
+
process?: (
|
|
84
|
+
params: TableInOutProcessParams<TArgs>,
|
|
85
|
+
state: TState,
|
|
86
|
+
batch: VgiBatch,
|
|
87
|
+
out: OutputCollector
|
|
88
|
+
) => void | Promise<void>;
|
|
89
|
+
/** Finalize: emit final batches after all input processed.
|
|
90
|
+
* Receives all worker states collected from storage (matches Python's finish(params, states)). */
|
|
91
|
+
finalize?: (
|
|
92
|
+
params: TableInOutProcessParams<TArgs>,
|
|
93
|
+
states: TState[]
|
|
94
|
+
) => VgiBatch[] | Promise<VgiBatch[]>;
|
|
95
|
+
// Metadata
|
|
96
|
+
projectionPushdown?: boolean;
|
|
97
|
+
filterPushdown?: boolean;
|
|
98
|
+
autoApplyFilters?: boolean;
|
|
99
|
+
stability?: FunctionStability;
|
|
100
|
+
examples?: FunctionExample[];
|
|
101
|
+
categories?: string[];
|
|
102
|
+
tags?: Record<string, string>;
|
|
103
|
+
maxWorkers?: number;
|
|
104
|
+
requiredSettings?: string[];
|
|
105
|
+
requiredSecrets?: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function defineTableInOutFunction<
|
|
109
|
+
TArgs = Record<string, any>,
|
|
110
|
+
TState = null,
|
|
111
|
+
>(config: TableInOutConfig<TArgs, TState>): VgiFunction {
|
|
112
|
+
|
|
113
|
+
// Build argument specs
|
|
114
|
+
const specs: ArgumentSpec[] = [];
|
|
115
|
+
let posIdx = 0;
|
|
116
|
+
|
|
117
|
+
// Positional args come before the table input
|
|
118
|
+
if (config.args) {
|
|
119
|
+
for (const [name, type] of Object.entries(config.args)) {
|
|
120
|
+
specs.push({
|
|
121
|
+
name,
|
|
122
|
+
position: posIdx++,
|
|
123
|
+
arrowType: type,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Table-in-out always has a table input (after positional args)
|
|
129
|
+
specs.push({
|
|
130
|
+
name: "data",
|
|
131
|
+
position: posIdx++,
|
|
132
|
+
arrowType: nullType(),
|
|
133
|
+
isTableInput: true,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (config.namedArgs) {
|
|
137
|
+
for (const [name, type] of Object.entries(config.namedArgs)) {
|
|
138
|
+
specs.push({
|
|
139
|
+
name,
|
|
140
|
+
position: name, // string position = named arg
|
|
141
|
+
arrowType: type,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const meta: FunctionMeta = {
|
|
147
|
+
name: config.name,
|
|
148
|
+
description: config.description,
|
|
149
|
+
stability: config.stability,
|
|
150
|
+
projectionPushdown: config.projectionPushdown,
|
|
151
|
+
filterPushdown: config.filterPushdown,
|
|
152
|
+
autoApplyFilters: config.autoApplyFilters,
|
|
153
|
+
examples: config.examples,
|
|
154
|
+
categories: config.categories,
|
|
155
|
+
tags: config.tags,
|
|
156
|
+
maxWorkers: config.maxWorkers,
|
|
157
|
+
requiredSettings: config.requiredSettings,
|
|
158
|
+
requiredSecrets: config.requiredSecrets,
|
|
159
|
+
hasFinalize: !!config.finalize,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function extractArgs(request: BindRequest): TArgs {
|
|
163
|
+
const args: Record<string, any> = {};
|
|
164
|
+
for (const spec of specs) {
|
|
165
|
+
if (spec.isTableInput) continue;
|
|
166
|
+
const defaultVal =
|
|
167
|
+
config.argDefaults?.[spec.name] !== undefined
|
|
168
|
+
? config.argDefaults[spec.name]
|
|
169
|
+
: undefined;
|
|
170
|
+
// For named args, position is a string (the arg name)
|
|
171
|
+
let val = request.arguments.get(spec.position, defaultVal);
|
|
172
|
+
// Arrow Int64 values come through as BigInt — coerce to number
|
|
173
|
+
if (typeof val === "bigint") val = Number(val);
|
|
174
|
+
args[spec.name] = val;
|
|
175
|
+
}
|
|
176
|
+
return args as TArgs;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
kind: "table_in_out",
|
|
181
|
+
meta,
|
|
182
|
+
argumentSpecs: specs,
|
|
183
|
+
|
|
184
|
+
async bind(request: BindRequest): Promise<BindResponse> {
|
|
185
|
+
if (config.onBind) {
|
|
186
|
+
const args = extractArgs(request);
|
|
187
|
+
const settings = batchToScalarDict(request.settings);
|
|
188
|
+
const secrets = batchToSecretDict(request.secrets);
|
|
189
|
+
const result = await config.onBind({
|
|
190
|
+
args,
|
|
191
|
+
bindCall: request,
|
|
192
|
+
settings,
|
|
193
|
+
secrets,
|
|
194
|
+
});
|
|
195
|
+
return {
|
|
196
|
+
output_schema: result.outputSchema,
|
|
197
|
+
opaque_data: result.opaqueData ?? null,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Default: pass through input schema
|
|
201
|
+
return {
|
|
202
|
+
output_schema: request.input_schema ?? schema([]),
|
|
203
|
+
opaque_data: null,
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
async globalInit(request: InitRequest): Promise<GlobalInitResponse> {
|
|
208
|
+
const executionId = new Uint8Array(16);
|
|
209
|
+
crypto.getRandomValues(executionId);
|
|
210
|
+
|
|
211
|
+
if (request.execution_id) {
|
|
212
|
+
return {
|
|
213
|
+
max_workers: DEFAULT_MAX_WORKERS,
|
|
214
|
+
execution_id: request.execution_id,
|
|
215
|
+
opaque_data: null,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (config.onInit) {
|
|
220
|
+
const args = extractArgs(request.bind_call);
|
|
221
|
+
return await config.onInit({
|
|
222
|
+
args,
|
|
223
|
+
initCall: request,
|
|
224
|
+
outputSchema: request.output_schema,
|
|
225
|
+
executionId,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
max_workers: config.maxWorkers ?? 1,
|
|
231
|
+
execution_id: executionId,
|
|
232
|
+
opaque_data: null,
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
createStreamHandlers(
|
|
237
|
+
request: InitRequest,
|
|
238
|
+
response: GlobalInitResponse,
|
|
239
|
+
accumulatedState?: any,
|
|
240
|
+
): StreamHandlers {
|
|
241
|
+
const args = extractArgs(request.bind_call);
|
|
242
|
+
const settings = batchToScalarDict(request.bind_call.settings);
|
|
243
|
+
const secrets = batchToSecretDict(request.bind_call.secrets);
|
|
244
|
+
|
|
245
|
+
// Apply projection pushdown only if the function supports it
|
|
246
|
+
const projIds = request.projection_ids && meta.projectionPushdown
|
|
247
|
+
? request.projection_ids
|
|
248
|
+
: null;
|
|
249
|
+
const outputSchema = projIds
|
|
250
|
+
? projectSchema(projIds, request.output_schema)
|
|
251
|
+
: request.output_schema;
|
|
252
|
+
|
|
253
|
+
// Deserialize pushdown filters. Pass a join-keys column lookup so that
|
|
254
|
+
// filters DuckDB promoted to join_keys (IN/OR lists, etc.) are
|
|
255
|
+
// materialized as InFilters rather than silently dropped.
|
|
256
|
+
const joinKeysLookup = buildJoinKeysLookup(request.join_keys);
|
|
257
|
+
const pushdownFilters = request.pushdown_filters
|
|
258
|
+
? deserializeFilters(request.pushdown_filters, joinKeysLookup)
|
|
259
|
+
: undefined;
|
|
260
|
+
|
|
261
|
+
// Create BoundStorage for cross-phase/cross-worker data sharing
|
|
262
|
+
const boundStorage = new BoundStorage(
|
|
263
|
+
defaultStorage,
|
|
264
|
+
response.execution_id ?? new Uint8Array(16),
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const processParams: TableInOutProcessParams<TArgs> = {
|
|
268
|
+
args,
|
|
269
|
+
initCall: request,
|
|
270
|
+
initResponse: response,
|
|
271
|
+
outputSchema,
|
|
272
|
+
settings,
|
|
273
|
+
secrets,
|
|
274
|
+
pushdownFilters,
|
|
275
|
+
storage: boundStorage,
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const phase = request.phase;
|
|
279
|
+
|
|
280
|
+
if (phase === TableInOutPhase.FINALIZE) {
|
|
281
|
+
// FINALIZE phase: producer mode.
|
|
282
|
+
// The actual collect+finalize work is deferred into the first
|
|
283
|
+
// producerFn call so the (now-async) FunctionStorage can be awaited
|
|
284
|
+
// there. Storing the materialized batches on the per-call state.
|
|
285
|
+
return {
|
|
286
|
+
outputSchema,
|
|
287
|
+
producerInit: () => ({ state: { batchIdx: 0 }, batches: null as VgiBatch[] | null }),
|
|
288
|
+
producerFn: async (
|
|
289
|
+
pState: { state: { batchIdx: number }; batches: VgiBatch[] | null },
|
|
290
|
+
out: OutputCollector
|
|
291
|
+
) => {
|
|
292
|
+
if (pState.batches == null) {
|
|
293
|
+
const finalizeStates: TState[] = [];
|
|
294
|
+
if (accumulatedState != null) {
|
|
295
|
+
finalizeStates.push(accumulatedState as TState);
|
|
296
|
+
}
|
|
297
|
+
// Collect any states persisted by INPUT exchanges. Subprocess
|
|
298
|
+
// path reads from SQLite; HTTP path reads from CF DO / wherever
|
|
299
|
+
// the configured storage backend lives.
|
|
300
|
+
const stored = await boundStorage.collect();
|
|
301
|
+
for (const bytes of stored) {
|
|
302
|
+
finalizeStates.push(deserializeUserState(bytes) as TState);
|
|
303
|
+
}
|
|
304
|
+
if (finalizeStates.length === 0) {
|
|
305
|
+
finalizeStates.push(
|
|
306
|
+
config.initialState
|
|
307
|
+
? config.initialState(processParams)
|
|
308
|
+
: (null as TState),
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
pState.batches = config.finalize
|
|
312
|
+
? await config.finalize(processParams, finalizeStates)
|
|
313
|
+
: [];
|
|
314
|
+
}
|
|
315
|
+
if (pState.state.batchIdx >= pState.batches.length) {
|
|
316
|
+
out.finish();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
out.emit(pState.batches[pState.state.batchIdx]);
|
|
320
|
+
pState.state.batchIdx++;
|
|
321
|
+
if (pState.state.batchIdx >= pState.batches.length) {
|
|
322
|
+
out.finish();
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// INPUT phase: exchange mode
|
|
329
|
+
const state = config.initialState
|
|
330
|
+
? config.initialState(processParams)
|
|
331
|
+
: (null as TState);
|
|
332
|
+
|
|
333
|
+
const processFn =
|
|
334
|
+
config.process ??
|
|
335
|
+
((_params: any, _state: any, batch: VgiBatch, out: OutputCollector) => {
|
|
336
|
+
// Default passthrough. When DuckDB pushed a projection, the declared
|
|
337
|
+
// output schema is narrowed to `projIds`, so the emitted batch must be
|
|
338
|
+
// narrowed too — otherwise it carries columns (and their dictionaries)
|
|
339
|
+
// the stream schema doesn't declare, and the C++ reader rejects the
|
|
340
|
+
// batch (e.g. "No record of dictionary type with id 1" when the
|
|
341
|
+
// dropped columns were dictionary-encoded ENUMs).
|
|
342
|
+
out.emit(projIds ? projectBatch(projIds, batch) : batch);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
outputSchema,
|
|
347
|
+
inputSchema: request.bind_call.input_schema ?? undefined,
|
|
348
|
+
exchangeInit: () => ({ state, processParams }),
|
|
349
|
+
exchangeFn: async (
|
|
350
|
+
eState: {
|
|
351
|
+
state: TState;
|
|
352
|
+
processParams: TableInOutProcessParams<TArgs>;
|
|
353
|
+
},
|
|
354
|
+
input: VgiBatch,
|
|
355
|
+
out: OutputCollector
|
|
356
|
+
) => {
|
|
357
|
+
const wrappedOut = (config.autoApplyFilters && pushdownFilters)
|
|
358
|
+
? new FilteringOutputCollector(out, pushdownFilters) as unknown as OutputCollector
|
|
359
|
+
: out;
|
|
360
|
+
await processFn(eState.processParams, eState.state, input, wrappedOut);
|
|
361
|
+
|
|
362
|
+
// Auto-persist accumulated state for FINALIZE recovery (matches Python/Go pattern)
|
|
363
|
+
if (eState.state != null) {
|
|
364
|
+
const serialized = serializeUserState(eState.state);
|
|
365
|
+
if (serialized != null) {
|
|
366
|
+
await boundStorage.put(serialized);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|