risingwavelabs/risingwave · warning

MATCH_RECOGNIZE SUM measure slot has no kernel

Error message

MATCH_RECOGNIZE SUM measure slot has no kernel

What it means

During result resolution (`resolve`, called from `next_pos`), a SUM measure slot has no aggregation kernel attached (`self.agg` is None). Per the code's own invariant this cannot happen because from_protobuf rejects such slots; the executor deliberately returns a NULL-convention error instead of panicking should that invariant ever break.

Solutions

  1. Construct measure slots only through from_protobuf, which enforces agg presence for SUM.
  2. Add/update tests to build slots via the deserialization path.
  3. Check whether state recovery loaded a plan from a mismatched/older version; re-create the job.

Example fix

// before
let slot = MeasureSlot { kind: MeasureSlotKind::Sum, agg: None };
// after
let slot = MeasureSlot::from_protobuf(&pb_with_agg_call, &error_report)?;
Defensive patterns

Strategy: type-guard

Validate before calling

// Before resolving, ensure the kernel exists:
if slot.kind == MeasureSlotKind::Sum && slot.agg.is_none() {
    return Ok(ScalarImpl::Float64(ordered_float::OrderedFloat(f64::NAN))); // or error early
}

Type guard

fn agg_kernel(slot: &MeasureSlot) -> Option<&Arc<dyn StreamingAggState>> {
    slot.agg.as_ref().map(|a| &a.func)
}

Try / catch

// Defensive resolution at the call site:
let Some(agg) = &slot.agg else {
    log::warn!("SUM measure without kernel; returning NULL");
    return Ok(None);
};

Prevention

When it happens

Trigger: resolve() hits MeasureSlotKind::Sum while self.agg is None — only possible if the from_protobuf invariant was bypassed (e.g. struct constructed directly in tests or via state recovery of a corrupt plan).

Common situations: Directly constructed AggSlot/measure structs in unit tests; corrupted recovered state; future code changes breaking the deserialization invariant.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/stream/src/executor/match_recognize/executor.rs:290

                .iter()
                .enumerate()
                .filter(|(_, l)| matches(l))
                .filter_map(|(j, _)| rows[start + j].row.datum_at(self.col_idx))
                .min_by(|a, b| a.default_cmp(b))
                .map(|r| r.into_scalar_impl()),
            MeasureSlotKind::Max => labels
                .iter()
                .enumerate()
                .filter(|(_, l)| matches(l))
                .filter_map(|(j, _)| rows[start + j].row.datum_at(self.col_idx))
                .max_by(|a, b| a.default_cmp(b))
                .map(|r| r.into_scalar_impl()),
            // Rejected in `from_protobuf`; a constructed slot never carries it. NULL (the
            // non-strict convention) rather than a panic, should that invariant ever break.
            MeasureSlotKind::Unspecified => None,
            MeasureSlotKind::Sum => {
                let agg = self.agg.as_ref().ok_or_else(|| {
                    anyhow::anyhow!("MATCH_RECOGNIZE SUM measure slot has no kernel")
                })?;
                // Feed the kernel a single-column chunk of the col values over the matching rows.
                let input: Vec<(Op, OwnedRow)> = labels
                    .iter()
                    .enumerate()
                    .filter(|(_, l)| matches(l))
                    .map(|(j, _)| (Op::Insert, OwnedRow::new(vec![col_at(j)])))
                    .collect();
                if input.is_empty() {
                    None
                } else {
                    let chunk = StreamChunk::from_rows(&input, std::slice::from_ref(&agg.col_type));
                    // A kernel error here is a DATA error — numeric overflow in SUM is the
                    // canonical one — on rows that recovery will replay verbatim: propagating it
                    // kills the actor and every restart replays the same rows into the same
                    // overflow, an unrecoverable crash loop from one bad match. Mirror what
                    // `NonStrictExpression` does for every other expression in this operator:
                    // report through the actor's error report and yield NULL for the measure.

View on GitHub (pinned to 6469eb736d)