risingwavelabs/risingwave · error
MATCH_RECOGNIZE node missing pattern
Error message
MATCH_RECOGNIZE node missing pattern
What it means
A MATCH_RECOGNIZE plan must carry a pattern node describing the regex-like row pattern to match. `new_boxed_executor` fails with this error when `node.pattern_node` is `None`, since the executor cannot compile an NFA without a pattern. The pattern is mandatory infrastructure for the operator, so its absence indicates a corrupt or incomplete plan.
Source
Thrown at src/stream/src/from_proto/match_recognize.rs:108
if order_key_indices.is_empty() {
return Err(anyhow::anyhow!("MATCH_RECOGNIZE plan carries an empty ORDER BY").into());
}
let defines = node
.defines
.iter()
.map(|d| CompiledDefine::from_protobuf(d, params.eval_error_report.clone()))
.collect::<crate::executor::StreamExecutorResult<Vec<_>>>()?;
let measures = node
.measures
.iter()
.map(|m| CompiledMeasure::from_protobuf(m, params.eval_error_report.clone()))
.collect::<crate::executor::StreamExecutorResult<Vec<_>>>()?;
let pattern_node = node
.pattern_node
.as_ref()
.ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing pattern"))?;
let pattern = pattern_from_protobuf(pattern_node)
.map_err(|e| anyhow::anyhow!("invalid MATCH_RECOGNIZE pattern: {e}"))?;
let nfa = Nfa::compile(&pattern);
// Fail fast on anything malformed rather than silently defaulting to PAST LAST ROW, which
// would mask a corrupt plan or a version skew.
let skip = {
use risingwave_pb::stream_plan::match_recognize_after_match_skip::Mode;
let pb_skip = node
.after_match_skip
.as_ref()
.ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing after_match_skip"))?;
let target = || {
pb_skip.target.clone().ok_or_else(|| {
anyhow::anyhow!("AFTER MATCH SKIP TO FIRST/LAST missing its target variable")
})
};
match pb_skip.mode() {View on GitHub (pinned to 6469eb736d)
Solutions
- Ensure the SQL MATCH_RECOGNIZE clause includes a PATTERN clause so the frontend emits `pattern_node`.
- Re-create the streaming job with a frontend version matching the compute node.
- Inspect the serialized actor plan and verify `pattern_node` is populated.
- In tests, always set `pattern_node` via `pattern_from_protobuf`-compatible structures before building.
Example fix
// before
let node = MatchRecognizeNode { pattern_node: None, .. };
// after
let node = MatchRecognizeNode { pattern_node: Some(pattern_node_proto), .. }; Defensive patterns
Strategy: validation
Validate before calling
// before building
if node.pattern_node.is_none() {
return Err(anyhow!("MATCH_RECOGNIZE node missing pattern"));
} Type guard
fn has_pattern(node: &MatchRecognizeNode) -> bool { node.pattern_node.is_some() } Try / catch
let pattern_node = node.pattern_node.as_ref().ok_or_else(|| anyhow::anyhow!("MATCH_RECOGNIZE node missing pattern"))?; Prevention
- Always write a PATTERN clause in MATCH_RECOGNIZE SQL.
- Keep frontend and stream proto definitions in sync so pattern_node serializes reliably.
- Add round-trip serialization tests for MATCH_RECOGNIZE nodes including the pattern.
When it happens
Trigger: `new_boxed_executor` calls `node.pattern_node.as_ref().ok_or_else(...)` and the protobuf oneof/optional `pattern_node` field is unset — plan serialization dropped it, the frontend never set it, or the proto was truncated/edited.
Common situations: Version skew where an older frontend omitted the pattern field; corrupted actor protos in-flight; tests constructing MATCH_RECOGNIZE nodes without `pattern_node`; proto schema evolution mistakes.
Related errors
- MATCH_RECOGNIZE plan carries an empty ORDER BY
- IcebergWithPkIndexWriterExecutor requires exactly two inputs
- missing downstream pk in iceberg sink desc
- unknown MATCH_RECOGNIZE input mode: {}
- unsupported MATCH_RECOGNIZE input mode: {other:?}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/529d6c2fe17b4e2c.
Report an issue: GitHub.