{"record":{"id":"22964d202e158201","repo":"risingwavelabs/risingwave","slug":"unreachable-non-comparator-or-unexpected-bound-st","errorCode":null,"errorMessage":"unreachable (non-comparator or unexpected bound state)","messagePattern":"unreachable \\(non-comparator or unexpected bound state\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/stream/src/executor/dynamic_filter.rs","lineNumber":229,"sourceCode":"    }\n\n    /// Returns the required range, whether the latest value is in lower bound (rather than upper)\n    /// and whether to insert or delete the range.\n    fn get_range(\n        &self,\n        curr: &Datum,\n        prev: Datum,\n    ) -> ((Bound<ScalarImpl>, Bound<ScalarImpl>), bool, bool) {\n        debug_assert_ne!(curr, &prev);\n        let curr_is_some = curr.is_some();\n        match (curr.clone(), prev) {\n            (Some(c), None) | (None, Some(c)) => {\n                let range = match self.comparator {\n                    GreaterThan => (Excluded(c), Unbounded),\n                    GreaterThanOrEqual => (Included(c), Unbounded),\n                    LessThan => (Unbounded, Excluded(c)),\n                    LessThanOrEqual => (Unbounded, Included(c)),\n                    _ => unreachable!(),\n                };\n                let is_insert = curr_is_some;\n                // The new bound is always towards the last known value\n                let is_lower = matches!(self.comparator, GreaterThan | GreaterThanOrEqual);\n                (range, is_lower, is_insert)\n            }\n            (Some(c), Some(p)) => {\n                if c.default_cmp(&p).is_lt() {\n                    let range = match self.comparator {\n                        GreaterThan | LessThanOrEqual => (Excluded(c), Included(p)),\n                        GreaterThanOrEqual | LessThan => (Included(c), Excluded(p)),\n                        _ => unreachable!(),\n                    };\n                    let is_insert = matches!(self.comparator, GreaterThan | GreaterThanOrEqual);\n                    (range, true, is_insert)\n                } else {\n                    // c > p\n                    let range = match self.comparator {","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/stream/src/executor/dynamic_filter.rs#L211-L247","documentation":"In get_range, when exactly one of curr/prev is Some, the code matches on self.comparator (PbExprNodeType) to build the new range bound. Only GreaterThan, GreaterThanOrEqual, LessThan and LessThanOrEqual are valid comparators for a dynamic filter; anything else hits `unreachable!()`. The panic means the executor was constructed with a comparator node type that is not one of the four supported comparison operators.","triggerScenarios":"get_range (invoked from execute_inner while processing a barrier/epoch on the right-side value) is called on a DynamicFilterExecutor whose `comparator` field was set to a PbExprNodeType outside {GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual} (e.g. Equal, or a non-comparison node).","commonSituations":"Hit by developers adding new comparator support to dynamic filters without updating all match arms in get_range, or by planner changes that instantiate a DynamicFilterExecutor with an unexpected expression node type (e.g. testing with Equal instead of an ordering comparison).","solutions":["Check the DynamicFilterExecutor::new call site / planner code that chooses the PbExprNodeType and ensure it is restricted to the four ordering comparators.","If a new comparator (e.g. Equal/NotEqual) is intended, add the corresponding arm to all match statements in get_range instead of relying on `_ => unreachable!()`.","Validate the comparator in the constructor and return a descriptive error at executor creation rather than panicking later."],"exampleFix":"// before\n_ => unreachable!(),\n// after\nother => return Err(StreamExecutorError::internal(anyhow::anyhow!(\n    \"unsupported dynamic filter comparator: {:?}\", other\n))),","handlingStrategy":"validation","validationCode":"// At executor construction:\nconst SUPPORTED: [PbExprNodeType; 4] = [GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual];\nassert!(SUPPORTED.contains(&comparator), \"unsupported dynamic filter comparator: {:?}\", comparator);","typeGuard":"fn is_ordering_comparator(c: &PbExprNodeType) -> bool {\n    matches!(c, PbExprNodeType::GreaterThan | PbExprNodeType::GreaterThanOrEqual | PbExprNodeType::LessThan | PbExprNodeType::LessThanOrEqual)\n}","tryCatchPattern":"match self.comparator {\n    GreaterThan => (Excluded(c), Unbounded),\n    GreaterThanOrEqual => (Included(c), Unbounded),\n    LessThan => (Unbounded, Excluded(c)),\n    LessThanOrEqual => (Unbounded, Included(c)),\n    other => return Err(StreamExecutorError::internal(anyhow::anyhow!(\"unsupported comparator {:?}\", other))),\n}","preventionTips":["Validate the comparator in DynamicFilterExecutor::new instead of trusting the planner.","Add a planner-side exhaustive check so only ordering comparators can instantiate a dynamic filter.","When adding comparators, grep for all `unreachable!()` sites in get_range and update each."],"tags":["rust","panic","executor","comparator","match-arm"],"backgroundTag":"invalid-enum-value","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"}