apache/druid · error · QueryUnsupportedException

Cannot handle subquery

Error message

Cannot handle subquery: %s

What it means

ServerManager.buildQueryToolChest (getQueryToolChest) verifies that the query's QueryToolChest supports subqueries via canPerformSubquery. If the top-level query's dataSource is a QueryDataSource (subquery) and the toolchest cannot perform subqueries, it throws QueryUnsupportedException naming the dataSource.

Solutions

  1. Rewrite the query so the outer query does not take a QueryDataSource (e.g. run subquery separately, or use SQL which plans supported subqueries)
  2. Use a query type that supports subqueries (groupBy, timeseries, scan) as the outer query
  3. Upgrade Druid if the subquery support was added for that query type in later releases
  4. If using a custom query type, implement canPerformSubquery in its QueryToolChest

Example fix

// before
GroupByQuery.builder().setDataSource(new QueryDataSource(topNQuery))...
// after
// run the subquery first and use its results via SQL or a table/inline datasource
client.run(subQuery); // then build outer query against materialized results
Defensive patterns

Strategy: try-catch

Validate before calling

DataSource ds = query.getDataSource();
if (ds instanceof QueryDataSource
    && !factory.getToolchest().canPerformSubquery(((QueryDataSource) ds).getQuery())) {
  throw new UnsupportedOperationException("Query type cannot handle subquery: " + ds);
}

Type guard

boolean supportsSubquery(QueryToolChest<?, ?> chest, Query<?> q) {
  return !(q.getDataSource() instanceof QueryDataSource)
      || chest.canPerformSubquery(((QueryDataSource) q.getDataSource()).getQuery());
}

Try / catch

try {
  return serverManager.buildQueryRunner(...);
} catch (QueryUnsupportedException e) {
  if (e.getMessage().startsWith("Cannot handle subquery")) {
    log.warn("Query type does not support subqueries: %s", e.getMessage());
  }
  throw e;
}

Prevention

When it happens

Trigger: Running a native query whose dataSource is another query on a query type that lacks subquery support (e.g. some extension query types like topN/scan variants historically, or custom query types) against the broker.

Common situations: Wrapping an unsupported query type inside a native groupBy/scan subquery; custom extension queries used as subqueries; older versions where certain query types did not implement canPerformSubquery.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at server/src/main/java/org/apache/druid/server/ServerManager.java:584

      final QueryUnsupportedException e = new QueryUnsupportedException(
          StringUtils.format("Unknown query type, [%s]", query.getClass())
      );
      log.makeAlert(e, "Error while executing a query[%s]", query.getId())
         .addData("dataSource", dataSourceFromQuery)
         .emit();
      throw e;
    }
    return factory;
  }

  protected static <T> QueryToolChest<T, Query<T>> getQueryToolChest(Query<T> query, QueryRunnerFactory<T, Query<T>> factory)
  {
    final DataSource dataSourceFromQuery = query.getDataSource();
    final QueryToolChest<T, Query<T>> toolChest = factory.getToolchest();
    // Make sure this query type can handle the subquery, if present.
    if ((dataSourceFromQuery instanceof QueryDataSource)
        && !toolChest.canPerformSubquery(((QueryDataSource) dataSourceFromQuery).getQuery())) {
      throw new QueryUnsupportedException(StringUtils.format("Cannot handle subquery: %s", dataSourceFromQuery));
    }
    return toolChest;
  }

  /**
   * {@link QueryRunner} that on run builds a set of {@link QueryRunner} for a set of {@link SegmentDescriptor} and
   * merges them using the {@link QueryToolChest}. The {@link VersionedIntervalTimeline} provides segment references,
   * which are registered with a closer as they are acquired, and then released in the baggage of merged result
   * {@link Sequence}
   */
  public class ResourceManagingQueryRunner<T> implements QueryRunner<T>
  {
    private final VersionedIntervalTimeline<String, DataSegment> timeline;
    private final QueryRunnerFactory<T, Query<T>> factory;
    private final QueryToolChest<T, Query<T>> toolChest;
    private final ExecutionVertex ev;
    private final Iterable<SegmentDescriptor> specs;

View on GitHub (pinned to 9b90983fd2)