@query-farm/vgi 0.24.1 → 0.24.2
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/dist/catalog/attach-option.d.ts +30 -0
- package/dist/catalog/read-only.d.ts +11 -0
- package/dist/index.js +55 -28
- package/dist/index.js.map +5 -5
- package/dist/worker-cf-entry.js +55 -28
- package/dist/worker-cf-entry.js.map +5 -5
- package/package.json +1 -1
- package/src/catalog/attach-option.ts +74 -0
- package/src/catalog/read-only.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@query-farm/vgi",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.2",
|
|
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)",
|
|
@@ -17,6 +17,11 @@
|
|
|
17
17
|
// default_value: Binary nullable -- serialized single-row RecordBatch with
|
|
18
18
|
// the default value under "value", or
|
|
19
19
|
// null when no default is declared
|
|
20
|
+
// required: Bool nullable -- the caller must supply this option at ATTACH
|
|
21
|
+
// time. Nullable and appended LAST so a peer
|
|
22
|
+
// that predates the column reads the batch by
|
|
23
|
+
// name and simply doesn't see it; absent and
|
|
24
|
+
// explicit-null both mean "not required".
|
|
20
25
|
//
|
|
21
26
|
// The extension reads the outer `attach_option_specs: list<binary>` column
|
|
22
27
|
// from CatalogInfo and validates user-supplied ATTACH options against the
|
|
@@ -28,6 +33,7 @@ import {
|
|
|
28
33
|
field,
|
|
29
34
|
utf8,
|
|
30
35
|
binary,
|
|
36
|
+
bool,
|
|
31
37
|
batchFromColumns,
|
|
32
38
|
serializeBatch,
|
|
33
39
|
serializeSchema,
|
|
@@ -55,6 +61,16 @@ export interface AttachOptionSpec {
|
|
|
55
61
|
* the options dict delivered to attach()).
|
|
56
62
|
*/
|
|
57
63
|
default?: unknown;
|
|
64
|
+
/**
|
|
65
|
+
* The caller must supply this option at ATTACH time. A catalog that cannot
|
|
66
|
+
* be attached without it advertises that fact at discovery, so a client can
|
|
67
|
+
* say so before attempting the attach rather than surfacing a failure that
|
|
68
|
+
* reads like an empty catalog.
|
|
69
|
+
*
|
|
70
|
+
* Mutually exclusive with `default` — an option that falls back to a value
|
|
71
|
+
* is by definition satisfiable without the caller.
|
|
72
|
+
*/
|
|
73
|
+
required?: boolean;
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
const SPEC_SCHEMA = makeSchema([
|
|
@@ -62,6 +78,7 @@ const SPEC_SCHEMA = makeSchema([
|
|
|
62
78
|
field("description", utf8(), false),
|
|
63
79
|
field("type", binary(), false),
|
|
64
80
|
field("default_value", binary(), true),
|
|
81
|
+
field("required", bool(), true),
|
|
65
82
|
]);
|
|
66
83
|
|
|
67
84
|
/**
|
|
@@ -69,6 +86,17 @@ const SPEC_SCHEMA = makeSchema([
|
|
|
69
86
|
* (one IPC-serialized RecordBatch with a single row).
|
|
70
87
|
*/
|
|
71
88
|
export function serializeAttachOptionSpec(spec: AttachOptionSpec): Uint8Array {
|
|
89
|
+
// Mirrors AttachOptionSpec.__post_init__ on the Python side: an option that
|
|
90
|
+
// falls back to a value is always satisfiable without the caller, so the
|
|
91
|
+
// combination is a declaration bug rather than a runtime condition.
|
|
92
|
+
if (spec.required && spec.default !== undefined && spec.default !== null) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Attach option '${spec.name}' is required but also declares a default ` +
|
|
95
|
+
`(${JSON.stringify(spec.default)}); an option with a default is always ` +
|
|
96
|
+
"satisfiable without the caller. Drop one.",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
72
100
|
// `type` is encoded as a serialized Arrow Schema with one field named
|
|
73
101
|
// "value" of the option's DataType. This lets the extension peek at the
|
|
74
102
|
// logical type without a separate enum.
|
|
@@ -91,6 +119,9 @@ export function serializeAttachOptionSpec(spec: AttachOptionSpec): Uint8Array {
|
|
|
91
119
|
description: [spec.description],
|
|
92
120
|
type: [typeBytes],
|
|
93
121
|
default_value: [defaultBytes],
|
|
122
|
+
// Written explicitly rather than left null so a reader sees `false`, not
|
|
123
|
+
// NULL, for an option that simply isn't required.
|
|
124
|
+
required: [spec.required ?? false],
|
|
94
125
|
},
|
|
95
126
|
SPEC_SCHEMA,
|
|
96
127
|
);
|
|
@@ -105,3 +136,46 @@ export function serializeAttachOptionSpecs(
|
|
|
105
136
|
): Uint8Array[] {
|
|
106
137
|
return Array.from(specs, serializeAttachOptionSpec);
|
|
107
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Raised when `attach` omits options declared `required`.
|
|
142
|
+
*
|
|
143
|
+
* Carries the option names in `missing` so a caller can act on them without
|
|
144
|
+
* parsing the message. Mirrors `MissingAttachOptionsError` on the Python side,
|
|
145
|
+
* message included — the extension's integration suite matches on its text.
|
|
146
|
+
*/
|
|
147
|
+
export class MissingAttachOptionsError extends Error {
|
|
148
|
+
readonly missing: string[];
|
|
149
|
+
|
|
150
|
+
constructor(catalogName: string, missing: string[]) {
|
|
151
|
+
const joined = missing.map((name) => `'${name}'`).join(", ");
|
|
152
|
+
super(
|
|
153
|
+
`Catalog '${catalogName}' cannot be attached without the required ` +
|
|
154
|
+
`option${missing.length > 1 ? "s" : ""} ${joined}.`,
|
|
155
|
+
);
|
|
156
|
+
this.name = "MissingAttachOptionsError";
|
|
157
|
+
this.missing = missing;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Throw if any `required` spec has no corresponding entry in `options`.
|
|
163
|
+
*
|
|
164
|
+
* Option names are matched case-insensitively, mirroring DuckDB's handling of
|
|
165
|
+
* ATTACH option keys.
|
|
166
|
+
*
|
|
167
|
+
* @throws {MissingAttachOptionsError} If a required option was not supplied.
|
|
168
|
+
*/
|
|
169
|
+
export function validateRequiredAttachOptions(
|
|
170
|
+
catalogName: string,
|
|
171
|
+
specs: Iterable<AttachOptionSpec>,
|
|
172
|
+
options: Record<string, unknown>,
|
|
173
|
+
): void {
|
|
174
|
+
const supplied = new Set(Object.keys(options).map((key) => key.toLowerCase()));
|
|
175
|
+
const missing = Array.from(specs)
|
|
176
|
+
.filter((spec) => spec.required && !supplied.has(spec.name.toLowerCase()))
|
|
177
|
+
.map((spec) => spec.name);
|
|
178
|
+
if (missing.length > 0) {
|
|
179
|
+
throw new MissingAttachOptionsError(catalogName, missing);
|
|
180
|
+
}
|
|
181
|
+
}
|
package/src/catalog/read-only.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { FunctionRegistry } from "../functions/registry.js";
|
|
|
26
26
|
import { argumentSpecsToSchema, macroArgumentsSchema } from "../arguments/argument-spec.js";
|
|
27
27
|
import { serializeSchema, serializeBatch, emptyBatch, batchFromColumns } from "../util/arrow/index.js";
|
|
28
28
|
import { resolveMetadata } from "../metadata/resolve.js";
|
|
29
|
+
import { type AttachOptionSpec, validateRequiredAttachOptions } from "./attach-option.js";
|
|
29
30
|
import { FunctionStability, NullHandling, OrderPreservation, DEFAULT_MAX_WORKERS } from "../types.js";
|
|
30
31
|
import { Arguments } from "../arguments/arguments.js";
|
|
31
32
|
|
|
@@ -58,6 +59,19 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
58
59
|
return [this._descriptor.name];
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Attach-time options this catalog declares, for the named catalog.
|
|
64
|
+
*
|
|
65
|
+
* Empty by default — the declarative descriptor path carries no attach
|
|
66
|
+
* options. A subclass that advertises specs in `catalogsInfo()` should
|
|
67
|
+
* return the same specs here so `attach()` enforces the `required` ones it
|
|
68
|
+
* advertised. Takes the catalog name because one interface may serve
|
|
69
|
+
* several catalogs with different option sets.
|
|
70
|
+
*/
|
|
71
|
+
attachOptionSpecs(_name: string): AttachOptionSpec[] {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
// Advertise the catalog's discovery record. The declarative path carries no
|
|
62
76
|
// version metadata or attach option specs, but it does surface the optional
|
|
63
77
|
// `source_url` from the descriptor so it flows through `catalog_catalogs`
|
|
@@ -82,6 +96,12 @@ export class ReadOnlyCatalogInterface extends CatalogInterface {
|
|
|
82
96
|
if (!this.catalogs().includes(name)) {
|
|
83
97
|
throw new Error(`No worker handles catalog '${name}'`);
|
|
84
98
|
}
|
|
99
|
+
// Refuse an attach that omits a declared-required option, so the caller
|
|
100
|
+
// gets the option name rather than a catalog that looks mysteriously
|
|
101
|
+
// empty. A subclass that overrides `attach` outright bypasses this and
|
|
102
|
+
// should call `validateRequiredAttachOptions` itself — mirrors
|
|
103
|
+
// CatalogInterface.catalog_attach on the Python side.
|
|
104
|
+
validateRequiredAttachOptions(name, this.attachOptionSpecs(name), options ?? {});
|
|
85
105
|
|
|
86
106
|
const attachOpaqueData = new Uint8Array(16);
|
|
87
107
|
crypto.getRandomValues(attachOpaqueData);
|