@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.
Files changed (206) hide show
  1. package/LICENSE +134 -0
  2. package/README.md +494 -0
  3. package/dist/__dbg.test-d.d.ts +1 -0
  4. package/dist/arguments/argument-spec.d.ts +51 -0
  5. package/dist/arguments/arguments.d.ts +13 -0
  6. package/dist/arrow/codec/branded.d.ts +83 -0
  7. package/dist/arrow/codec/registry.d.ts +29 -0
  8. package/dist/arrow/codec/repr.d.ts +23 -0
  9. package/dist/arrow/codec/type-descriptors.d.ts +109 -0
  10. package/dist/arrow/impl-arrowjs/build.d.ts +19 -0
  11. package/dist/arrow/impl-arrowjs/canonical.d.ts +15 -0
  12. package/dist/arrow/impl-arrowjs/empty.d.ts +7 -0
  13. package/dist/arrow/impl-arrowjs/filter.d.ts +11 -0
  14. package/dist/arrow/impl-arrowjs/index.d.ts +11 -0
  15. package/dist/arrow/impl-arrowjs/ipc.d.ts +24 -0
  16. package/dist/arrow/impl-arrowjs/iterate.d.ts +39 -0
  17. package/dist/arrow/impl-arrowjs/project.d.ts +10 -0
  18. package/dist/arrow/impl-arrowjs/schema.d.ts +62 -0
  19. package/dist/arrow/impl-arrowjs/statistics.d.ts +13 -0
  20. package/dist/arrow/impl-flechette/build.d.ts +14 -0
  21. package/dist/arrow/impl-flechette/canonical.d.ts +18 -0
  22. package/dist/arrow/impl-flechette/empty.d.ts +9 -0
  23. package/dist/arrow/impl-flechette/filter.d.ts +10 -0
  24. package/dist/arrow/impl-flechette/index.d.ts +11 -0
  25. package/dist/arrow/impl-flechette/ipc.d.ts +33 -0
  26. package/dist/arrow/impl-flechette/iterate.d.ts +29 -0
  27. package/dist/arrow/impl-flechette/message-meta.d.ts +10 -0
  28. package/dist/arrow/impl-flechette/normalize-type.d.ts +11 -0
  29. package/dist/arrow/impl-flechette/project.d.ts +3 -0
  30. package/dist/arrow/impl-flechette/schema.d.ts +47 -0
  31. package/dist/arrow/impl-flechette/statistics.d.ts +13 -0
  32. package/dist/arrow/index.d.ts +11 -0
  33. package/dist/arrow/predicates.d.ts +47 -0
  34. package/dist/arrow/repr.test-d.d.ts +52 -0
  35. package/dist/arrow/schema-types.d.ts +71 -0
  36. package/dist/arrow/types.d.ts +57 -0
  37. package/dist/catalog/attach-option.d.ts +33 -0
  38. package/dist/catalog/attach-options.d.ts +30 -0
  39. package/dist/catalog/composite.d.ts +33 -0
  40. package/dist/catalog/descriptors.d.ts +154 -0
  41. package/dist/catalog/interface.d.ts +239 -0
  42. package/dist/catalog/read-only.d.ts +33 -0
  43. package/dist/client/client.d.ts +189 -0
  44. package/dist/client/errors.d.ts +26 -0
  45. package/dist/client/helpers.d.ts +12 -0
  46. package/dist/client/protocol.d.ts +16 -0
  47. package/dist/client/types.d.ts +125 -0
  48. package/dist/client-entry.d.ts +11 -0
  49. package/dist/client-entry.js +48 -0
  50. package/dist/client-entry.js.map +9 -0
  51. package/dist/codec/asd.d.ts +25 -0
  52. package/dist/crypto.d.ts +36 -0
  53. package/dist/errors.d.ts +24 -0
  54. package/dist/filter-pushdown/collector.d.ts +32 -0
  55. package/dist/filter-pushdown/deserialize.d.ts +16 -0
  56. package/dist/filter-pushdown/evaluate.d.ts +51 -0
  57. package/dist/filter-pushdown/index.d.ts +4 -0
  58. package/dist/filter-pushdown/types.d.ts +84 -0
  59. package/dist/functions/aggregate.d.ts +129 -0
  60. package/dist/functions/copy-from.d.ts +81 -0
  61. package/dist/functions/copy-to.d.ts +121 -0
  62. package/dist/functions/registry.d.ts +16 -0
  63. package/dist/functions/scalar.d.ts +98 -0
  64. package/dist/functions/storage-cf-do.d.ts +47 -0
  65. package/dist/functions/storage.d.ts +86 -0
  66. package/dist/functions/table-buffering.d.ts +99 -0
  67. package/dist/functions/table-in-out.d.ts +64 -0
  68. package/dist/functions/table.d.ts +118 -0
  69. package/dist/functions/types.d.ts +157 -0
  70. package/dist/generated/vgi-client.d.ts +466 -0
  71. package/dist/generated/vgi-protocol-schemas.d.ts +115 -0
  72. package/dist/index.core.d.ts +42 -0
  73. package/dist/index.d.ts +2 -0
  74. package/dist/index.js +10888 -0
  75. package/dist/index.js.map +85 -0
  76. package/dist/metadata/resolve.d.ts +3 -0
  77. package/dist/metadata/serialize.d.ts +12 -0
  78. package/dist/metadata/types.d.ts +47 -0
  79. package/dist/protocol/dispatch.d.ts +23 -0
  80. package/dist/protocol/handlers/aggregate.d.ts +3 -0
  81. package/dist/protocol/handlers/catalog/admin.d.ts +3 -0
  82. package/dist/protocol/handlers/catalog/index.d.ts +3 -0
  83. package/dist/protocol/handlers/catalog/index_methods.d.ts +3 -0
  84. package/dist/protocol/handlers/catalog/macro.d.ts +3 -0
  85. package/dist/protocol/handlers/catalog/shared.d.ts +70 -0
  86. package/dist/protocol/handlers/catalog/table.d.ts +3 -0
  87. package/dist/protocol/handlers/catalog/view.d.ts +3 -0
  88. package/dist/protocol/handlers/function.d.ts +8 -0
  89. package/dist/protocol/handlers/shared.d.ts +31 -0
  90. package/dist/protocol/handlers/table-buffering.d.ts +3 -0
  91. package/dist/protocol/serialize.d.ts +4 -0
  92. package/dist/protocol/serializers/arguments.d.ts +3 -0
  93. package/dist/protocol/serializers/bind.d.ts +9 -0
  94. package/dist/protocol/serializers/cardinality.d.ts +7 -0
  95. package/dist/protocol/serializers/init.d.ts +9 -0
  96. package/dist/protocol/serializers/shared.d.ts +12 -0
  97. package/dist/protocol/state-serializer.d.ts +23 -0
  98. package/dist/protocol/types.d.ts +129 -0
  99. package/dist/secrets/helpers.d.ts +15 -0
  100. package/dist/types.d.ts +52 -0
  101. package/dist/util/arrow/index.d.ts +1 -0
  102. package/dist/util/bytes.d.ts +2 -0
  103. package/dist/util/statistics.d.ts +7 -0
  104. package/dist/worker-cf-entry.d.ts +30 -0
  105. package/dist/worker-cf-entry.js +8767 -0
  106. package/dist/worker-cf-entry.js.map +74 -0
  107. package/dist/worker.d.ts +36 -0
  108. package/package.json +96 -0
  109. package/src/arguments/argument-spec.ts +200 -0
  110. package/src/arguments/arguments.ts +81 -0
  111. package/src/arrow/codec/branded.ts +206 -0
  112. package/src/arrow/codec/registry.ts +674 -0
  113. package/src/arrow/codec/repr.ts +181 -0
  114. package/src/arrow/codec/type-descriptors.ts +137 -0
  115. package/src/arrow/impl-arrowjs/build.ts +88 -0
  116. package/src/arrow/impl-arrowjs/canonical.ts +509 -0
  117. package/src/arrow/impl-arrowjs/empty.ts +72 -0
  118. package/src/arrow/impl-arrowjs/filter.ts +53 -0
  119. package/src/arrow/impl-arrowjs/index.ts +54 -0
  120. package/src/arrow/impl-arrowjs/ipc.ts +67 -0
  121. package/src/arrow/impl-arrowjs/iterate.ts +159 -0
  122. package/src/arrow/impl-arrowjs/project.ts +73 -0
  123. package/src/arrow/impl-arrowjs/schema.ts +256 -0
  124. package/src/arrow/impl-arrowjs/statistics.ts +185 -0
  125. package/src/arrow/impl-flechette/build.ts +65 -0
  126. package/src/arrow/impl-flechette/canonical.ts +289 -0
  127. package/src/arrow/impl-flechette/empty.ts +20 -0
  128. package/src/arrow/impl-flechette/filter.ts +42 -0
  129. package/src/arrow/impl-flechette/index.ts +54 -0
  130. package/src/arrow/impl-flechette/ipc.ts +110 -0
  131. package/src/arrow/impl-flechette/iterate.ts +121 -0
  132. package/src/arrow/impl-flechette/message-meta.ts +175 -0
  133. package/src/arrow/impl-flechette/normalize-type.ts +99 -0
  134. package/src/arrow/impl-flechette/project.ts +64 -0
  135. package/src/arrow/impl-flechette/schema.ts +202 -0
  136. package/src/arrow/impl-flechette/statistics.ts +198 -0
  137. package/src/arrow/index.ts +129 -0
  138. package/src/arrow/predicates.ts +66 -0
  139. package/src/arrow/repr.test-d.ts +189 -0
  140. package/src/arrow/schema-types.ts +213 -0
  141. package/src/arrow/types.ts +74 -0
  142. package/src/catalog/attach-option.ts +107 -0
  143. package/src/catalog/attach-options.ts +79 -0
  144. package/src/catalog/composite.ts +180 -0
  145. package/src/catalog/descriptors.ts +164 -0
  146. package/src/catalog/interface.ts +721 -0
  147. package/src/catalog/read-only.ts +909 -0
  148. package/src/client/client.ts +1348 -0
  149. package/src/client/errors.ts +69 -0
  150. package/src/client/helpers.ts +56 -0
  151. package/src/client/protocol.ts +33 -0
  152. package/src/client/types.ts +140 -0
  153. package/src/client-entry.ts +112 -0
  154. package/src/codec/asd.ts +147 -0
  155. package/src/crypto.ts +143 -0
  156. package/src/errors.ts +61 -0
  157. package/src/filter-pushdown/collector.ts +147 -0
  158. package/src/filter-pushdown/deserialize.ts +253 -0
  159. package/src/filter-pushdown/evaluate.ts +591 -0
  160. package/src/filter-pushdown/index.ts +28 -0
  161. package/src/filter-pushdown/types.ts +91 -0
  162. package/src/functions/aggregate.ts +384 -0
  163. package/src/functions/copy-from.ts +282 -0
  164. package/src/functions/copy-to.ts +319 -0
  165. package/src/functions/registry.ts +258 -0
  166. package/src/functions/scalar.ts +356 -0
  167. package/src/functions/storage-cf-do.ts +303 -0
  168. package/src/functions/storage.ts +521 -0
  169. package/src/functions/table-buffering.ts +562 -0
  170. package/src/functions/table-in-out.ts +373 -0
  171. package/src/functions/table.ts +521 -0
  172. package/src/functions/types.ts +192 -0
  173. package/src/generated/vgi-client.ts +560 -0
  174. package/src/generated/vgi-protocol-schemas.ts +864 -0
  175. package/src/index.core.ts +267 -0
  176. package/src/index.ts +11 -0
  177. package/src/metadata/resolve.ts +109 -0
  178. package/src/metadata/serialize.ts +242 -0
  179. package/src/metadata/types.ts +60 -0
  180. package/src/protocol/dispatch.ts +52 -0
  181. package/src/protocol/handlers/aggregate.ts +355 -0
  182. package/src/protocol/handlers/catalog/admin.ts +368 -0
  183. package/src/protocol/handlers/catalog/index.ts +25 -0
  184. package/src/protocol/handlers/catalog/index_methods.ts +65 -0
  185. package/src/protocol/handlers/catalog/macro.ts +114 -0
  186. package/src/protocol/handlers/catalog/shared.ts +291 -0
  187. package/src/protocol/handlers/catalog/table.ts +401 -0
  188. package/src/protocol/handlers/catalog/view.ts +119 -0
  189. package/src/protocol/handlers/function.ts +302 -0
  190. package/src/protocol/handlers/shared.ts +80 -0
  191. package/src/protocol/handlers/table-buffering.ts +207 -0
  192. package/src/protocol/serialize.ts +23 -0
  193. package/src/protocol/serializers/arguments.ts +160 -0
  194. package/src/protocol/serializers/bind.ts +212 -0
  195. package/src/protocol/serializers/cardinality.ts +45 -0
  196. package/src/protocol/serializers/init.ts +247 -0
  197. package/src/protocol/serializers/shared.ts +46 -0
  198. package/src/protocol/state-serializer.ts +181 -0
  199. package/src/protocol/types.ts +153 -0
  200. package/src/secrets/helpers.ts +70 -0
  201. package/src/types.ts +71 -0
  202. package/src/util/arrow/index.ts +23 -0
  203. package/src/util/bytes.ts +14 -0
  204. package/src/util/statistics.ts +40 -0
  205. package/src/worker-cf-entry.ts +172 -0
  206. package/src/worker.ts +212 -0
@@ -0,0 +1,1348 @@
1
+ // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
+ // VgiClient — high-level client for calling VGI worker functions and catalog API.
3
+ // Works with any RpcClient (subprocess or HTTP transport).
4
+
5
+ import { type VgiSchema, schema as schema_, type VgiField, field, type VgiBatch, type VgiDataType, utf8, binary, list } from "../arrow/index.js";
6
+ import { type RpcClient, type StreamSession } from "@query-farm/vgi-rpc";
7
+ import {
8
+ serializeBindRequest,
9
+ deserializeBindResponse,
10
+ serializeInitRequest,
11
+ deserializeGlobalInitResponse,
12
+ } from "../protocol/serialize.js";
13
+ import type {
14
+ BindRequest,
15
+ BindResponse,
16
+ InitRequest,
17
+ GlobalInitResponse,
18
+ } from "../protocol/types.js";
19
+ import { FunctionType, TableInOutPhase } from "../types.js";
20
+ import { Arguments } from "../arguments/arguments.js";
21
+ import {
22
+ batchFromRows,
23
+ batchFromColumns,
24
+ iterRows,
25
+ } from "../util/arrow/index.js";
26
+ import {
27
+ type SchemaInfo,
28
+ decodeSchemaInfo,
29
+ type TableInfo,
30
+ decodeTableInfo,
31
+ type ViewInfo,
32
+ decodeViewInfo,
33
+ type FunctionInfo,
34
+ decodeFunctionInfo,
35
+ type MacroInfo,
36
+ decodeMacroInfo,
37
+ type MacroType,
38
+ type CatalogAttachResult,
39
+ type AttachOpaqueData,
40
+ type TransactionOpaqueData,
41
+ decodeCatalogInfo,
42
+ type CatalogInfo,
43
+ type ScanFunctionResult,
44
+ decodeScanFunctionResult,
45
+ } from "../catalog/interface.js";
46
+ import { wrapRequest, unwrapResult } from "./protocol.js";
47
+ import { toUint8Array } from "../util/bytes.js";
48
+ import { serializeAttachOptions } from "../catalog/attach-options.js";
49
+ import { VgiClientError, wrapRpcWithErrorEnrichment } from "./errors.js";
50
+ import { deserializeInfoList, deserializeTags, toAsyncIterator } from "./helpers.js";
51
+
52
+ export { VgiClientError };
53
+ import type {
54
+ VgiClientOptions,
55
+ TableFunctionOptions,
56
+ ScalarFunctionOptions,
57
+ TableInOutFunctionOptions,
58
+ OnCreateConflict,
59
+ CatalogFunctionType,
60
+ CatalogMacroType,
61
+ CatalogAttachOptions,
62
+ OrderByPushdown,
63
+ TablesamplePushdown,
64
+ AttachOptionValue,
65
+ } from "./types.js";
66
+
67
+ /**
68
+ * High-level client for calling VGI worker functions and catalog API.
69
+ *
70
+ * Works with any RpcClient transport (subprocess or HTTP):
71
+ * ```ts
72
+ * import { subprocessConnect } from "@query-farm/vgi-rpc";
73
+ * import { VgiClient, Arguments } from "vgi";
74
+ *
75
+ * const rpc = subprocessConnect(["./my-worker"]);
76
+ * const client = new VgiClient(rpc);
77
+ *
78
+ * for await (const rows of client.tableFunctionRows({
79
+ * functionName: "sequence",
80
+ * arguments: new Arguments([10]),
81
+ * })) {
82
+ * console.log(rows);
83
+ * }
84
+ * client.close();
85
+ * ```
86
+ */
87
+ export class VgiClient {
88
+ private readonly rpc: RpcClient;
89
+ private readonly defaultAttachOpaqueData: Uint8Array | null;
90
+
91
+ /**
92
+ * Construct a VgiClient wrapping an `RpcClient` transport.
93
+ *
94
+ * `options.attachOpaqueData` sets a client-wide default attach ID used by every
95
+ * function call that doesn't supply its own. Per-call `attachOpaqueData` on
96
+ * `TableFunctionOptions`/`ScalarFunctionOptions`/`TableInOutFunctionOptions`
97
+ * takes precedence — useful when a single client talks to multiple
98
+ * attached catalogs.
99
+ */
100
+ constructor(rpc: RpcClient, options?: VgiClientOptions) {
101
+ this.rpc = wrapRpcWithErrorEnrichment(rpc);
102
+ this.defaultAttachOpaqueData = options?.attachOpaqueData ?? null;
103
+ }
104
+
105
+ // ==========================================================================
106
+ // Private helpers
107
+ // ==========================================================================
108
+
109
+ private async _doBind(
110
+ functionName: string,
111
+ functionType: FunctionType,
112
+ args: Arguments,
113
+ inputSchema: VgiSchema | null,
114
+ settings: VgiBatch | null,
115
+ secrets: VgiBatch | null,
116
+ transactionOpaqueData: Uint8Array | null,
117
+ attachOpaqueData: Uint8Array | null = null,
118
+ onBind?: ((response: BindResponse) => void) | null,
119
+ ): Promise<{ request: BindRequest; response: BindResponse }> {
120
+ const request: BindRequest = {
121
+ function_name: functionName,
122
+ arguments: args,
123
+ function_type: functionType,
124
+ input_schema: inputSchema,
125
+ settings: settings ?? null,
126
+ secrets: secrets ?? null,
127
+ attach_opaque_data: attachOpaqueData ?? this.defaultAttachOpaqueData,
128
+ transaction_opaque_data: transactionOpaqueData ?? null,
129
+ resolved_secrets_provided: false,
130
+ };
131
+
132
+ const requestBatch = serializeBindRequest(request);
133
+ const rpcResult = await this.rpc.call("bind", wrapRequest(requestBatch));
134
+ if (!rpcResult) throw new VgiClientError("bind returned null");
135
+ const inner = unwrapResult(rpcResult);
136
+ const response = deserializeBindResponse(inner);
137
+ if (onBind) onBind(response);
138
+ return { request, response };
139
+ }
140
+
141
+ private async _doInit(
142
+ bindRequest: BindRequest,
143
+ bindResponse: BindResponse,
144
+ opts?: {
145
+ projectionIds?: number[] | null;
146
+ pushdownFilters?: VgiBatch | null;
147
+ phase?: TableInOutPhase | null;
148
+ executionId?: Uint8Array | null;
149
+ orderBy?: OrderByPushdown | null;
150
+ tablesample?: TablesamplePushdown | null;
151
+ joinKeys?: VgiBatch[] | null;
152
+ },
153
+ ): Promise<{ session: StreamSession; initResponse: GlobalInitResponse }> {
154
+ const ob = opts?.orderBy ?? null;
155
+ const ts = opts?.tablesample ?? null;
156
+ const initRequest: InitRequest = {
157
+ bind_call: bindRequest,
158
+ output_schema: bindResponse.output_schema,
159
+ bind_opaque_data: bindResponse.opaque_data,
160
+ projection_ids: opts?.projectionIds ?? null,
161
+ pushdown_filters: opts?.pushdownFilters ?? null,
162
+ join_keys: opts?.joinKeys ?? [],
163
+ phase: opts?.phase ?? null,
164
+ finalize_state_id: null,
165
+ order_by_column_name: ob?.columnName ?? null,
166
+ order_by_direction: ob?.direction ?? null,
167
+ order_by_null_order: ob?.nullOrder ?? null,
168
+ order_by_limit: ob?.limit == null ? null : BigInt(ob.limit),
169
+ tablesample_percentage: ts?.percentage ?? null,
170
+ tablesample_seed: ts?.seed == null ? null : BigInt(ts.seed),
171
+ execution_id: opts?.executionId ?? null,
172
+ init_opaque_data: null,
173
+ };
174
+
175
+ const initBatch = serializeInitRequest(initRequest);
176
+ const session = await this.rpc.stream("init", wrapRequest(initBatch));
177
+
178
+ const headerDict = session.header;
179
+ if (!headerDict) throw new VgiClientError("init did not return a header");
180
+ const initResponse = deserializeGlobalInitResponse(headerDict);
181
+
182
+ return { session, initResponse };
183
+ }
184
+
185
+ // ==========================================================================
186
+ // Table function — RecordBatch API
187
+ // ==========================================================================
188
+
189
+ /** Call a table function, yielding output as RecordBatch instances. */
190
+ async *tableFunction(
191
+ opts: TableFunctionOptions,
192
+ ): AsyncGenerator<VgiBatch> {
193
+ const args = opts.arguments ?? new Arguments();
194
+
195
+ const { request: bindReq, response: bindResp } = await this._doBind(
196
+ opts.functionName,
197
+ FunctionType.TABLE,
198
+ args,
199
+ null,
200
+ opts.settings ?? null,
201
+ null,
202
+ opts.transactionOpaqueData ?? null,
203
+ opts.attachOpaqueData ?? null,
204
+ opts.onBind ?? null,
205
+ );
206
+
207
+ const { session } = await this._doInit(bindReq, bindResp, {
208
+ projectionIds: opts.projectionIds,
209
+ pushdownFilters: opts.pushdownFilters,
210
+ orderBy: opts.orderBy,
211
+ tablesample: opts.tablesample,
212
+ joinKeys: opts.joinKeys,
213
+ });
214
+
215
+ const outputSchema = bindResp.output_schema;
216
+ try {
217
+ for await (const rows of session) {
218
+ if (rows.length > 0) {
219
+ yield batchFromRows(rows, outputSchema);
220
+ }
221
+ }
222
+ } finally {
223
+ session.close();
224
+ }
225
+ }
226
+
227
+ // ==========================================================================
228
+ // Scalar function
229
+ // ==========================================================================
230
+
231
+ /** Call a scalar function, yielding output as row objects. */
232
+ async *scalarFunctionRows(
233
+ opts: ScalarFunctionOptions,
234
+ ): AsyncGenerator<Record<string, any>[]> {
235
+ // Peek first batch to get input schema
236
+ const inputIter = toAsyncIterator(opts.input);
237
+ const first = await inputIter.next();
238
+ if (first.done) {
239
+ throw new VgiClientError(
240
+ `scalarFunction(${opts.functionName}): input iterator yielded no batches; ` +
241
+ `at least one batch is required to determine the input schema`,
242
+ );
243
+ }
244
+
245
+ const firstBatch: VgiBatch = first.value;
246
+ const inputSchema = firstBatch.schema;
247
+
248
+ const args = opts.arguments ?? new Arguments();
249
+
250
+ const { request: bindReq, response: bindResp } = await this._doBind(
251
+ opts.functionName,
252
+ FunctionType.SCALAR,
253
+ args,
254
+ inputSchema,
255
+ opts.settings ?? null,
256
+ opts.secrets ?? null,
257
+ opts.transactionOpaqueData ?? null,
258
+ opts.attachOpaqueData ?? null,
259
+ opts.onBind ?? null,
260
+ );
261
+
262
+ const { session } = await this._doInit(bindReq, bindResp);
263
+
264
+ try {
265
+ // Exchange first batch
266
+ const outRows = await session.exchange([...iterRows(firstBatch)]);
267
+ if (outRows.length > 0) yield outRows;
268
+
269
+ // Exchange remaining batches
270
+ for await (const batch of { [Symbol.asyncIterator]: () => inputIter }) {
271
+ const result = await session.exchange([...iterRows(batch)]);
272
+ if (result.length > 0) yield result;
273
+ }
274
+ } finally {
275
+ session.close();
276
+ }
277
+ }
278
+
279
+ /** Call a scalar function, yielding output as RecordBatch instances. */
280
+ async *scalarFunction(
281
+ opts: ScalarFunctionOptions,
282
+ ): AsyncGenerator<VgiBatch> {
283
+ // Same path as scalarFunctionRows, but with the output schema captured
284
+ // here so we can pack rows back into batches without a re-bind.
285
+ const inputIter = toAsyncIterator(opts.input);
286
+ const first = await inputIter.next();
287
+ if (first.done) {
288
+ throw new VgiClientError(
289
+ `scalarFunction(${opts.functionName}): input iterator yielded no batches; ` +
290
+ `at least one batch is required to determine the input schema`,
291
+ );
292
+ }
293
+
294
+ const firstBatch: VgiBatch = first.value;
295
+ const inputSchema = firstBatch.schema;
296
+
297
+ const args = opts.arguments ?? new Arguments();
298
+
299
+ const { request: bindReq, response: bindResp } = await this._doBind(
300
+ opts.functionName,
301
+ FunctionType.SCALAR,
302
+ args,
303
+ inputSchema,
304
+ opts.settings ?? null,
305
+ opts.secrets ?? null,
306
+ opts.transactionOpaqueData ?? null,
307
+ opts.attachOpaqueData ?? null,
308
+ opts.onBind ?? null,
309
+ );
310
+
311
+ const { session } = await this._doInit(bindReq, bindResp);
312
+ const outputSchema = bindResp.output_schema;
313
+ try {
314
+ const outRows = await session.exchange([...iterRows(firstBatch)]);
315
+ if (outRows.length > 0) yield batchFromRows(outRows, outputSchema);
316
+
317
+ for await (const batch of { [Symbol.asyncIterator]: () => inputIter }) {
318
+ const result = await session.exchange([...iterRows(batch)]);
319
+ if (result.length > 0) yield batchFromRows(result, outputSchema);
320
+ }
321
+ } finally {
322
+ session.close();
323
+ }
324
+ }
325
+
326
+ // ==========================================================================
327
+ // Table-in-out function
328
+ // ==========================================================================
329
+
330
+ /** Call a table-in-out function, yielding output as row objects. */
331
+ async *tableInOutFunctionRows(
332
+ opts: TableInOutFunctionOptions,
333
+ ): AsyncGenerator<Record<string, any>[]> {
334
+ const inputIter = toAsyncIterator(opts.input);
335
+ const first = await inputIter.next();
336
+ if (first.done) {
337
+ throw new VgiClientError(
338
+ `tableInOutFunction(${opts.functionName}): input iterator yielded no batches; ` +
339
+ `at least one batch is required to determine the input schema`,
340
+ );
341
+ }
342
+
343
+ const firstBatch: VgiBatch = first.value;
344
+ const inputSchema = firstBatch.schema;
345
+
346
+ const args = opts.arguments ?? new Arguments();
347
+
348
+ const { request: bindReq, response: bindResp } = await this._doBind(
349
+ opts.functionName,
350
+ FunctionType.TABLE, // table-in-out uses TABLE function type at the bind level
351
+ args,
352
+ inputSchema,
353
+ opts.settings ?? null,
354
+ null,
355
+ opts.transactionOpaqueData ?? null,
356
+ opts.attachOpaqueData ?? null,
357
+ opts.onBind ?? null,
358
+ );
359
+
360
+ // Phase 1: INPUT. We keep the init response around for its `execution_id`,
361
+ // which we must echo back to pair the FINALIZE phase with this call.
362
+ const { session: inputSession, initResponse } = await this._doInit(
363
+ bindReq,
364
+ bindResp,
365
+ {
366
+ projectionIds: opts.projectionIds,
367
+ pushdownFilters: opts.pushdownFilters,
368
+ phase: TableInOutPhase.INPUT,
369
+ orderBy: opts.orderBy,
370
+ tablesample: opts.tablesample,
371
+ joinKeys: opts.joinKeys,
372
+ },
373
+ );
374
+
375
+ try {
376
+ const outRows = await inputSession.exchange([...iterRows(firstBatch)]);
377
+ if (outRows.length > 0) yield outRows;
378
+
379
+ for await (const batch of { [Symbol.asyncIterator]: () => inputIter }) {
380
+ const result = await inputSession.exchange([...iterRows(batch)]);
381
+ if (result.length > 0) yield result;
382
+ }
383
+ } finally {
384
+ inputSession.close();
385
+ }
386
+
387
+ // Phase 2: FINALIZE
388
+ const { session: finalizeSession } = await this._doInit(
389
+ bindReq,
390
+ bindResp,
391
+ {
392
+ projectionIds: opts.projectionIds,
393
+ pushdownFilters: opts.pushdownFilters,
394
+ phase: TableInOutPhase.FINALIZE,
395
+ executionId: initResponse.execution_id,
396
+ orderBy: opts.orderBy,
397
+ tablesample: opts.tablesample,
398
+ joinKeys: opts.joinKeys,
399
+ },
400
+ );
401
+
402
+ try {
403
+ for await (const rows of finalizeSession) {
404
+ if (rows.length > 0) yield rows;
405
+ }
406
+ } finally {
407
+ finalizeSession.close();
408
+ }
409
+ }
410
+
411
+ /** Call a table-in-out function, yielding output as RecordBatch instances. */
412
+ async *tableInOutFunction(
413
+ opts: TableInOutFunctionOptions,
414
+ ): AsyncGenerator<VgiBatch> {
415
+ const inputIter = toAsyncIterator(opts.input);
416
+ const first = await inputIter.next();
417
+ if (first.done) {
418
+ throw new VgiClientError(
419
+ `tableInOutFunction(${opts.functionName}): input iterator yielded no batches; ` +
420
+ `at least one batch is required to determine the input schema`,
421
+ );
422
+ }
423
+
424
+ const firstBatch: VgiBatch = first.value;
425
+ const inputSchema = firstBatch.schema;
426
+
427
+ const args = opts.arguments ?? new Arguments();
428
+
429
+ const { request: bindReq, response: bindResp } = await this._doBind(
430
+ opts.functionName,
431
+ FunctionType.TABLE,
432
+ args,
433
+ inputSchema,
434
+ opts.settings ?? null,
435
+ null,
436
+ opts.transactionOpaqueData ?? null,
437
+ opts.attachOpaqueData ?? null,
438
+ opts.onBind ?? null,
439
+ );
440
+
441
+ const { session: inputSession, initResponse } = await this._doInit(
442
+ bindReq,
443
+ bindResp,
444
+ {
445
+ projectionIds: opts.projectionIds,
446
+ pushdownFilters: opts.pushdownFilters,
447
+ phase: TableInOutPhase.INPUT,
448
+ orderBy: opts.orderBy,
449
+ tablesample: opts.tablesample,
450
+ joinKeys: opts.joinKeys,
451
+ },
452
+ );
453
+
454
+ const outputSchema = bindResp.output_schema;
455
+ try {
456
+ const outRows = await inputSession.exchange([...iterRows(firstBatch)]);
457
+ if (outRows.length > 0) yield batchFromRows(outRows, outputSchema);
458
+
459
+ for await (const batch of { [Symbol.asyncIterator]: () => inputIter }) {
460
+ const result = await inputSession.exchange([...iterRows(batch)]);
461
+ if (result.length > 0) yield batchFromRows(result, outputSchema);
462
+ }
463
+ } finally {
464
+ inputSession.close();
465
+ }
466
+
467
+ const { session: finalizeSession } = await this._doInit(
468
+ bindReq,
469
+ bindResp,
470
+ {
471
+ projectionIds: opts.projectionIds,
472
+ pushdownFilters: opts.pushdownFilters,
473
+ phase: TableInOutPhase.FINALIZE,
474
+ executionId: initResponse.execution_id,
475
+ orderBy: opts.orderBy,
476
+ tablesample: opts.tablesample,
477
+ joinKeys: opts.joinKeys,
478
+ },
479
+ );
480
+
481
+ try {
482
+ for await (const rows of finalizeSession) {
483
+ if (rows.length > 0) yield batchFromRows(rows, outputSchema);
484
+ }
485
+ } finally {
486
+ finalizeSession.close();
487
+ }
488
+ }
489
+
490
+ // ==========================================================================
491
+ // Row convenience for table function
492
+ // ==========================================================================
493
+
494
+ /** Call a table function, yielding output as row objects. */
495
+ async *tableFunctionRows(
496
+ opts: TableFunctionOptions,
497
+ ): AsyncGenerator<Record<string, any>[]> {
498
+ const args = opts.arguments ?? new Arguments();
499
+ const { request: bindReq, response: bindResp } = await this._doBind(
500
+ opts.functionName,
501
+ FunctionType.TABLE,
502
+ args,
503
+ null,
504
+ opts.settings ?? null,
505
+ null,
506
+ opts.transactionOpaqueData ?? null,
507
+ opts.attachOpaqueData ?? null,
508
+ opts.onBind ?? null,
509
+ );
510
+ const { session } = await this._doInit(bindReq, bindResp, {
511
+ projectionIds: opts.projectionIds,
512
+ pushdownFilters: opts.pushdownFilters,
513
+ orderBy: opts.orderBy,
514
+ tablesample: opts.tablesample,
515
+ joinKeys: opts.joinKeys,
516
+ });
517
+ try {
518
+ for await (const rows of session) {
519
+ if (rows.length > 0) yield rows;
520
+ }
521
+ } finally {
522
+ session.close();
523
+ }
524
+ }
525
+
526
+ // ==========================================================================
527
+ // Catalog API
528
+ // ==========================================================================
529
+
530
+ /**
531
+ * List all catalogs with their advertised version metadata.
532
+ *
533
+ * Each entry has `{name, implementation_version?, data_version_spec?}`.
534
+ * Versioned workers populate the version fields; read-only workers
535
+ * leave both null.
536
+ */
537
+ async catalogsInfo(): Promise<CatalogInfo[]> {
538
+ const result = await this.rpc.call("catalog_catalogs", {});
539
+ if (!result) return [];
540
+ const inner = unwrapResult(result);
541
+ return deserializeInfoList(inner.items, decodeCatalogInfo);
542
+ }
543
+
544
+ /** List all available catalog names (shorthand for `catalogsInfo().map(c => c.name)`). */
545
+ async catalogs(): Promise<string[]> {
546
+ const infos = await this.catalogsInfo();
547
+ return infos.map((i) => i.name);
548
+ }
549
+
550
+ /**
551
+ * Attach a catalog by name. Returns connection details including the
552
+ * attachOpaqueData.
553
+ *
554
+ * `opts.options` is a plain key→value map; column types are inferred from
555
+ * the value at runtime (see AttachOptionValue for the mapping). Use
556
+ * `opts.optionsBytes` instead when you need Arrow types the inference
557
+ * can't express (Decimal, Timestamp, Int32 vs Int64, nested structs).
558
+ * Providing both throws.
559
+ *
560
+ * Versioned catalogs (see vgi-example-versioned-worker) validate
561
+ * `dataVersionSpec` / `implementationVersion` at attach time and echo
562
+ * back the resolved values on the result — callers can read those from
563
+ * `result.resolved_data_version` / `result.resolved_implementation_version`.
564
+ *
565
+ * @example
566
+ * ```ts
567
+ * const result = await client.catalogAttach("my_catalog", {
568
+ * options: { region: "us-east-1", maxCachedRows: 1000n, readOnly: true },
569
+ * dataVersionSpec: "1.0.0",
570
+ * });
571
+ * ```
572
+ */
573
+ async catalogAttach(
574
+ name: string,
575
+ opts?: CatalogAttachOptions,
576
+ ): Promise<CatalogAttachResult> {
577
+ if (opts?.options != null && opts?.optionsBytes != null) {
578
+ throw new VgiClientError(
579
+ "catalogAttach: cannot specify both `options` and `optionsBytes`",
580
+ );
581
+ }
582
+ let wireOptions: Uint8Array | null;
583
+ if (opts?.optionsBytes != null) {
584
+ // Escape hatch: raw pre-serialized bytes. Same zero-byte guard as
585
+ // before — pyarrow's IPC reader rejects 0-byte input on a non-null
586
+ // binary field.
587
+ wireOptions = opts.optionsBytes.byteLength === 0 ? null : opts.optionsBytes;
588
+ } else {
589
+ wireOptions = serializeAttachOptions(opts?.options);
590
+ }
591
+
592
+ const schema = schema_([
593
+ field("name", utf8(), false),
594
+ field("options", binary(), true),
595
+ field("data_version_spec", utf8(), true),
596
+ field("implementation_version", utf8(), true),
597
+ ]);
598
+ const innerBatch = batchFromColumns(
599
+ {
600
+ name: [name],
601
+ options: [wireOptions],
602
+ data_version_spec: [opts?.dataVersionSpec ?? null],
603
+ implementation_version: [opts?.implementationVersion ?? null],
604
+ },
605
+ schema,
606
+ );
607
+ const result = await this.rpc.call("catalog_attach", wrapRequest(innerBatch));
608
+ if (!result) throw new VgiClientError("catalog_attach returned null");
609
+ const inner = unwrapResult(result);
610
+ return {
611
+ attach_opaque_data: toUint8Array(inner.attach_opaque_data),
612
+ supports_transactions: inner.supports_transactions ?? false,
613
+ supports_time_travel: inner.supports_time_travel ?? false,
614
+ catalog_version_frozen: inner.catalog_version_frozen ?? false,
615
+ catalog_version: Number(inner.catalog_version ?? 0),
616
+ attach_opaque_data_required: inner.attach_opaque_data_required ?? true,
617
+ default_schema: inner.default_schema ?? "main",
618
+ settings: inner.settings
619
+ ? (Array.isArray(inner.settings) ? inner.settings : [...inner.settings]).map(toUint8Array)
620
+ : [],
621
+ secret_types: inner.secret_types
622
+ ? (Array.isArray(inner.secret_types) ? inner.secret_types : [...inner.secret_types]).map(toUint8Array)
623
+ : [],
624
+ comment: inner.comment ?? null,
625
+ tags: deserializeTags(inner.tags),
626
+ supports_column_statistics: inner.supports_column_statistics ?? false,
627
+ resolved_data_version: inner.resolved_data_version ?? null,
628
+ resolved_implementation_version: inner.resolved_implementation_version ?? null,
629
+ };
630
+ }
631
+
632
+ /** Detach a previously-attached catalog. */
633
+ async catalogDetach(attachOpaqueData: AttachOpaqueData): Promise<void> {
634
+ await this.rpc.call("catalog_detach", { attach_opaque_data: attachOpaqueData });
635
+ }
636
+
637
+ /**
638
+ * Create a new catalog.
639
+ *
640
+ * `options` is a plain key→value map; column types are inferred from the
641
+ * value at runtime (see AttachOptionValue). Use `optionsBytes` instead
642
+ * when you need Arrow types the inference can't express. Providing both
643
+ * throws.
644
+ */
645
+ async catalogCreate(
646
+ name: string,
647
+ onConflict: OnCreateConflict,
648
+ options?:
649
+ | Uint8Array
650
+ | { options?: Record<string, AttachOptionValue>; optionsBytes?: Uint8Array },
651
+ ): Promise<void> {
652
+ let wireOptions: Uint8Array | null;
653
+ if (options == null) {
654
+ wireOptions = null;
655
+ } else if (options instanceof Uint8Array) {
656
+ // Backwards-compatible escape hatch: caller pre-serialized the options batch.
657
+ wireOptions = options.byteLength === 0 ? null : options;
658
+ } else {
659
+ if (options.options != null && options.optionsBytes != null) {
660
+ throw new VgiClientError(
661
+ "catalogCreate: cannot specify both `options` and `optionsBytes`",
662
+ );
663
+ }
664
+ if (options.optionsBytes != null) {
665
+ wireOptions = options.optionsBytes.byteLength === 0 ? null : options.optionsBytes;
666
+ } else {
667
+ wireOptions = serializeAttachOptions(options.options);
668
+ }
669
+ }
670
+ await this.rpc.call("catalog_create", {
671
+ name,
672
+ on_conflict: onConflict,
673
+ options: wireOptions,
674
+ });
675
+ }
676
+
677
+ /** Drop a catalog by name. */
678
+ async catalogDrop(name: string): Promise<void> {
679
+ await this.rpc.call("catalog_drop", { name });
680
+ }
681
+
682
+ /** Get the current catalog version number. */
683
+ async catalogVersion(
684
+ attachOpaqueData: AttachOpaqueData,
685
+ transactionOpaqueData?: TransactionOpaqueData,
686
+ ): Promise<number> {
687
+ const result = await this.rpc.call("catalog_version", {
688
+ attach_opaque_data: attachOpaqueData,
689
+ transaction_opaque_data: transactionOpaqueData ?? null,
690
+ });
691
+ if (!result) throw new VgiClientError("catalog_version returned null");
692
+ const inner = unwrapResult(result);
693
+ return Number(inner.version ?? 0);
694
+ }
695
+
696
+ /** Begin a new transaction. Returns the transaction ID. */
697
+ async transactionBegin(attachOpaqueData: AttachOpaqueData): Promise<Uint8Array> {
698
+ const result = await this.rpc.call("catalog_transaction_begin", {
699
+ attach_opaque_data: attachOpaqueData,
700
+ });
701
+ if (!result) throw new VgiClientError("transaction_begin returned null");
702
+ const inner = unwrapResult(result);
703
+ if (!inner.transaction_opaque_data) {
704
+ throw new VgiClientError("transaction_begin returned no transaction_opaque_data");
705
+ }
706
+ return toUint8Array(inner.transaction_opaque_data);
707
+ }
708
+
709
+ /** Commit a transaction. */
710
+ async transactionCommit(
711
+ attachOpaqueData: AttachOpaqueData,
712
+ transactionOpaqueData: TransactionOpaqueData,
713
+ ): Promise<void> {
714
+ await this.rpc.call("catalog_transaction_commit", {
715
+ attach_opaque_data: attachOpaqueData,
716
+ transaction_opaque_data: transactionOpaqueData,
717
+ });
718
+ }
719
+
720
+ /** Rollback a transaction. */
721
+ async transactionRollback(
722
+ attachOpaqueData: AttachOpaqueData,
723
+ transactionOpaqueData: TransactionOpaqueData,
724
+ ): Promise<void> {
725
+ await this.rpc.call("catalog_transaction_rollback", {
726
+ attach_opaque_data: attachOpaqueData,
727
+ transaction_opaque_data: transactionOpaqueData,
728
+ });
729
+ }
730
+
731
+ /** List schemas in an attached catalog. */
732
+ async schemas(
733
+ attachOpaqueData: AttachOpaqueData,
734
+ transactionOpaqueData?: TransactionOpaqueData,
735
+ ): Promise<SchemaInfo[]> {
736
+ const result = await this.rpc.call("catalog_schemas", {
737
+ attach_opaque_data: attachOpaqueData,
738
+ transaction_opaque_data: transactionOpaqueData ?? null,
739
+ });
740
+ if (!result) return [];
741
+ const inner = unwrapResult(result);
742
+ return deserializeInfoList(inner.items, decodeSchemaInfo);
743
+ }
744
+
745
+ /** Get a schema by name, or null if not found. */
746
+ async schemaGet(
747
+ attachOpaqueData: AttachOpaqueData,
748
+ name: string,
749
+ transactionOpaqueData?: TransactionOpaqueData,
750
+ ): Promise<SchemaInfo | null> {
751
+ const result = await this.rpc.call("catalog_schema_get", {
752
+ attach_opaque_data: attachOpaqueData,
753
+ name,
754
+ transaction_opaque_data: transactionOpaqueData ?? null,
755
+ });
756
+ if (!result) return null;
757
+ const inner = unwrapResult(result);
758
+ const items = deserializeInfoList(inner.items, decodeSchemaInfo);
759
+ return items.length > 0 ? items[0] : null;
760
+ }
761
+
762
+ /** Create a new schema. */
763
+ async schemaCreate(
764
+ attachOpaqueData: AttachOpaqueData,
765
+ name: string,
766
+ opts?: {
767
+ onConflict?: OnCreateConflict;
768
+ comment?: string | null;
769
+ tags?: Record<string, string> | null;
770
+ transactionOpaqueData?: TransactionOpaqueData;
771
+ },
772
+ ): Promise<void> {
773
+ const tagsMap = opts?.tags
774
+ ? new Map(Object.entries(opts.tags))
775
+ : null;
776
+ await this.rpc.call("catalog_schema_create", {
777
+ attach_opaque_data: attachOpaqueData,
778
+ name,
779
+ on_conflict: opts?.onConflict ?? "error",
780
+ comment: opts?.comment ?? null,
781
+ tags: tagsMap,
782
+ transaction_opaque_data: opts?.transactionOpaqueData ?? null,
783
+ });
784
+ }
785
+
786
+ /** Drop a schema by name. */
787
+ async schemaDrop(
788
+ attachOpaqueData: AttachOpaqueData,
789
+ name: string,
790
+ ignoreNotFound?: boolean,
791
+ cascade?: boolean,
792
+ transactionOpaqueData?: TransactionOpaqueData,
793
+ ): Promise<void> {
794
+ await this.rpc.call("catalog_schema_drop", {
795
+ attach_opaque_data: attachOpaqueData,
796
+ name,
797
+ ignore_not_found: ignoreNotFound ?? false,
798
+ cascade: cascade ?? false,
799
+ transaction_opaque_data: transactionOpaqueData ?? null,
800
+ });
801
+ }
802
+
803
+ /** List tables in a schema. */
804
+ async schemaContentsTables(
805
+ attachOpaqueData: AttachOpaqueData,
806
+ name: string,
807
+ transactionOpaqueData?: TransactionOpaqueData,
808
+ ): Promise<TableInfo[]> {
809
+ const result = await this.rpc.call("catalog_schema_contents_tables", {
810
+ attach_opaque_data: attachOpaqueData,
811
+ name,
812
+ transaction_opaque_data: transactionOpaqueData ?? null,
813
+ });
814
+ if (!result) return [];
815
+ const inner = unwrapResult(result);
816
+ return deserializeInfoList(inner.items, decodeTableInfo);
817
+ }
818
+
819
+ /** List views in a schema. */
820
+ async schemaContentsViews(
821
+ attachOpaqueData: AttachOpaqueData,
822
+ name: string,
823
+ transactionOpaqueData?: TransactionOpaqueData,
824
+ ): Promise<ViewInfo[]> {
825
+ const result = await this.rpc.call("catalog_schema_contents_views", {
826
+ attach_opaque_data: attachOpaqueData,
827
+ name,
828
+ transaction_opaque_data: transactionOpaqueData ?? null,
829
+ });
830
+ if (!result) return [];
831
+ const inner = unwrapResult(result);
832
+ return deserializeInfoList(inner.items, decodeViewInfo);
833
+ }
834
+
835
+ /** List functions in a schema, filtered by type. */
836
+ async schemaContentsFunctions(
837
+ attachOpaqueData: AttachOpaqueData,
838
+ name: string,
839
+ type: CatalogFunctionType,
840
+ transactionOpaqueData?: TransactionOpaqueData,
841
+ ): Promise<FunctionInfo[]> {
842
+ const result = await this.rpc.call("catalog_schema_contents_functions", {
843
+ attach_opaque_data: attachOpaqueData,
844
+ name,
845
+ type,
846
+ transaction_opaque_data: transactionOpaqueData ?? null,
847
+ });
848
+ if (!result) return [];
849
+ const inner = unwrapResult(result);
850
+ return deserializeInfoList(inner.items, decodeFunctionInfo);
851
+ }
852
+
853
+ /** Get a table by name, or null if not found. */
854
+ async tableGet(
855
+ attachOpaqueData: AttachOpaqueData,
856
+ schemaName: string,
857
+ name: string,
858
+ transactionOpaqueData?: TransactionOpaqueData,
859
+ ): Promise<TableInfo | null> {
860
+ const result = await this.rpc.call("catalog_table_get", {
861
+ attach_opaque_data: attachOpaqueData,
862
+ schema_name: schemaName,
863
+ name,
864
+ transaction_opaque_data: transactionOpaqueData ?? null,
865
+ });
866
+ if (!result) return null;
867
+ const inner = unwrapResult(result);
868
+ const items = deserializeInfoList(inner.items, decodeTableInfo);
869
+ return items.length > 0 ? items[0] : null;
870
+ }
871
+
872
+ /** Create a new table. */
873
+ async tableCreate(
874
+ attachOpaqueData: AttachOpaqueData,
875
+ schemaName: string,
876
+ name: string,
877
+ columns: Uint8Array,
878
+ onConflict: OnCreateConflict,
879
+ notNullConstraints?: number[],
880
+ uniqueConstraints?: number[][],
881
+ checkConstraints?: string[],
882
+ transactionOpaqueData?: TransactionOpaqueData,
883
+ ): Promise<void> {
884
+ await this.rpc.call("catalog_table_create", {
885
+ attach_opaque_data: attachOpaqueData,
886
+ schema_name: schemaName,
887
+ name,
888
+ columns,
889
+ on_conflict: onConflict,
890
+ not_null_constraints: notNullConstraints ?? null,
891
+ unique_constraints: uniqueConstraints ?? null,
892
+ check_constraints: checkConstraints ?? null,
893
+ transaction_opaque_data: transactionOpaqueData ?? null,
894
+ });
895
+ }
896
+
897
+ /** Drop a table by name. */
898
+ async tableDrop(
899
+ attachOpaqueData: AttachOpaqueData,
900
+ schemaName: string,
901
+ name: string,
902
+ ignoreNotFound?: boolean,
903
+ cascade?: boolean,
904
+ transactionOpaqueData?: TransactionOpaqueData,
905
+ ): Promise<void> {
906
+ await this.rpc.call("catalog_table_drop", {
907
+ attach_opaque_data: attachOpaqueData,
908
+ schema_name: schemaName,
909
+ name,
910
+ ignore_not_found: ignoreNotFound ?? false,
911
+ cascade: cascade ?? false,
912
+ transaction_opaque_data: transactionOpaqueData ?? null,
913
+ });
914
+ }
915
+
916
+ /**
917
+ * Get the scan function for a table — tells DuckDB which function to call
918
+ * to read the table data (e.g. `read_parquet` with a path argument). Used
919
+ * by the VGI extension during query planning.
920
+ */
921
+ async tableScanFunctionGet(
922
+ attachOpaqueData: AttachOpaqueData,
923
+ schemaName: string,
924
+ name: string,
925
+ atUnit?: string | null,
926
+ atValue?: string | null,
927
+ transactionOpaqueData?: TransactionOpaqueData,
928
+ ): Promise<ScanFunctionResult> {
929
+ const result = await this.rpc.call("catalog_table_scan_function_get", {
930
+ attach_opaque_data: attachOpaqueData,
931
+ schema_name: schemaName,
932
+ name,
933
+ at_unit: atUnit ?? null,
934
+ at_value: atValue ?? null,
935
+ transaction_opaque_data: transactionOpaqueData ?? null,
936
+ });
937
+ if (!result) throw new VgiClientError("table_scan_function_get returned null");
938
+ return decodeScanFunctionResult(unwrapResult(result));
939
+ }
940
+
941
+ /** Set or clear the comment on a table. */
942
+ async tableCommentSet(
943
+ attachOpaqueData: AttachOpaqueData,
944
+ schemaName: string,
945
+ name: string,
946
+ comment?: string | null,
947
+ ignoreNotFound?: boolean,
948
+ transactionOpaqueData?: TransactionOpaqueData,
949
+ ): Promise<void> {
950
+ await this.rpc.call("catalog_table_comment_set", {
951
+ attach_opaque_data: attachOpaqueData,
952
+ schema_name: schemaName,
953
+ name,
954
+ comment: comment ?? null,
955
+ ignore_not_found: ignoreNotFound ?? null,
956
+ transaction_opaque_data: transactionOpaqueData ?? null,
957
+ });
958
+ }
959
+
960
+ /** Rename a table. */
961
+ async tableRename(
962
+ attachOpaqueData: AttachOpaqueData,
963
+ schemaName: string,
964
+ name: string,
965
+ newName: string,
966
+ ignoreNotFound?: boolean,
967
+ transactionOpaqueData?: TransactionOpaqueData,
968
+ ): Promise<void> {
969
+ await this.rpc.call("catalog_table_rename", {
970
+ attach_opaque_data: attachOpaqueData,
971
+ schema_name: schemaName,
972
+ name,
973
+ new_name: newName,
974
+ ignore_not_found: ignoreNotFound ?? null,
975
+ transaction_opaque_data: transactionOpaqueData ?? null,
976
+ });
977
+ }
978
+
979
+ /** Add a column to a table. */
980
+ async tableColumnAdd(
981
+ attachOpaqueData: AttachOpaqueData,
982
+ schemaName: string,
983
+ name: string,
984
+ columnName: string,
985
+ columnType: string,
986
+ defaultValue?: string | null,
987
+ ignoreNotFound?: boolean,
988
+ transactionOpaqueData?: TransactionOpaqueData,
989
+ ): Promise<void> {
990
+ await this.rpc.call("catalog_table_column_add", {
991
+ attach_opaque_data: attachOpaqueData,
992
+ schema_name: schemaName,
993
+ name,
994
+ column_name: columnName,
995
+ column_type: columnType,
996
+ default_value: defaultValue ?? null,
997
+ ignore_not_found: ignoreNotFound ?? null,
998
+ transaction_opaque_data: transactionOpaqueData ?? null,
999
+ });
1000
+ }
1001
+
1002
+ /** Drop a column from a table. */
1003
+ async tableColumnDrop(
1004
+ attachOpaqueData: AttachOpaqueData,
1005
+ schemaName: string,
1006
+ name: string,
1007
+ columnName: string,
1008
+ ignoreNotFound?: boolean,
1009
+ transactionOpaqueData?: TransactionOpaqueData,
1010
+ ): Promise<void> {
1011
+ await this.rpc.call("catalog_table_column_drop", {
1012
+ attach_opaque_data: attachOpaqueData,
1013
+ schema_name: schemaName,
1014
+ name,
1015
+ column_name: columnName,
1016
+ ignore_not_found: ignoreNotFound ?? null,
1017
+ transaction_opaque_data: transactionOpaqueData ?? null,
1018
+ });
1019
+ }
1020
+
1021
+ /** Rename a column in a table. */
1022
+ async tableColumnRename(
1023
+ attachOpaqueData: AttachOpaqueData,
1024
+ schemaName: string,
1025
+ name: string,
1026
+ columnName: string,
1027
+ newName: string,
1028
+ ignoreNotFound?: boolean,
1029
+ transactionOpaqueData?: TransactionOpaqueData,
1030
+ ): Promise<void> {
1031
+ await this.rpc.call("catalog_table_column_rename", {
1032
+ attach_opaque_data: attachOpaqueData,
1033
+ schema_name: schemaName,
1034
+ name,
1035
+ column_name: columnName,
1036
+ new_name: newName,
1037
+ ignore_not_found: ignoreNotFound ?? null,
1038
+ transaction_opaque_data: transactionOpaqueData ?? null,
1039
+ });
1040
+ }
1041
+
1042
+ /** Set the default value for a column. */
1043
+ async tableColumnDefaultSet(
1044
+ attachOpaqueData: AttachOpaqueData,
1045
+ schemaName: string,
1046
+ name: string,
1047
+ columnName: string,
1048
+ defaultValue: string,
1049
+ ignoreNotFound?: boolean,
1050
+ transactionOpaqueData?: TransactionOpaqueData,
1051
+ ): Promise<void> {
1052
+ await this.rpc.call("catalog_table_column_default_set", {
1053
+ attach_opaque_data: attachOpaqueData,
1054
+ schema_name: schemaName,
1055
+ name,
1056
+ column_name: columnName,
1057
+ default_value: defaultValue,
1058
+ ignore_not_found: ignoreNotFound ?? null,
1059
+ transaction_opaque_data: transactionOpaqueData ?? null,
1060
+ });
1061
+ }
1062
+
1063
+ /** Remove the default value from a column. */
1064
+ async tableColumnDefaultDrop(
1065
+ attachOpaqueData: AttachOpaqueData,
1066
+ schemaName: string,
1067
+ name: string,
1068
+ columnName: string,
1069
+ ignoreNotFound?: boolean,
1070
+ transactionOpaqueData?: TransactionOpaqueData,
1071
+ ): Promise<void> {
1072
+ await this.rpc.call("catalog_table_column_default_drop", {
1073
+ attach_opaque_data: attachOpaqueData,
1074
+ schema_name: schemaName,
1075
+ name,
1076
+ column_name: columnName,
1077
+ ignore_not_found: ignoreNotFound ?? null,
1078
+ transaction_opaque_data: transactionOpaqueData ?? null,
1079
+ });
1080
+ }
1081
+
1082
+ /**
1083
+ * Change the type of a column.
1084
+ *
1085
+ * `columnDefinition` is a serialized Arrow Schema with a single field whose
1086
+ * name identifies the target column and whose type is the new column type.
1087
+ * `expression` is an optional SQL expression used to convert existing values.
1088
+ */
1089
+ async tableColumnTypeChange(
1090
+ attachOpaqueData: AttachOpaqueData,
1091
+ schemaName: string,
1092
+ name: string,
1093
+ columnDefinition: Uint8Array,
1094
+ expression?: string | null,
1095
+ ignoreNotFound?: boolean,
1096
+ transactionOpaqueData?: TransactionOpaqueData,
1097
+ ): Promise<void> {
1098
+ await this.rpc.call("catalog_table_column_type_change", {
1099
+ attach_opaque_data: attachOpaqueData,
1100
+ schema_name: schemaName,
1101
+ name,
1102
+ column_definition: columnDefinition,
1103
+ expression: expression ?? null,
1104
+ ignore_not_found: ignoreNotFound ?? false,
1105
+ transaction_opaque_data: transactionOpaqueData ?? null,
1106
+ });
1107
+ }
1108
+
1109
+ /** Set a NOT NULL constraint on a column. */
1110
+ async tableNotNullSet(
1111
+ attachOpaqueData: AttachOpaqueData,
1112
+ schemaName: string,
1113
+ name: string,
1114
+ columnName: string,
1115
+ ignoreNotFound?: boolean,
1116
+ transactionOpaqueData?: TransactionOpaqueData,
1117
+ ): Promise<void> {
1118
+ await this.rpc.call("catalog_table_not_null_set", {
1119
+ attach_opaque_data: attachOpaqueData,
1120
+ schema_name: schemaName,
1121
+ name,
1122
+ column_name: columnName,
1123
+ ignore_not_found: ignoreNotFound ?? null,
1124
+ transaction_opaque_data: transactionOpaqueData ?? null,
1125
+ });
1126
+ }
1127
+
1128
+ /** Remove a NOT NULL constraint from a column. */
1129
+ async tableNotNullDrop(
1130
+ attachOpaqueData: AttachOpaqueData,
1131
+ schemaName: string,
1132
+ name: string,
1133
+ columnName: string,
1134
+ ignoreNotFound?: boolean,
1135
+ transactionOpaqueData?: TransactionOpaqueData,
1136
+ ): Promise<void> {
1137
+ await this.rpc.call("catalog_table_not_null_drop", {
1138
+ attach_opaque_data: attachOpaqueData,
1139
+ schema_name: schemaName,
1140
+ name,
1141
+ column_name: columnName,
1142
+ ignore_not_found: ignoreNotFound ?? null,
1143
+ transaction_opaque_data: transactionOpaqueData ?? null,
1144
+ });
1145
+ }
1146
+
1147
+ /** Get a view by name, or null if not found. */
1148
+ async viewGet(
1149
+ attachOpaqueData: AttachOpaqueData,
1150
+ schemaName: string,
1151
+ name: string,
1152
+ transactionOpaqueData?: TransactionOpaqueData,
1153
+ ): Promise<ViewInfo | null> {
1154
+ const result = await this.rpc.call("catalog_view_get", {
1155
+ attach_opaque_data: attachOpaqueData,
1156
+ schema_name: schemaName,
1157
+ name,
1158
+ transaction_opaque_data: transactionOpaqueData ?? null,
1159
+ });
1160
+ if (!result) return null;
1161
+ const inner = unwrapResult(result);
1162
+ const items = deserializeInfoList(inner.items, decodeViewInfo);
1163
+ return items.length > 0 ? items[0] : null;
1164
+ }
1165
+
1166
+ /** Create a new view. */
1167
+ async viewCreate(
1168
+ attachOpaqueData: AttachOpaqueData,
1169
+ schemaName: string,
1170
+ name: string,
1171
+ definition: string,
1172
+ onConflict: OnCreateConflict,
1173
+ transactionOpaqueData?: TransactionOpaqueData,
1174
+ ): Promise<void> {
1175
+ await this.rpc.call("catalog_view_create", {
1176
+ attach_opaque_data: attachOpaqueData,
1177
+ schema_name: schemaName,
1178
+ name,
1179
+ definition,
1180
+ on_conflict: onConflict,
1181
+ transaction_opaque_data: transactionOpaqueData ?? null,
1182
+ });
1183
+ }
1184
+
1185
+ /** Drop a view by name. */
1186
+ async viewDrop(
1187
+ attachOpaqueData: AttachOpaqueData,
1188
+ schemaName: string,
1189
+ name: string,
1190
+ ignoreNotFound?: boolean,
1191
+ cascade?: boolean,
1192
+ transactionOpaqueData?: TransactionOpaqueData,
1193
+ ): Promise<void> {
1194
+ await this.rpc.call("catalog_view_drop", {
1195
+ attach_opaque_data: attachOpaqueData,
1196
+ schema_name: schemaName,
1197
+ name,
1198
+ ignore_not_found: ignoreNotFound ?? false,
1199
+ cascade: cascade ?? false,
1200
+ transaction_opaque_data: transactionOpaqueData ?? null,
1201
+ });
1202
+ }
1203
+
1204
+ /** Rename a view. */
1205
+ async viewRename(
1206
+ attachOpaqueData: AttachOpaqueData,
1207
+ schemaName: string,
1208
+ name: string,
1209
+ newName: string,
1210
+ ignoreNotFound?: boolean,
1211
+ transactionOpaqueData?: TransactionOpaqueData,
1212
+ ): Promise<void> {
1213
+ await this.rpc.call("catalog_view_rename", {
1214
+ attach_opaque_data: attachOpaqueData,
1215
+ schema_name: schemaName,
1216
+ name,
1217
+ new_name: newName,
1218
+ ignore_not_found: ignoreNotFound ?? null,
1219
+ transaction_opaque_data: transactionOpaqueData ?? null,
1220
+ });
1221
+ }
1222
+
1223
+ /** Set or clear the comment on a view. */
1224
+ async viewCommentSet(
1225
+ attachOpaqueData: AttachOpaqueData,
1226
+ schemaName: string,
1227
+ name: string,
1228
+ comment?: string | null,
1229
+ ignoreNotFound?: boolean,
1230
+ transactionOpaqueData?: TransactionOpaqueData,
1231
+ ): Promise<void> {
1232
+ await this.rpc.call("catalog_view_comment_set", {
1233
+ attach_opaque_data: attachOpaqueData,
1234
+ schema_name: schemaName,
1235
+ name,
1236
+ comment: comment ?? null,
1237
+ ignore_not_found: ignoreNotFound ?? null,
1238
+ transaction_opaque_data: transactionOpaqueData ?? null,
1239
+ });
1240
+ }
1241
+
1242
+ // ==========================================================================
1243
+ // Macro Catalog API
1244
+ // ==========================================================================
1245
+
1246
+ /** Get a macro by name, or null if not found. */
1247
+ async macroGet(
1248
+ attachOpaqueData: AttachOpaqueData,
1249
+ schemaName: string,
1250
+ name: string,
1251
+ transactionOpaqueData?: TransactionOpaqueData,
1252
+ ): Promise<MacroInfo | null> {
1253
+ const result = await this.rpc.call("catalog_macro_get", {
1254
+ attach_opaque_data: attachOpaqueData,
1255
+ schema_name: schemaName,
1256
+ name,
1257
+ transaction_opaque_data: transactionOpaqueData ?? null,
1258
+ });
1259
+ if (!result) return null;
1260
+ const inner = unwrapResult(result);
1261
+ const items = deserializeInfoList(inner.items, decodeMacroInfo);
1262
+ return items.length > 0 ? items[0] : null;
1263
+ }
1264
+
1265
+ /** List macros in a schema, filtered by type. */
1266
+ async schemaContentsMacros(
1267
+ attachOpaqueData: AttachOpaqueData,
1268
+ name: string,
1269
+ type: CatalogMacroType,
1270
+ transactionOpaqueData?: TransactionOpaqueData,
1271
+ ): Promise<MacroInfo[]> {
1272
+ const result = await this.rpc.call("catalog_schema_contents_macros", {
1273
+ attach_opaque_data: attachOpaqueData,
1274
+ name,
1275
+ type,
1276
+ transaction_opaque_data: transactionOpaqueData ?? null,
1277
+ });
1278
+ if (!result) return [];
1279
+ const inner = unwrapResult(result);
1280
+ return deserializeInfoList(inner.items, decodeMacroInfo);
1281
+ }
1282
+
1283
+ /** Create a new macro. */
1284
+ async macroCreate(
1285
+ attachOpaqueData: AttachOpaqueData,
1286
+ schemaName: string,
1287
+ name: string,
1288
+ macroType: MacroType,
1289
+ parameters: string[],
1290
+ definition: string,
1291
+ onConflict: OnCreateConflict,
1292
+ parameterDefaultValues?: Uint8Array | null,
1293
+ argumentsSchema?: Uint8Array | null,
1294
+ transactionOpaqueData?: TransactionOpaqueData,
1295
+ ): Promise<void> {
1296
+ const schema = schema_([
1297
+ field("attach_opaque_data", binary(), true),
1298
+ field("schema_name", utf8(), false),
1299
+ field("name", utf8(), false),
1300
+ field("macro_type", utf8(), false),
1301
+ field("parameters", list(field("item", utf8(), false)), false),
1302
+ field("definition", utf8(), false),
1303
+ field("on_conflict", utf8(), false),
1304
+ field("parameter_default_values", binary(), true),
1305
+ field("arguments_schema", binary(), true),
1306
+ field("transaction_opaque_data", binary(), true),
1307
+ ]);
1308
+ const innerBatch = batchFromColumns(
1309
+ {
1310
+ attach_opaque_data: [attachOpaqueData],
1311
+ schema_name: [schemaName],
1312
+ name: [name],
1313
+ macro_type: [macroType],
1314
+ parameters: [parameters],
1315
+ definition: [definition],
1316
+ on_conflict: [onConflict],
1317
+ parameter_default_values: [parameterDefaultValues ?? null],
1318
+ arguments_schema: [argumentsSchema ?? null],
1319
+ transaction_opaque_data: [transactionOpaqueData ?? null],
1320
+ },
1321
+ schema,
1322
+ );
1323
+ await this.rpc.call("catalog_macro_create", wrapRequest(innerBatch));
1324
+ }
1325
+
1326
+ /** Drop a macro by name. */
1327
+ async macroDrop(
1328
+ attachOpaqueData: AttachOpaqueData,
1329
+ schemaName: string,
1330
+ name: string,
1331
+ ignoreNotFound?: boolean,
1332
+ transactionOpaqueData?: TransactionOpaqueData,
1333
+ ): Promise<void> {
1334
+ await this.rpc.call("catalog_macro_drop", {
1335
+ attach_opaque_data: attachOpaqueData,
1336
+ schema_name: schemaName,
1337
+ name,
1338
+ ignore_not_found: ignoreNotFound ?? false,
1339
+ transaction_opaque_data: transactionOpaqueData ?? null,
1340
+ });
1341
+ }
1342
+
1343
+ /** Close the underlying RPC connection. */
1344
+ close(): void {
1345
+ this.rpc.close();
1346
+ }
1347
+ }
1348
+