risingwavelabs/risingwave · error · BatchError
GenerateSeriesExecutor should not have child!
Error message
GenerateSeriesExecutor should not have child!
What it means
TableFunctionExecutorBuilder (e.g. generate_series) requires zero child executors. Table functions are leaf operators whose output rows are generated from their expression arguments, so an input executor means the plan incorrectly wired a child under the table function node.
Source
Thrown at src/batch/executors/src/executor/table_function.rs:75
// remove the first column and expand the second column if its data type is struct
yield match chunk.column_at(1).as_ref() {
ArrayImpl::Struct(struct_array) => struct_array.into(),
_ => chunk.split_column_at(1).1,
};
}
}
}
pub struct TableFunctionExecutorBuilder {}
impl TableFunctionExecutorBuilder {}
impl BoxedExecutorBuilder for TableFunctionExecutorBuilder {
async fn new_boxed_executor(
source: &ExecutorBuilder<'_>,
inputs: Vec<BoxedExecutor>,
) -> Result<BoxedExecutor> {
ensure!(
inputs.is_empty(),
"GenerateSeriesExecutor should not have child!"
);
let node = try_match_expand!(
source.plan_node().get_node_body().unwrap(),
NodeBody::TableFunction
)?;
let identity = source.plan_node().get_identity().clone();
let chunk_size = source.context().get_config().developer.chunk_size;
let table_function = build_from_prost(node.table_function.as_ref().unwrap(), chunk_size)?;
let schema = if let DataType::Struct(fields) = table_function.return_type() {
(&fields).into()
} else {
Schema {View on GitHub (pinned to 6469eb736d)
Solutions
- Check the plan: the TableFunction node must have no children; fix the planner code that produced a child.
- Ensure table-function arguments are passed as expressions (value_node.relation_expr), not as child executors.
- When building TableFunctionExecutor manually, supply an empty inputs vector.
- File an internal bug with the reproducing SQL if the planner looks correct.
Example fix
// before: child executor fed to table function let inputs = vec![child_executor]; // after: table function as leaf, data via expression arguments let inputs = vec![];
Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: table functions must have no child executors assert!(inputs.is_empty(), "TableFunction must be a leaf node");
Type guard
fn is_leaf(inputs: &[BoxedExecutor]) -> bool { inputs.is_empty() } Try / catch
match builder.new_boxed_executor(&src, inputs).await {
Err(e) if e.to_string().contains("should not have child") => /* fix plan arity, log plan tree */,
other => other,
} Prevention
- Pass table function arguments as expressions, never as child executors.
- Add planner tests for generate_series/unnest queries.
- Check plan-node arity after any frontend refactor.
When it happens
Trigger: new_boxed_executor is invoked with a non-empty `inputs` vec because a TableFunction plan node has children in the batch plan tree.
Common situations: Planner bugs when planning queries with generate_series/unnest-style table functions; incorrectly nesting a table function under another operator in hand-built plans; test fixtures wiring children into leaf nodes.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Row sequential scan should not have input executor!
- Source should not have input executor!
- Row sequential scan should not have input executor!
- ValuesExecutor should have no child!
- VectorIndexNearest should have an input executor!
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/f124172d6faa8ad6.
Report an issue: GitHub.