@query-farm/vgi 0.38.0 → 0.39.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.
@@ -1,9 +1,9 @@
1
1
  import { type VgiBatch } from "../arrow/index.js";
2
2
  import type { RpcClient } from "@query-farm/vgi-rpc";
3
- import { type SchemaInfo, type TableInfo, type ViewInfo, type FunctionInfo, type MacroInfo, type MacroType, type CatalogAttachResult, type SchemaContentsInfo, type AttachOpaqueData, type TransactionOpaqueData, type CatalogInfo, type ScanFunctionResult } from "../catalog/interface.js";
3
+ import { type SchemaInfo, type TableInfo, type ViewInfo, type FunctionInfo, type MacroInfo, type MacroType, type CatalogAttachResult, type SchemaContentsInfo, type IndexInfo, type AttachOpaqueData, type TransactionOpaqueData, type CatalogInfo, type ScanFunctionResult } from "../catalog/interface.js";
4
4
  import { VgiClientError } from "./errors.js";
5
5
  export { VgiClientError };
6
- import type { VgiClientOptions, TableFunctionOptions, ScalarFunctionOptions, TableInOutFunctionOptions, OnCreateConflict, CatalogFunctionType, CatalogMacroType, CatalogAttachOptions, AttachOptionValue } from "./types.js";
6
+ import type { VgiClientOptions, TableFunctionOptions, ScalarFunctionOptions, TableInOutFunctionOptions, OnCreateConflict, CatalogFunctionType, CatalogMacroType, CatalogAttachOptions, AttachOptionValue, CatalogSnapshot, LoadCatalogOptions } from "./types.js";
7
7
  /**
8
8
  * High-level client for calling VGI worker functions and catalog API.
9
9
  *
@@ -103,6 +103,9 @@ export declare class VgiClient {
103
103
  * current the worker answers `not_modified` with no schemas. `etag` null
104
104
  * means the worker does not revalidate. Each schema's wire `path` is checked
105
105
  * against its decoded `SchemaInfo.path`.
106
+ *
107
+ * This is the raw RPC. To enumerate a catalog, use `loadCatalog`, which
108
+ * checks the capability, revalidates and falls back to the per-schema RPCs.
106
109
  */
107
110
  catalogContents(attachOpaqueData: AttachOpaqueData, ifNoneMatch?: string | null): Promise<{
108
111
  catalog_version: number;
@@ -110,6 +113,30 @@ export declare class VgiClient {
110
113
  not_modified: boolean;
111
114
  schemas: SchemaContentsInfo[];
112
115
  }>;
116
+ /**
117
+ * Enumerate the whole catalog: every schema with all of its tables, views,
118
+ * scalar/aggregate/table functions, scalar/table macros and indexes.
119
+ *
120
+ * Honors the attach result's `supports_catalog_contents` the way the DuckDB
121
+ * extension does:
122
+ *
123
+ * - advertised (and `useCatalogContents` not false, no transaction): one
124
+ * `catalog_contents` call. With `previous.etag` it is conditional
125
+ * (`if_none_match`); `not_modified` returns `previous`'s contents.
126
+ * - not advertised: `catalog_schemas` + per-schema
127
+ * `catalog_schema_contents_*` calls -- `catalog_contents` is never sent.
128
+ * Kinds whose `estimated_object_count` is exactly 0 are skipped.
129
+ * - advertised but the call fails (or answers malformed, e.g. a
130
+ * `SchemaContents.path` that disagrees with its `SchemaInfo.path`): falls
131
+ * back to the per-schema calls and reports the failure in
132
+ * `fallback_error`.
133
+ *
134
+ * A full answer older than `previous` (a lagging replica) is retried once,
135
+ * then the per-schema path is used.
136
+ */
137
+ loadCatalog(attach: Pick<CatalogAttachResult, "attach_opaque_data" | "supports_catalog_contents">, opts?: LoadCatalogOptions): Promise<CatalogSnapshot>;
138
+ /** `loadCatalog` without `catalog_contents`: catalog_schemas + per-schema calls. */
139
+ private _loadCatalogPerSchema;
113
140
  /** Detach a previously-attached catalog. */
114
141
  catalogDetach(attachOpaqueData: AttachOpaqueData): Promise<void>;
115
142
  /**
@@ -153,6 +180,8 @@ export declare class VgiClient {
153
180
  schemaContentsViews(attachOpaqueData: AttachOpaqueData, path: string[] | string, transactionOpaqueData?: TransactionOpaqueData): Promise<ViewInfo[]>;
154
181
  /** List functions in a schema, filtered by type. */
155
182
  schemaContentsFunctions(attachOpaqueData: AttachOpaqueData, path: string[] | string, type: CatalogFunctionType, transactionOpaqueData?: TransactionOpaqueData): Promise<FunctionInfo[]>;
183
+ /** List indexes in a schema. */
184
+ schemaContentsIndexes(attachOpaqueData: AttachOpaqueData, path: string[] | string, transactionOpaqueData?: TransactionOpaqueData): Promise<IndexInfo[]>;
156
185
  /** Get a table by name, or null if not found. */
157
186
  tableGet(attachOpaqueData: AttachOpaqueData, schemaPath: string[] | string, name: string, transactionOpaqueData?: TransactionOpaqueData): Promise<TableInfo | null>;
158
187
  /** Create a new table. */
@@ -2,6 +2,7 @@ import type { VgiBatch } from "../arrow/index.js";
2
2
  import type { Arguments } from "../arguments/arguments.js";
3
3
  import type { AttachOptionValue } from "../catalog/attach-options.js";
4
4
  import type { BindResponse } from "../protocol/types.js";
5
+ import type { SchemaContentsInfo, TransactionOpaqueData } from "../catalog/interface.js";
5
6
  import { OrderByDirection, OrderByNullOrder } from "../protocol/types.js";
6
7
  export type { AttachOptionValue };
7
8
  export { OrderByDirection, OrderByNullOrder };
@@ -46,7 +47,7 @@ export interface CatalogAttachOptions {
46
47
  /** Conflict resolution strategy for create operations. */
47
48
  export type OnCreateConflict = "error" | "ignore" | "replace";
48
49
  /** DuckDB catalog function type filter (sent as uppercase wire values). */
49
- export type CatalogFunctionType = "SCALAR_FUNCTION" | "TABLE_FUNCTION";
50
+ export type CatalogFunctionType = "SCALAR_FUNCTION" | "AGGREGATE_FUNCTION" | "TABLE_FUNCTION";
50
51
  /** Macro type filter for schema contents listing (uppercase wire values). */
51
52
  export type CatalogMacroType = "SCALAR_MACRO" | "TABLE_MACRO";
52
53
  /** Options for constructing a VgiClient. */
@@ -136,3 +137,57 @@ export interface TableInOutFunctionOptions {
136
137
  */
137
138
  hasFinalize?: boolean;
138
139
  }
140
+ /**
141
+ * A whole-catalog snapshot returned by `VgiClient.loadCatalog`: every schema
142
+ * with all of its tables, views, functions, macros and indexes.
143
+ *
144
+ * Hold on to it and pass it back as `LoadCatalogOptions.previous` to
145
+ * revalidate: when it carries an `etag` the client sends that as
146
+ * `if_none_match`, and a `not_modified` answer returns this snapshot's
147
+ * contents unchanged.
148
+ */
149
+ export interface CatalogSnapshot {
150
+ /**
151
+ * Version the snapshot was taken at, as reported by `catalog_contents`.
152
+ * `null` when it was assembled from the per-schema RPCs (which carry no
153
+ * version); 0 means the worker does not track versions.
154
+ */
155
+ catalog_version: number | null;
156
+ /** Validator to revalidate with; null = the worker does not revalidate. */
157
+ etag: string | null;
158
+ /**
159
+ * How the contents were obtained: one `catalog_contents` call, or
160
+ * `catalog_schemas` + per-schema `catalog_schema_contents_*` calls.
161
+ */
162
+ source: "catalog_contents" | "per_schema";
163
+ /** True when this load was a `not_modified` revalidation of `previous`. */
164
+ not_modified: boolean;
165
+ /**
166
+ * Set when the catalog advertised `catalog_contents` but the call failed
167
+ * (or returned a malformed answer) and the client fell back to the
168
+ * per-schema RPCs: the failure's message.
169
+ */
170
+ fallback_error: string | null;
171
+ /** One entry per schema, parents before children. */
172
+ schemas: SchemaContentsInfo[];
173
+ }
174
+ /** Options for `VgiClient.loadCatalog`. */
175
+ export interface LoadCatalogOptions {
176
+ /**
177
+ * A snapshot from an earlier load of the same attach. Its `etag` (if any)
178
+ * is sent as `if_none_match`; a `not_modified` answer keeps its contents.
179
+ */
180
+ previous?: CatalogSnapshot | null;
181
+ /**
182
+ * Set false to never call `catalog_contents` (the client-side equivalent of
183
+ * DuckDB's `SET vgi_catalog_contents = false`). Default true: it is used
184
+ * whenever the attach result advertised `supports_catalog_contents`.
185
+ */
186
+ useCatalogContents?: boolean;
187
+ /**
188
+ * Enumerate inside this transaction. `catalog_contents` returns only the
189
+ * committed catalog (it takes no transaction), so a transaction-scoped load
190
+ * always uses the per-schema RPCs.
191
+ */
192
+ transactionOpaqueData?: TransactionOpaqueData;
193
+ }
@@ -2388,21 +2388,23 @@ function normalizeValue(raw, type) {
2388
2388
  return out;
2389
2389
  }
2390
2390
  if (isMap(type)) {
2391
+ const entries = type.children[0].type;
2392
+ const valueType = entries.children[1].type;
2391
2393
  const out = {};
2394
+ const put = (k, v) => {
2395
+ out[String(k)] = normalizeValue(v, valueType);
2396
+ };
2392
2397
  if (raw[Symbol.iterator]) {
2393
2398
  for (const entry of raw) {
2394
2399
  if (Array.isArray(entry)) {
2395
- out[String(entry[0])] = String(entry[1] ?? "");
2400
+ put(entry[0], entry[1]);
2396
2401
  } else if (entry && typeof entry === "object") {
2397
- const k = entry.key ?? entry[0];
2398
- const v = entry.value ?? entry[1];
2399
- out[String(k)] = v == null ? "" : String(v);
2402
+ put(entry.key ?? entry[0], entry.value ?? entry[1]);
2400
2403
  }
2401
2404
  }
2402
2405
  } else if (typeof raw === "object") {
2403
- for (const [k, v] of Object.entries(raw)) {
2404
- out[String(k)] = v == null ? "" : String(v);
2405
- }
2406
+ for (const [k, v] of Object.entries(raw))
2407
+ put(k, v);
2406
2408
  }
2407
2409
  return out;
2408
2410
  }
@@ -3973,6 +3975,84 @@ class VgiClient {
3973
3975
  schemas
3974
3976
  };
3975
3977
  }
3978
+ async loadCatalog(attach, opts) {
3979
+ const attachOpaqueData = attach.attach_opaque_data;
3980
+ const previous = opts?.previous ?? null;
3981
+ const useContents = attach.supports_catalog_contents === true && opts?.useCatalogContents !== false && opts?.transactionOpaqueData == null;
3982
+ if (!useContents) {
3983
+ return this._loadCatalogPerSchema(attachOpaqueData, opts?.transactionOpaqueData, null);
3984
+ }
3985
+ let fallbackError = null;
3986
+ const ifNoneMatch = previous?.etag ?? null;
3987
+ const knownVersion = previous?.catalog_version ?? 0;
3988
+ for (let attempt = 0;attempt < 2; attempt++) {
3989
+ let response;
3990
+ try {
3991
+ response = await this.catalogContents(attachOpaqueData, ifNoneMatch);
3992
+ } catch (err) {
3993
+ fallbackError = err instanceof Error ? err.message : String(err);
3994
+ break;
3995
+ }
3996
+ if (response.not_modified) {
3997
+ if (previous == null || ifNoneMatch == null) {
3998
+ fallbackError = "catalog_contents answered not_modified to an unconditional request";
3999
+ break;
4000
+ }
4001
+ return {
4002
+ ...previous,
4003
+ catalog_version: response.catalog_version !== 0 ? response.catalog_version : previous.catalog_version,
4004
+ etag: response.etag ?? ifNoneMatch,
4005
+ not_modified: true,
4006
+ fallback_error: null
4007
+ };
4008
+ }
4009
+ if (response.catalog_version !== 0 && knownVersion !== 0 && response.catalog_version < knownVersion) {
4010
+ fallbackError = `catalog_contents snapshot version ${response.catalog_version} is older than the known version ${knownVersion}`;
4011
+ continue;
4012
+ }
4013
+ return {
4014
+ catalog_version: response.catalog_version,
4015
+ etag: response.etag,
4016
+ source: "catalog_contents",
4017
+ not_modified: false,
4018
+ fallback_error: null,
4019
+ schemas: response.schemas
4020
+ };
4021
+ }
4022
+ return this._loadCatalogPerSchema(attachOpaqueData, undefined, fallbackError);
4023
+ }
4024
+ async _loadCatalogPerSchema(attachOpaqueData, transactionOpaqueData, fallbackError) {
4025
+ const schemas = await this.schemas(attachOpaqueData, transactionOpaqueData);
4026
+ const out = [];
4027
+ for (const schema of schemas) {
4028
+ const counts = schema.estimated_object_count ?? {};
4029
+ const path = schema.path;
4030
+ const has = (kind) => {
4031
+ const n = counts[kind];
4032
+ return n == null || Number(n) !== 0;
4033
+ };
4034
+ const t = transactionOpaqueData;
4035
+ out.push({
4036
+ schema,
4037
+ tables: has("table") ? await this.schemaContentsTables(attachOpaqueData, path, t) : [],
4038
+ views: has("view") ? await this.schemaContentsViews(attachOpaqueData, path, t) : [],
4039
+ scalar_functions: has("scalar_function") ? await this.schemaContentsFunctions(attachOpaqueData, path, "SCALAR_FUNCTION", t) : [],
4040
+ aggregate_functions: has("aggregate_function") ? await this.schemaContentsFunctions(attachOpaqueData, path, "AGGREGATE_FUNCTION", t) : [],
4041
+ table_functions: has("table_function") ? await this.schemaContentsFunctions(attachOpaqueData, path, "TABLE_FUNCTION", t) : [],
4042
+ scalar_macros: has("macro") ? await this.schemaContentsMacros(attachOpaqueData, path, "SCALAR_MACRO", t) : [],
4043
+ table_macros: has("macro") ? await this.schemaContentsMacros(attachOpaqueData, path, "TABLE_MACRO", t) : [],
4044
+ indexes: has("index") ? await this.schemaContentsIndexes(attachOpaqueData, path, t) : []
4045
+ });
4046
+ }
4047
+ return {
4048
+ catalog_version: null,
4049
+ etag: null,
4050
+ source: "per_schema",
4051
+ not_modified: false,
4052
+ fallback_error: fallbackError,
4053
+ schemas: out
4054
+ };
4055
+ }
3976
4056
  async catalogDetach(attachOpaqueData) {
3977
4057
  await this.rpc.call("catalog_detach", { attach_opaque_data: attachOpaqueData });
3978
4058
  }
@@ -4111,6 +4191,17 @@ class VgiClient {
4111
4191
  const inner = unwrapResult(result);
4112
4192
  return deserializeInfoList(inner.items, decodeFunctionInfo);
4113
4193
  }
4194
+ async schemaContentsIndexes(attachOpaqueData, path, transactionOpaqueData) {
4195
+ const result = await this.rpc.call("catalog_schema_contents_indexes", {
4196
+ attach_opaque_data: attachOpaqueData,
4197
+ path: normalizeSchemaPath(path),
4198
+ transaction_opaque_data: transactionOpaqueData ?? null
4199
+ });
4200
+ if (!result)
4201
+ return [];
4202
+ const inner = unwrapResult(result);
4203
+ return deserializeInfoList(inner.items, decodeIndexInfo);
4204
+ }
4114
4205
  async tableGet(attachOpaqueData, schemaPath, name, transactionOpaqueData) {
4115
4206
  const result = await this.rpc.call("catalog_table_get", {
4116
4207
  attach_opaque_data: attachOpaqueData,
@@ -4549,4 +4640,4 @@ export {
4549
4640
  typeSignature
4550
4641
  };
4551
4642
 
4552
- //# debugId=AD4326E5920F408164756E2164756E21
4643
+ //# debugId=A700197CD6EACFA764756E2164756E21