Scalar functions
On this page
Vectorized one-row-to-one-value functions and reflection-driven ScalarFn dispatch.
interface IScalarFunction
Section titled “interface IScalarFunction”public interface IScalarFunctionDescription
The raw contract a scalar function implements. A hardcoded implementation (see examples/01-minimal-scalar-worker 's UpperCaseFunction , or the ANY-typed dynamic- output fixtures under fixtures/QueryFarm.Vgi.ExampleWorker/Scalar ) is a fully supported way to implement this interface directly; the attribute-driven convenience base class ScalarFn ( [Param] / [ConstParam] / [Setting] / [OutputLength] reflection dispatch, ported from vgi-java's ScalarFn.ComputePlan /vgi-python's ScalarFunction ) builds on top of this same contract without changing it.
class ScalarBindParams
Section titled “class ScalarBindParams”public sealed class ScalarBindParamsDescription
Parameters an IScalarFunction sees at bind time.
Public members
public Schema? InputSchema { get; init; }The concrete per-call argument schema DuckDB resolved (decoded from Protocol.BindRequest.InputSchema) — populated for a normal call; null for a zero-argument function or a pre-bind catalog probe. Needed by an ANY-typed function to derive its actual (promoted) output type — see IScalarFunction.ResolveOutputSchema.
public byte[] Arguments { get; init; }Opaque, not-yet-decoded serialized argument-schema/const-value bytes from Protocol.BindRequest.Arguments — an embedded IPC batch with a single column named args whose type is struct(positional_0: T0, positional_1: T1, …) , one row, indexed 0.. over CONST parameters only (in Compute declaration order) — ScalarFn decodes this itself for [ConstParam] parameters; a hand-rolled IScalarFunction needing const values directly (e.g. a nested struct/binary const) can decode it the same way.
public byte[]? Secrets { get; init; }Opaque, not-yet-decoded serialized Protocol.BindRequest.Secrets bytes — an embedded IPC batch, one row, one column per resolved secret. Decode with Internal.SecretArgCodec.Decode. null when this function declared no IScalarFunction.RequiredSecrets (or none matched) — a scalar function only sees a STATICALLY pre-resolved secret here (see Attributes.SecretAttribute), never a dynamic scope-based one.
public byte[]? Settings { get; init; }Opaque, not-yet-decoded serialized Protocol.BindRequest.Settings bytes — an embedded IPC batch, one row, columns named literally by DuckDB setting key. null when this function declared no IScalarFunction.RequiredSettings.
public required string FunctionName { get; init; }class ScalarFn
Section titled “class ScalarFn”public abstract class ScalarFn : IScalarFunctionDescription
Attribute-driven convenience base class for IScalarFunction — reflects a subclass's own Compute method exactly once (see ComputePlan) and dispatches Process against it, so a subclass just writes: private void Compute([Param] StringArray value, StringArray.Builder result) { for (var i = 0; i < value.Length; i++) { if (value.IsNull(i)) { result.AppendNull(); continue; } result.Append(value.GetString(i).ToUpperInvariant()); } } See ComputePlan's doc comment for exactly which parameter shapes are supported (and which ANY-typed/varargs shapes are deliberately NOT — those implement IScalarFunction directly instead).
Public members
public RecordBatch Process(ScalarProcessParams processParams) ;public abstract string Name { get; }public virtual FunctionNullHandling? NullHandling;public virtual FunctionStability? Stability;public virtual IReadOnlyDictionary<string, string>? CacheControlMetadata;public virtual IReadOnlyList<RequiredSecret> RequiredSecrets;public virtual IReadOnlyList<string> RequiredSettings;public virtual Schema ArgumentsSchema;public virtual Schema OutputSchema;public virtual Schema ResolveOutputSchema(Schema? inputSchema) ;public virtual string Description;public virtual string SchemaName;public virtual void Bind(ScalarBindParams bindParams) ;class ScalarProcessParams
Section titled “class ScalarProcessParams”public sealed class ScalarProcessParamsDescription
Parameters an IScalarFunction sees per exchange turn.
Public members
public byte[] Arguments { get; init; }Same shape/meaning as ScalarBindParams.Arguments — carried again on every batch (rather than cached as instance state on the shared, potentially concurrently-invoked IScalarFunction singleton) so const-argument values stay correct across concurrent calls of the same function with different const arguments.
public byte[]? Secrets { get; init; }Same shape/meaning as ScalarBindParams.Secrets, threaded through per-batch for the same reason as Arguments.
public byte[]? Settings { get; init; }Same shape/meaning as ScalarBindParams.Settings, threaded through per-batch for the same reason as Arguments.
public required RecordBatch Input { get; init; }One column per positional argument, in declaration order. Column NAMES on the wire are DuckDB's own synthetic "col_0", "col_1", … — not IScalarFunction.ArgumentsSchema's (cosmetic) names — so implementations should index by position, not by name.
public required Schema OutputSchema { get; init; }The RESOLVED per-call output schema (from IScalarFunction.ResolveOutputSchema) — the returned RecordBatch must use exactly this schema.