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.
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)
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 following row cannot have preceding rows
- frame starting from current row cannot have preceding rows
- Invalid order key: empty item in `{expr}`
- Invalid order key item `{item}`
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/c8c44a8d52255381.
Report an issue: GitHub.