risingwavelabs/risingwave · error

aggregate function is not supported

Error message

aggregate function is not supported

What it means

The default trait implementation of `call_agg_create_state` on RisingWave's UDF expression trait always fails with this message. It exists so scalar/table UDFs only need to implement aggregate hooks when they actually represent an aggregate function; calling the aggregate path on a UDF that did not override it hits this bail.

Source

Thrown at src/expr/core/src/sig/udf.rs:142

    Table,
    Aggregate,
}

/// UDF implementation.
#[async_trait::async_trait]
pub trait UdfImpl: std::fmt::Debug + Send + Sync {
    /// Call the scalar function.
    async fn call(&self, input: &RecordBatch) -> Result<RecordBatch>;

    /// Call the table function.
    async fn call_table_function<'a>(
        &'a self,
        input: &'a RecordBatch,
    ) -> Result<BoxStream<'a, Result<RecordBatch>>>;

    /// For aggregate function, create the initial state.
    async fn call_agg_create_state(&self) -> Result<ArrayRef> {
        bail!("aggregate function is not supported");
    }

    /// For aggregate function, accumulate or retract the state.
    async fn call_agg_accumulate_or_retract(
        &self,
        _state: &ArrayRef,
        _ops: &BooleanArray,
        _input: &RecordBatch,
    ) -> Result<ArrayRef> {
        bail!("aggregate function is not supported");
    }

    /// For aggregate function, get aggregate result from the state.
    async fn call_agg_finish(&self, _state: &ArrayRef) -> Result<ArrayRef> {
        bail!("aggregate function is not supported");
    }

    /// Whether the UDF talks in legacy mode.

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Implement `call_agg_create_state` (plus `call_agg_accumulate_or_retract` and `call_agg_finish`) for the UDF if it is meant to be an aggregate.
  2. If the UDF is scalar-only, fix the query/planning so it is not used as an aggregate (or register it with the correct function kind).
  3. Verify the UDF descriptor/kind in the function catalog declares `AGGREGATE` only when the aggregate trait methods are provided.

Example fix

// before
impl UdfImpl for MyUdf {
    async fn eval(&self, input: &RecordBatch) -> Result<ArrayRef> { /* scalar eval */ }
}
// after
#[async_trait]
impl UdfImpl for MyUdf {
    async fn eval(&self, input: &RecordBatch) -> Result<ArrayRef> { /* scalar eval */ }
    async fn call_agg_create_state(&self) -> Result<ArrayRef> {
        Ok(self.state_builder.build_initial_state())
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Rust: check trait capabilities before aggregate use
fn supports_aggregate(u: &dyn UdfImpl) -> bool { u.implements_agg() }

Type guard

fn is_aggregate_udf(f: &FunctionDesc) -> bool { matches!(f.kind, FunctionKind::Aggregate) }

Try / catch

match udf.call_agg_create_state().await {
    Ok(state) => state,
    Err(e) if e.to_string().contains("aggregate function is not supported") => {
        return Err(anyhow!("UDF '{}' cannot be used as an aggregate", name));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling `call_agg_create_state()` on a `UdfExpr`/UDF wrapper whose underlying implementation only overrides the scalar (or table-function) evaluation methods and leaves `call_agg_create_state` as the trait default — e.g. running the aggregate streaming/batch executor over an external UDF registered as a scalar function.

Common situations: A developer registers a Python/SQL/external UDF intended to be used with GROUP BY aggregation but implements it as a scalar UDF; or a planner/executor bug routes an aggregate call down the UDF path without the aggregate trait methods being implemented.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/4eeaa02f7ba3e681. Report an issue: GitHub.