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
- Construct measure slots only through from_protobuf, which enforces agg presence for SUM.
- Add/update tests to build slots via the deserialization path.
- 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
- Only construct MeasureSlot via from_protobuf.
- Add debug_assert!(agg.is_some()) for Sum kinds in constructors.
- Cover SUM/AVG slots in deserialization unit tests.
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
- MATCH_RECOGNIZE SUM/AVG measure slot missing agg_call
- provisional match references seq
- AFTER MATCH SKIP TO FIRST/LAST missing its target variable
- All valid CDC connectors should have returned by now
- BatchMatchRecognize is not implemented yet
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)