risingwavelabs/risingwave · error
UDF returned negative row index
Error message
UDF returned negative row index
What it means
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.
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.
Example fix
// before (Python UDF) return list(range(-1, n)) # starts at -1 // after return list(range(0, n))
Defensive patterns
Strategy: validation
Validate before calling
# Python UDF: validate indices before returning assert all(i >= 0 for i in indices), "negative row index generated"
Try / catch
match chunk_result {
Ok(c) => c,
Err(e) if e.to_string().contains("negative row index") => {
bail!("UDF produced negative indices; check index generation logic");
}
Err(e) => return Err(e.into()),
} Prevention
- 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
When it happens
Trigger: 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)`.
Common situations: A buggy UDF generating indices with an off-by-offset computation; UDF logic that emits sentinel -1 for missing rows.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- UDF returned columns, but expected 2
- UDF returned at column 0, but expected
- UDF returned at column 1, but expected
- UDF aggregate has rows, but expected exactly 1
- UDF returned a value of type
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/041dc9ebd8c26dbf.
Report an issue: GitHub.
Appendix: source
Thrown at src/expr/core/src/table_function/user_defined.rs:106
}
/// Check if the output chunk is valid.
fn check_output(&self, output: &DataChunk) -> Result<()> {
if output.columns().len() != 2 {
bail!(
"UDF returned {} columns, but expected 2",
output.columns().len()
);
}
if output.column_at(0).data_type() != DataType::Int32 {
bail!(
"UDF returned {:?} at column 0, but expected {:?}",
output.column_at(0).data_type(),
DataType::Int32,
);
}
if output.column_at(0).as_int32().raw_iter().any(|i| i < 0) {
bail!("UDF returned negative row index");
}
if !output
.column_at(1)
.data_type()
.equals_datatype(&self.return_type)
{
bail!(
"UDF returned {:?} at column 1, but expected {:?}",
output.column_at(1).data_type(),
&self.return_type,
);
}
Ok(())
}
}
pub fn new_user_defined(prost: &PbTableFunction, chunk_size: usize) -> Result<BoxedTableFunction> {
let udf = prost.get_udf()?;View on GitHub (pinned to 6469eb736d)