{"record":{"id":"2554ac11edeb4f22","repo":"influxdata/influxdb","slug":"expected-at-least-1-child-execution-plan-node","errorCode":null,"errorMessage":"expected at least 1 child execution plan node","messagePattern":"expected at least 1 child execution plan node","errorType":"exception","errorClass":"DataFusionError","httpStatus":null,"severity":"error","filePath":"core/iox_query/src/util.rs","lineNumber":201,"sourceCode":"    let idx = schema.index_of(name)?;\n    let mut intervals = calculate_field_intervals(session_state, Arc::clone(&schema), expr)?;\n    Ok(intervals.swap_remove(idx))\n}\n\n/// Take in 1 or more children, and returns a singular [`ExecutionPlan`].\n///\n/// If there are multiple children, it unions in order to return a singular execution plan.\npub fn union_multiple_children(\n    mut children: Vec<Arc<dyn ExecutionPlan>>,\n) -> Result<Arc<dyn ExecutionPlan>, DataFusionError> {\n    if children.is_empty() {\n        return Err(internal_datafusion_err!(\n            \"expected at least 1 child execution plan node\"\n        ));\n    }\n\n    if children.len() == 1 {\n        return Ok(children.pop().expect(\"unreachable\"));\n    }\n    UnionExec::try_new(children)\n}\n\n#[cfg(test)]\nmod tests {\n    use datafusion::common::ScalarValue;\n    use datafusion::common::rounding::next_down;\n    use datafusion::execution::context::SessionContext;\n    use datafusion::logical_expr::{col, lit};\n    use schema::{InfluxFieldType, TIME_DATA_TIMEZONE, builder::SchemaBuilder};\n\n    use super::*;\n\n    fn time_interval(lower: Option<i64>, upper: Option<i64>) -> Interval {\n        let lower = ScalarValue::TimestampNanosecond(lower, TIME_DATA_TIMEZONE());\n        let upper = ScalarValue::TimestampNanosecond(upper, TIME_DATA_TIMEZONE());\n        Interval::try_new(lower, upper).unwrap()","sourceCodeStart":183,"sourceCodeEnd":219,"githubUrl":"https://github.com/influxdata/influxdb/blob/06200ef96ba82c5f6727e5038a83af8e722c6875/core/iox_query/src/util.rs#L183-L219","documentation":"union_multiple_children returns an internal_datafusion_err when given zero child execution plans. It collapses a single child to that child and otherwise builds a UnionExec, neither of which is meaningful for an empty child list.","triggerScenarios":"Calling union_multiple_children with an empty Vec of Arc<dyn ExecutionPlan>, e.g. after optimizer rules pruned all inputs of what was a union.","commonSituations":"Custom optimizer rules removing all union children without removing the union node; plan builders that assemble children lists dynamically and can end up empty; chunk-to-physical-node conversion with no chunks.","solutions":["Ensure at least one child plan exists before calling union_multiple_children","Handle the empty-children case upstream by returning an EmptyExec/placeholder","Fix the optimizer rule that produced a union node with no inputs"],"exampleFix":"// before\nlet plan = union_multiple_children(children)?; // children may be empty\n// after\nensure!(!children.is_empty(), \"union requires at least one child plan\");\nlet plan = union_multiple_children(children)?;","handlingStrategy":"validation","validationCode":"if children.is_empty() { return Err(plan_err(\"union needs at least one child\")); }","typeGuard":"fn is_unionable(children: &[Arc<dyn ExecutionPlan>]) -> bool { !children.is_empty() }","tryCatchPattern":"match union_multiple_children(children) {\n    Err(e) if e.to_string().contains(\"expected at least 1 child\") => EmptyExec::new(schema).into(),\n    other => other?,\n}","preventionTips":["Guard optimizer rules that can empty union inputs","Fall back to EmptyExec for zero children","Assert child count in plan-builder helpers"],"tags":["rust","execution-plan","union","empty-input"],"backgroundTag":"missing-required-argument","analyzedSha":"06200ef96ba82c5f6727e5038a83af8e722c6875","analyzedAt":"2026-09-19T12:55:30.003Z","contentChangedAt":"2026-09-19T12:55:30.003Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}