{"record":{"id":"95a1d1f87291eaef","repo":"risingwavelabs/risingwave","slug":"udf-returned-but-expected","errorCode":null,"errorMessage":"UDF returned {:?}, but expected {:?}","messagePattern":"UDF returned (.+?), but expected (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/expr/core/src/expr/expr_udf.rs","lineNumber":138,"sourceCode":"\n        let arrow_output = arrow_output_result?;\n\n        if arrow_output.num_rows() != input.cardinality() {\n            bail!(\n                \"UDF returned {} rows, but expected {}\",\n                arrow_output.num_rows(),\n                input.cardinality(),\n            );\n        }\n\n        let output = self.arrow_convert.from_record_batch(&arrow_output)?;\n        let output = output.expand_vis(input.visibility().clone());\n\n        let Some(array) = output.columns().first() else {\n            bail!(\"UDF returned no columns\");\n        };\n        if !array.data_type().equals_datatype(&self.return_type) {\n            bail!(\n                \"UDF returned {:?}, but expected {:?}\",\n                array.data_type(),\n                self.return_type,\n            );\n        }\n\n        // handle optional error column\n        if let Some(errors) = output.columns().get(1) {\n            if errors.data_type() != DataType::Varchar {\n                bail!(\n                    \"UDF returned errors column with invalid type: {:?}\",\n                    errors.data_type()\n                );\n            }\n            let errors = errors\n                .as_utf8()\n                .iter()\n                .filter_map(|msg| msg.map(|s| ExprError::Custom(s.into())))","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/core/src/expr/expr_udf.rs#L120-L156","documentation":"The UDF's first output column's Arrow data type is checked against the declared RisingWave return type of the expression. If the Arrow type does not equal the expected type, evaluation bails showing both. This guards against UDF implementations whose declared schema does not match what they actually produce.","triggerScenarios":"A UDF returns data of a different type than its declared return type — e.g. declares Int32 but returns Int64, or declares a RisingWave type whose Arrow mapping differs from the produced array type.","commonSituations":"UDF signature changed but the CREATE FUNCTION return type wasn't updated; SDK/default type mapping mismatches (e.g. string vs large_string Arrow variants); remote UDF returning different types on certain inputs.","solutions":["Align the UDF's actual output type with its declared return type (cast inside the UDF)","Update the CREATE FUNCTION return type to match the real output","Check the UDF SDK's type mapping for the declared type and adjust","If it's an Arrow variant mismatch (utf8 vs large_utf8), fix the UDF's array construction"],"exampleFix":"-- before\nCREATE FUNCTION f(x int) RETURNS int AS ... -- UDF actually returns bigint\n-- after\nCREATE FUNCTION f(x int) RETURNS bigint AS ...","handlingStrategy":"type-guard","validationCode":"// In UDF: verify output type before returning\nassert!(array.data_type().equals_datatype(&expected_arrow_type), \"UDF output type mismatch\");","typeGuard":"fn matches_return_type(arr: &dyn Array, rt: &DataType) -> bool { arr.data_type().equals_datatype(rt) }","tryCatchPattern":"match res { Err(e) if e.to_string().contains(\"but expected\") => reconcile_udf_signature(&e), Ok(v) => v, Err(e) => return Err(e) }","preventionTips":["Keep CREATE FUNCTION return types in sync with UDF code","Cast output to the declared type inside the UDF","Re-test UDFs after changing their output types"],"tags":["rust","udf","arrow","type-mismatch"],"backgroundTag":"dtype-mismatch","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}