{"record":{"id":"625652f5109508fa","repo":"risingwavelabs/risingwave","slug":"range-frame-offset-subtract-expression-must-be-syn","errorCode":null,"errorMessage":"range frame offset subtract expression must be sync","messagePattern":"range frame offset subtract expression must be sync","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/expr/core/src/window_function/range.rs","lineNumber":351,"sourceCode":"        let offset_expr =\n            LiteralExpression::new(offset_data_type.clone(), Some(self.offset.clone()));\n        let add_expr = build_func(\n            PbExprType::Add,\n            order_data_type.clone(),\n            vec![input_expr.clone().boxed(), offset_expr.clone().boxed()],\n        )?;\n        let crate::expr::BoxedExpression::Sync(add_expr) = add_expr else {\n            bail!(\"range frame offset add expression must be sync\");\n        };\n        self.add_expr = Some(add_expr);\n\n        let sub_expr = build_func(\n            PbExprType::Subtract,\n            order_data_type.clone(),\n            vec![input_expr.boxed(), offset_expr.boxed()],\n        )?;\n        let crate::expr::BoxedExpression::Sync(sub_expr) = sub_expr else {\n            bail!(\"range frame offset subtract expression must be sync\");\n        };\n        self.sub_expr = Some(sub_expr);\n        Ok(())\n    }\n\n    pub fn new_for_test(\n        offset: ScalarImpl,\n        order_data_type: &DataType,\n        offset_data_type: &DataType,\n    ) -> Self {\n        let mut offset = Self::new(offset);\n        offset.prepare(order_data_type, offset_data_type).unwrap();\n        offset\n    }\n}\n\nimpl Deref for RangeFrameOffset {\n    type Target = ScalarImpl;","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/core/src/window_function/range.rs#L333-L369","documentation":"When building a RANGE window frame, the framework constructs a Subtract expression used to compute `order_col - offset` for each row. The window function executor drives this expression from its sync (synchronous) evaluation path, so only sync expressions are acceptable. If the built Subtract expression is not of the `BoxedExpression::Sync` variant, construction fails.","triggerScenarios":"Deserializing a RANGE frame boundary from protobuf (via `from_protobuf`) where the offset expression compiled by the frontend to a Subtract that is async/non-sync, e.g. involving an expression type that only has an async evaluation implementation.","commonSituations":"Frontend/backend expression-kind mismatch after adding a new expression type without a sync eval implementation; protobuf produced by a newer frontend with expression kinds the older backend builder wraps in a non-sync variant.","solutions":["Check which expression type the frontend emits for the RANGE frame offset Subtract and ensure its Rust implementation exposes a sync `eval` returning `BoxedExpression::Sync` from the builder","Update `build_func`/the relevant expression constructor so Subtract over this data type builds a Sync expression","Verify frontend and backend versions match so protobuf expression nodes are understood by the builder"],"exampleFix":"// before: expression built without sync variant\nlet sub_expr = build_func(PbExprType::Subtract, order_data_type, vec![input_expr.boxed(), offset_expr.boxed()])?;\n// after: ensure the constructor returns BoxedExpression::Sync\nlet crate::expr::BoxedExpression::Sync(sub_expr) = sub_expr else {\n    bail!(\"range frame offset subtract expression must be sync\");\n};","handlingStrategy":"try-catch","validationCode":"// Rust: check sync variant before use\nfn ensure_sync(e: &BoxedExpression) -> Result<()> {\n    if !matches!(e, crate::expr::BoxedExpression::Sync(_)) {\n        bail!(\"expression is not sync\");\n    }\n    Ok(())\n}","typeGuard":"fn is_sync(e: &BoxedExpression) -> bool { matches!(e, BoxedExpression::Sync(_)) }","tryCatchPattern":"match build_range_frame(...) {\n    Ok(f) => f,\n    Err(e) if e.to_string().contains(\"must be sync\") => fallback_to_nonsync_path_or_report(e),\n    Err(e) => return Err(e),\n}","preventionTips":["Keep expression builder constructors returning Sync variants for all arithmetic ops","Add a unit test asserting Subtract on each supported type builds a Sync expression","Pin frontend/backend versions in deployment"],"tags":["rust","window-function","expression-serialization"],"backgroundTag":"internal-invariant-violation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}