@query-farm/vgi 0.29.0 → 0.29.1

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 (90) hide show
  1. package/README.md +26 -1
  2. package/dist/arguments/argument-spec.d.ts +4 -2
  3. package/dist/catalog/composite.d.ts +13 -13
  4. package/dist/catalog/descriptors.d.ts +7 -3
  5. package/dist/catalog/interface.d.ts +67 -40
  6. package/dist/catalog/read-only.d.ts +32 -15
  7. package/dist/client/client.d.ts +36 -29
  8. package/dist/client/types.d.ts +2 -0
  9. package/dist/client-entry.d.ts +1 -1
  10. package/dist/client-entry.js +441 -279
  11. package/dist/client-entry.js.map +12 -11
  12. package/dist/filter-pushdown/capabilities.d.ts +3 -0
  13. package/dist/filter-pushdown/deserialize.d.ts +14 -15
  14. package/dist/filter-pushdown/evaluate.d.ts +14 -44
  15. package/dist/filter-pushdown/index.d.ts +2 -2
  16. package/dist/filter-pushdown/types.d.ts +120 -68
  17. package/dist/functions/aggregate.d.ts +4 -0
  18. package/dist/functions/registry.d.ts +5 -5
  19. package/dist/functions/scalar.d.ts +6 -1
  20. package/dist/functions/table-buffering.d.ts +9 -0
  21. package/dist/functions/table-in-out.d.ts +13 -0
  22. package/dist/functions/table.d.ts +9 -2
  23. package/dist/functions/types.d.ts +22 -2
  24. package/dist/generated/vgi-client.d.ts +56 -27
  25. package/dist/generated/vgi-protocol-schemas.d.ts +15 -0
  26. package/dist/http/fetch.d.ts +7 -1
  27. package/dist/index.core.d.ts +2 -1
  28. package/dist/index.js +2317 -1354
  29. package/dist/index.js.map +43 -40
  30. package/dist/metadata/types.d.ts +15 -2
  31. package/dist/protocol/handlers/shared.d.ts +3 -1
  32. package/dist/protocol/serializers/splits.d.ts +1 -1
  33. package/dist/protocol/types.d.ts +9 -3
  34. package/dist/schema-path.d.ts +7 -0
  35. package/dist/serve-entry.d.ts +22 -1
  36. package/dist/serve-entry.js +412 -238
  37. package/dist/serve-entry.js.map +27 -26
  38. package/dist/split-token.d.ts +1 -1
  39. package/dist/types.d.ts +8 -0
  40. package/dist/worker-cf-entry.js +2030 -1201
  41. package/dist/worker-cf-entry.js.map +40 -36
  42. package/dist/worker.d.ts +1 -1
  43. package/package.json +3 -3
  44. package/src/arguments/argument-spec.ts +12 -4
  45. package/src/catalog/composite.ts +26 -26
  46. package/src/catalog/descriptors.ts +12 -3
  47. package/src/catalog/interface.ts +98 -48
  48. package/src/catalog/read-only.ts +124 -67
  49. package/src/client/client.ts +92 -72
  50. package/src/client/types.ts +2 -0
  51. package/src/client-entry.ts +2 -1
  52. package/src/filter-pushdown/capabilities.ts +13 -0
  53. package/src/filter-pushdown/collector.ts +22 -35
  54. package/src/filter-pushdown/deserialize.ts +657 -221
  55. package/src/filter-pushdown/evaluate.ts +410 -499
  56. package/src/filter-pushdown/index.ts +16 -2
  57. package/src/filter-pushdown/types.ts +88 -71
  58. package/src/functions/aggregate.ts +9 -1
  59. package/src/functions/registry.ts +24 -22
  60. package/src/functions/scalar.ts +26 -0
  61. package/src/functions/table-buffering.ts +46 -7
  62. package/src/functions/table-in-out.ts +187 -30
  63. package/src/functions/table.ts +58 -20
  64. package/src/functions/types.ts +24 -1
  65. package/src/generated/vgi-client.ts +61 -29
  66. package/src/generated/vgi-protocol-schemas.ts +225 -54
  67. package/src/http/fetch.ts +12 -0
  68. package/src/index.core.ts +2 -0
  69. package/src/metadata/resolve.ts +43 -1
  70. package/src/metadata/serialize.ts +43 -5
  71. package/src/metadata/types.ts +17 -1
  72. package/src/protocol/dispatch.ts +1 -1
  73. package/src/protocol/handlers/aggregate.ts +13 -7
  74. package/src/protocol/handlers/catalog/admin.ts +6 -6
  75. package/src/protocol/handlers/catalog/index_methods.ts +2 -2
  76. package/src/protocol/handlers/catalog/macro.ts +4 -4
  77. package/src/protocol/handlers/catalog/shared.ts +2 -0
  78. package/src/protocol/handlers/catalog/table.ts +16 -16
  79. package/src/protocol/handlers/catalog/view.ts +5 -5
  80. package/src/protocol/handlers/function.ts +2 -2
  81. package/src/protocol/handlers/shared.ts +12 -3
  82. package/src/protocol/handlers/table-buffering.ts +4 -4
  83. package/src/protocol/serializers/bind.ts +15 -6
  84. package/src/protocol/serializers/splits.ts +4 -2
  85. package/src/protocol/types.ts +9 -3
  86. package/src/schema-path.ts +25 -0
  87. package/src/serve-entry.ts +65 -3
  88. package/src/split-token.ts +2 -2
  89. package/src/types.ts +9 -0
  90. package/src/worker.ts +49 -4
package/dist/worker.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FunctionRegistry } from "./functions/registry.js";
2
2
  import type { VgiFunction } from "./functions/types.js";
3
- import type { CatalogDescriptor } from "./catalog/descriptors.js";
3
+ import { type CatalogDescriptor } from "./catalog/descriptors.js";
4
4
  import type { CatalogInterface } from "./catalog/interface.js";
5
5
  import { ReadOnlyCatalogInterface } from "./catalog/read-only.js";
6
6
  export interface WorkerConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@query-farm/vgi",
3
- "version": "0.29.0",
3
+ "version": "0.29.1",
4
4
  "description": "TypeScript framework for extending DuckDB with user-defined functions via the Vector Gateway Interface (VGI) over Arrow IPC.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Query Farm LLC (https://query.farm)",
@@ -90,12 +90,12 @@
90
90
  },
91
91
  "peerDependencies": {
92
92
  "@query-farm/apache-arrow": "^21.1.1",
93
- "@query-farm/vgi-rpc": ">=0.22.0 <0.23.0"
93
+ "@query-farm/vgi-rpc": ">=0.23.0 <0.24.0"
94
94
  },
95
95
  "devDependencies": {
96
96
  "@duckdb/node-api": "^1.5.5-r.4",
97
97
  "@query-farm/apache-arrow": "^21.1.1",
98
- "@query-farm/vgi-rpc": "^0.22.0",
98
+ "@query-farm/vgi-rpc": "^0.23.0",
99
99
  "@types/bun": "latest",
100
100
  "typescript": "^7.0.2"
101
101
  },
@@ -120,20 +120,28 @@ export function formatRange(
120
120
  /**
121
121
  * Encode raw {@link ArgumentConstraints} into the pre-computed constraint
122
122
  * fields of an {@link ArgumentSpec} (presence-only — absent constraints yield
123
- * no field). Mirrors Python's `_constraint_kwargs`.
123
+ * no field). `runtimeDefault`, when present, is authoritative for
124
+ * `defaultJson`, keeping runtime binding and discovery metadata in sync.
125
+ * Mirrors Python's `_constraint_kwargs`.
124
126
  */
125
127
  export function constraintSpecFields(
126
128
  constraints: ArgumentConstraints | undefined,
129
+ runtimeDefault?: unknown,
127
130
  ): Pick<ArgumentSpec, "defaultJson" | "choicesJson" | "rangeNotation" | "pattern"> {
128
131
  const fields: Pick<
129
132
  ArgumentSpec,
130
133
  "defaultJson" | "choicesJson" | "rangeNotation" | "pattern"
131
134
  > = {};
132
- if (constraints === undefined) return fields;
133
-
134
- if ("default" in constraints && constraints.default !== undefined) {
135
+ // argDefaults is the runtime source of truth. Surface that same value to
136
+ // discovery so callers do not have to duplicate it as
137
+ // argConstraints[name].default. Keep the latter as a backwards-compatible
138
+ // discovery-only fallback for APIs that do not have a runtime default.
139
+ if (runtimeDefault !== undefined) {
140
+ fields.defaultJson = safeJson(runtimeDefault);
141
+ } else if (constraints !== undefined && "default" in constraints && constraints.default !== undefined) {
135
142
  fields.defaultJson = safeJson(constraints.default);
136
143
  }
144
+ if (constraints === undefined) return fields;
137
145
  if (constraints.choices !== undefined) {
138
146
  fields.choicesJson = safeJson([...constraints.choices]);
139
147
  }
@@ -119,56 +119,56 @@ export class CompositeCatalogInterface extends CatalogInterface {
119
119
  return await this._route(attachOpaqueData).schemas(attachOpaqueData, transactionOpaqueData);
120
120
  }
121
121
 
122
- override async schemaGet(attachOpaqueData: AttachOpaqueData, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<SchemaInfo | null> {
123
- return await this._route(attachOpaqueData).schemaGet(attachOpaqueData, name, transactionOpaqueData);
122
+ override async schemaGet(attachOpaqueData: AttachOpaqueData, path: string[], transactionOpaqueData?: TransactionOpaqueData): Promise<SchemaInfo | null> {
123
+ return await this._route(attachOpaqueData).schemaGet(attachOpaqueData, path, transactionOpaqueData);
124
124
  }
125
125
 
126
- override async schemaContentsTables(attachOpaqueData: AttachOpaqueData, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo[]> {
127
- return await this._route(attachOpaqueData).schemaContentsTables(attachOpaqueData, name, transactionOpaqueData);
126
+ override async schemaContentsTables(attachOpaqueData: AttachOpaqueData, path: string[], transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo[]> {
127
+ return await this._route(attachOpaqueData).schemaContentsTables(attachOpaqueData, path, transactionOpaqueData);
128
128
  }
129
129
 
130
- override async schemaContentsViews(attachOpaqueData: AttachOpaqueData, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo[]> {
131
- return await this._route(attachOpaqueData).schemaContentsViews(attachOpaqueData, name, transactionOpaqueData);
130
+ override async schemaContentsViews(attachOpaqueData: AttachOpaqueData, path: string[], transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo[]> {
131
+ return await this._route(attachOpaqueData).schemaContentsViews(attachOpaqueData, path, transactionOpaqueData);
132
132
  }
133
133
 
134
- override async schemaContentsFunctions(attachOpaqueData: AttachOpaqueData, name: string, type: string, transactionOpaqueData?: TransactionOpaqueData): Promise<FunctionInfo[]> {
135
- return await this._route(attachOpaqueData).schemaContentsFunctions(attachOpaqueData, name, type, transactionOpaqueData);
134
+ override async schemaContentsFunctions(attachOpaqueData: AttachOpaqueData, path: string[], type: string, transactionOpaqueData?: TransactionOpaqueData): Promise<FunctionInfo[]> {
135
+ return await this._route(attachOpaqueData).schemaContentsFunctions(attachOpaqueData, path, type, transactionOpaqueData);
136
136
  }
137
137
 
138
- override async schemaContentsMacros(attachOpaqueData: AttachOpaqueData, name: string, type: string, transactionOpaqueData?: TransactionOpaqueData): Promise<MacroInfo[]> {
139
- return await this._route(attachOpaqueData).schemaContentsMacros(attachOpaqueData, name, type, transactionOpaqueData);
138
+ override async schemaContentsMacros(attachOpaqueData: AttachOpaqueData, path: string[], type: string, transactionOpaqueData?: TransactionOpaqueData): Promise<MacroInfo[]> {
139
+ return await this._route(attachOpaqueData).schemaContentsMacros(attachOpaqueData, path, type, transactionOpaqueData);
140
140
  }
141
141
 
142
- override async schemaContentsIndexes(attachOpaqueData: AttachOpaqueData, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<IndexInfo[]> {
143
- return await this._route(attachOpaqueData).schemaContentsIndexes(attachOpaqueData, name, transactionOpaqueData);
142
+ override async schemaContentsIndexes(attachOpaqueData: AttachOpaqueData, path: string[], transactionOpaqueData?: TransactionOpaqueData): Promise<IndexInfo[]> {
143
+ return await this._route(attachOpaqueData).schemaContentsIndexes(attachOpaqueData, path, transactionOpaqueData);
144
144
  }
145
145
 
146
- override async indexGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<IndexInfo | null> {
147
- return await this._route(attachOpaqueData).indexGet(attachOpaqueData, schemaName, name, transactionOpaqueData);
146
+ override async indexGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<IndexInfo | null> {
147
+ return await this._route(attachOpaqueData).indexGet(attachOpaqueData, schemaPath, name, transactionOpaqueData);
148
148
  }
149
149
 
150
- override async tableGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, atUnit?: string, atValue?: string, transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo | null> {
151
- return await this._route(attachOpaqueData).tableGet(attachOpaqueData, schemaName, name, atUnit, atValue, transactionOpaqueData);
150
+ override async tableGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, atUnit?: string, atValue?: string, transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo | null> {
151
+ return await this._route(attachOpaqueData).tableGet(attachOpaqueData, schemaPath, name, atUnit, atValue, transactionOpaqueData);
152
152
  }
153
153
 
154
- override async tableScanFunctionGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, atUnit?: string, atValue?: string, transactionOpaqueData?: TransactionOpaqueData): Promise<any> {
155
- return await this._route(attachOpaqueData).tableScanFunctionGet(attachOpaqueData, schemaName, name, atUnit, atValue, transactionOpaqueData);
154
+ override async tableScanFunctionGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, atUnit?: string, atValue?: string, transactionOpaqueData?: TransactionOpaqueData): Promise<any> {
155
+ return await this._route(attachOpaqueData).tableScanFunctionGet(attachOpaqueData, schemaPath, name, atUnit, atValue, transactionOpaqueData);
156
156
  }
157
157
 
158
- override async tableScanBranchesGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, atUnit?: string, atValue?: string, transactionOpaqueData?: TransactionOpaqueData): Promise<any> {
159
- return await this._route(attachOpaqueData).tableScanBranchesGet(attachOpaqueData, schemaName, name, atUnit, atValue, transactionOpaqueData);
158
+ override async tableScanBranchesGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, atUnit?: string, atValue?: string, transactionOpaqueData?: TransactionOpaqueData): Promise<any> {
159
+ return await this._route(attachOpaqueData).tableScanBranchesGet(attachOpaqueData, schemaPath, name, atUnit, atValue, transactionOpaqueData);
160
160
  }
161
161
 
162
- override async tableColumnStatisticsGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<{ bytes: Uint8Array; cacheMaxAgeSeconds: number | null } | null> {
163
- return await this._route(attachOpaqueData).tableColumnStatisticsGet(attachOpaqueData, schemaName, name, transactionOpaqueData);
162
+ override async tableColumnStatisticsGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<{ bytes: Uint8Array; cacheMaxAgeSeconds: number | null } | null> {
163
+ return await this._route(attachOpaqueData).tableColumnStatisticsGet(attachOpaqueData, schemaPath, name, transactionOpaqueData);
164
164
  }
165
165
 
166
- override async viewGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo | null> {
167
- return await this._route(attachOpaqueData).viewGet(attachOpaqueData, schemaName, name, transactionOpaqueData);
166
+ override async viewGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo | null> {
167
+ return await this._route(attachOpaqueData).viewGet(attachOpaqueData, schemaPath, name, transactionOpaqueData);
168
168
  }
169
169
 
170
- override async macroGet(attachOpaqueData: AttachOpaqueData, schemaName: string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<MacroInfo | null> {
171
- return await this._route(attachOpaqueData).macroGet(attachOpaqueData, schemaName, name, transactionOpaqueData);
170
+ override async macroGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[], name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<MacroInfo | null> {
171
+ return await this._route(attachOpaqueData).macroGet(attachOpaqueData, schemaPath, name, transactionOpaqueData);
172
172
  }
173
173
 
174
174
  override async copyFromFormats(attachOpaqueData: AttachOpaqueData, transactionOpaqueData?: TransactionOpaqueData): Promise<CopyFromFormatInfo[]> {
@@ -19,7 +19,7 @@ export interface ForeignKeyDef {
19
19
  columns: string[];
20
20
  referencedTable: string;
21
21
  referencedColumns: string[];
22
- referencedSchema?: string;
22
+ referencedSchemaPath?: string[];
23
23
  }
24
24
 
25
25
  export type DefaultValue = string | number | boolean | null;
@@ -137,7 +137,10 @@ export interface IndexDescriptor {
137
137
  }
138
138
 
139
139
  export interface SchemaDescriptor {
140
- name: string;
140
+ /** Raw identifier components, from the catalog root to this schema. */
141
+ path?: string[];
142
+ /** @deprecated Protocol 1.x compatibility; prefer `path: [name]`. */
143
+ name?: string;
141
144
  tables?: TableDescriptor[];
142
145
  views?: ViewDescriptor[];
143
146
  macros?: MacroDescriptor[];
@@ -147,6 +150,12 @@ export interface SchemaDescriptor {
147
150
  tags?: Record<string, string>;
148
151
  }
149
152
 
153
+ export function schemaDescriptorPath(schema: SchemaDescriptor): string[] {
154
+ if (schema.path) return schema.path;
155
+ if (schema.name) return [schema.name];
156
+ throw new Error("SchemaDescriptor requires a non-empty path");
157
+ }
158
+
150
159
  export interface SecretTypeDescriptor {
151
160
  name: string;
152
161
  description: string;
@@ -180,7 +189,7 @@ export interface CatalogDescriptor {
180
189
  * records on `catalog_attach.global_functions`.
181
190
  *
182
191
  * Every entry must also be declared in exactly one of this catalog's
183
- * schemas — the `FunctionInfo.schema_name` it carries is the bind-dispatch
192
+ * schemas — the `FunctionInfo.schema_path` it carries is the bind-dispatch
184
193
  * key, not a sentinel. Mirrors vgi-python's `Catalog.global_functions`.
185
194
  */
186
195
  globalFunctions?: VgiFunction[];
@@ -3,6 +3,7 @@
3
3
  // Matching Python's vgi/catalog/catalog_interface.py
4
4
 
5
5
  import { CatalogReadOnlyError } from "../errors.js";
6
+ import { schemaPathsEqual } from "../schema-path.js";
6
7
 
7
8
  export type AttachOpaqueData = Uint8Array;
8
9
  export type TransactionOpaqueData = Uint8Array;
@@ -179,11 +180,20 @@ export interface ScanFunctionResult {
179
180
  namedArguments: Record<string, unknown>;
180
181
  /** DuckDB extensions to load before calling the function. */
181
182
  requiredExtensions: string[];
183
+ /**
184
+ * Catalog schema path `functionName` is registered in (protocol 2.0.0). A function
185
+ * name is unique only within a schema, so a client that doesn't know this
186
+ * cannot tell which implementation a colliding name refers to — set it
187
+ * whenever the resolving code already knows the schema. `null` for a
188
+ * pre-1.5.0 peer, or when the resolved function is a native DuckDB function
189
+ * with no VGI-side schema of its own (e.g. `read_parquet`).
190
+ */
191
+ schemaPath: string[] | null;
182
192
  }
183
193
 
184
194
  /**
185
195
  * Encode a ScanFunctionResult wire record — the single-row batch shape
186
- * `{ function_name, arguments, required_extensions }` that
196
+ * `{ function_name, arguments, required_extensions, schema_path }` that
187
197
  * `catalog_table_scan_function_get` returns and that a function-backed table
188
198
  * inlines into `TableInfo.scan_function`.
189
199
  *
@@ -194,11 +204,15 @@ export interface ScanFunctionResult {
194
204
  *
195
205
  * `argumentsBytes` is the already-serialized inner arguments batch (one column
196
206
  * per argument: `arg_<index>` positional, bare name for named).
207
+ *
208
+ * `schemaPath` (protocol 2.0.0) identifies the schema that registers `functionName`;
209
+ * leave it null when the caller has no VGI-side schema to report.
197
210
  */
198
211
  export function encodeScanFunctionResult(
199
212
  functionName: string,
200
213
  argumentsBytes: Uint8Array,
201
214
  requiredExtensions: string[] = [],
215
+ schemaPath: string[] | null = null,
202
216
  ): Uint8Array {
203
217
  return serializeBatch(
204
218
  batchFromColumns(
@@ -206,6 +220,7 @@ export function encodeScanFunctionResult(
206
220
  function_name: [functionName],
207
221
  arguments: [argumentsBytes],
208
222
  required_extensions: [requiredExtensions],
223
+ schema_path: [schemaPath],
209
224
  },
210
225
  ScanFunctionResultSchema as any,
211
226
  ),
@@ -216,9 +231,10 @@ export function encodeScanFunctionResult(
216
231
  * Decode a ScanFunctionResult from a wire-shape inner result dict.
217
232
  *
218
233
  * The wire shape (per ScanFunctionResultSchema) is
219
- * `{ function_name: utf8, arguments: binary, required_extensions: list<utf8> }`,
220
- * where `arguments` is a serialized single-row batch with one column per
221
- * argument: `arg_<index>` for positional args, the bare name for named args.
234
+ * `{ function_name: utf8, arguments: binary, required_extensions: list<utf8>,
235
+ * schema_path: list<utf8>? }`, where `arguments` is a serialized single-row batch
236
+ * with one column per argument: `arg_<index>` for positional args, the bare
237
+ * name for named args.
222
238
  */
223
239
  export function decodeScanFunctionResult(inner: Record<string, unknown>): ScanFunctionResult {
224
240
  const argsBytes = inner.arguments;
@@ -252,6 +268,13 @@ export function decodeScanFunctionResult(inner: Record<string, unknown>): ScanFu
252
268
  positionalArguments,
253
269
  namedArguments,
254
270
  requiredExtensions,
271
+ // Absent for a pre-1.5.0 peer — the column itself won't be on the wire in
272
+ // that case, which reads the same as "present but null".
273
+ schemaPath: inner.schema_path == null
274
+ ? null
275
+ : (Array.isArray(inner.schema_path) ? inner.schema_path : [...(inner.schema_path as Iterable<unknown>)])
276
+ .filter((value) => value != null)
277
+ .map(String),
255
278
  };
256
279
  }
257
280
 
@@ -266,11 +289,23 @@ export function decodeScanFunctionResult(inner: Record<string, unknown>): ScanFu
266
289
  * `ScanBranch` expects, so it passes through untouched.
267
290
  */
268
291
  export function singleBranchResult(
269
- legacy: { function_name: string; arguments: unknown; required_extensions?: string[] },
292
+ legacy: {
293
+ function_name: string;
294
+ arguments: unknown;
295
+ required_extensions?: string[];
296
+ schema_path?: string[] | null;
297
+ },
270
298
  ): { branches: Uint8Array[]; required_extensions: string[] } {
271
299
  return {
272
300
  branches: [
273
- encodeScanBranch({ function_name: legacy.function_name, arguments: toUint8Array(legacy.arguments) }),
301
+ encodeScanBranch({
302
+ function_name: legacy.function_name,
303
+ arguments: toUint8Array(legacy.arguments),
304
+ // This shim has no independent way to know the function's own schema;
305
+ // propagate whatever `tableScanFunctionGet` already resolved rather
306
+ // than dropping a value the legacy response carries.
307
+ schema_path: legacy.schema_path ?? null,
308
+ }),
274
309
  ],
275
310
  required_extensions: legacy.required_extensions ?? [],
276
311
  };
@@ -296,11 +331,12 @@ function encodeScanBranch(cols: {
296
331
  branch_filter?: string | null;
297
332
  writable?: boolean;
298
333
  source_catalog?: string | null;
299
- source_schema?: string | null;
334
+ source_schema_path?: string[] | null;
300
335
  source_table?: string | null;
301
336
  format_name?: string | null;
302
337
  format_locations?: string[] | null;
303
338
  format_options?: Uint8Array | null;
339
+ schema_path?: string[] | null;
304
340
  }): Uint8Array {
305
341
  return serializeBatch(
306
342
  batchFromColumns(
@@ -310,11 +346,12 @@ function encodeScanBranch(cols: {
310
346
  branch_filter: [cols.branch_filter ?? null],
311
347
  writable: [cols.writable ?? false],
312
348
  source_catalog: [cols.source_catalog ?? null],
313
- source_schema: [cols.source_schema ?? null],
349
+ source_schema_path: [cols.source_schema_path ?? null],
314
350
  source_table: [cols.source_table ?? null],
315
351
  format_name: [cols.format_name ?? null],
316
352
  format_locations: [cols.format_locations ?? null],
317
353
  format_options: [cols.format_options ?? null],
354
+ schema_path: [cols.schema_path ?? null],
318
355
  },
319
356
  ScanBranchSchema as any,
320
357
  ),
@@ -354,12 +391,25 @@ export interface ScanBranchInput {
354
391
  /**
355
392
  * Catalog-table branch (lakehouse federation): leave `functionName` empty and
356
393
  * set these to scan the base table
357
- * `sourceCatalog.sourceSchema.sourceTable` in a companion catalog instead of
394
+ * `sourceCatalog.sourceSchemaPath.sourceTable` in a companion catalog instead of
358
395
  * calling a table function.
359
396
  */
360
397
  sourceCatalog?: string | null;
361
- sourceSchema?: string | null;
398
+ sourceSchemaPath?: string[] | null;
362
399
  sourceTable?: string | null;
400
+ /**
401
+ * Function branch only — the catalog schema that registers `functionName`
402
+ * (protocol 2.0.0). Without it a client has to guess (the table's own
403
+ * schema, then the catalog's default schema) which implementation
404
+ * `functionName` refers to when the same name is registered in more than
405
+ * one schema. Leave unset for a catalog-table or FORMAT branch, and for a
406
+ * branch that delegates straight to a native DuckDB function
407
+ * (`read_parquet`, `read_csv_auto`, `iceberg_scan`, …) — neither has a
408
+ * VGI-side schema of its own to report, which is why the field is optional
409
+ * rather than required. Not to be confused with `sourceSchemaPath` above, which
410
+ * names a catalog-table branch's *source table's* schema.
411
+ */
412
+ schemaPath?: string[] | null;
363
413
  }
364
414
 
365
415
  /**
@@ -431,11 +481,12 @@ export function buildScanBranchesResult(
431
481
  branch_filter: branch.branchFilter,
432
482
  writable: branch.writable,
433
483
  source_catalog: branch.sourceCatalog,
434
- source_schema: branch.sourceSchema,
484
+ source_schema_path: branch.sourceSchemaPath,
435
485
  source_table: branch.sourceTable,
436
486
  format_name: branch.formatName,
437
487
  format_locations: branch.formatLocations,
438
488
  format_options: branch.formatOptions,
489
+ schema_path: branch.schemaPath,
439
490
  }),
440
491
  );
441
492
  return {
@@ -517,18 +568,18 @@ export abstract class CatalogInterface {
517
568
  }
518
569
  schemaGet(
519
570
  attachOpaqueData: AttachOpaqueData,
520
- name: string,
571
+ path: string[],
521
572
  transactionOpaqueData?: TransactionOpaqueData
522
573
  ): Awaitable<SchemaInfo | null> {
523
574
  const all = this.schemas(attachOpaqueData, transactionOpaqueData);
524
575
  if (isPromise(all)) {
525
- return all.then((arr) => arr.find((s) => s.name === name) ?? null);
576
+ return all.then((arr) => arr.find((s) => schemaPathsEqual(s.path, path)) ?? null);
526
577
  }
527
- return all.find((s) => s.name === name) ?? null;
578
+ return all.find((s) => schemaPathsEqual(s.path, path)) ?? null;
528
579
  }
529
580
  schemaCreate(
530
581
  attachOpaqueData: AttachOpaqueData,
531
- name: string,
582
+ path: string[],
532
583
  comment?: string | null,
533
584
  tags?: any,
534
585
  transactionOpaqueData?: TransactionOpaqueData
@@ -537,7 +588,7 @@ export abstract class CatalogInterface {
537
588
  }
538
589
  schemaDrop(
539
590
  attachOpaqueData: AttachOpaqueData,
540
- name: string,
591
+ path: string[],
541
592
  ignoreNotFound?: boolean,
542
593
  cascade?: boolean,
543
594
  transactionOpaqueData?: TransactionOpaqueData
@@ -546,21 +597,21 @@ export abstract class CatalogInterface {
546
597
  }
547
598
  schemaContentsTables(
548
599
  attachOpaqueData: AttachOpaqueData,
549
- name: string,
600
+ path: string[],
550
601
  transactionOpaqueData?: TransactionOpaqueData
551
602
  ): Awaitable<TableInfo[]> {
552
603
  return [];
553
604
  }
554
605
  schemaContentsViews(
555
606
  attachOpaqueData: AttachOpaqueData,
556
- name: string,
607
+ path: string[],
557
608
  transactionOpaqueData?: TransactionOpaqueData
558
609
  ): Awaitable<ViewInfo[]> {
559
610
  return [];
560
611
  }
561
612
  schemaContentsFunctions(
562
613
  attachOpaqueData: AttachOpaqueData,
563
- name: string,
614
+ path: string[],
564
615
  type: string,
565
616
  transactionOpaqueData?: TransactionOpaqueData
566
617
  ): Awaitable<FunctionInfo[]> {
@@ -568,7 +619,7 @@ export abstract class CatalogInterface {
568
619
  }
569
620
  tableGet(
570
621
  attachOpaqueData: AttachOpaqueData,
571
- schemaName: string,
622
+ schemaPath: string[],
572
623
  name: string,
573
624
  atUnit?: string,
574
625
  atValue?: string,
@@ -578,7 +629,7 @@ export abstract class CatalogInterface {
578
629
  }
579
630
  tableCreate(
580
631
  attachOpaqueData: AttachOpaqueData,
581
- schemaName: string,
632
+ schemaPath: string[],
582
633
  name: string,
583
634
  columns: Uint8Array,
584
635
  onConflict: string,
@@ -591,7 +642,7 @@ export abstract class CatalogInterface {
591
642
  }
592
643
  tableDrop(
593
644
  attachOpaqueData: AttachOpaqueData,
594
- schemaName: string,
645
+ schemaPath: string[],
595
646
  name: string,
596
647
  ignoreNotFound?: boolean,
597
648
  transactionOpaqueData?: TransactionOpaqueData
@@ -600,7 +651,7 @@ export abstract class CatalogInterface {
600
651
  }
601
652
  tableScanFunctionGet(
602
653
  attachOpaqueData: AttachOpaqueData,
603
- schemaName: string,
654
+ schemaPath: string[],
604
655
  name: string,
605
656
  atUnit?: string,
606
657
  atValue?: string,
@@ -616,14 +667,14 @@ export abstract class CatalogInterface {
616
667
  */
617
668
  tableScanBranchesGet(
618
669
  attachOpaqueData: AttachOpaqueData,
619
- schemaName: string,
670
+ schemaPath: string[],
620
671
  name: string,
621
672
  atUnit?: string,
622
673
  atValue?: string,
623
674
  transactionOpaqueData?: TransactionOpaqueData
624
675
  ): Awaitable<any> {
625
676
  const legacy = this.tableScanFunctionGet(
626
- attachOpaqueData, schemaName, name, atUnit, atValue, transactionOpaqueData,
677
+ attachOpaqueData, schemaPath, name, atUnit, atValue, transactionOpaqueData,
627
678
  );
628
679
  return isPromise(legacy)
629
680
  ? legacy.then((r) => singleBranchResult(r))
@@ -639,7 +690,7 @@ export abstract class CatalogInterface {
639
690
  */
640
691
  tableColumnStatisticsGet(
641
692
  attachOpaqueData: AttachOpaqueData,
642
- schemaName: string,
693
+ schemaPath: string[],
643
694
  name: string,
644
695
  transactionOpaqueData?: TransactionOpaqueData,
645
696
  ): Awaitable<{ bytes: Uint8Array; cacheMaxAgeSeconds: number | null } | null> {
@@ -647,7 +698,7 @@ export abstract class CatalogInterface {
647
698
  }
648
699
  tableCommentSet(
649
700
  attachOpaqueData: AttachOpaqueData,
650
- schemaName: string,
701
+ schemaPath: string[],
651
702
  name: string,
652
703
  comment?: string | null,
653
704
  ignoreNotFound?: boolean,
@@ -657,7 +708,7 @@ export abstract class CatalogInterface {
657
708
  }
658
709
  tableRename(
659
710
  attachOpaqueData: AttachOpaqueData,
660
- schemaName: string,
711
+ schemaPath: string[],
661
712
  name: string,
662
713
  newName: string,
663
714
  ignoreNotFound?: boolean,
@@ -667,7 +718,7 @@ export abstract class CatalogInterface {
667
718
  }
668
719
  tableColumnAdd(
669
720
  attachOpaqueData: AttachOpaqueData,
670
- schemaName: string,
721
+ schemaPath: string[],
671
722
  name: string,
672
723
  columnName: string,
673
724
  columnType: string,
@@ -679,7 +730,7 @@ export abstract class CatalogInterface {
679
730
  }
680
731
  tableColumnDrop(
681
732
  attachOpaqueData: AttachOpaqueData,
682
- schemaName: string,
733
+ schemaPath: string[],
683
734
  name: string,
684
735
  columnName: string,
685
736
  ignoreNotFound?: boolean,
@@ -689,7 +740,7 @@ export abstract class CatalogInterface {
689
740
  }
690
741
  tableColumnRename(
691
742
  attachOpaqueData: AttachOpaqueData,
692
- schemaName: string,
743
+ schemaPath: string[],
693
744
  name: string,
694
745
  columnName: string,
695
746
  newName: string,
@@ -700,7 +751,7 @@ export abstract class CatalogInterface {
700
751
  }
701
752
  tableColumnDefaultSet(
702
753
  attachOpaqueData: AttachOpaqueData,
703
- schemaName: string,
754
+ schemaPath: string[],
704
755
  name: string,
705
756
  columnName: string,
706
757
  defaultValue: string,
@@ -711,7 +762,7 @@ export abstract class CatalogInterface {
711
762
  }
712
763
  tableColumnDefaultDrop(
713
764
  attachOpaqueData: AttachOpaqueData,
714
- schemaName: string,
765
+ schemaPath: string[],
715
766
  name: string,
716
767
  columnName: string,
717
768
  ignoreNotFound?: boolean,
@@ -721,7 +772,7 @@ export abstract class CatalogInterface {
721
772
  }
722
773
  tableColumnTypeChange(
723
774
  attachOpaqueData: AttachOpaqueData,
724
- schemaName: string,
775
+ schemaPath: string[],
725
776
  name: string,
726
777
  columnName: string,
727
778
  newType: string,
@@ -732,7 +783,7 @@ export abstract class CatalogInterface {
732
783
  }
733
784
  tableNotNullSet(
734
785
  attachOpaqueData: AttachOpaqueData,
735
- schemaName: string,
786
+ schemaPath: string[],
736
787
  name: string,
737
788
  columnName: string,
738
789
  ignoreNotFound?: boolean,
@@ -742,7 +793,7 @@ export abstract class CatalogInterface {
742
793
  }
743
794
  tableNotNullDrop(
744
795
  attachOpaqueData: AttachOpaqueData,
745
- schemaName: string,
796
+ schemaPath: string[],
746
797
  name: string,
747
798
  columnName: string,
748
799
  ignoreNotFound?: boolean,
@@ -752,7 +803,7 @@ export abstract class CatalogInterface {
752
803
  }
753
804
  viewGet(
754
805
  attachOpaqueData: AttachOpaqueData,
755
- schemaName: string,
806
+ schemaPath: string[],
756
807
  name: string,
757
808
  transactionOpaqueData?: TransactionOpaqueData
758
809
  ): Awaitable<ViewInfo | null> {
@@ -760,7 +811,7 @@ export abstract class CatalogInterface {
760
811
  }
761
812
  viewCreate(
762
813
  attachOpaqueData: AttachOpaqueData,
763
- schemaName: string,
814
+ schemaPath: string[],
764
815
  name: string,
765
816
  definition: string,
766
817
  onConflict: string,
@@ -770,7 +821,7 @@ export abstract class CatalogInterface {
770
821
  }
771
822
  viewDrop(
772
823
  attachOpaqueData: AttachOpaqueData,
773
- schemaName: string,
824
+ schemaPath: string[],
774
825
  name: string,
775
826
  ignoreNotFound?: boolean,
776
827
  transactionOpaqueData?: TransactionOpaqueData
@@ -779,7 +830,7 @@ export abstract class CatalogInterface {
779
830
  }
780
831
  viewRename(
781
832
  attachOpaqueData: AttachOpaqueData,
782
- schemaName: string,
833
+ schemaPath: string[],
783
834
  name: string,
784
835
  newName: string,
785
836
  ignoreNotFound?: boolean,
@@ -789,7 +840,7 @@ export abstract class CatalogInterface {
789
840
  }
790
841
  viewCommentSet(
791
842
  attachOpaqueData: AttachOpaqueData,
792
- schemaName: string,
843
+ schemaPath: string[],
793
844
  name: string,
794
845
  comment?: string | null,
795
846
  ignoreNotFound?: boolean,
@@ -799,7 +850,7 @@ export abstract class CatalogInterface {
799
850
  }
800
851
  macroGet(
801
852
  attachOpaqueData: AttachOpaqueData,
802
- schemaName: string,
853
+ schemaPath: string[],
803
854
  name: string,
804
855
  transactionOpaqueData?: TransactionOpaqueData
805
856
  ): Awaitable<MacroInfo | null> {
@@ -807,7 +858,7 @@ export abstract class CatalogInterface {
807
858
  }
808
859
  macroCreate(
809
860
  attachOpaqueData: AttachOpaqueData,
810
- schemaName: string,
861
+ schemaPath: string[],
811
862
  name: string,
812
863
  macroType: MacroType,
813
864
  parameters: string[],
@@ -827,7 +878,7 @@ export abstract class CatalogInterface {
827
878
  }
828
879
  macroDrop(
829
880
  attachOpaqueData: AttachOpaqueData,
830
- schemaName: string,
881
+ schemaPath: string[],
831
882
  name: string,
832
883
  ignoreNotFound?: boolean,
833
884
  transactionOpaqueData?: TransactionOpaqueData
@@ -836,7 +887,7 @@ export abstract class CatalogInterface {
836
887
  }
837
888
  schemaContentsMacros(
838
889
  attachOpaqueData: AttachOpaqueData,
839
- name: string,
890
+ path: string[],
840
891
  type: string,
841
892
  transactionOpaqueData?: TransactionOpaqueData
842
893
  ): Awaitable<MacroInfo[]> {
@@ -844,14 +895,14 @@ export abstract class CatalogInterface {
844
895
  }
845
896
  schemaContentsIndexes(
846
897
  attachOpaqueData: AttachOpaqueData,
847
- name: string,
898
+ path: string[],
848
899
  transactionOpaqueData?: TransactionOpaqueData
849
900
  ): Awaitable<IndexInfo[]> {
850
901
  return [];
851
902
  }
852
903
  indexGet(
853
904
  attachOpaqueData: AttachOpaqueData,
854
- schemaName: string,
905
+ schemaPath: string[],
855
906
  name: string,
856
907
  transactionOpaqueData?: TransactionOpaqueData
857
908
  ): Awaitable<IndexInfo | null> {
@@ -882,4 +933,3 @@ export abstract class CatalogInterface {
882
933
  transactionOpaqueData: TransactionOpaqueData
883
934
  ): Awaitable<void> {}
884
935
  }
885
-