{"record":{"id":"0d1c98833c5016f5","repo":"apache/druid","slug":"must-have-exactly-one-child","errorCode":null,"errorMessage":"Must have exactly one child","messagePattern":"Must have exactly one child","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":400,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/QueryDataSource.java","lineNumber":85,"sourceCode":"    return getQueryDataSources();\n  }\n\n  private List<DataSource> getQueryDataSources()\n  {\n    if (query instanceof UnionQuery) {\n      return ((UnionQuery) query).getDataSources();\n    }\n    return Collections.singletonList(query.getDataSource());\n  }\n\n  @Override\n  public DataSource withChildren(List<DataSource> children)\n  {\n    if (query instanceof UnionQuery) {\n      return new QueryDataSource(((UnionQuery) query).withDataSources(children));\n    } else {\n      if (children.size() != 1) {\n        throw new IAE(\"Must have exactly one child\");\n      }\n      return new QueryDataSource(query.withDataSource(children.get(0)));\n    }\n  }\n\n  @Override\n  public boolean isCacheable(boolean isBroker)\n  {\n    return false;\n  }\n\n  @Override\n  public boolean isGlobal()\n  {\n    return false;\n  }\n\n  @Override","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/QueryDataSource.java#L67-L103","documentation":"QueryDataSource wraps exactly one query, so its withChildren() expects a single child DataSource. For non-union queries the method validates that the replacement children list has exactly one element and throws IllegalArgumentException otherwise. Callers must supply one DataSource that will replace the inner query's data source.","triggerScenarios":"Calling DataSource.withChildren() on a QueryDataSource whose inner query is not a UnionQuery, passing an empty list or a list with 2+ children (typically from generic data-source rewrite/planning code).","commonSituations":"Custom query-rewriting or optimization code (flattening data source trees, join planning) that computes children generically and mis-counts for QueryDataSource; tooling that treats all DataSource subtypes uniformly.","solutions":["Ensure the children list passed to withChildren() contains exactly one DataSource for non-union QueryDataSource instances.","If the inner query is a union, pass the children to the union's withDataSources path instead.","Check the concrete DataSource type before deciding how many children to pass.","Inspect the data source tree (the wrapped query) to confirm the expected shape before rewriting."],"exampleFix":"// before\nList<DataSource> children = gatherAllChildren(dataSource);\nDataSource rewritten = dataSource.withChildren(children);\n// after\nList<DataSource> children = gatherAllChildren(dataSource);\nif (!(dataSource instanceof QueryDataSource) || children.size() != 1) {\n  throw new IllegalStateException(\"Expected exactly one child for QueryDataSource, got \" + children.size());\n}\nDataSource rewritten = dataSource.withChildren(Collections.singletonList(children.get(0)));","handlingStrategy":"type-guard","validationCode":"if (!(dataSource instanceof QueryDataSource)) {\n  throw new IllegalStateException(\"withChildren called on non-QueryDataSource\");\n}\nif (children.size() != 1) {\n  throw new IllegalArgumentException(\"QueryDataSource requires exactly one child, got \" + children.size());\n}","typeGuard":"static boolean isRewritableQueryDataSource(DataSource ds, List<DataSource> children) {\n  return ds instanceof QueryDataSource && children.size() == 1;\n}","tryCatchPattern":"try {\n  rewritten = dataSource.withChildren(children);\n} catch (IllegalArgumentException e) {\n  if (e.getMessage().contains(\"exactly one child\")) {\n    rewritten = dataSource.withChildren(Collections.singletonList(children.get(0)));\n  } else { throw e; }\n}","preventionTips":["Handle UnionQuery children separately before generic rewriting","Write unit tests for withChildren over every DataSource subtype","Never assume child count without checking the concrete DataSource class"],"tags":["druid","datasource","query-rewrite","argument-count"],"backgroundTag":"missing-required-argument","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}