{"record":{"id":"9e01978213d5c533","repo":"risingwavelabs/risingwave","slug":"udf-returned-at-column-0-but-expected","errorCode":null,"errorMessage":"UDF returned {:?} at column 0, but expected {:?}","messagePattern":"UDF returned (.+?) at column 0, but expected (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/expr/core/src/table_function/user_defined.rs","lineNumber":99,"sourceCode":"\n            let output = DataChunk::new(\n                vec![origin_indices.into_ref(), output.column_at(1).clone()],\n                output.visibility().clone(),\n            );\n            yield output;\n        }\n    }\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            );","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/core/src/table_function/user_defined.rs#L81-L117","documentation":"Guard in check_output for table-function UDFs: the UDF's output column 0 (the index column) has a type other than the required Int32. The generic generator protocol mandates an i32 index as column 0; a wrongly declared UDF return type triggers this error during evaluation.","triggerScenarios":"The UDF returns a two-column batch but column 0 is Int64/UInt32/Utf8 etc., so `output.column_at(0).data_type() != DataType::Int32` holds.","commonSituations":"A Python UDF emits 64-bit integers (Python ints default to int64 in pyarrow); a generated index column typed as Int64 in the UDF implementation.","solutions":["Change the UDF to emit the row-index column as Arrow Int32 (e.g. cast to `pa.int32()` in pyarrow).","If the UDF cannot change, cast the index column in the UDF wrapper/service before returning the batch.","Update any SDK examples/docs where the index was produced as Int64."],"exampleFix":"// before (Python)\nreturn pa.record_batch([pa.array(range(n)), values], names=[\"i\", \"v\"])  # i is int64\n// after\nreturn pa.record_batch([pa.array(range(n), pa.int32()), values], names=[\"i\", \"v\"])","handlingStrategy":"validation","validationCode":"# Python UDF: force index column to int32 before returning\nindices = pa.array(range(n), type=pa.int32())","typeGuard":null,"tryCatchPattern":"match chunk_result {\n    Ok(c) => c,\n    Err(e) if e.to_string().contains(\"at column 0, but expected\") => {\n        bail!(\"UDF index column must be Arrow Int32; fix UDF return types\");\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Always construct the index column with an explicit pa.int32() type in Python UDFs","Remember Python ints become int64 by default in pyarrow — cast explicitly","Document the required output schema in the UDF template/SDK"],"tags":["udf","table-function","type-mismatch","arrow"],"backgroundTag":"type-mismatch","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"}