risingwavelabs/risingwave · error
frame start cannot be UNBOUNDED FOLLOWING
Error message
frame start cannot be UNBOUNDED FOLLOWING
What it means
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.
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.
Example fix
-- before OVER (ORDER BY ts ROWS BETWEEN UNBOUNDED FOLLOWING AND CURRENT ROW) -- after OVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
Defensive patterns
Strategy: validation
Validate before calling
// pre-validate before constructing Frame debug_assert!(!matches!(start, FrameBound::UnboundedFollowing), "start cannot be UNBOUNDED FOLLOWING");
Type guard
fn start_not_following_unbounded<T>(s: &FrameBound<T>) -> bool { !matches!(s, FrameBound::UnboundedFollowing) } Try / catch
match frame.validate() {
Ok(()) => (),
Err(e) if e.to_string().contains("frame start cannot be UNBOUNDED FOLLOWING") => bail!("reversed window frame bounds"),
Err(e) => return Err(e.into()),
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Typo'd or machine-generated OVER clauses; internal APIs/tests constructing `Frame` with invalid bounds without prior binder checks.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- frame end cannot be UNBOUNDED PRECEDING
- frame starting from current row cannot have preceding rows
- frame starting from following row cannot have preceding rows
- Backfill order strategy has a cycle
- batch refresh materialized view must have snapshot backfill…
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/c8c44a8d52255381.
Report an issue: GitHub.
Appendix: source
Thrown at src/expr/core/src/window_function/call.rs:221
}
impl<T> FrameBound<T> {
fn offset_value(&self) -> Option<&T> {
match self {
UnboundedPreceding | UnboundedFollowing | CurrentRow => None,
Preceding(offset) | Following(offset) => Some(offset),
}
}
pub(super) fn validate_bounds(
start: &Self,
end: &Self,
offset_checker: impl Fn(&T) -> Result<()>,
) -> Result<()> {
match (start, end) {
(_, UnboundedPreceding) => bail!("frame end cannot be UNBOUNDED PRECEDING"),
(UnboundedFollowing, _) => {
bail!("frame start cannot be UNBOUNDED FOLLOWING")
}
(Following(_), CurrentRow) | (Following(_), Preceding(_)) => {
bail!("frame starting from following row cannot have preceding rows")
}
(CurrentRow, Preceding(_)) => {
bail!("frame starting from current row cannot have preceding rows")
}
_ => {}
}
for bound in [start, end] {
if let Some(offset) = bound.offset_value() {
offset_checker(offset)?;
}
}
Ok(())
}View on GitHub (pinned to 6469eb736d)