{"record":{"id":"c8c44a8d52255381","repo":"risingwavelabs/risingwave","slug":"frame-start-cannot-be-unbounded-following","errorCode":null,"errorMessage":"frame start cannot be UNBOUNDED FOLLOWING","messagePattern":"frame start cannot be UNBOUNDED FOLLOWING","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/expr/core/src/window_function/call.rs","lineNumber":221,"sourceCode":"}\n\nimpl<T> FrameBound<T> {\n    fn offset_value(&self) -> Option<&T> {\n        match self {\n            UnboundedPreceding | UnboundedFollowing | CurrentRow => None,\n            Preceding(offset) | Following(offset) => Some(offset),\n        }\n    }\n\n    pub(super) fn validate_bounds(\n        start: &Self,\n        end: &Self,\n        offset_checker: impl Fn(&T) -> Result<()>,\n    ) -> Result<()> {\n        match (start, end) {\n            (_, UnboundedPreceding) => bail!(\"frame end cannot be UNBOUNDED PRECEDING\"),\n            (UnboundedFollowing, _) => {\n                bail!(\"frame start cannot be UNBOUNDED FOLLOWING\")\n            }\n            (Following(_), CurrentRow) | (Following(_), Preceding(_)) => {\n                bail!(\"frame starting from following row cannot have preceding rows\")\n            }\n            (CurrentRow, Preceding(_)) => {\n                bail!(\"frame starting from current row cannot have preceding rows\")\n            }\n            _ => {}\n        }\n\n        for bound in [start, end] {\n            if let Some(offset) = bound.offset_value() {\n                offset_checker(offset)?;\n            }\n        }\n\n        Ok(())\n    }","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/core/src/window_function/call.rs#L203-L239","documentation":"Guard in FrameBound::validate_bounds for window frames: a frame whose start bound is UNBOUNDED FOLLOWING is invalid per SQL semantics (the start must not come after the end), so window frame construction fails with this error.","triggerScenarios":"A frame is built where `start` is `FrameBound::UnboundedFollowing` — e.g. `ROWS BETWEEN UNBOUNDED FOLLOWING AND ...` reaches validation (bypassing or after the binder), typically from direct `Frame` construction or deserialized plans.","commonSituations":"Typo'd or machine-generated OVER clauses; internal APIs/tests constructing `Frame` with invalid bounds without prior binder checks.","solutions":["Rewrite the SQL frame to start at or before the current row (e.g. `ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING`).","If generating SQL programmatically, validate bound ordering before emitting the clause.","Consider handling this class of invalid frames in the binder so users get a SQL-level error instead of an internal one."],"exampleFix":"-- before\nOVER (ORDER BY ts ROWS BETWEEN UNBOUNDED FOLLOWING AND CURRENT ROW)\n-- after\nOVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)","handlingStrategy":"validation","validationCode":"// pre-validate before constructing Frame\ndebug_assert!(!matches!(start, FrameBound::UnboundedFollowing), \"start cannot be UNBOUNDED FOLLOWING\");","typeGuard":"fn start_not_following_unbounded<T>(s: &FrameBound<T>) -> bool { !matches!(s, FrameBound::UnboundedFollowing) }","tryCatchPattern":"match frame.validate() {\n    Ok(()) => (),\n    Err(e) if e.to_string().contains(\"frame start cannot be UNBOUNDED FOLLOWING\") => bail!(\"reversed window frame bounds\"),\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Emit frames only with start <= end ordering from SQL generators","Catch reversed bounds at bind time for better UX","Test all start/end FrameBound combinations in unit tests"],"tags":["window-function","frame-bounds","validation","sql"],"backgroundTag":"invalid-argument-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"}