Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

User-Defined Functions

The CASE expression in Conditional Routing works, but the banding logic is trapped inside one junction. A UDF packages it as a named function every node can call. UDFs are written in Roto, compiled to native code, and operate on whole Arrow columns at a time; the source is inline in the statement — there is no separate upload step. See Declaration.

Declare The Function

BEGIN;

CREATE UDF amount_band
  WITH ROTO_0_13
  ARGS (amount I64)
  RETURNS STRING
  CODE $roto$
    fn amount_band(amount: I64Column) -> StringColumn {
        when_s(amount.gt_s(9999), "vip")
            .when_s(amount.gt_s(999), "high")
            .otherwise_s("routine")
    }
  $roto$;

The declared signature must exactly match the Roto entry function — same name, matching column types (an I64 argument is an I64Column), no implicit casts. $roto$ ... $roto$ is a dollar-quoted string, so the body spans lines verbatim. The available column operations are cataloged in Roto Column Operations; UDFs are deterministic unless declared VOLATILE (Nulls, Errors, And Volatility).

Call It

Calls always use the udf:: qualifier and compose with builtins and route filters (Calling UDFs):

CREATE RELAY banded_orders SCHEMA order_tiered UNBRANCHED;

CREATE JUNCTION band_orders
  FROM orders
  UNBRANCHED
  TO banded_orders
    INHERIT ALL
    SET tier = udf::amount_band(input.amount)
    WHERE udf::amount_band(input.amount) != "routine"
    FLUSH IMMEDIATE
    ON MESSAGE ERROR LOG;

COMMIT;

This reuses the order_tiered schema from Conditional Routing — the same enrichment, now expressed once and callable from any junction, filter, or route in the domain.

Watch It Classify

nervix-cli --domain quickstart subscribe band_watch banded_orders

Produce orders with amounts 500, 2500, and 25000 on the Kafka topic (the junction reads the Kafka-fed orders relay). The 500 order is filtered out by the route WHERE; the others arrive with "tier":"high" and "tier":"vip".

Lifecycle notes: changing the body means DROP UDF amount_band; and recreating it, and the drop is rejected while any node still references the function (Introspection And Lifecycle). For where UDFs sit between builtins and WASM processors, see Choosing An Extension Tier.

Next: aggregate the stream in Window Aggregates.