@query-farm/vgi 0.29.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/arguments/argument-spec.d.ts +4 -2
- package/dist/catalog/composite.d.ts +13 -13
- package/dist/catalog/descriptors.d.ts +7 -3
- package/dist/catalog/interface.d.ts +67 -40
- package/dist/catalog/read-only.d.ts +32 -15
- package/dist/client/client.d.ts +36 -29
- package/dist/client/types.d.ts +2 -0
- package/dist/client-entry.d.ts +1 -1
- package/dist/client-entry.js +442 -283
- package/dist/client-entry.js.map +12 -11
- package/dist/filter-pushdown/capabilities.d.ts +3 -0
- package/dist/filter-pushdown/deserialize.d.ts +14 -15
- package/dist/filter-pushdown/evaluate.d.ts +14 -44
- package/dist/filter-pushdown/index.d.ts +2 -2
- package/dist/filter-pushdown/types.d.ts +120 -68
- package/dist/functions/aggregate.d.ts +4 -0
- package/dist/functions/registry.d.ts +5 -5
- package/dist/functions/scalar.d.ts +6 -1
- package/dist/functions/table-buffering.d.ts +9 -0
- package/dist/functions/table-in-out.d.ts +13 -0
- package/dist/functions/table.d.ts +9 -2
- package/dist/functions/types.d.ts +22 -2
- package/dist/generated/vgi-client.d.ts +57 -31
- package/dist/generated/vgi-protocol-schemas.d.ts +15 -0
- package/dist/http/fetch.d.ts +7 -1
- package/dist/index.core.d.ts +2 -1
- package/dist/index.js +2320 -1363
- package/dist/index.js.map +44 -41
- package/dist/metadata/types.d.ts +15 -2
- package/dist/protocol/handlers/shared.d.ts +3 -1
- package/dist/protocol/serializers/splits.d.ts +1 -1
- package/dist/protocol/types.d.ts +9 -3
- package/dist/schema-path.d.ts +7 -0
- package/dist/serve-entry.d.ts +22 -1
- package/dist/serve-entry.js +414 -243
- package/dist/serve-entry.js.map +28 -27
- package/dist/split-token.d.ts +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/worker-cf-entry.js +2033 -1210
- package/dist/worker-cf-entry.js.map +41 -37
- package/dist/worker.d.ts +1 -1
- package/package.json +3 -3
- package/src/arguments/argument-spec.ts +12 -4
- package/src/catalog/composite.ts +26 -26
- package/src/catalog/descriptors.ts +12 -3
- package/src/catalog/interface.ts +98 -48
- package/src/catalog/read-only.ts +125 -71
- package/src/client/client.ts +92 -72
- package/src/client/types.ts +2 -0
- package/src/client-entry.ts +2 -1
- package/src/filter-pushdown/capabilities.ts +13 -0
- package/src/filter-pushdown/collector.ts +22 -35
- package/src/filter-pushdown/deserialize.ts +657 -221
- package/src/filter-pushdown/evaluate.ts +410 -499
- package/src/filter-pushdown/index.ts +16 -2
- package/src/filter-pushdown/types.ts +88 -71
- package/src/functions/aggregate.ts +9 -1
- package/src/functions/registry.ts +24 -22
- package/src/functions/scalar.ts +26 -0
- package/src/functions/table-buffering.ts +46 -7
- package/src/functions/table-in-out.ts +187 -30
- package/src/functions/table.ts +58 -20
- package/src/functions/types.ts +24 -1
- package/src/generated/vgi-client.ts +62 -33
- package/src/generated/vgi-protocol-schemas.ts +226 -58
- package/src/http/client-bundle.ts +1 -1
- package/src/http/fetch.ts +12 -0
- package/src/index.core.ts +2 -0
- package/src/metadata/resolve.ts +43 -1
- package/src/metadata/serialize.ts +43 -5
- package/src/metadata/types.ts +17 -1
- package/src/protocol/dispatch.ts +1 -1
- package/src/protocol/handlers/aggregate.ts +13 -7
- package/src/protocol/handlers/catalog/admin.ts +6 -6
- package/src/protocol/handlers/catalog/index_methods.ts +2 -2
- package/src/protocol/handlers/catalog/macro.ts +4 -4
- package/src/protocol/handlers/catalog/shared.ts +2 -0
- package/src/protocol/handlers/catalog/table.ts +16 -16
- package/src/protocol/handlers/catalog/view.ts +5 -5
- package/src/protocol/handlers/function.ts +2 -2
- package/src/protocol/handlers/shared.ts +12 -3
- package/src/protocol/handlers/table-buffering.ts +4 -4
- package/src/protocol/serializers/bind.ts +15 -6
- package/src/protocol/serializers/splits.ts +4 -2
- package/src/protocol/types.ts +9 -3
- package/src/schema-path.ts +25 -0
- package/src/serve-entry.ts +65 -3
- package/src/split-token.ts +2 -2
- package/src/types.ts +9 -0
- package/src/worker.ts +49 -4
package/src/catalog/read-only.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright 2025, 2026 Query Farm LLC - https://query.farm
|
|
2
2
|
// ReadOnlyCatalogInterface: derives catalog from registered functions + descriptors.
|
|
3
3
|
|
|
4
|
-
import { type VgiSchema, schema as schema_, type VgiField, field, type VgiDataType, int64, utf8, binary, bool, nullType, list, isInt } from "../arrow/index.js";
|
|
4
|
+
import { type VgiSchema, schema as schema_, type VgiField, field, type VgiDataType, int64, utf8, binary, bool, nullType, list, isInt, typeSignature } from "../arrow/index.js";
|
|
5
5
|
import {
|
|
6
6
|
CatalogInterface,
|
|
7
7
|
type AttachOpaqueData,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
encodeScanFunctionResult,
|
|
21
21
|
encodeFunctionInfo,
|
|
22
22
|
} from "./interface.js";
|
|
23
|
-
import type
|
|
23
|
+
import { schemaDescriptorPath, type CatalogDescriptor, type SchemaDescriptor, type TableDescriptor, type ViewDescriptor, type MacroDescriptor, type SettingDescriptor, type SecretTypeDescriptor, type ForeignKeyDef, type DefaultValue } from "./descriptors.js";
|
|
24
24
|
import { serializeColumnStatistics } from "../util/statistics.js";
|
|
25
25
|
import type { VgiFunction } from "../functions/types.js";
|
|
26
26
|
import type { FunctionRegistry } from "../functions/registry.js";
|
|
@@ -30,6 +30,8 @@ import { resolveMetadata } from "../metadata/resolve.js";
|
|
|
30
30
|
import { type AttachOptionSpec, validateRequiredAttachOptions } from "./attach-option.js";
|
|
31
31
|
import { FunctionStability, NullHandling, OrderPreservation, DEFAULT_MAX_WORKERS } from "../types.js";
|
|
32
32
|
import { Arguments } from "../arguments/arguments.js";
|
|
33
|
+
import { normalizeSchemaPath, schemaPathDisplay, schemaPathsEqual } from "../schema-path.js";
|
|
34
|
+
import { ForeignKeyInfoSchema } from "../generated/vgi-protocol-schemas.js";
|
|
33
35
|
|
|
34
36
|
export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
35
37
|
private _descriptor: CatalogDescriptor;
|
|
@@ -46,16 +48,39 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
46
48
|
// is declared in more than one schema. Table-backing functions are indexed
|
|
47
49
|
// under their table's schema for the same reason.
|
|
48
50
|
for (const sch of descriptor.schemas ?? []) {
|
|
49
|
-
for (const fn of sch.functions ?? []) registry.registerInSchema(fn, sch
|
|
51
|
+
for (const fn of sch.functions ?? []) registry.registerInSchema(fn, schemaDescriptorPath(sch), descriptor.name);
|
|
50
52
|
for (const tbl of sch.tables ?? []) {
|
|
51
53
|
for (const key of ["function", "insertFunction", "updateFunction", "deleteFunction"] as const) {
|
|
52
54
|
const fn = (tbl as any)[key];
|
|
53
|
-
if (fn) registry.registerInSchema(fn, sch
|
|
55
|
+
if (fn) registry.registerInSchema(fn, schemaDescriptorPath(sch), descriptor.name);
|
|
54
56
|
}
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Which schema declares `fn`, by object identity, or null if no schema does.
|
|
63
|
+
*
|
|
64
|
+
* A table's backing function is NOT necessarily declared in the table's own
|
|
65
|
+
* schema — `data.large_sequence` scans via `sequence`, which the example
|
|
66
|
+
* catalog declares in `main`. Populating `ScanFunctionResult.schemaPath` /
|
|
67
|
+
* `ScanBranch.schemaPath` (protocol 2.0.0) correctly needs this reverse
|
|
68
|
+
* lookup, not the `schemaPath` parameter of the call being served.
|
|
69
|
+
*
|
|
70
|
+
* Identity, not name: the whole point of the field is to disambiguate one
|
|
71
|
+
* name declared in several schemas, so a name match would answer the very
|
|
72
|
+
* question being asked. Null when the function is deliberately unadvertised
|
|
73
|
+
* (`cache_multicol`) — it has no schema to report and the client falls back
|
|
74
|
+
* to its own heuristic, which is exactly the pre-1.5.0 behaviour.
|
|
75
|
+
* Mirrors vgi-python's `ReadOnlyCatalogInterface._schema_for_function`.
|
|
76
|
+
*/
|
|
77
|
+
private _schemaForFunction(fn: VgiFunction): string[] | null {
|
|
78
|
+
for (const sch of this._descriptor.schemas ?? []) {
|
|
79
|
+
if ((sch.functions ?? []).includes(fn)) return schemaDescriptorPath(sch);
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
59
84
|
catalogs(): string[] {
|
|
60
85
|
return [this._descriptor.name];
|
|
61
86
|
}
|
|
@@ -168,7 +193,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
168
193
|
schemas(attachOpaqueData: AttachOpaqueData, transactionOpaqueData?: TransactionOpaqueData): SchemaInfo[] {
|
|
169
194
|
return this._descriptor.schemas.map((s) => ({
|
|
170
195
|
attach_opaque_data: attachOpaqueData,
|
|
171
|
-
|
|
196
|
+
path: schemaDescriptorPath(s),
|
|
172
197
|
comment: s.comment ?? null,
|
|
173
198
|
tags: s.tags ?? {},
|
|
174
199
|
estimated_object_count: this._estimatedObjectCount(s),
|
|
@@ -199,14 +224,14 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
199
224
|
|
|
200
225
|
override schemaGet(
|
|
201
226
|
attachOpaqueData: AttachOpaqueData,
|
|
202
|
-
|
|
227
|
+
path: string[],
|
|
203
228
|
transactionOpaqueData?: TransactionOpaqueData
|
|
204
229
|
): SchemaInfo | null {
|
|
205
|
-
const desc = this._descriptor.schemas.find((s) => s
|
|
230
|
+
const desc = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), path));
|
|
206
231
|
if (!desc) return null;
|
|
207
232
|
return {
|
|
208
233
|
attach_opaque_data: attachOpaqueData,
|
|
209
|
-
|
|
234
|
+
path: schemaDescriptorPath(desc),
|
|
210
235
|
comment: desc.comment ?? null,
|
|
211
236
|
tags: desc.tags ?? {},
|
|
212
237
|
estimated_object_count: this._estimatedObjectCount(desc),
|
|
@@ -215,10 +240,10 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
215
240
|
|
|
216
241
|
override async schemaContentsTables(
|
|
217
242
|
attachOpaqueData: AttachOpaqueData,
|
|
218
|
-
|
|
243
|
+
path: string[],
|
|
219
244
|
transactionOpaqueData?: TransactionOpaqueData
|
|
220
245
|
): Promise<TableInfo[]> {
|
|
221
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
246
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), path));
|
|
222
247
|
if (!schema || !schema.tables) return [];
|
|
223
248
|
|
|
224
249
|
return await Promise.all(schema.tables.map(async (t) => {
|
|
@@ -273,7 +298,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
273
298
|
const notNullIndices = resolveIndices(colSchema, t.notNull);
|
|
274
299
|
const uniqueIndices = resolveIndexGroups(colSchema, t.unique);
|
|
275
300
|
const pkIndices = resolveIndexGroups(colSchema, t.primaryKey);
|
|
276
|
-
const fkBytes = serializeForeignKeys(t.foreignKey,
|
|
301
|
+
const fkBytes = serializeForeignKeys(t.foreignKey, path);
|
|
277
302
|
|
|
278
303
|
// Validate required_filters: an AND (outer list) of OR-groups (inner
|
|
279
304
|
// lists). Each OR-group must be non-empty, and the leading dotted segment
|
|
@@ -311,17 +336,14 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
311
336
|
comment: t.comment ?? null,
|
|
312
337
|
tags: t.tags ?? {},
|
|
313
338
|
name: t.name,
|
|
314
|
-
|
|
339
|
+
schema_path: normalizeSchemaPath(path),
|
|
315
340
|
columns,
|
|
316
341
|
not_null_constraints: notNullIndices,
|
|
317
342
|
unique_constraints: uniqueIndices,
|
|
318
343
|
check_constraints: t.check ?? [],
|
|
319
344
|
primary_key_constraints: pkIndices,
|
|
320
345
|
foreign_key_constraints: fkBytes,
|
|
321
|
-
|
|
322
|
-
supports_update: false,
|
|
323
|
-
supports_delete: false,
|
|
324
|
-
supports_returning: false,
|
|
346
|
+
write_result_modes: {},
|
|
325
347
|
supports_column_statistics: t.statistics != null && Object.keys(t.statistics).length > 0,
|
|
326
348
|
// Inline scan_function only for purely function-backed tables.
|
|
327
349
|
// When explicit `columns` are also declared, the table is on the
|
|
@@ -329,7 +351,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
329
351
|
// shadow the per-call dispatch and prevent workers from varying
|
|
330
352
|
// the scan function (e.g. version-resolved time-travel).
|
|
331
353
|
scan_function: t.function && !t.columns
|
|
332
|
-
? inlineScanFunction(t.function, t.arguments)
|
|
354
|
+
? inlineScanFunction(t.function, t.arguments, this._schemaForFunction(t.function))
|
|
333
355
|
: null,
|
|
334
356
|
insert_function: null,
|
|
335
357
|
update_function: null,
|
|
@@ -347,11 +369,11 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
347
369
|
|
|
348
370
|
override tableColumnStatisticsGet(
|
|
349
371
|
attachOpaqueData: AttachOpaqueData,
|
|
350
|
-
|
|
372
|
+
schemaPath: string[],
|
|
351
373
|
name: string,
|
|
352
374
|
transactionOpaqueData?: TransactionOpaqueData,
|
|
353
375
|
): { bytes: Uint8Array; cacheMaxAgeSeconds: number | null } | null {
|
|
354
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
376
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), schemaPath));
|
|
355
377
|
if (!schema || !schema.tables) return null;
|
|
356
378
|
const table = schema.tables.find((t) => t.name === name);
|
|
357
379
|
if (!table || !table.statistics) return null;
|
|
@@ -365,17 +387,17 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
365
387
|
|
|
366
388
|
override schemaContentsViews(
|
|
367
389
|
attachOpaqueData: AttachOpaqueData,
|
|
368
|
-
|
|
390
|
+
path: string[],
|
|
369
391
|
transactionOpaqueData?: TransactionOpaqueData
|
|
370
392
|
): ViewInfo[] {
|
|
371
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
393
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), path));
|
|
372
394
|
if (!schema || !schema.views) return [];
|
|
373
395
|
|
|
374
396
|
return schema.views.map((v) => ({
|
|
375
397
|
comment: v.comment ?? null,
|
|
376
398
|
tags: v.tags ?? {},
|
|
377
399
|
name: v.name,
|
|
378
|
-
|
|
400
|
+
schema_path: normalizeSchemaPath(path),
|
|
379
401
|
definition: v.definition,
|
|
380
402
|
column_comments: v.columnComments ?? {},
|
|
381
403
|
}));
|
|
@@ -383,11 +405,11 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
383
405
|
|
|
384
406
|
override schemaContentsFunctions(
|
|
385
407
|
attachOpaqueData: AttachOpaqueData,
|
|
386
|
-
|
|
408
|
+
path: string[],
|
|
387
409
|
type: string,
|
|
388
410
|
transactionOpaqueData?: TransactionOpaqueData
|
|
389
411
|
): FunctionInfo[] {
|
|
390
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
412
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), path));
|
|
391
413
|
if (!schema || !schema.functions) return [];
|
|
392
414
|
|
|
393
415
|
return schema.functions
|
|
@@ -401,21 +423,44 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
401
423
|
if (t === "AGGREGATE_FUNCTION") return (f.kind as string) === "aggregate";
|
|
402
424
|
return true;
|
|
403
425
|
})
|
|
404
|
-
.map((f) => this._functionToInfo(f,
|
|
426
|
+
.map((f) => this._functionToInfo(f, path));
|
|
405
427
|
}
|
|
406
428
|
|
|
407
429
|
/**
|
|
408
430
|
* Build the wire `FunctionInfo` for one declared function.
|
|
409
431
|
*
|
|
410
|
-
* `
|
|
432
|
+
* `schemaPath` is the schema the function actually lives in — it is the
|
|
411
433
|
* bind-dispatch key, so a function advertised through more than one route
|
|
412
434
|
* (schema contents *and* `global_functions`) must carry the same value in
|
|
413
435
|
* both. Shared by `schemaContentsFunctions` and `_globalFunctionInfos`.
|
|
414
436
|
*/
|
|
415
|
-
private _functionToInfo(f: VgiFunction,
|
|
437
|
+
private _functionToInfo(f: VgiFunction, path: string[]): FunctionInfo {
|
|
416
438
|
const meta = resolveMetadata(f);
|
|
417
439
|
const argSchema = argumentSpecsToSchema(f.argumentSpecs);
|
|
418
440
|
const argBytes = serializeSchema(argSchema);
|
|
441
|
+
const defaults = f.meta.parameterDefaultValues ?? null;
|
|
442
|
+
if (defaults) {
|
|
443
|
+
if (defaults.numRows !== 1) {
|
|
444
|
+
throw new Error(`parameterDefaultValues must contain exactly one row, got ${defaults.numRows}`);
|
|
445
|
+
}
|
|
446
|
+
let argumentIndex = 0;
|
|
447
|
+
for (const defaultField of defaults.schema.fields) {
|
|
448
|
+
while (argumentIndex < argSchema.fields.length && argSchema.fields[argumentIndex].name !== defaultField.name) {
|
|
449
|
+
argumentIndex++;
|
|
450
|
+
}
|
|
451
|
+
if (argumentIndex === argSchema.fields.length) {
|
|
452
|
+
throw new Error(`parameterDefaultValues field '${defaultField.name}' is not in argument signature order`);
|
|
453
|
+
}
|
|
454
|
+
const argumentField = argSchema.fields[argumentIndex];
|
|
455
|
+
if (typeSignature(argumentField.type) !== typeSignature(defaultField.type)) {
|
|
456
|
+
throw new Error(
|
|
457
|
+
`parameterDefaultValues field '${defaultField.name}' has type ${typeSignature(defaultField.type)}, ` +
|
|
458
|
+
`expected ${typeSignature(argumentField.type)}`,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
argumentIndex++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
419
464
|
|
|
420
465
|
// Use the function's default output schema if available,
|
|
421
466
|
// otherwise empty schema (table functions determine output at bind time)
|
|
@@ -443,12 +488,16 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
443
488
|
comment: null,
|
|
444
489
|
tags,
|
|
445
490
|
name: f.meta.name,
|
|
446
|
-
|
|
491
|
+
schema_path: normalizeSchemaPath(path),
|
|
447
492
|
function_type: meta.functionType.toUpperCase() as "SCALAR" | "TABLE" | "TABLE_BUFFERING" | "AGGREGATE",
|
|
448
493
|
arguments: argBytes,
|
|
449
494
|
output_schema: outputSchemaBytes,
|
|
495
|
+
parameter_default_values: defaults
|
|
496
|
+
? serializeBatch(defaults)
|
|
497
|
+
: null,
|
|
450
498
|
stability: meta.stability as any,
|
|
451
499
|
null_handling: meta.nullHandling as any,
|
|
500
|
+
argument_monotonicity: meta.argumentMonotonicity,
|
|
452
501
|
description: meta.description,
|
|
453
502
|
examples: meta.examples.map((e) => ({
|
|
454
503
|
sql: e.sql,
|
|
@@ -463,7 +512,16 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
463
512
|
// The C++ extension only honours this for rowid-bearing tables.
|
|
464
513
|
late_materialization:
|
|
465
514
|
(f.kind as string) === "table" && f.meta.lateMaterialization === true ? true : null,
|
|
466
|
-
|
|
515
|
+
filter_semantic_profiles: meta.filterSemanticProfiles,
|
|
516
|
+
additional_filter_functions: meta.additionalFilterFunctions.map((value) => ({
|
|
517
|
+
namespace: value.namespace, name: value.name, version: BigInt(value.version) as any,
|
|
518
|
+
})),
|
|
519
|
+
runtime_filter_algorithms: meta.runtimeFilterAlgorithms.map((value) => ({
|
|
520
|
+
namespace: value.namespace, name: value.name, version: BigInt(value.version) as any,
|
|
521
|
+
})),
|
|
522
|
+
filter_evaluation_contexts: meta.filterEvaluationContexts.map((value) => ({
|
|
523
|
+
profile: value.profile, provider_fingerprint: value.providerFingerprint,
|
|
524
|
+
})),
|
|
467
525
|
order_preservation: meta.preservesOrder as any,
|
|
468
526
|
max_workers: meta.maxWorkers ?? DEFAULT_MAX_WORKERS,
|
|
469
527
|
// Opt-in features the read-only catalog's functions do not use; the
|
|
@@ -533,7 +591,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
533
591
|
/**
|
|
534
592
|
* Build `FunctionInfo` records for `CatalogDescriptor.globalFunctions`.
|
|
535
593
|
*
|
|
536
|
-
* Each entry's `
|
|
594
|
+
* Each entry's `schema_path` is the schema it actually lives in — the
|
|
537
595
|
* globally visible name is derived client-side from
|
|
538
596
|
* `global_function_prefix`, so `name` stays unprefixed here. An entry not
|
|
539
597
|
* declared in any schema is a descriptor bug, so it throws rather than
|
|
@@ -544,32 +602,32 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
544
602
|
const globals = this._descriptor.globalFunctions ?? [];
|
|
545
603
|
if (globals.length === 0) return [];
|
|
546
604
|
|
|
547
|
-
const schemaOf = new Map<VgiFunction, string>();
|
|
605
|
+
const schemaOf = new Map<VgiFunction, string[]>();
|
|
548
606
|
for (const sch of this._descriptor.schemas) {
|
|
549
607
|
for (const fn of sch.functions ?? []) {
|
|
550
|
-
if (!schemaOf.has(fn)) schemaOf.set(fn, sch
|
|
608
|
+
if (!schemaOf.has(fn)) schemaOf.set(fn, schemaDescriptorPath(sch));
|
|
551
609
|
}
|
|
552
610
|
}
|
|
553
611
|
|
|
554
612
|
return globals.map((fn) => {
|
|
555
|
-
const
|
|
556
|
-
if (
|
|
613
|
+
const schemaPath = schemaOf.get(fn);
|
|
614
|
+
if (schemaPath === undefined) {
|
|
557
615
|
throw new Error(
|
|
558
616
|
`Catalog '${this._descriptor.name}': '${fn.meta.name}' is listed in ` +
|
|
559
617
|
`globalFunctions but is not declared in any schema.`,
|
|
560
618
|
);
|
|
561
619
|
}
|
|
562
|
-
return this._functionToInfo(fn,
|
|
620
|
+
return this._functionToInfo(fn, schemaPath);
|
|
563
621
|
});
|
|
564
622
|
}
|
|
565
623
|
|
|
566
624
|
override schemaContentsMacros(
|
|
567
625
|
attachOpaqueData: AttachOpaqueData,
|
|
568
|
-
|
|
626
|
+
path: string[],
|
|
569
627
|
type: string,
|
|
570
628
|
transactionOpaqueData?: TransactionOpaqueData
|
|
571
629
|
): MacroInfo[] {
|
|
572
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
630
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), path));
|
|
573
631
|
if (!schema || !schema.macros) return [];
|
|
574
632
|
|
|
575
633
|
return schema.macros
|
|
@@ -584,7 +642,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
584
642
|
comment: m.comment ?? null,
|
|
585
643
|
tags: m.tags ?? {},
|
|
586
644
|
name: m.name,
|
|
587
|
-
|
|
645
|
+
schema_path: normalizeSchemaPath(path),
|
|
588
646
|
macro_type: (m.macroType === "scalar" ? "SCALAR" : "TABLE") as "SCALAR" | "TABLE",
|
|
589
647
|
parameters: m.parameters,
|
|
590
648
|
parameter_default_values: m.parameterDefaultValues ?? new Uint8Array(0),
|
|
@@ -601,16 +659,16 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
601
659
|
|
|
602
660
|
override schemaContentsIndexes(
|
|
603
661
|
attachOpaqueData: AttachOpaqueData,
|
|
604
|
-
|
|
662
|
+
path: string[],
|
|
605
663
|
transactionOpaqueData?: TransactionOpaqueData
|
|
606
664
|
): IndexInfo[] {
|
|
607
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
665
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), path));
|
|
608
666
|
if (!schema || !schema.indexes) return [];
|
|
609
667
|
return schema.indexes.map((idx) => ({
|
|
610
668
|
comment: idx.comment ?? null,
|
|
611
669
|
tags: idx.tags ?? {},
|
|
612
670
|
name: idx.name,
|
|
613
|
-
|
|
671
|
+
schema_path: normalizeSchemaPath(path),
|
|
614
672
|
table_name: idx.tableName,
|
|
615
673
|
index_type: idx.indexType ?? "ART",
|
|
616
674
|
constraint_type: (idx.constraintType ?? "NONE") as IndexConstraintType,
|
|
@@ -621,29 +679,29 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
621
679
|
|
|
622
680
|
override indexGet(
|
|
623
681
|
attachOpaqueData: AttachOpaqueData,
|
|
624
|
-
|
|
682
|
+
schemaPath: string[],
|
|
625
683
|
name: string,
|
|
626
684
|
transactionOpaqueData?: TransactionOpaqueData
|
|
627
685
|
): IndexInfo | null {
|
|
628
|
-
const all = this.schemaContentsIndexes(attachOpaqueData,
|
|
686
|
+
const all = this.schemaContentsIndexes(attachOpaqueData, schemaPath, transactionOpaqueData);
|
|
629
687
|
return all.find((i) => i.name === name) ?? null;
|
|
630
688
|
}
|
|
631
689
|
|
|
632
690
|
override macroGet(
|
|
633
691
|
attachOpaqueData: AttachOpaqueData,
|
|
634
|
-
|
|
692
|
+
schemaPath: string[],
|
|
635
693
|
name: string,
|
|
636
694
|
transactionOpaqueData?: TransactionOpaqueData
|
|
637
695
|
): MacroInfo | null {
|
|
638
|
-
const macros = this.schemaContentsMacros(attachOpaqueData,
|
|
639
|
-
const tableMacros = this.schemaContentsMacros(attachOpaqueData,
|
|
696
|
+
const macros = this.schemaContentsMacros(attachOpaqueData, schemaPath, "SCALAR_MACRO", transactionOpaqueData);
|
|
697
|
+
const tableMacros = this.schemaContentsMacros(attachOpaqueData, schemaPath, "TABLE_MACRO", transactionOpaqueData);
|
|
640
698
|
const all = [...macros, ...tableMacros];
|
|
641
699
|
return all.find((m) => m.name.toLowerCase() === name.toLowerCase()) ?? null;
|
|
642
700
|
}
|
|
643
701
|
|
|
644
702
|
override tableScanFunctionGet(
|
|
645
703
|
attachOpaqueData: AttachOpaqueData,
|
|
646
|
-
|
|
704
|
+
schemaPath: string[],
|
|
647
705
|
name: string,
|
|
648
706
|
atUnit?: string,
|
|
649
707
|
atValue?: string,
|
|
@@ -652,18 +710,18 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
652
710
|
validateAtParams(atUnit, atValue);
|
|
653
711
|
|
|
654
712
|
// Find the table descriptor
|
|
655
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
713
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), schemaPath));
|
|
656
714
|
if (!schema || !schema.tables) {
|
|
657
|
-
throw new Error(`Table '${name}' not found in schema
|
|
715
|
+
throw new Error(`Table '${name}' not found in schema ${schemaPathDisplay(schemaPath)}`);
|
|
658
716
|
}
|
|
659
717
|
const table = schema.tables.find((t) => t.name === name);
|
|
660
718
|
if (!table) {
|
|
661
|
-
throw new Error(`Table '${name}' not found in schema
|
|
719
|
+
throw new Error(`Table '${name}' not found in schema ${schemaPathDisplay(schemaPath)}`);
|
|
662
720
|
}
|
|
663
721
|
|
|
664
722
|
// Reject AT clause on tables that don't support time travel
|
|
665
723
|
if (atUnit && !table.supportsTimeTravel) {
|
|
666
|
-
throw new Error(`Table
|
|
724
|
+
throw new Error(`Table ${schemaPathDisplay(schemaPath)}.${name} does not support time travel queries`);
|
|
667
725
|
}
|
|
668
726
|
|
|
669
727
|
if (table.function) {
|
|
@@ -675,6 +733,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
675
733
|
function_name: table.function.meta.name,
|
|
676
734
|
arguments: argBytes,
|
|
677
735
|
required_extensions: [],
|
|
736
|
+
schema_path: this._schemaForFunction(table.function),
|
|
678
737
|
};
|
|
679
738
|
}
|
|
680
739
|
|
|
@@ -683,7 +742,7 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
683
742
|
|
|
684
743
|
override async tableGet(
|
|
685
744
|
attachOpaqueData: AttachOpaqueData,
|
|
686
|
-
|
|
745
|
+
schemaPath: string[],
|
|
687
746
|
name: string,
|
|
688
747
|
atUnit?: string,
|
|
689
748
|
atValue?: string,
|
|
@@ -692,25 +751,25 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
692
751
|
validateAtParams(atUnit, atValue);
|
|
693
752
|
|
|
694
753
|
// Find the table descriptor to check supports_time_travel
|
|
695
|
-
const schema = this._descriptor.schemas.find((s) => s
|
|
754
|
+
const schema = this._descriptor.schemas.find((s) => schemaPathsEqual(schemaDescriptorPath(s), schemaPath));
|
|
696
755
|
if (schema?.tables) {
|
|
697
756
|
const tableDesc = schema.tables.find((t) => t.name === name);
|
|
698
757
|
if (tableDesc && atUnit && !tableDesc.supportsTimeTravel) {
|
|
699
|
-
throw new Error(`Table
|
|
758
|
+
throw new Error(`Table ${schemaPathDisplay(schemaPath)}.${name} does not support time travel queries`);
|
|
700
759
|
}
|
|
701
760
|
}
|
|
702
761
|
|
|
703
|
-
const tables = await this.schemaContentsTables(attachOpaqueData,
|
|
762
|
+
const tables = await this.schemaContentsTables(attachOpaqueData, schemaPath, transactionOpaqueData);
|
|
704
763
|
return tables.find((t) => t.name === name) ?? null;
|
|
705
764
|
}
|
|
706
765
|
|
|
707
766
|
override viewGet(
|
|
708
767
|
attachOpaqueData: AttachOpaqueData,
|
|
709
|
-
|
|
768
|
+
schemaPath: string[],
|
|
710
769
|
name: string,
|
|
711
770
|
transactionOpaqueData?: TransactionOpaqueData
|
|
712
771
|
): ViewInfo | null {
|
|
713
|
-
const views = this.schemaContentsViews(attachOpaqueData,
|
|
772
|
+
const views = this.schemaContentsViews(attachOpaqueData, schemaPath, transactionOpaqueData);
|
|
714
773
|
return views.find((v) => v.name === name) ?? null;
|
|
715
774
|
}
|
|
716
775
|
|
|
@@ -786,14 +845,7 @@ function resolveIndexGroups(schema: VgiSchema, groups?: string[][]): number[][]
|
|
|
786
845
|
return groups.map((group) => resolveIndices(schema, group));
|
|
787
846
|
}
|
|
788
847
|
|
|
789
|
-
|
|
790
|
-
field("fk_columns", list(field("item", utf8(), true)), false),
|
|
791
|
-
field("pk_columns", list(field("item", utf8(), true)), false),
|
|
792
|
-
field("referenced_table", utf8(), false),
|
|
793
|
-
field("referenced_schema", utf8(), false),
|
|
794
|
-
]);
|
|
795
|
-
|
|
796
|
-
function serializeForeignKeys(fks?: ForeignKeyDef[], schemaName?: string): Uint8Array[] {
|
|
848
|
+
function serializeForeignKeys(fks?: ForeignKeyDef[], schemaPath?: string[]): Uint8Array[] {
|
|
797
849
|
if (!fks || fks.length === 0) return [];
|
|
798
850
|
return fks.map((fk) =>
|
|
799
851
|
serializeBatch(
|
|
@@ -802,9 +854,9 @@ function serializeForeignKeys(fks?: ForeignKeyDef[], schemaName?: string): Uint8
|
|
|
802
854
|
fk_columns: [fk.columns],
|
|
803
855
|
pk_columns: [fk.referencedColumns],
|
|
804
856
|
referenced_table: [fk.referencedTable],
|
|
805
|
-
|
|
857
|
+
referenced_schema_path: [fk.referencedSchemaPath ?? schemaPath ?? ["main"]],
|
|
806
858
|
},
|
|
807
|
-
|
|
859
|
+
ForeignKeyInfoSchema
|
|
808
860
|
)
|
|
809
861
|
)
|
|
810
862
|
);
|
|
@@ -1001,14 +1053,16 @@ function inferScalarType(val: any): VgiDataType {
|
|
|
1001
1053
|
|
|
1002
1054
|
// Build the inlined `scan_function` payload for a function-backed table.
|
|
1003
1055
|
// Mirrors python's _inline_function_result: a serialized one-row batch of
|
|
1004
|
-
// ScanFunctionResultSchema (function_name, arguments, required_extensions
|
|
1005
|
-
// The C++ extension uses these bytes verbatim and skips the
|
|
1006
|
-
// catalog_table_scan_function_get RPC
|
|
1056
|
+
// ScanFunctionResultSchema (function_name, arguments, required_extensions,
|
|
1057
|
+
// schema_path). The C++ extension uses these bytes verbatim and skips the
|
|
1058
|
+
// catalog_table_scan_function_get RPC, so the schema this inlines has to agree
|
|
1059
|
+
// with what that RPC would have answered.
|
|
1007
1060
|
function inlineScanFunction(
|
|
1008
1061
|
func: VgiFunction,
|
|
1009
1062
|
args: Arguments | undefined,
|
|
1063
|
+
schemaPath: string[] | null,
|
|
1010
1064
|
): Uint8Array {
|
|
1011
1065
|
const argSchema = argumentSpecsToSchema(func.argumentSpecs);
|
|
1012
1066
|
const argBytes = serializeArgsBatch(args ?? new Arguments(), argSchema);
|
|
1013
|
-
return encodeScanFunctionResult(func.meta.name, argBytes);
|
|
1067
|
+
return encodeScanFunctionResult(func.meta.name, argBytes, [], schemaPath);
|
|
1014
1068
|
}
|