apache/druid · error · IllegalStateException

Cannot handle subquery: %s

Error message

Cannot handle subquery: %s

What it means

Thrown by SinkQuerySegmentWalker.getQueryRunnerForSegments when a query's dataSource is a QueryDataSource (subquery) whose inner query's tool chest cannot perform subqueries. Druid throws this to reject running a subquery against this segment walker's query type because the inner query runner factory does not support it.

Source

Thrown at server/src/main/java/org/apache/druid/segment/realtime/appenderator/SinkQuerySegmentWalker.java:196

    // Sanity check: make sure the query is based on the table we're meant to handle.
    if (!ev.getBaseTableDataSource().getName().equals(dataSource)) {
      throw new ISE("Cannot handle datasource: %s", dataSourceFromQuery);
    }

    final QueryRunnerFactory<T, Query<T>> factory = conglomerate.findFactory(query);
    if (factory == null) {
      throw new ISE("Unknown query type[%s].", query.getClass());
    }

    final QueryToolChest<T, Query<T>> toolChest = factory.getToolchest();
    final boolean skipIncrementalSegment = query.context().getBoolean(CONTEXT_SKIP_INCREMENTAL_SEGMENT, false);
    final AtomicLong cpuTimeAccumulator = new AtomicLong(0L);

    // Make sure this query type can handle the subquery, if present.
    if ((dataSourceFromQuery instanceof QueryDataSource)
        && !toolChest.canPerformSubquery(((QueryDataSource) dataSourceFromQuery).getQuery())) {
      throw new ISE("Cannot handle subquery: %s", dataSourceFromQuery);
    }

    // segmentMapFn maps each base Segment into a joined Segment if necessary.
    final SegmentMapFunction segmentMapFn = JvmUtils.safeAccumulateThreadCpuTime(
        cpuTimeAccumulator,
        () -> ev.createSegmentMapFunction(policyEnforcer)
    );

    // We compute the join cache key here itself so it doesn't need to be re-computed for every segment
    final Optional<byte[]> cacheKeyPrefix = Optional.ofNullable(query.getDataSource().getCacheKey());

    // We need to report data for each Sink all-or-nothing, which means we need to acquire references for all
    // subsegments (FireHydrants) of a segment (Sink) at once. To ensure they are properly released even when a
    // query fails or is canceled, we acquire *all* sink reference upfront, and release them all when the main
    // QueryRunner returned by this method is closed. (We can't do the acquisition and releasing at the level of
    // each FireHydrant's runner, since then it wouldn't be properly all-or-nothing on a per-Sink basis.)
    final List<SinkSegmentReference> allSegmentReferences = new ArrayList<>();
    // Distinct Sinks (Druid segments) actually queried by this node. Tracked separately from

View on GitHub (pinned to 9b90983fd2)

Solutions

  1. Rewrite the query to avoid nesting the unsupported inner query type (e.g. replace the inner query with a direct datasource or a supported query type).
  2. Use a query type for the inner query that supports subqueries (groupBy/topN in Classic engine) or run the subquery separately and pass its results as an inline datasource.
  3. If using SQL, translate the SQL differently (avoid unsupported nesting) or ensure the query runs on the broker/MSQ engine which supports subqueries.

Example fix

// before
new TopNQueryBuilder...query(new TableDataSource("x"))... but wrapping a scan as subquery
GroupByQuery sub = new GroupByQuery.Builder()...build();
QueryDataSource ds = new QueryDataSource(new ScanQuery...); // scan subquery not supported here

// after
// use a supported inner query type, or materialize results into an inline datasource
InlineDataSource ds = InlineDataSource.fromIterable(results, rowSignature);
Defensive patterns

Strategy: validation

Validate before calling

// Java: check subquery support before issuing
if (dataSource instanceof QueryDataSource) {
  QueryToolChest<?, ?> chest = factory.getToolchest();
  if (!chest.canPerformSubquery(((QueryDataSource) dataSource).getQuery())) {
    throw new IllegalArgumentException("Subquery not supported for this query type");
  }
}

Try / catch

try { runner.query(query) } catch (IllegalStateException e) { if (e.getMessage().startsWith("Cannot handle subquery")) { /* rewrite query without subquery */ } else throw e; }

Prevention

When it happens

Trigger: Issuing a query whose datasource is a subquery (QueryDataSource) where the inner query's QueryToolChest.canPerformSubquery returns false; e.g. routing a topN/groupBy-with-subquery against a segment walker backed by a query type lacking subquery support (such as certain datasource query types via AppenderatorAdvisor/segments path).

Common situations: Running nested queries (e.g. a scan or select inside a groupBy) in streaming/realtime paths where the underlying query runner factory doesn't support subqueries; issuing SQL that plans to an unsupported native subquery against a realtime task.

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/f1ba27fa7d6b9ba1. Report an issue: GitHub.