@query-farm/vgi 0.13.0 → 0.15.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/dist/index.js +97 -3
- package/dist/index.js.map +3 -3
- package/package.json +3 -3
- package/src/http/describe-json.ts +146 -18
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@query-farm/vgi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
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)",
|
|
@@ -75,12 +75,12 @@
|
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
77
77
|
"@query-farm/apache-arrow": "^21.1.1",
|
|
78
|
-
"@query-farm/vgi-rpc": "^0.
|
|
78
|
+
"@query-farm/vgi-rpc": "^0.12.0"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@duckdb/node-api": "^1.5.2-r.1",
|
|
82
82
|
"@query-farm/apache-arrow": "^21.1.1",
|
|
83
|
-
"@query-farm/vgi-rpc": "^0.
|
|
83
|
+
"@query-farm/vgi-rpc": "^0.12.0",
|
|
84
84
|
"@types/bun": "latest",
|
|
85
85
|
"typescript": "^6.0.3"
|
|
86
86
|
},
|
|
@@ -31,6 +31,7 @@ import type {
|
|
|
31
31
|
CatalogAttachResult,
|
|
32
32
|
CatalogInfo,
|
|
33
33
|
FunctionInfo,
|
|
34
|
+
MacroInfo,
|
|
34
35
|
SchemaInfo,
|
|
35
36
|
TableInfo,
|
|
36
37
|
ViewInfo,
|
|
@@ -104,6 +105,32 @@ function typeToString(type: VgiDataType): string {
|
|
|
104
105
|
return s ?? String(type.typeId);
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Raw Arrow type string (e.g. `int64`, `string`, `double`) matching pyarrow's
|
|
110
|
+
* `str(type)`. Unlike {@link typeToString} (DuckDB-ish names used for table/view
|
|
111
|
+
* columns), macro arg types are carried verbatim in `describe.json` — the shared
|
|
112
|
+
* landing page maps Arrow → DuckDB client-side — so they must be byte-identical
|
|
113
|
+
* to what the Python reference producer emits for cross-language parity.
|
|
114
|
+
*/
|
|
115
|
+
function arrowTypeString(type: VgiDataType): string {
|
|
116
|
+
if (isNull(type)) return "null";
|
|
117
|
+
if (isBool(type)) return "bool";
|
|
118
|
+
if (isUtf8(type)) return "string";
|
|
119
|
+
if (isBinary(type)) return "binary";
|
|
120
|
+
if (isInt(type)) {
|
|
121
|
+
const bw = (type as { bitWidth?: number }).bitWidth ?? 32;
|
|
122
|
+
const signed =
|
|
123
|
+
(type as { isSigned?: boolean }).isSigned ?? (type as { signed?: boolean }).signed ?? true;
|
|
124
|
+
return `${signed ? "" : "u"}int${bw}`;
|
|
125
|
+
}
|
|
126
|
+
if (isFloat(type)) {
|
|
127
|
+
const precision = (type as { precision?: number }).precision ?? 2;
|
|
128
|
+
return precision === 0 ? "halffloat" : precision === 1 ? "float" : "double";
|
|
129
|
+
}
|
|
130
|
+
const s = (type as { toString?: () => string }).toString?.();
|
|
131
|
+
return (s ?? String(type.typeId)).toLowerCase();
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
function fieldMeta(field: VgiField, key: string): string | undefined {
|
|
108
135
|
return field.metadata.get(key);
|
|
109
136
|
}
|
|
@@ -189,6 +216,85 @@ function functionArgs(fn: FunctionInfo): ArgJson[] {
|
|
|
189
216
|
return args;
|
|
190
217
|
}
|
|
191
218
|
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Macro mapping
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
// A scalar macro is invoked exactly like a scalar function in SQL, and a table
|
|
224
|
+
// macro like a table function; surface them in the same buckets so the landing
|
|
225
|
+
// page lists a catalog's full callable surface (VGI workers commonly expose
|
|
226
|
+
// their "functions" as declarative macros). Mirrors Python `_macro_display_type`.
|
|
227
|
+
function macroDisplayType(m: MacroInfo): "scalar" | "table" {
|
|
228
|
+
return String(m.macro_type).toUpperCase() === "SCALAR" ? "scalar" : "table";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** JSON-encode a decoded macro default scalar, matching Python's `json.dumps`. */
|
|
232
|
+
function macroDefaultJson(value: unknown): string {
|
|
233
|
+
// int64 defaults decode to BigInt, which JSON.stringify rejects; a bigint's
|
|
234
|
+
// decimal string is already its JSON integer encoding (0n -> "0").
|
|
235
|
+
if (typeof value === "bigint") return value.toString();
|
|
236
|
+
try {
|
|
237
|
+
return JSON.stringify(value);
|
|
238
|
+
} catch {
|
|
239
|
+
return String(value);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function macroArgs(m: MacroInfo): ArgJson[] {
|
|
244
|
+
// Defaulted parameters are optional and callable by name in DuckDB, so we
|
|
245
|
+
// present them as named args (with their default); the rest are positional.
|
|
246
|
+
const defaults = new Map<string, unknown>();
|
|
247
|
+
const defBytes = m.parameter_default_values;
|
|
248
|
+
if (defBytes && defBytes.length > 0) {
|
|
249
|
+
try {
|
|
250
|
+
const batch = deserializeBatch(defBytes);
|
|
251
|
+
for (const field of batch.schema.fields) {
|
|
252
|
+
try {
|
|
253
|
+
defaults.set(field.name, batch.getChild(field.name)?.get(0));
|
|
254
|
+
} catch {
|
|
255
|
+
// skip an undecodable default column
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
// no usable defaults
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const schema = readSchemaSafe(m.arguments_schema);
|
|
264
|
+
const fields = schema ? schema.fields : null;
|
|
265
|
+
const fieldByName = new Map<string, VgiField>();
|
|
266
|
+
if (fields) for (const f of fields) fieldByName.set(f.name, f);
|
|
267
|
+
const names = fields ? fields.map((f) => f.name) : m.parameters;
|
|
268
|
+
|
|
269
|
+
const args: ArgJson[] = [];
|
|
270
|
+
for (const name of names) {
|
|
271
|
+
const field = fieldByName.get(name);
|
|
272
|
+
// Macro parameters are untyped unless a typed default pins them; show ANY
|
|
273
|
+
// rather than the Arrow null placeholder.
|
|
274
|
+
const arg: ArgJson =
|
|
275
|
+
field !== undefined && !isNull(field.type)
|
|
276
|
+
? { name, type: arrowTypeString(field.type) }
|
|
277
|
+
: { name, type: "ANY" };
|
|
278
|
+
const doc = field !== undefined ? fieldMeta(field, VGI_DOC_KEY) : undefined;
|
|
279
|
+
if (doc) arg.desc = doc;
|
|
280
|
+
if (defaults.has(name)) {
|
|
281
|
+
arg.named = true;
|
|
282
|
+
arg.default = macroDefaultJson(defaults.get(name));
|
|
283
|
+
}
|
|
284
|
+
args.push(arg);
|
|
285
|
+
}
|
|
286
|
+
return args;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function macroToObject(m: MacroInfo): Record<string, unknown> {
|
|
290
|
+
return {
|
|
291
|
+
name: m.name,
|
|
292
|
+
type: macroDisplayType(m),
|
|
293
|
+
doc: m.comment ?? "",
|
|
294
|
+
args: macroArgs(m),
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
192
298
|
// ---------------------------------------------------------------------------
|
|
193
299
|
// Catalog contents
|
|
194
300
|
// ---------------------------------------------------------------------------
|
|
@@ -221,6 +327,18 @@ async function schemaFunctions(iface: CatalogInterface, aod: Uint8Array, name: s
|
|
|
221
327
|
return out;
|
|
222
328
|
}
|
|
223
329
|
|
|
330
|
+
async function schemaMacros(iface: CatalogInterface, aod: Uint8Array, name: string): Promise<MacroInfo[]> {
|
|
331
|
+
const out: MacroInfo[] = [];
|
|
332
|
+
for (const kind of ["SCALAR_MACRO", "TABLE_MACRO"]) {
|
|
333
|
+
try {
|
|
334
|
+
out.push(...(await iface.schemaContentsMacros(aod, name, kind)));
|
|
335
|
+
} catch {
|
|
336
|
+
// best-effort — keep whatever we collected
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
|
|
224
342
|
interface Counts {
|
|
225
343
|
schemas: number;
|
|
226
344
|
tables: number;
|
|
@@ -246,6 +364,7 @@ async function buildSchemas(
|
|
|
246
364
|
const tablesRaw = (await schemaContents(iface, aod, si.name, "TABLE")) as TableInfo[];
|
|
247
365
|
const viewsRaw = (await schemaContents(iface, aod, si.name, "VIEW")) as ViewInfo[];
|
|
248
366
|
const funcsRaw = await schemaFunctions(iface, aod, si.name);
|
|
367
|
+
const macrosRaw = await schemaMacros(iface, aod, si.name);
|
|
249
368
|
|
|
250
369
|
const tables = tablesRaw.map((t) => {
|
|
251
370
|
const schema = readSchemaSafe(t.columns);
|
|
@@ -259,24 +378,33 @@ async function buildSchemas(
|
|
|
259
378
|
def: v.definition,
|
|
260
379
|
}));
|
|
261
380
|
|
|
262
|
-
const functions = funcsRaw
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
381
|
+
const functions: Record<string, unknown>[] = funcsRaw.map((fn) => {
|
|
382
|
+
const obj: Record<string, unknown> = {
|
|
383
|
+
name: fn.name,
|
|
384
|
+
type: functionDisplayType(fn),
|
|
385
|
+
doc: fn.description ?? "",
|
|
386
|
+
args: functionArgs(fn),
|
|
387
|
+
};
|
|
388
|
+
const ret = functionReturns(fn);
|
|
389
|
+
if (ret) obj.returns = ret;
|
|
390
|
+
return obj;
|
|
391
|
+
});
|
|
392
|
+
// Fold macros into the same scalar/table buckets (VGI catalogs commonly
|
|
393
|
+
// expose their callable surface as declarative macros).
|
|
394
|
+
functions.push(...macrosRaw.map((m) => macroToObject(m)));
|
|
395
|
+
// Deterministic ordering across functions + macros, matching the Python
|
|
396
|
+
// reference producer's `sort(key=(type, name))`.
|
|
397
|
+
functions.sort(
|
|
398
|
+
(a, b) =>
|
|
399
|
+
String(a.type).localeCompare(String(b.type)) || String(a.name).localeCompare(String(b.name)),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const schemaObj: Record<string, unknown> = { name: si.name };
|
|
403
|
+
if (si.comment) schemaObj.doc = si.comment;
|
|
404
|
+
schemaObj.tables = tables;
|
|
405
|
+
schemaObj.views = views;
|
|
406
|
+
schemaObj.functions = functions;
|
|
407
|
+
schemas.push(schemaObj);
|
|
280
408
|
counts.schemas += 1;
|
|
281
409
|
counts.tables += tables.length;
|
|
282
410
|
counts.views += views.length;
|