{"record":{"id":"a30bd40fbcd9bb5c","repo":"risingwavelabs/risingwave","slug":"match-recognize-node-missing-after-match-skip","errorCode":null,"errorMessage":"MATCH_RECOGNIZE node missing after_match_skip","messagePattern":"MATCH_RECOGNIZE node missing after_match_skip","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/stream/src/from_proto/match_recognize.rs","lineNumber":120,"sourceCode":"            .map(|m| CompiledMeasure::from_protobuf(m, params.eval_error_report.clone()))\n            .collect::<crate::executor::StreamExecutorResult<Vec<_>>>()?;\n\n        let pattern_node = node\n            .pattern_node\n            .as_ref()\n            .ok_or_else(|| anyhow::anyhow!(\"MATCH_RECOGNIZE node missing pattern\"))?;\n        let pattern = pattern_from_protobuf(pattern_node)\n            .map_err(|e| anyhow::anyhow!(\"invalid MATCH_RECOGNIZE pattern: {e}\"))?;\n        let nfa = Nfa::compile(&pattern);\n\n        // Fail fast on anything malformed rather than silently defaulting to PAST LAST ROW, which\n        // would mask a corrupt plan or a version skew.\n        let skip = {\n            use risingwave_pb::stream_plan::match_recognize_after_match_skip::Mode;\n            let pb_skip = node\n                .after_match_skip\n                .as_ref()\n                .ok_or_else(|| anyhow::anyhow!(\"MATCH_RECOGNIZE node missing after_match_skip\"))?;\n            let target = || {\n                pb_skip.target.clone().ok_or_else(|| {\n                    anyhow::anyhow!(\"AFTER MATCH SKIP TO FIRST/LAST missing its target variable\")\n                })\n            };\n            match pb_skip.mode() {\n                Mode::PastLastRow => SkipMode::PastLastRow,\n                Mode::ToNextRow => SkipMode::ToNextRow,\n                Mode::ToFirst => SkipMode::ToFirst(target()?),\n                Mode::ToLast => SkipMode::ToLast(target()?),\n                Mode::Unspecified => {\n                    return Err(anyhow::anyhow!(\n                        \"invalid MATCH_RECOGNIZE after_match_skip mode: {}\",\n                        pb_skip.mode\n                    )\n                    .into());\n                }\n            }","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/stream/src/from_proto/match_recognize.rs#L102-L138","documentation":"The MATCH_RECOGNIZE stream plan proto carries no `after_match_skip` field, which the decoder requires. Every plan produced by the binder must state how matches are skipped (PAST LAST ROW, TO NEXT ROW, TO FIRST/LAST), so its absence means a corrupt or partially serialized plan. The decoder fails fast instead of guessing a default, which would silently change query semantics.","triggerScenarios":"`new_boxed_executor` receives a `MatchRecognizeNode` proto where `after_match_skip` is `None` while `pattern_node` is set — e.g. truncated proto serialization or a producer that predates the skip clause.","commonSituations":"Rolling upgrade with an older meta node emitting plans without the skip field while the compute node requires it; corrupted plan persistence; manual plan injection in tests.","solutions":["Ensure all cluster nodes run the same RisingWave version (restart meta and compute nodes).","Re-create the streaming job so the plan is re-serialized by the current binder.","If this appears in a test, populate `after_match_skip` on the MatchRecognizeNode proto.","If reproducible on identical versions, report a serialization bug."],"exampleFix":"// before (test constructing the node)\nlet node = MatchRecognizeNode { pattern_node: Some(pattern), ..Default::default() };\n// after\nlet node = MatchRecognizeNode {\n    pattern_node: Some(pattern),\n    after_match_skip: Some(AfterMatchSkip { mode: Mode::PastLastRow as i32, target: None }),\n    ..Default::default()\n};","handlingStrategy":"validation","validationCode":"// Validate the node before handing it to the executor\nfn skip_present(node: &MatchRecognizeNode) -> Result<(), String> {\n    if node.after_match_skip.is_some() { Ok(()) } else { Err(\"after_match_skip missing\".into()) }\n}","typeGuard":null,"tryCatchPattern":"// Catch executor-build failure and include sink/plan identity in logs\nmatch MatchRecognizeExecutor::new(...) {\n    Ok(e) => e,\n    Err(err) => return Err(err.context(\"decoding MATCH_RECOGNIZE plan\")),\n}","preventionTips":["Never construct MatchRecognizeNode protos by hand without after_match_skip.","Use Default-derived builders that force explicit mode setting.","Keep producer and consumer versions aligned."],"tags":["streaming","match-recognize","protobuf","missing-field"],"backgroundTag":"missing-required-argument","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}