risingwavelabs/risingwave · error

MATCH_RECOGNIZE carries only one of the two WITHIN…

Error message

MATCH_RECOGNIZE carries only one of the two WITHIN expressions (predicate: {}, deadline: {}); the binder emits both or neither

What it means

MATCH_RECOGNIZE WITHIN support is expressed as a pair: a span predicate (`within`) and a cached deadline expression (`within_deadline`). The binder always emits both or neither; the executor detects a plan carrying exactly one and rejects it. This guards against a silent behavior change: with only `within` present, the executor would evaluate the deadline path and reject every candidate row, making the view return zero rows forever.

Solutions

  1. Align cluster node versions so producer and consumer agree on the WITHIN encoding (restart meta and compute nodes).
  2. Re-create the streaming job so the plan is regenerated with both fields.
  3. If constructing the node in a test, set both `within` and `within_deadline` (or neither).
  4. If reproducible on matching versions, report a plan-serialization bug.

Example fix

// before (test fixture)
MatchRecognizeNode { within: Some(predicate), within_deadline: None, .. }
// after
MatchRecognizeNode { within: Some(predicate), within_deadline: Some(deadline), .. }
Defensive patterns

Strategy: validation

Validate before calling

// Pair-check before executor construction
fn within_pair_ok(node: &MatchRecognizeNode) -> bool {
    node.within.is_some() == node.within_deadline.is_some()
}

Try / catch

// Fail with context rather than silently defaulting
if node.within.is_some() != node.within_deadline.is_some() {
    return Err(anyhow!("WITHIN predicate/deadline mismatch in MATCH_RECOGNIZE plan"));
}

Prevention

When it happens

Trigger: `new_boxed_executor` decodes a MatchRecognizeNode where the Option-ness of `within` and `within_deadline` differ (one is Some, the other None).

Common situations: Version skew between a producer that serializes only one field and a consumer that requires both; corrupted plan persistence; a hand-built proto in tests missing one of the two fields.

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/773b0dd998a758a1. Report an issue: GitHub.

Appendix: source

Thrown at src/stream/src/from_proto/match_recognize.rs:166

        // per row. See `eval_deadline` in the executor.
        let within_deadline = node
            .within_deadline
            .as_ref()
            .map(|e| {
                build_non_strict_from_prost(
                    e,
                    DeadlineErrorReport::new(params.eval_error_report.clone()),
                )
            })
            .transpose()?;
        // The two WITHIN expressions are a correctness-coupled pair, and the coupling tightened when
        // the executor's span check started reading the cached deadline instead of evaluating the
        // predicate: `within` present with `within_deadline` absent now rejects EVERY candidate, so
        // the view would silently produce zero rows. The binder only ever emits both or neither
        // (`lower_within`), so a plan carrying one is corrupt — fail loud, as the rest of this
        // decoder does, rather than emitting nothing forever.
        if within.is_some() != within_deadline.is_some() {
            return Err(anyhow::anyhow!(
                "MATCH_RECOGNIZE carries only one of the two WITHIN expressions \
                 (predicate: {}, deadline: {}); the binder emits both or neither",
                within.is_some(),
                within_deadline.is_some(),
            )
            .into());
        }
        // The deadline is compared directly against the order key and the watermark
        // (`ScalarRefImpl::default_cmp` panics across variants — an actor crash loop that recovery
        // replays), and the span predicate is a boolean. The binder guarantees both
        // (`lower_within`); re-state it here so a skewed or corrupt plan fails at build time.
        let order_key_type = input
            .schema()
            .fields
            .get(order_key_indices[0])
            .map(|f| f.data_type())
            .ok_or_else(|| {
                anyhow::anyhow!(

View on GitHub (pinned to 6469eb736d)