vgi.table_in_out_function
Module overview
Framework for implementing streaming table-in-table-out functions.
TableInOutGenerator processes input batches via a per-batch callback.
Each call to process() emits one output batch via out.emit().
TableInOutFunction provides a simpler callback API (transform/finish)
with automatic state serialization for distributed processing.
function pack_int_cursor
Section titled “function pack_int_cursor”pack_int_cursor(value: int) -> bytes
Encode a signed int64 cursor (e.g., last log_id consumed).
class RowTransformFunction
Section titled “class RowTransformFunction”Bases: TableInOutGenerator[TArgs, None]
Description
Blended (“UNNEST-style”) table-in-out: positional args ARE per-row input columns.
A RowTransformFunction collapses the classic either/or between a standard
table function (literal args only) and a table-in-out function (an explicit
TABLE subquery arg). Its positional Arg\s declare its per-row input
columns — real typed args, NO synthetic TABLE placeholder — so ONE
registration serves every call shape:
f(52, 13) -- literal -> one input rowFROM t, f(t.x, t.y) -- columns -> streaming inputSELECT ... FROM t, LATERAL f(t.x, t.y)Contract.
- Positional args are the input columns; they are read from
batchinprocess()(by declared name for fixed args, positionally for varargs — use :meth:input_columns). They are NOT surfaced onparams.args. - Named (
str-position) args stay bind-time scalars onparams.args. - Map-shaped, per-row: implement
process()to emit output viaout.emit(). 1->1, 1->N, 1->0 all work. There is no finalize — afinalize()/finish()override is rejected atresolve_metadata(DuckDB forbidsFinalExecuteunder correlated LATERAL, one of the call shapes blended must serve). Accumulating functions use a classicTableInputtable-in-out or aTableBufferingFunction. - A positional
constarg is rejected (in the column form DuckDB sweeps a constant into the input subquery; in the literal form it is indistinguishable from an input column). Use a named arg for optional config, or classicTableInputmode for a required constant.
Subclassing RowTransformFunction (not a Meta flag) IS the blended
signal — a per-arg or Meta flag could be forgotten on one of N same-named
overloads; inheritance cannot. function_type stays TABLE; the resolver
sets ResolvedMetadata.input_from_args=True (see
metadata._detect_input_from_args).
Methods
method input_columns
Section titled “method input_columns”input_columns(batch: pa.RecordBatch) -> list[pa.Array]This row-batch’s input columns, positionally.
For a varargs blended function the runtime column names are not known
at declaration time, so read them positionally with this helper. For
fixed-arity blended functions read by declared name (batch.column(name)).
Inherited members (19)
get_metadatamethod · from MetadataMixin — Get the resolved metadata for this function class.describemethod · from MetadataMixin — Get metadata as a dictionary (for JSON serialization).loggerattribute · from Functionstorageattribute · from FunctionFunctionArgumentsattribute · from TableFunctionBaseon_bindmethod · from TableInOutGenerator — Pass-through default — output schema is the input schema.bindmethod · from TableFunctionBase — Bind protocol entry point. Do not override; useon_bind().on_initmethod · from TableFunctionBase — One-time setup after bind, before processing batches.global_initmethod · from TableFunctionBase — Global init protocol entry point. Do not override; useon_init().cardinalitymethod · from TableFunctionBase — Return the cardinality for the output.dynamic_to_stringmethod · from TableFunctionBase — Return diagnostics rendered as Extra Info under EXPLAIN ANALYZE.statisticsmethod · from TableFunctionBase — Return per-output-column statistics for this invocation.pushdown_filtersmethod · from TableFunctionBase — Get deserialized pushdown filters, or None if not present.state_classattribute · from TableInOutGeneratorhas_finalize_overridemethod · from TableInOutGenerator — Whether this class’sfinalize/finishrepresents real work.initial_statemethod · from TableInOutGenerator — Create initial processing state. Override whenTStateis used.processmethod · from TableInOutGenerator — Process one input batch.finalizemethod · from TableInOutGenerator — Finalize processing and produce any remaining output.on_cancelmethod · from TableInOutGenerator
class TableInOutFunction
Section titled “class TableInOutFunction”Bases: TableInOutGenerator[TArgs, TState]
Description
Simplified base class using transform/finish callbacks.
This class provides a simpler API for common use cases where you don’t need
to work directly with OutputCollector. Instead of implementing process()
directly, you override transform() and optionally finish() as regular methods.
TState is optional. If not provided, state management is disabled and
transform() will always receive state=None. When TState is an
ArrowSerializableDataclass, state is automatically saved to storage
after each transform() call for distributed processing.
Attributes
attribute state_class
Section titled “attribute state_class”type[TState] | None
The TState dataclass type, inferred automatically from
the generic type parameters; None disables state management.
Methods
method transform
Section titled “method transform”transform(
batch: pa.RecordBatch,
params: ProcessParams[TArgs],
state: TState | None,
) -> pa.RecordBatch | list[pa.RecordBatch]Transform a single input batch.
Override this method to implement your transformation logic. This is called once for each input batch.
method finish
Section titled “method finish”finish(
params: ProcessParams[TArgs],
states: list[TState],
) -> list[pa.RecordBatch]Return final batches after all input is processed.
Override this method to emit results after all input batches have been processed. This is useful for aggregations, sorting, or any operation that needs to see all data before producing output.
method initial_state
Section titled “method initial_state”initial_state(params: ProcessParams[TArgs]) -> TState | NoneCreate the initial state for processing.
Override this method to initialize the state object before processing begins.
method process
Section titled “method process”process(
params: ProcessParams[TArgs],
state: TState,
batch: pa.RecordBatch,
out: OutputCollector,
) -> NoneProcess input batches by calling transform(). Do not override.
This method implements the exchange protocol by calling your transform()
method for each input batch. State is automatically saved to storage
after each call for distributed processing.
method finalize
Section titled “method finalize”finalize(params: ProcessParams[TArgs]) -> list[pa.RecordBatch]Emit final batches by calling finish(). Do not override.
This method collects serialized states from all workers, deserializes
them, and passes them to your finish() method.
Inherited members (15)
get_metadatamethod · from MetadataMixin — Get the resolved metadata for this function class.describemethod · from MetadataMixin — Get metadata as a dictionary (for JSON serialization).loggerattribute · from Functionstorageattribute · from FunctionFunctionArgumentsattribute · from TableFunctionBaseon_bindmethod · from TableInOutGenerator — Pass-through default — output schema is the input schema.bindmethod · from TableFunctionBase — Bind protocol entry point. Do not override; useon_bind().on_initmethod · from TableFunctionBase — One-time setup after bind, before processing batches.global_initmethod · from TableFunctionBase — Global init protocol entry point. Do not override; useon_init().cardinalitymethod · from TableFunctionBase — Return the cardinality for the output.dynamic_to_stringmethod · from TableFunctionBase — Return diagnostics rendered as Extra Info under EXPLAIN ANALYZE.statisticsmethod · from TableFunctionBase — Return per-output-column statistics for this invocation.pushdown_filtersmethod · from TableFunctionBase — Get deserialized pushdown filters, or None if not present.has_finalize_overridemethod · from TableInOutGenerator — Whether this class’sfinalize/finishrepresents real work.on_cancelmethod · from TableInOutGenerator
class TableInOutFunctionStateNoOp
Section titled “class TableInOutFunctionStateNoOp”Bases: ArrowSerializableDataclass
Description
No-op state class for TableInOutFunction when no state is needed.
class TableInOutGenerator
Section titled “class TableInOutGenerator”Bases: TableFunctionBase[TArgs]
Description
Base class for streaming table functions that transform Arrow RecordBatches.
Each call to process() should emit exactly one output batch via out.emit().
Use TState to persist state between process() calls.
For functions that need a finalize phase (e.g., aggregation), override
finalize() to return the final output batches.
Attributes
attribute state_class
Section titled “attribute state_class”type[ArrowSerializableDataclass] | None
Concrete ArrowSerializableDataclass type subclasses set
to opt into framework-managed state; None (the default) means
process()/finalize() get state=None and the framework skips its
round-trip.
Methods
method has_finalize_override
Section titled “method has_finalize_override”has_finalize_override() -> boolWhether this class’s finalize/finish represents real work.
Returns True iff either:
- The class’s
Metadeclareshas_finalizeasTrueorFalse(explicit override — the declared value wins, even if it disagrees with the auto-detection). - Auto-detection finds a user subclass (one that is itself a
TableInOutGeneratorsubclass) strictly above the VGI bases in the MRO defining a callablefinishorfinalizeattribute.
The framework uses this to decide whether to advertise a finalize
callback to DuckDB; DuckDB rejects LATERAL with correlated input on
table functions that register in_out_function_final.
method on_bind
Section titled “method on_bind”on_bind(params: BindParams[TArgs]) -> BindResponsePass-through default — output schema is the input schema.
Override to compute a dynamic output type or validate arguments.
See TableFunctionBase.on_bind for the broader contract.
method initial_state
Section titled “method initial_state”initial_state(params: ProcessParams[TArgs]) -> TState | NoneCreate initial processing state. Override when TState is used.
Called once during init to create the state object that will be
passed to process() on each input batch.
method process
Section titled “method process”process(
params: ProcessParams[TArgs],
state: TState,
batch: pa.RecordBatch,
out: OutputCollector,
) -> NoneProcess one input batch.
Called once per input batch during the INPUT phase. Must call
out.emit(batch) exactly once to produce output.
Use out.client_log(level, message) for in-band logging.
method finalize
Section titled “method finalize”finalize(params: ProcessParams[TArgs]) -> list[pa.RecordBatch]Finalize processing and produce any remaining output.
Called after all input batches have been processed during the FINALIZE phase. Override to emit buffered or aggregated results.
method on_cancel
Section titled “method on_cancel”on_cancel(params: ProcessParams[TArgs], state: TState | None) -> NoneInherited members (12)
get_metadatamethod · from MetadataMixin — Get the resolved metadata for this function class.describemethod · from MetadataMixin — Get metadata as a dictionary (for JSON serialization).loggerattribute · from Functionstorageattribute · from FunctionFunctionArgumentsattribute · from TableFunctionBasebindmethod · from TableFunctionBase — Bind protocol entry point. Do not override; useon_bind().on_initmethod · from TableFunctionBase — One-time setup after bind, before processing batches.global_initmethod · from TableFunctionBase — Global init protocol entry point. Do not override; useon_init().cardinalitymethod · from TableFunctionBase — Return the cardinality for the output.dynamic_to_stringmethod · from TableFunctionBase — Return diagnostics rendered as Extra Info under EXPLAIN ANALYZE.statisticsmethod · from TableFunctionBase — Return per-output-column statistics for this invocation.pushdown_filtersmethod · from TableFunctionBase — Get deserialized pushdown filters, or None if not present.
function unpack_int_cursor
Section titled “function unpack_int_cursor”unpack_int_cursor(cursor: bytes, default: int = -1) -> int
Decode a packed int64 cursor; b"" returns default.
Use default=-1 (before-first sentinel) to start at the beginning
of a state_log when no prior cursor exists.