risingwavelabs/risingwave · error
UDF returned {:?}, but expected {:?}
Error message
UDF returned {:?}, but expected {:?} What it means
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.
Source
Thrown at src/expr/core/src/expr/expr_udf.rs:138
let arrow_output = arrow_output_result?;
if arrow_output.num_rows() != input.cardinality() {
bail!(
"UDF returned {} rows, but expected {}",
arrow_output.num_rows(),
input.cardinality(),
);
}
let output = self.arrow_convert.from_record_batch(&arrow_output)?;
let output = output.expand_vis(input.visibility().clone());
let Some(array) = output.columns().first() else {
bail!("UDF returned no columns");
};
if !array.data_type().equals_datatype(&self.return_type) {
bail!(
"UDF returned {:?}, but expected {:?}",
array.data_type(),
self.return_type,
);
}
// handle optional error column
if let Some(errors) = output.columns().get(1) {
if errors.data_type() != DataType::Varchar {
bail!(
"UDF returned errors column with invalid type: {:?}",
errors.data_type()
);
}
let errors = errors
.as_utf8()
.iter()
.filter_map(|msg| msg.map(|s| ExprError::Custom(s.into())))View on GitHub (pinned to 6469eb736d)
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
Example fix
-- before CREATE FUNCTION f(x int) RETURNS int AS ... -- UDF actually returns bigint -- after CREATE FUNCTION f(x int) RETURNS bigint AS ...
Defensive patterns
Strategy: type-guard
Validate before calling
// In UDF: verify output type before returning assert!(array.data_type().equals_datatype(&expected_arrow_type), "UDF output type mismatch");
Type guard
fn matches_return_type(arr: &dyn Array, rt: &DataType) -> bool { arr.data_type().equals_datatype(rt) } Try / catch
match res { Err(e) if e.to_string().contains("but expected") => reconcile_udf_signature(&e), Ok(v) => v, Err(e) => return Err(e) } Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- UDF returned a value of type {} while the declared return ty
- column '{}' type mismatch: LanceDB type is {:?}, RisingWave
- UDF aggregate {what} has {} rows, but expected exactly 1
- UDF returned {} rows, but expected {}
- UDF returned no columns
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/95a1d1f87291eaef.
Report an issue: GitHub.