risingwavelabs/risingwave · error

unknown MATCH_RECOGNIZE input mode

Error message

unknown MATCH_RECOGNIZE input mode: {}

What it means

MATCH_RECOGNIZE input mode is transported as a protobuf enum. A wire value outside the known enum range decodes as `Unspecified`, which would silently be treated as event-time — the correctness contract of the executor. The decode path therefore rejects a non-zero raw value that reads back as `Unspecified`, guarding against future/unknown enum variants from newer frontends.

Solutions

  1. Upgrade the compute node (and all binaries) to the same version as the frontend that emitted the new input mode.
  2. Re-generate the streaming job so the plan uses a mode the running binaries understand.
  3. Inspect the actor proto's `input_mode` field for an out-of-range value.
  4. If intentional, extend the executor's match arm to support the new mode before deploying it.

Example fix

// before
if node.input_mode != 0 && node.input_mode() == MatchRecognizeInputMode::Unspecified { /* reject */ }
// after: keep the guard, but upgrade binaries so the new mode maps to a known variant
MatchRecognizeInputMode::StreamingTime => { /* handle new mode explicitly */ }
Defensive patterns

Strategy: validation

Validate before calling

// decode guard before executor construction
if node.input_mode != 0 && node.input_mode() == MatchRecognizeInputMode::Unspecified {
    return Err(anyhow!("unknown MATCH_RECOGNIZE input mode: {}", node.input_mode));
}

Type guard

fn is_known_input_mode(v: i32) -> bool {
    MatchRecognizeInputMode::try_from(v).is_ok()
}

Try / catch

match MatchRecognizeInputMode::try_from(node.input_mode) { Ok(m) => use(m), Err(_) => Err(anyhow!("unknown MATCH_RECOGNIZE input mode: {}", node.input_mode)) }

Prevention

When it happens

Trigger: `new_boxed_executor` for the MATCH_RECOGNIZE node sees `node.input_mode != 0` while the protobuf accessor maps it to `MatchRecognizeInputMode::Unspecified` — i.e. an unrecognized/out-of-range integer on the wire (e.g. a mode added by a newer frontend).

Common situations: Version skew: compute node older than the frontend that introduced a new input mode; corrupted or hand-edited plan protos; replaying plan fragments across versions.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


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

Appendix: source

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

    async fn new_boxed_executor(
        params: ExecutorParams,
        node: &MatchRecognizeNode,
        store: impl StateStore,
    ) -> StreamResult<Executor> {
        let [input]: [_; 1] = params.input.try_into().unwrap();

        // This executor's entire correctness rests on the ordered-input contract the EVENT_TIME
        // plan (an EowcSort upstream in the same fragment) provides. A different input mode —
        // PROCESSING_TIME is reserved, unimplemented — must fail here, not silently run against
        // rows whose ordering guarantee does not hold.
        // An out-of-range wire value decodes as `Unspecified` through the accessor, which would
        // silently run an unknown future mode as event-time — the one contract this executor's
        // correctness rests on. Reject it like every other enum in this decode path; a raw 0
        // (genuinely unset) is accepted as event-time since this frontend always writes it.
        if node.input_mode != 0 && node.input_mode() == MatchRecognizeInputMode::Unspecified {
            return Err(
                anyhow::anyhow!("unknown MATCH_RECOGNIZE input mode: {}", node.input_mode).into(),
            );
        }
        match node.input_mode() {
            MatchRecognizeInputMode::Unspecified | MatchRecognizeInputMode::EventTime => {}
            other => {
                return Err(
                    anyhow::anyhow!("unsupported MATCH_RECOGNIZE input mode: {other:?}").into(),
                );
            }
        }

        let partition_key_indices = node.partition_by.iter().map(|&i| i as usize).collect();
        // ORDER BY is carried as `ColumnOrder`. v1 only supports the default ascending order (the
        // binder rejects anything else); assert it here too so a non-ascending plan fails fast
        // rather than being silently sorted ascending by the executor.
        let order_key_indices = node
            .order_by
            .iter()

View on GitHub (pinned to 6469eb736d)