{"record":{"id":"041dc9ebd8c26dbf","repo":"risingwavelabs/risingwave","slug":"udf-returned-negative-row-index","errorCode":null,"errorMessage":"UDF returned negative row index","messagePattern":"UDF returned negative row index","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/expr/core/src/table_function/user_defined.rs","lineNumber":106,"sourceCode":"    }\n\n    /// Check if the output chunk is valid.\n    fn check_output(&self, output: &DataChunk) -> Result<()> {\n        if output.columns().len() != 2 {\n            bail!(\n                \"UDF returned {} columns, but expected 2\",\n                output.columns().len()\n            );\n        }\n        if output.column_at(0).data_type() != DataType::Int32 {\n            bail!(\n                \"UDF returned {:?} at column 0, but expected {:?}\",\n                output.column_at(0).data_type(),\n                DataType::Int32,\n            );\n        }\n        if output.column_at(0).as_int32().raw_iter().any(|i| i < 0) {\n            bail!(\"UDF returned negative row index\");\n        }\n        if !output\n            .column_at(1)\n            .data_type()\n            .equals_datatype(&self.return_type)\n        {\n            bail!(\n                \"UDF returned {:?} at column 1, but expected {:?}\",\n                output.column_at(1).data_type(),\n                &self.return_type,\n            );\n        }\n        Ok(())\n    }\n}\n\npub fn new_user_defined(prost: &PbTableFunction, chunk_size: usize) -> Result<BoxedTableFunction> {\n    let udf = prost.get_udf()?;","sourceCodeStart":88,"sourceCodeEnd":124,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/core/src/table_function/user_defined.rs#L88-L124","documentation":"Guard in check_output for table-function UDFs: the Int32 index column returned by the UDF contains a negative row index. Indices must be non-negative to reference valid rows of the input chunk, so evaluation fails when a UDF emits a negative index.","triggerScenarios":"The UDF returns Int32 column 0 containing negative values (e.g. a computed index like `i - offset` or an overflowing computation), detected by `raw_iter().any(|i| i < 0)`.","commonSituations":"A buggy UDF generating indices with an off-by-offset computation; UDF logic that emits sentinel -1 for missing rows.","solutions":["Fix the UDF so the index column starts at 0 and strictly increases without negatives.","If -1 was used as a sentinel, use nulls in the value column instead of negative indices.","Add a self-check inside the UDF that clamps/asserts index validity before returning."],"exampleFix":"// before (Python UDF)\nreturn list(range(-1, n))  # starts at -1\n// after\nreturn list(range(0, n))","handlingStrategy":"validation","validationCode":"# Python UDF: validate indices before returning\nassert all(i >= 0 for i in indices), \"negative row index generated\"","typeGuard":null,"tryCatchPattern":"match chunk_result {\n    Ok(c) => c,\n    Err(e) if e.to_string().contains(\"negative row index\") => {\n        bail!(\"UDF produced negative indices; check index generation logic\");\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Generate indices with range(0, n) — never offset or sentinel-negative values","Use nulls in the value column for missing rows, not -1 indices","Add an assertion in the UDF that indices are strictly non-negative"],"tags":["udf","table-function","validation","arrow"],"backgroundTag":"value-out-of-range","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"}