apache/druid · error · IllegalArgumentException
Must have exactly one child
Error message
Must have exactly one child
What it means
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.
Source
Thrown at processing/src/main/java/org/apache/druid/query/QueryDataSource.java:85
return getQueryDataSources();
}
private List<DataSource> getQueryDataSources()
{
if (query instanceof UnionQuery) {
return ((UnionQuery) query).getDataSources();
}
return Collections.singletonList(query.getDataSource());
}
@Override
public DataSource withChildren(List<DataSource> children)
{
if (query instanceof UnionQuery) {
return new QueryDataSource(((UnionQuery) query).withDataSources(children));
} else {
if (children.size() != 1) {
throw new IAE("Must have exactly one child");
}
return new QueryDataSource(query.withDataSource(children.get(0)));
}
}
@Override
public boolean isCacheable(boolean isBroker)
{
return false;
}
@Override
public boolean isGlobal()
{
return false;
}
@OverrideView on GitHub (pinned to 9b90983fd2)
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.
Example fix
// before
List<DataSource> children = gatherAllChildren(dataSource);
DataSource rewritten = dataSource.withChildren(children);
// after
List<DataSource> children = gatherAllChildren(dataSource);
if (!(dataSource instanceof QueryDataSource) || children.size() != 1) {
throw new IllegalStateException("Expected exactly one child for QueryDataSource, got " + children.size());
}
DataSource rewritten = dataSource.withChildren(Collections.singletonList(children.get(0))); Defensive patterns
Strategy: type-guard
Validate before calling
if (!(dataSource instanceof QueryDataSource)) {
throw new IllegalStateException("withChildren called on non-QueryDataSource");
}
if (children.size() != 1) {
throw new IllegalArgumentException("QueryDataSource requires exactly one child, got " + children.size());
} Type guard
static boolean isRewritableQueryDataSource(DataSource ds, List<DataSource> children) {
return ds instanceof QueryDataSource && children.size() == 1;
} Try / catch
try {
rewritten = dataSource.withChildren(children);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("exactly one child")) {
rewritten = dataSource.withChildren(Collections.singletonList(children.get(0)));
} else { throw e; }
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Cannot handle dataSource [%s]
- Function[%s] %s
- Expected a TableDataSource, got data source type [%s]
- Policy can't be null for RestrictedDataSource
- Expected [1] child, got [%d]
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/0d1c98833c5016f5.
Report an issue: GitHub.