risingwavelabs/risingwave · error

offset of `RowsFrameBound` must be `Integer`

Error message

offset of `RowsFrameBound` must be `Integer`

What it means

The legacy protobuf `RowsFrameBound` carried offsets as a oneof: either an Integer or a Datum. ROWS frames count rows, so the offset must be an integer row count; a Datum offset is only meaningful for RANGE frames. The decoder rejects Datum offsets explicitly.

Source

Thrown at src/expr/core/src/window_function/rows.rs:103

impl FrameBoundsImpl for RowsFrameBounds {
    fn validate(&self) -> Result<()> {
        FrameBound::validate_bounds(&self.start, &self.end, |_| Ok(()))
    }
}

pub type RowsFrameBound = FrameBound<usize>;

impl RowsFrameBound {
    pub(super) fn from_protobuf_legacy(bound: &PbBound) -> Result<Self> {
        use risingwave_pb::expr::window_frame::bound::PbOffset;

        let offset = bound.get_offset()?;
        let bound = match offset {
            PbOffset::Integer(offset) => Self::from_protobuf(&PbRowsFrameBound {
                r#type: bound.get_type()? as _,
                offset: Some(*offset),
            })?,
            PbOffset::Datum(_) => bail!("offset of `RowsFrameBound` must be `Integer`"),
        };
        Ok(bound)
    }

    fn from_protobuf(bound: &PbRowsFrameBound) -> Result<Self> {
        let bound = match bound.get_type()? {
            PbBoundType::Unspecified => bail!("unspecified type of `RowsFrameBound`"),
            PbBoundType::UnboundedPreceding => Self::UnboundedPreceding,
            PbBoundType::Preceding => Self::Preceding(*bound.get_offset()? as usize),
            PbBoundType::CurrentRow => Self::CurrentRow,
            PbBoundType::Following => Self::Following(*bound.get_offset()? as usize),
            PbBoundType::UnboundedFollowing => Self::UnboundedFollowing,
        };
        Ok(bound)
    }

    fn to_protobuf(&self) -> PbRowsFrameBound {
        let (r#type, offset) = match self {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Regenerate the plan so the frontend emits the offset as `PbOffset::Integer`
  2. If you must handle Datum offsets, convert them to integers before deserialization or use the non-legacy path
  3. Verify frontend/backend versions agree on frame bound encoding

Example fix

// before: datum offset in RowsFrameBound proto
// offset { datum { ... } }
// after: integer offset
// offset { integer: 3 }
Defensive patterns

Strategy: validation

Validate before calling

fn valid_rows_offset(b: &PbRowsFrameBoundLegacy) -> bool {
    matches!(b.offset, Some(PbOffset::Integer(_)))
}

Type guard

fn has_integer_offset(o: &PbOffset) -> bool { matches!(o, PbOffset::Integer(_)) }

Try / catch

match RowsFrameBound::from_protobuf_legacy(&pb) {
    Ok(b) => b,
    Err(e) if e.to_string().contains("must be `Integer`") => return Err(anyhow!("ROWS frame offset must be an integer literal")),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Calling `from_protobuf_legacy` on a `RowsFrameBound` whose protobuf oneof `offset` is set to the `Datum` variant instead of `Integer`.

Common situations: Plan fragments produced by an old or altered frontend that encoded the frame offset as a literal datum; hand-crafted or corrupted protobuf payloads.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


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