risingwavelabs/risingwave · error

frame end cannot be UNBOUNDED PRECEDING

Error message

frame end cannot be UNBOUNDED PRECEDING

What it means

`Frame::validate_bounds` checks that a window frame's [start, end) bound pair is logically ordered. An end bound of UNBOUNDED PRECEDING would put the frame end before any possible start, so it is rejected outright.

Solutions

  1. Fix the SQL so the frame end is not before the start (e.g. `ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW`).
  2. If writing SQL programmatically, swap start/end so the smaller bound is the start.
  3. Ensure the SQL binder rejects reversed frames early with a user-friendly message before reaching this internal validation.

Example fix

-- before
OVER (ORDER BY ts ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING)
-- after
OVER (ORDER BY ts ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
Defensive patterns

Strategy: validation

Validate before calling

// pre-validate before constructing Frame
debug_assert!(!matches!(end, FrameBound::UnboundedPreceding), "end cannot be UNBOUNDED PRECEDING");

Type guard

fn bounds_ordered<T>(s: &FrameBound<T>, e: &FrameBound<T>) -> bool { !matches!(e, FrameBound::UnboundedPreceding) }

Try / catch

match frame.validate() {
    Ok(()) => (),
    Err(e) if e.to_string().contains("frame end cannot be UNBOUNDED PRECEDING") => bail!("reversed window frame bounds"),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: A window frame is parsed/planned whose end bound resolves to `FrameBound::UnboundedPreceding` — e.g. SQL like `ROWS BETWEEN CURRENT ROW AND UNBOUNDED PRECEDING` reaches validation (usually after binder should have rejected it, or via direct struct/protobuf construction).

Common situations: Hand-written SQL with reversed frame bounds relying on the binder to catch it but hitting the executor-side validation; programmatic construction of `Frame` in tests or internal APIs.

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


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/d95a940cf66db4b7. Report an issue: GitHub.

Appendix: source

Thrown at src/expr/core/src/window_function/call.rs:219

    Following(T),
    UnboundedFollowing,
}

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)?;
            }
        }

View on GitHub (pinned to 6469eb736d)