@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.
Files changed (91) 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 +442 -283
  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 +57 -31
  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 +2320 -1363
  29. package/dist/index.js.map +44 -41
  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 +414 -243
  37. package/dist/serve-entry.js.map +28 -27
  38. package/dist/split-token.d.ts +1 -1
  39. package/dist/types.d.ts +8 -0
  40. package/dist/worker-cf-entry.js +2033 -1210
  41. package/dist/worker-cf-entry.js.map +41 -37
  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 +125 -71
  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 +62 -33
  66. package/src/generated/vgi-protocol-schemas.ts +226 -58
  67. package/src/http/client-bundle.ts +1 -1
  68. package/src/http/fetch.ts +12 -0
  69. package/src/index.core.ts +2 -0
  70. package/src/metadata/resolve.ts +43 -1
  71. package/src/metadata/serialize.ts +43 -5
  72. package/src/metadata/types.ts +17 -1
  73. package/src/protocol/dispatch.ts +1 -1
  74. package/src/protocol/handlers/aggregate.ts +13 -7
  75. package/src/protocol/handlers/catalog/admin.ts +6 -6
  76. package/src/protocol/handlers/catalog/index_methods.ts +2 -2
  77. package/src/protocol/handlers/catalog/macro.ts +4 -4
  78. package/src/protocol/handlers/catalog/shared.ts +2 -0
  79. package/src/protocol/handlers/catalog/table.ts +16 -16
  80. package/src/protocol/handlers/catalog/view.ts +5 -5
  81. package/src/protocol/handlers/function.ts +2 -2
  82. package/src/protocol/handlers/shared.ts +12 -3
  83. package/src/protocol/handlers/table-buffering.ts +4 -4
  84. package/src/protocol/serializers/bind.ts +15 -6
  85. package/src/protocol/serializers/splits.ts +4 -2
  86. package/src/protocol/types.ts +9 -3
  87. package/src/schema-path.ts +25 -0
  88. package/src/serve-entry.ts +65 -3
  89. package/src/split-token.ts +2 -2
  90. package/src/types.ts +9 -0
  91. package/src/worker.ts +49 -4
@@ -4,22 +4,36 @@
4
4
  // transparently. Ported from vgi-python/vgi/table_filter_pushdown.py.
5
5
 
6
6
  export {
7
+ FILTER_ENCODING,
8
+ FILTER_VERSION,
9
+ DUCKDB_STANDARD_V1,
10
+ NO_EVALUATION_CONTEXT,
11
+ DUCKDB_SESSION_CONTEXT,
7
12
  ComparisonOp,
13
+ type ArithmeticOp,
14
+ type EvaluationContext,
15
+ type Filter,
8
16
  type AndFilter,
9
17
  type ConstantFilter,
10
18
  type ExpressionFilter,
11
19
  type ExprNode,
12
- type Filter,
13
20
  type InFilter,
14
21
  type IsNotNullFilter,
15
22
  type IsNullFilter,
16
23
  type OrFilter,
17
24
  type StructFilter,
25
+ type FilterExpression,
26
+ type FilterPredicate,
27
+ type FilterSet,
28
+ type FunctionIdentity,
29
+ type PredicateMode,
30
+ type PredicateSource,
31
+ type StandardFilterFunction,
18
32
  } from "./types.js";
19
33
 
20
34
  export { PushdownFilters } from "./evaluate.js";
21
35
 
22
- export { buildJoinKeysLookup, deserializeFilters } from "./deserialize.js";
36
+ export { FilterV2Error, applyFilterDelta, buildJoinKeysLookup, deserializeFilters } from "./deserialize.js";
23
37
 
24
38
  export {
25
39
  FilteringOutputCollector,
@@ -1,91 +1,108 @@
1
1
  // Copyright 2025, 2026 Query Farm LLC - https://query.farm
2
- // Filter pushdown type definitions.
2
+
3
+ import type { VgiBatch, VgiDataType, VgiField } from "../arrow/index.js";
4
+
5
+ export const FILTER_ENCODING = "vgi.filters.v2";
6
+ export const FILTER_VERSION = "2";
7
+ export const DUCKDB_STANDARD_V1 = "vgi.duckdb.standard.v1";
8
+ export const NO_EVALUATION_CONTEXT = "vgi.none.v1";
9
+ export const DUCKDB_SESSION_CONTEXT = "vgi.duckdb.session.v1";
3
10
 
4
11
  export enum ComparisonOp {
5
12
  EQ = "eq",
6
13
  NE = "ne",
7
- GT = "gt",
8
- GE = "ge",
9
14
  LT = "lt",
10
15
  LE = "le",
16
+ GT = "gt",
17
+ GE = "ge",
18
+ DISTINCT_FROM = "distinct_from",
19
+ NOT_DISTINCT_FROM = "not_distinct_from",
11
20
  }
12
21
 
13
- export interface ConstantFilter {
14
- type: "constant";
15
- columnName: string;
16
- columnIndex: number;
17
- op: ComparisonOp;
18
- value: any;
19
- }
20
-
21
- export interface IsNullFilter {
22
- type: "is_null";
23
- columnName: string;
24
- columnIndex: number;
25
- }
22
+ export type ArithmeticOp = "add" | "subtract" | "multiply" | "divide" | "modulo";
23
+ export type PredicateMode = "required" | "advisory";
24
+ export type PredicateSource = "query" | "join" | "top_n" | "split_refinement" | "other";
25
+ export type StandardFilterFunction = "starts_with" | "ends_with" | "contains" | "list_contains";
26
26
 
27
- export interface IsNotNullFilter {
28
- type: "is_not_null";
29
- columnName: string;
30
- columnIndex: number;
27
+ export interface FunctionIdentity {
28
+ namespace: string;
29
+ name: string;
30
+ version: number;
31
31
  }
32
32
 
33
- export interface InFilter {
34
- type: "in";
35
- columnName: string;
36
- columnIndex: number;
37
- values: Set<any>;
33
+ export interface EvaluationContext {
34
+ profile: string;
35
+ timeZone?: string;
36
+ calendar?: string;
37
+ defaultCollation?: string;
38
+ ieeeFloatingPointOps?: boolean;
39
+ integerDivision?: boolean;
40
+ providerFingerprint?: string;
38
41
  }
39
42
 
40
- export interface AndFilter {
41
- type: "and";
42
- columnName: string;
43
- columnIndex: number;
44
- children: Filter[];
45
- }
43
+ export type FilterExpression =
44
+ | { node: "column_ref"; columnIndex: number; columnName: string; dataType?: VgiDataType }
45
+ | { node: "field_ref"; expression: FilterExpression; fieldIndex: number; fieldName: string; dataType: VgiDataType }
46
+ | { node: "literal"; valueRef: number; field: VgiField; value: unknown }
47
+ | { node: "comparison"; op: ComparisonOp; left: FilterExpression; right: FilterExpression }
48
+ | { node: "and" | "or"; children: FilterExpression[] }
49
+ | { node: "not"; expression: FilterExpression }
50
+ | { node: "is_null"; expression: FilterExpression; negated: boolean }
51
+ | { node: "in"; expression: FilterExpression; set: FilterSet; negated: boolean }
52
+ | { node: "cast"; expression: FilterExpression; typeRef: number; field: VgiField }
53
+ | { node: "arithmetic"; op: ArithmeticOp; left: FilterExpression; right: FilterExpression }
54
+ | { node: "negate"; expression: FilterExpression }
55
+ | {
56
+ node: "call";
57
+ function: StandardFilterFunction | FunctionIdentity;
58
+ arguments: FilterExpression[];
59
+ options?: Record<string, unknown>;
60
+ }
61
+ | {
62
+ node: "runtime_filter";
63
+ algorithm: FunctionIdentity;
64
+ input: FilterExpression;
65
+ artifactRef: number;
66
+ field: VgiField;
67
+ artifact: unknown;
68
+ nullHandling: "pass" | "reject";
69
+ supported: boolean;
70
+ };
46
71
 
47
- export interface OrFilter {
48
- type: "or";
49
- columnName: string;
50
- columnIndex: number;
51
- children: Filter[];
52
- }
72
+ export type FilterSet =
73
+ | { kind: "literal"; valueRef: number; field: VgiField; values: unknown[] }
74
+ | {
75
+ kind: "external";
76
+ batchIndex: number;
77
+ columnIndex: number;
78
+ columnName: string;
79
+ values: unknown[];
80
+ batch: VgiBatch;
81
+ };
53
82
 
54
- export interface StructFilter {
55
- type: "struct";
56
- columnName: string;
57
- columnIndex: number;
58
- childIndex: number;
59
- childName: string;
60
- childFilter: Filter;
83
+ export interface FilterPredicate {
84
+ id: string;
85
+ revision: number;
86
+ mode: PredicateMode;
87
+ source: PredicateSource;
88
+ expression: FilterExpression;
61
89
  }
62
90
 
63
- export type Filter =
64
- | ConstantFilter
65
- | IsNullFilter
66
- | IsNotNullFilter
67
- | InFilter
68
- | AndFilter
69
- | OrFilter
70
- | StructFilter
71
- | ExpressionFilter;
72
-
73
- /**
74
- * Expression-tree filter. The expr is a parsed AST; evaluation matches
75
- * DuckDB's semantics for the function names the table function advertises in
76
- * `supportedExpressionFilters` (e.g. list_contains, starts_with, contains,
77
- * &&/st_intersects_extent for spatial).
78
- */
79
- export interface ExpressionFilter {
80
- type: "expression";
81
- columnName: string;
82
- columnIndex: number;
83
- expr: ExprNode;
91
+ export interface FilterCapabilities {
92
+ extensionFunctions?: ReadonlyArray<FunctionIdentity>;
93
+ runtimeAlgorithms?: ReadonlyArray<FunctionIdentity>;
94
+ evaluationContexts?: ReadonlyArray<{ profile: string; providerFingerprint?: string | null }>;
84
95
  }
85
96
 
86
- export type ExprNode =
87
- | { expr_type: "column_ref"; index: number }
88
- | { expr_type: "constant"; value: any }
89
- | { expr_type: "function"; function_name: string; children: ExprNode[] }
90
- | { expr_type: "comparison"; op: ComparisonOp; left: ExprNode; right: ExprNode }
91
- | { expr_type: "conjunction"; conjunction_type: "and" | "or"; children: ExprNode[] };
97
+ // Source aliases ease the protocol-2.0 transition for applications that imported
98
+ // the old names. The wire model itself is v2-only.
99
+ export type Filter = FilterExpression;
100
+ export type ExprNode = FilterExpression;
101
+ export type ConstantFilter = FilterExpression;
102
+ export type IsNullFilter = FilterExpression;
103
+ export type IsNotNullFilter = FilterExpression;
104
+ export type InFilter = FilterExpression;
105
+ export type AndFilter = FilterExpression;
106
+ export type OrFilter = FilterExpression;
107
+ export type StructFilter = FilterExpression;
108
+ export type ExpressionFilter = FilterExpression;
@@ -64,6 +64,8 @@ export interface AggregateBindParams<TArgs = Record<string, any>> {
64
64
  inputSchema: VgiSchema | null;
65
65
  settings: Record<string, any>;
66
66
  secrets: Record<string, Record<string, any>>;
67
+ /** Resolved names aligned with the complete logical argument order. */
68
+ argumentNames: (string | null)[] | null;
67
69
  }
68
70
 
69
71
  export interface AggregateUpdateParams<TArgs = Record<string, any>, TState = any> {
@@ -128,6 +130,8 @@ export interface AggregateFunctionConfig<TArgs = Record<string, any>, TState = a
128
130
  onBind?: (params: AggregateBindParams<TArgs>) => VgiDataType | Promise<VgiDataType>;
129
131
  /** Optional per-arg default values (positional only here). */
130
132
  argDefaults?: Record<string, any>;
133
+ /** Authoritative typed defaults: one row, defaulted parameters only. */
134
+ parameterDefaultValues?: VgiBatch | null;
131
135
  /**
132
136
  * Per-argument discovery constraints (choices / ge / le / gt / lt / pattern),
133
137
  * keyed by argument name. Surfaced via `vgi_function_arguments()` and enforced
@@ -356,7 +360,10 @@ export function defineAggregate<TArgs = Record<string, any>, TState = any>(
356
360
  isAnyType,
357
361
  isVarargs: varargsSet.has(name),
358
362
  isConst,
359
- ...constraintSpecFields(config.argConstraints?.[name]),
363
+ ...constraintSpecFields(
364
+ config.argConstraints?.[name],
365
+ config.argDefaults?.[name],
366
+ ),
360
367
  });
361
368
  }
362
369
  }
@@ -367,6 +374,7 @@ export function defineAggregate<TArgs = Record<string, any>, TState = any>(
367
374
  examples: config.examples,
368
375
  categories: config.categories,
369
376
  tags: config.tags,
377
+ parameterDefaultValues: config.parameterDefaultValues,
370
378
  nullHandling: config.nullHandling as any,
371
379
  requiredSecrets: config.requiredSecrets,
372
380
  };
@@ -11,18 +11,19 @@ import type { VgiFunction } from "./types.js";
11
11
  import type { Arguments } from "../arguments/arguments.js";
12
12
  import type { ArgumentSpec } from "../arguments/argument-spec.js";
13
13
  import { FunctionNotFoundError } from "../errors.js";
14
+ import { schemaPathDisplay, schemaPathKey } from "../schema-path.js";
14
15
 
15
16
  export interface OverloadContext {
16
17
  arguments?: Arguments;
17
18
  inputSchema?: VgiSchema | null;
18
19
  isScalar?: boolean;
19
20
  /**
20
- * Catalog schema that declares the function, from `BindRequest.schema_name`.
21
+ * Catalog schema that declares the function, from `BindRequest.schema_path`.
21
22
  * A worker may register the same name in more than one schema, so the bare
22
23
  * name is not a unique key. When set, resolution is scoped to that schema and
23
24
  * never falls back to another one; when absent, every schema is searched.
24
25
  */
25
- schemaName?: string | null;
26
+ schemaPath?: string[] | null;
26
27
  /**
27
28
  * Catalog that owns the function, resolved from the bind's
28
29
  * `attach_opaque_data`. Two catalogs served by one worker may each declare
@@ -205,13 +206,13 @@ function filterByArgumentTypes(
205
206
  }
206
207
 
207
208
  /** Key for the schema-scoped index: lowercased schema, then function name. */
208
- function schemaKey(schemaName: string, functionName: string): string {
209
- return `${schemaName.toLowerCase()}\u0000${functionName}`;
209
+ function schemaKey(schemaPath: string[], functionName: string): string {
210
+ return `${schemaPathKey(schemaPath)}\u0001${functionName}`;
210
211
  }
211
212
 
212
213
  /** Key for the catalog-scoped index: lowercased catalog, then the schema key. */
213
- function catalogKey(catalogName: string, schemaName: string, functionName: string): string {
214
- return `${catalogName.toLowerCase()}\u0000${schemaKey(schemaName, functionName)}`;
214
+ function catalogKey(catalogName: string, schemaPath: string[], functionName: string): string {
215
+ return `${catalogName.toLowerCase()}\u0000${schemaKey(schemaPath, functionName)}`;
215
216
  }
216
217
 
217
218
  export class FunctionRegistry {
@@ -234,12 +235,12 @@ export class FunctionRegistry {
234
235
  }
235
236
 
236
237
  /**
237
- * Record that `func` is declared in `schemaName`, in addition to the flat
238
+ * Record that `func` is declared in `schemaPath`, in addition to the flat
238
239
  * by-name index. Idempotent, and independent of `register()` so a function
239
240
  * reachable from several schemas (e.g. a scan function referenced by tables in
240
241
  * more than one schema) resolves from any of them.
241
242
  */
242
- registerInSchema(func: VgiFunction, schemaName: string, catalogName?: string): void {
243
+ registerInSchema(func: VgiFunction, schemaPath: string[], catalogName?: string): void {
243
244
  const add = (map: Map<string, VgiFunction[]>, key: string) => {
244
245
  const bucket = map.get(key);
245
246
  if (!bucket) {
@@ -248,20 +249,21 @@ export class FunctionRegistry {
248
249
  }
249
250
  if (!bucket.includes(func)) bucket.push(func);
250
251
  };
251
- add(this._bySchema, schemaKey(schemaName, func.meta.name));
252
+ add(this._bySchema, schemaKey(schemaPath, func.meta.name));
252
253
  if (catalogName) {
253
- add(this._byCatalog, catalogKey(catalogName, schemaName, func.meta.name));
254
+ add(this._byCatalog, catalogKey(catalogName, schemaPath, func.meta.name));
254
255
  }
255
256
  }
256
257
 
257
258
  /** Schemas that declare `functionName`, sorted — for error messages. */
258
- schemasFor(functionName: string): string[] {
259
- const out: string[] = [];
259
+ schemasFor(functionName: string): string[][] {
260
+ const out: string[][] = [];
260
261
  for (const key of this._bySchema.keys()) {
261
- const [schema, name] = key.split("\u0000");
262
- if (name === functionName) out.push(schema);
262
+ const separator = key.lastIndexOf("\u0001");
263
+ const name = key.slice(separator + 1);
264
+ if (name === functionName) out.push(key.slice(0, separator).split("\u0000"));
263
265
  }
264
- return out.sort();
266
+ return out.sort((left, right) => schemaPathKey(left).localeCompare(schemaPathKey(right)));
265
267
  }
266
268
 
267
269
  get(name: string, context?: OverloadContext): VgiFunction {
@@ -273,23 +275,23 @@ export class FunctionRegistry {
273
275
  // Catalog first: it is the only key that separates two catalogs declaring
274
276
  // the same schema and name.
275
277
  const catalogName = context?.catalogName;
276
- const schemaName = context?.schemaName;
277
- if (catalogName && schemaName) {
278
- const scoped = this._byCatalog.get(catalogKey(catalogName, schemaName, name));
278
+ const schemaPath = context?.schemaPath;
279
+ if (catalogName && schemaPath) {
280
+ const scoped = this._byCatalog.get(catalogKey(catalogName, schemaPath, name));
279
281
  if (scoped && scoped.length > 0) {
280
282
  return this._disambiguate(name, scoped, context);
281
283
  }
282
284
  }
283
- if (schemaName) {
284
- const scoped = this._bySchema.get(schemaKey(schemaName, name));
285
+ if (schemaPath) {
286
+ const scoped = this._bySchema.get(schemaKey(schemaPath, name));
285
287
  if (scoped && scoped.length > 0) {
286
288
  candidates = scoped;
287
289
  } else if (candidates && candidates.length > 0) {
288
290
  const schemas = this.schemasFor(name);
289
291
  if (schemas.length > 0) {
290
292
  throw new Error(
291
- `Function '${name}' is not registered in schema '${schemaName}'. ` +
292
- `It is available in: [${schemas.join(", ")}]`,
293
+ `Function '${name}' is not registered in schema ${schemaPathDisplay(schemaPath)}. ` +
294
+ `It is available in: [${schemas.map(schemaPathDisplay).join(", ")}]`,
293
295
  );
294
296
  }
295
297
  }
@@ -21,6 +21,7 @@ import {
21
21
  FunctionType,
22
22
  FunctionStability,
23
23
  NullHandling,
24
+ ArgumentMonotonicity,
24
25
  DEFAULT_MAX_WORKERS,
25
26
  } from "../types.js";
26
27
  import { RowCountMismatchError } from "../errors.js";
@@ -63,6 +64,8 @@ export interface ScalarBindParameters {
63
64
  secrets: Record<string, Record<string, any>>;
64
65
  /** Original bind request — exposes attach_opaque_data, transaction_opaque_data, function_type, etc. */
65
66
  bindCall: BindRequest;
67
+ /** Resolved names aligned with the complete logical argument order. */
68
+ argumentNames: (string | null)[] | null;
66
69
  }
67
70
 
68
71
  // ============================================================================
@@ -178,9 +181,12 @@ export interface ScalarFunctionConfig<
178
181
  // Metadata
179
182
  stability?: FunctionStability;
180
183
  nullHandling?: NullHandling;
184
+ /** One scalar monotonicity claim per ordered parameter declaration. */
185
+ argumentMonotonicity?: ArgumentMonotonicity[] | null;
181
186
  examples?: FunctionExample[];
182
187
  categories?: string[];
183
188
  tags?: Record<string, string>;
189
+ parameterDefaultValues?: VgiBatch | null;
184
190
  maxWorkers?: number;
185
191
  requiredSettings?: string[];
186
192
  requiredSecrets?: string[];
@@ -262,6 +268,23 @@ export function defineScalarFunction<
262
268
  }
263
269
  }
264
270
 
271
+ if (config.argumentMonotonicity !== undefined && config.argumentMonotonicity !== null) {
272
+ if (config.argumentMonotonicity.length !== specs.length) {
273
+ throw new Error(
274
+ `defineScalarFunction("${config.name}"): argumentMonotonicity has ` +
275
+ `${config.argumentMonotonicity.length} entries, expected ${specs.length} declaration slots`,
276
+ );
277
+ }
278
+ const allowed = new Set<string>(Object.values(ArgumentMonotonicity));
279
+ for (const [index, value] of config.argumentMonotonicity.entries()) {
280
+ if (!allowed.has(value)) {
281
+ throw new Error(
282
+ `defineScalarFunction("${config.name}"): argumentMonotonicity[${index}] has unknown value ${String(value)}`,
283
+ );
284
+ }
285
+ }
286
+ }
287
+
265
288
  // Raw const-arg constraints for bind-time enforcement, keyed by arg name.
266
289
  // Only the ordered `parameters` API carries constraints (the legacy
267
290
  // params/constParams path does not), so only its const params appear here.
@@ -287,9 +310,11 @@ export function defineScalarFunction<
287
310
  description: config.description,
288
311
  stability: config.stability,
289
312
  nullHandling: config.nullHandling,
313
+ argumentMonotonicity: config.argumentMonotonicity,
290
314
  examples: config.examples,
291
315
  categories: config.categories,
292
316
  tags: config.tags,
317
+ parameterDefaultValues: config.parameterDefaultValues,
293
318
  maxWorkers: config.maxWorkers,
294
319
  requiredSettings: config.requiredSettings,
295
320
  requiredSecrets: config.requiredSecrets,
@@ -339,6 +364,7 @@ export function defineScalarFunction<
339
364
  settings,
340
365
  secrets,
341
366
  bindCall: request,
367
+ argumentNames: request.argument_names ?? null,
342
368
  });
343
369
  } else if (config.returns) {
344
370
  outputType = config.returns;
@@ -39,13 +39,17 @@ import type {
39
39
  StreamHandlers,
40
40
  FunctionExample,
41
41
  } from "./types.js";
42
- import type { ArgumentSpec } from "../arguments/argument-spec.js";
43
- import { assertArrowType, assertArrowTypes } from "../arguments/argument-spec.js";
42
+ import type { ArgumentConstraints, ArgumentSpec } from "../arguments/argument-spec.js";
43
+ import {
44
+ assertArrowType,
45
+ assertArrowTypes,
46
+ constraintSpecFields,
47
+ validateConstConstraints,
48
+ } from "../arguments/argument-spec.js";
44
49
  import { batchToScalarDict, batchToSecretDict, projectSchema } from "../util/arrow/index.js";
45
50
  import { batchFromColumns, readCanonicalValue } from "../arrow/index.js";
46
51
  import { codecFor } from "../arrow/codec/registry.js";
47
52
  import {
48
- buildJoinKeysLookup,
49
53
  deserializeFilters,
50
54
  FilteringOutputCollector,
51
55
  type PushdownFilters,
@@ -71,6 +75,8 @@ export interface TableBufferingBindParams<TArgs = Record<string, any>> {
71
75
  /** True on the second bind pass, after the connector resolved the secrets
72
76
  * requested via the onBind `lookupSecret*` return fields on the first pass. */
73
77
  resolvedSecretsProvided: boolean;
78
+ /** Resolved names aligned with the complete logical argument order. */
79
+ argumentNames?: (string | null)[] | null;
74
80
  }
75
81
 
76
82
  export interface TableBufferingParams<TArgs = Record<string, any>> {
@@ -107,6 +113,12 @@ export interface TableBufferingConfig<
107
113
  args?: Record<string, VgiDataType>;
108
114
  namedArgs?: Record<string, VgiDataType>;
109
115
  argDefaults?: Record<string, any>;
116
+ /** Authoritative typed defaults: one row, defaulted parameters only. */
117
+ parameterDefaultValues?: VgiBatch | null;
118
+ /** Argument descriptions keyed by argument name. */
119
+ argDocs?: Record<string, string>;
120
+ /** Discovery constraints, also enforced for scalar arguments at bind. */
121
+ argConstraints?: Record<string, ArgumentConstraints>;
110
122
  /** Bind: default passes through input schema. May be async. Return non-empty
111
123
  * `lookupSecret*` lists on the first pass to request DuckDB secrets; the
112
124
  * connector then re-binds with `resolvedSecretsProvided` and the resolved
@@ -196,7 +208,16 @@ export function defineTableBufferingFunction<
196
208
  let posIdx = 0;
197
209
  if (config.args) {
198
210
  for (const [name, type] of Object.entries(config.args)) {
199
- specs.push({ name, position: posIdx++, arrowType: type });
211
+ specs.push({
212
+ name,
213
+ position: posIdx++,
214
+ arrowType: type,
215
+ doc: config.argDocs?.[name],
216
+ ...constraintSpecFields(
217
+ config.argConstraints?.[name],
218
+ config.argDefaults?.[name],
219
+ ),
220
+ });
200
221
  }
201
222
  }
202
223
  specs.push({
@@ -207,7 +228,16 @@ export function defineTableBufferingFunction<
207
228
  });
208
229
  if (config.namedArgs) {
209
230
  for (const [name, type] of Object.entries(config.namedArgs)) {
210
- specs.push({ name, position: name, arrowType: type });
231
+ specs.push({
232
+ name,
233
+ position: name,
234
+ arrowType: type,
235
+ doc: config.argDocs?.[name],
236
+ ...constraintSpecFields(
237
+ config.argConstraints?.[name],
238
+ config.argDefaults?.[name],
239
+ ),
240
+ });
211
241
  }
212
242
  }
213
243
 
@@ -221,6 +251,7 @@ export function defineTableBufferingFunction<
221
251
  examples: config.examples,
222
252
  categories: config.categories,
223
253
  tags: config.tags,
254
+ parameterDefaultValues: config.parameterDefaultValues,
224
255
  maxWorkers: config.maxWorkers,
225
256
  requiredSettings: config.requiredSettings,
226
257
  requiredSecrets: config.requiredSecrets,
@@ -242,6 +273,8 @@ export function defineTableBufferingFunction<
242
273
  : undefined;
243
274
  let val = request.arguments.get(spec.position, defaultVal);
244
275
  if (typeof val === "bigint") val = Number(val);
276
+ const constraints = config.argConstraints?.[spec.name];
277
+ if (constraints) validateConstConstraints(spec.name, constraints, val);
245
278
  args[spec.name] = val;
246
279
  }
247
280
  return args as TArgs;
@@ -265,6 +298,7 @@ export function defineTableBufferingFunction<
265
298
  settings,
266
299
  secrets,
267
300
  resolvedSecretsProvided: request.resolved_secrets_provided ?? false,
301
+ argumentNames: request.argument_names ?? null,
268
302
  });
269
303
  return {
270
304
  output_schema: result.outputSchema,
@@ -326,9 +360,14 @@ export function defineTableBufferingFunction<
326
360
  ? projectSchema(projIds, request.output_schema)
327
361
  : request.output_schema;
328
362
 
329
- const joinKeysLookup = buildJoinKeysLookup(request.join_keys);
330
363
  const pushdownFilters = request.pushdown_filters
331
- ? deserializeFilters(request.pushdown_filters, joinKeysLookup)
364
+ ? deserializeFilters(request.pushdown_filters, {
365
+ outputSchema: request.output_schema,
366
+ joinKeyBatches: request.join_keys,
367
+ extensionFunctions: meta.additionalFilterFunctions,
368
+ runtimeAlgorithms: meta.runtimeFilterAlgorithms,
369
+ evaluationContexts: meta.filterEvaluationContexts,
370
+ })
332
371
  : undefined;
333
372
 
334
373
  const bound = new BoundStorage(