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;
  }

  @Override

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Ensure the children list passed to withChildren() contains exactly one DataSource for non-union QueryDataSource instances.
  2. If the inner query is a union, pass the children to the union's withDataSources path instead.
  3. Check the concrete DataSource type before deciding how many children to pass.
  4. 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

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


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/0d1c98833c5016f5. Report an issue: GitHub.