{"record":{"id":"9971b561a87070f3","repo":"risingwavelabs/risingwave","slug":"dynamic-filter-condition-eval-must-return-bool-arr","errorCode":null,"errorMessage":"dynamic filter condition eval must return bool array","messagePattern":"dynamic filter condition eval must return bool array","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/stream/src/executor/dynamic_filter.rs","lineNumber":118,"sourceCode":"            Some(cond.eval_infallible(chunk).await)\n        } else {\n            None\n        };\n\n        let below_watermark = if let Some(cond) = below_watermark_condition {\n            Some(cond.eval_infallible(chunk).await)\n        } else {\n            None\n        };\n\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 => {","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/stream/src/executor/dynamic_filter.rs#L100-L136","documentation":"This is an internal invariant panic in the DynamicFilter executor. After evaluating the dynamic filter condition expression over a chunk via `eval_infallible`, the code expects the result to be a Bool array (one boolean per row). If the expression evaluator returned any other array type (Int32, Utf8, etc.), the executor cannot compute row visibility and panics. It should never happen for a correctly planned dynamic filter, since the frontend always builds the condition as a boolean expression.","triggerScenarios":"apply_batch is called (from execute_inner) with a Some(filter_condition) whose NonStrictExpression evaluates (via eval_infallible on the StreamChunk) to an ArrayImpl that is not ArrayImpl::Bool. This would require the planner/expr dispatcher to produce a non-boolean return type for the dynamic filter predicate.","commonSituations":"Practically only hit by RisingWave developers changing the dynamic-filter planning code, the expression type inference, or adding new expression node types that break the boolean return-type contract of the generated filter condition; end users cannot trigger it via SQL.","solutions":["Check how the dynamic filter condition is built in the frontend planner and ensure the expression's return type is Boolean (literal_type checked as boolean).","Inspect the comparator/condition construction (PbExprNodeType dispatch) for changes that could yield a non-bool expression result.","Capture the chunk and the expression's return_type at the panic site and file an issue with the plan; wrap executor polling in the stream catch mechanism if resilience is needed."],"exampleFix":"// before\nlet satisfied = if let ArrayImpl::Bool(results) = &**array { results.value_at(idx).unwrap_or(false) } else { panic!(\"...\") };\n// after\nlet satisfied = match &**array {\n    ArrayImpl::Bool(results) => results.value_at(idx).unwrap_or(false),\n    other => return Err(StreamExecutorError::internal(anyhow::anyhow!(\n        \"dynamic filter condition eval returned {:?}, expected Bool array\", other.data_type()\n    ))),\n};","handlingStrategy":"type-guard","validationCode":"// Before relying on eval results:\nlet array = filter_results.as_ref().expect(\"condition present\");\nassert!(matches!(&**array, ArrayImpl::Bool(_)), \"filter cond must eval to Bool array\");","typeGuard":"fn is_bool_array(a: &ArrayImpl) -> bool { matches!(a, ArrayImpl::Bool(_)) }","tryCatchPattern":"// In RisingWave stream executors panics abort the actor; guard at plan time instead:\nmatch &**array {\n    ArrayImpl::Bool(b) => b.value_at(idx).unwrap_or(false),\n    other => return Err(StreamExecutorError::internal(anyhow::anyhow!(\"expected Bool array, got {:?}\", other.data_type()))),\n}","preventionTips":["Always plan dynamic filter conditions with a Boolean return type and assert it in the planner.","Add a unit test asserting eval_infallible on a sample chunk yields ArrayImpl::Bool for every supported comparator.","Prefer returning StreamExecutorError::internal over panic! so the actor fails gracefully with context."],"tags":["rust","streaming","panic","executor","type-mismatch"],"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-14T11:17:12.474Z"}