Function metadata
How a function describes itself. A nested Meta class sets the SQL name, the human-readable
description, examples, and the behavioural flags DuckDB’s planner reads — parallelism, order
preservation, pushdown support. Every value has a sensible default, so Meta is optional and you
set only what you need. No inheritance, no registration call.
Prerequisites
Section titled “Prerequisites”- A working function of any shape (see Function patterns).
The basics
Section titled “The basics”Meta works on every shape — ScalarFunction, TableFunctionGenerator, TableInOutFunction,
AggregateFunction, TableBufferingFunction. Set the attributes you care about:
from typing import Annotated
from vgi import ScalarFunction, Param, ConstParam, Returns
import pyarrow as pa
import pyarrow.compute as pc
class MultiplyFunction(ScalarFunction):
"""Multiplies a value by a constant factor."""
class Meta:
name = "multiply"
description = "Multiplies a value by a constant factor"
categories = ["numeric", "transform"]
@classmethod
def compute(
cls,
value: Annotated[pa.Int64Array, Param(doc="Integer value to multiply")],
factor: Annotated[int, ConstParam("Multiplication factor")],
) -> Annotated[pa.Int64Array, Returns()]:
return pc.multiply(value, factor)
That registers as multiply(BIGINT, BIGINT) → BIGINT. Two defaults are worth knowing because they
mean you can often omit Meta entirely:
namedefaults to the class name in snake_case —MultiplyFunctionwould have registered asmultiply_functionwithout the override.descriptiondefaults to the first line of the class docstring, so the docstring above would have served on its own.
Reading it back
Section titled “Reading it back”Every function class can report its own resolved metadata — after defaults, inheritance, and signature introspection have been applied:
meta = MultiplyFunction.get_metadata()
print(meta.name) # "multiply"
print(meta.max_workers) # None (unbounded)
print(meta.parameters) # [ParameterInfo(name='value', ...), ParameterInfo(name='factor', ...)]
info = MultiplyFunction.describe() # the same thing as a JSON-serializable dict
get_metadata() is what the framework itself calls when it builds the registration a client sees,
so it’s the authoritative answer to “what did I actually declare?” — including the parameter list
derived from your compute() annotations, which you never wrote out by hand.
Available Meta attributes
Section titled “Available Meta attributes”| Attribute | Type | Default | Description |
|---|---|---|---|
name | str | Class name → snake_case | Function registration name |
description | str | First docstring line | Human-readable description |
categories | list[str] | [] | Classification tags |
tags | dict[str, str] | {} | Custom key-value tags, surfaced on FunctionInfo |
examples | list | [] | SQL examples (str or FunctionExample) |
max_workers | int|None | None (unlimited) | Max parallel workers |
stability | FunctionStability | CONSISTENT | Output determinism |
null_handling | NullHandling | DEFAULT | NULL input behavior |
required_settings | list[str] | [] | Required DuckDB settings |
projection_pushdown | bool | True | Enable column pruning |
filter_pushdown | bool | False | Enable filter pushdown |
preserves_order | OrderPreservation | PRESERVES_ORDER | Row order guarantee |
order_dependent | OrderDependence | NOT_ORDER_DEPENDENT | Aggregate order sensitivity |
distinct_dependent | DistinctDependence | NOT_DISTINCT_DEPENDENT | Aggregate DISTINCT sensitivity |
supports_window | bool | False | Aggregate implements the window() callback |
streaming_partitioned | bool | False | Aggregate opts into the streaming-partitioned protocol |
output_type | pa.DataType|AnyArrow | Required for ScalarFunction | Scalar output type |
Buffering-only attributes
Section titled “Buffering-only attributes”These are meaningful only on a TableBufferingFunction, and govern how the sink and source phases
are scheduled:
| Attribute | Default | Description |
|---|---|---|
requires_input_batch_index | False | Each process() call carries a globally-unique monotonic params.batch_index, so a parallel sink can reconstruct source order — see Recovering input order. Mutually exclusive with sink_order_dependent. |
sink_order_dependent | False | Run the sink single-threaded so every process() call arrives in source order. The blunt alternative to the batch index — simpler, but it gives up sink parallelism. |
source_order_dependent | False | Run the source phase single-threaded, draining finalize_state_ids in combine-returned order. |
has_finalize (whether you overrode finalize()/finish()) and input_from_args (whether you
subclassed RowTransformFunction)
are resolved from the class itself, not set in Meta. The C++ extension reads both to decide how to
register the function, which is exactly why they can’t be hand-declared and get out of sync.
Metadata inheritance
Section titled “Metadata inheritance”Meta attributes are inherited from parent classes, so a family of related functions can share a
base:
class FilterFunction(TableInOutFunction):
class Meta:
categories = ["filter"]
preserves_order = OrderPreservation.PRESERVES_ORDER
class PositiveFilter(FilterFunction):
class Meta:
description = "Keep only positive values"
# Inherits categories=["filter"] and preserves_order from the parent
Serializing it for registration
Section titled “Serializing it for registration”Metadata crosses the wire as Arrow — this is how a client learns what a worker offers. You rarely call these directly (the worker does it during attach), but they’re the seam if you’re building tooling:
from vgi import functions_to_arrow
from vgi.metadata import arrow_to_functions
# Worker side: describe the available functions as a RecordBatch.
batch = functions_to_arrow([MultiplyFunction, PositiveFilter])
# Client side: read them back.
for info in arrow_to_functions(batch):
print(f"{info.name}: {info.description}")
Per-argument documentation and constraints travel separately, as Arrow field metadata on the argument schema — see Argument serialization → Discovery metadata.
Next steps
Section titled “Next steps”- Declare argument constraints → Function API → Declaring value constraints.
- The optimizer flags in context → Integrate with the optimizer.
- Exact types → API Reference: metadata.