{"record":{"id":"7ea34044887a5754","repo":"risingwavelabs/risingwave","slug":"below-watermark-check-condition-eval-must-return-b","errorCode":null,"errorMessage":"below watermark check condition eval must return bool array","messagePattern":"below watermark check condition eval must return bool array","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/stream/src/executor/dynamic_filter.rs","lineNumber":128,"sourceCode":"\n        for (idx, (op, row)) in chunk.rows().enumerate() {\n            let left_val = row.datum_at(self.key_l).to_owned_datum();\n\n            let satisfied_dyn_filter_cond = if let Some(array) = &filter_results {\n                if let ArrayImpl::Bool(results) = &**array {\n                    results.value_at(idx).unwrap_or(false)\n                } else {\n                    panic!(\"dynamic filter condition eval must return bool array\")\n                }\n            } else {\n                // A NULL right value implies a false evaluation for all rows\n                false\n            };\n            let below_watermark = if let Some(array) = &below_watermark {\n                if let ArrayImpl::Bool(results) = &**array {\n                    results.value_at(idx).unwrap_or(false)\n                } else {\n                    panic!(\"below watermark check condition eval must return bool array\")\n                }\n            } else {\n                // there was no state cleaning watermark before\n                false\n            };\n\n            match op {\n                Op::Insert | Op::Delete => {\n                    new_ops.push(op);\n                    if satisfied_dyn_filter_cond {\n                        new_visibility.append(true);\n                    } else {\n                        new_visibility.append(false);\n                    }\n                }\n                Op::UpdateDelete => {\n                    last_res = satisfied_dyn_filter_cond;\n                }","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/stream/src/executor/dynamic_filter.rs#L110-L146","documentation":"Companion panic to the dynamic-filter one: the optional `below_watermark_condition` expression (used for state cleaning) is expected to evaluate to a Bool array over the chunk. If eval_infallible returns any non-boolean array, the executor cannot decide which rows are below the cleaning watermark and panics. This is an internal contract between the frontend planner (which builds the below-watermark expression) and this executor.","triggerScenarios":"apply_batch is called with Some(below_watermark_condition) and the expression's eval_infallible result is an ArrayImpl other than Bool for the chunk at index idx.","commonSituations":"Seen only when developing or modifying RisingWave's watermark/state-cleaning planning path, e.g. after changing how the below-watermark predicate is inferred or adding a new key/comparator type that breaks boolean type inference.","solutions":["Verify the below-watermark condition is planned as a Boolean expression (check return_type inference in the frontend for dynamic filter state cleaning).","Diff recent planner changes affecting watermark condition generation; revert or fix the non-boolean expression construction.","Replace the panic with a StreamExecutorError::internal carrying the actual array type to aid debugging, then re-run the madsim/e2e tests for dynamic filter."],"exampleFix":"// before\n} else { panic!(\"below watermark check condition eval must return bool array\") }\n// after\n} else {\n    return Err(StreamExecutorError::internal(anyhow::anyhow!(\n        \"below watermark condition eval must return Bool array, got {}\",\n        array.data_type()\n    )));\n}","handlingStrategy":"type-guard","validationCode":"if let Some(array) = &below_watermark {\n    assert!(matches!(&**array, ArrayImpl::Bool(_)), \"below-watermark cond must eval to Bool array\");\n}","typeGuard":"fn is_bool_array(a: &ArrayImpl) -> bool { matches!(a, ArrayImpl::Bool(_)) }","tryCatchPattern":"match &**array {\n    ArrayImpl::Bool(b) => b.value_at(idx).unwrap_or(false),\n    other => return Err(StreamExecutorError::internal(anyhow::anyhow!(\"below-watermark cond returned {:?}\", other.data_type()))),\n}","preventionTips":["Ensure the below-watermark predicate is inferred with Boolean type in the frontend planner.","Cover the watermark state-cleaning path in planner tests so type regressions are caught before runtime.","Convert panics into typed internal errors to keep streaming actors recoverable."],"tags":["rust","streaming","panic","executor","watermark"],"backgroundTag":"internal-invariant-violation","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"}