risingwavelabs/risingwave · error
UDF returned no columns
Error message
UDF returned no columns
What it means
After converting the UDF's Arrow RecordBatch into RisingWave's column format, the expression takes the first column as the result. If the batch has zero columns there is no result to return, so it bails. A scalar/vectorized UDF must always produce at least one output column.
Source
Thrown at src/expr/core/src/expr/expr_udf.rs:135
self.metrics
.memory_usage_bytes
.set(self.runtime.memory_usage() as i64);
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 = errorsView on GitHub (pinned to 6469eb736d)
Solutions
- Fix the UDF to always return at least the result column
- Check the remote UDF service response and its serialization
- Verify the Arrow->RisingWave conversion path isn't dropping columns
- Test the UDF with a simple constant function to isolate where columns are lost
Defensive patterns
Strategy: try-catch
Validate before calling
// In UDF: ensure at least one output column assert!(!record_batch.columns().is_empty(), "UDF must return a result column");
Type guard
fn has_output_columns(b: &RecordBatch) -> bool { !b.columns().is_empty() } Try / catch
match res { Err(e) if e.to_string().contains("UDF returned no columns") => log_and_report_udf_bug(&e), Ok(v) => v, Err(e) => return Err(e) } Prevention
- Always append the result column in UDF implementations
- Smoke-test UDFs with a trivial constant function
- Check remote UDF service responses are non-empty
When it happens
Trigger: A UDF implementation or remote UDF service returns an empty RecordBatch (zero columns) — e.g. the function forgot to append its result column, or a conversion layer dropped all columns.
Common situations: Custom UDFs returning only an error column or nothing; remote UDF services returning malformed payloads; SDK conversion bugs.
Understand the failure class
Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.
Related errors
- UDF returned a value of type {} while the declared return ty
- UDF aggregate {what} has {} rows, but expected exactly 1
- UDF returned {} rows, but expected {}
- UDF returned {:?}, but expected {:?}
- UDF returned errors column with invalid type: {:?}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/970ccf8cee05f264.
Report an issue: GitHub.