apache/druid · error · IllegalArgumentException
Cannot query dataSource locally
Error message
Cannot query dataSource locally: %s
What it means
LocalQuerySegmentWalker can only execute queries whose dataSource is locally runnable (e.g. table/lookup sources). If the query's DataSource cannot run on the local walker (like a query dataSource or join type the local walker does not support), it throws IAE naming the dataSource.
Solutions
- Run the query on the broker/router with the distributed walker instead of the local one
- Rewrite the query so its dataSource is a plain table source (flatten the subquery or run subqueries separately)
- Check ExecutionVertex.canRunQueryUsingLocalWalker semantics and adapt the query shape
- Enable/extend wrangler support for the dataSource type if running in an embedded/test cluster
Example fix
// before
query with dataSource = new QueryDataSource(nestedGroupBy)
submitted directly to local walker
// after
run nestedGroupBy first, then use its results as a table/inline dataSource:
Query<?> inner = ...; // execute separately
TableDataSource outer = new TableDataSource("myTable"); Defensive patterns
Strategy: try-catch
Validate before calling
ExecutionVertex ev = ExecutionVertex.of(query);
if (!ev.canRunQueryUsingLocalWalker()) {
throw new IllegalArgumentException("dataSource not locally runnable: " + ev.getBaseDataSource());
} Type guard
boolean locallyRunnable(Query<?> q) {
return ExecutionVertex.of(q).canRunQueryUsingLocalWalker();
} Try / catch
try {
return walker.getQueryRunnerForIntervals(query, intervals);
} catch (IAE e) {
if (e.getMessage().startsWith("Cannot query dataSource locally")) {
log.warn("Re-routing query to distributed broker: %s", e.getMessage());
return distributedBroker.getQueryRunnerForIntervals(query, intervals);
}
throw e;
} Prevention
- Only submit plain table-source queries to nodes using the local walker
- Route nested/complex native queries through the broker
- Check ExecutionVertex.canRunQueryUsingLocalWalker before execution in embedded setups
- Avoid custom dataSource types unless the walker supports them
When it happens
Trigger: Issuing a native query with a QueryDataSource/union/unsupported join directly against a broker configured to use the local segment walker; querying a dataSource type (e.g. inline in some configs) not supported by the wrangler.
Common situations: Submitting nested native queries to a node that only has the local walker (no distributed broker); tests or embedded cluster setups running queries whose dataSource requires MSQ or a router.
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
- Cannot handle subquery
- Cannot handle dataSource
- Cannot handle datasource
- Asked to remove data segment from a data source that…
- Cannot accept children
AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07).
Data as JSON: /api/errors/bab1d9c2b444bc16.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/main/java/org/apache/druid/server/LocalQuerySegmentWalker.java:91
PolicyEnforcer policyEnforcer,
ServiceEmitter emitter
)
{
this.conglomerate = conglomerate;
this.segmentWrangler = segmentWrangler;
this.joinableFactoryWrapper = joinableFactoryWrapper;
this.scheduler = scheduler;
this.policyEnforcer = policyEnforcer;
this.emitter = emitter;
}
@Override
public <T> QueryRunner<T> getQueryRunnerForIntervals(final Query<T> query, final Iterable<Interval> intervals)
{
ExecutionVertex ev = ExecutionVertex.of(query);
if (!ev.canRunQueryUsingLocalWalker()) {
throw new IAE("Cannot query dataSource locally: %s", ev.getBaseDataSource());
}
// wrap in ReferenceCountingSegment, these aren't currently managed by SegmentManager so reference tracking doesn't
// matter, but at least some or all will be in a future PR
final Iterable<Optional<Segment>> segments =
FunctionalIterable.create(segmentWrangler.getSegmentsForIntervals(ev.getBaseDataSource(), intervals))
.transform(ReferenceCountedSegmentProvider::unmanaged);
final AtomicLong cpuAccumulator = new AtomicLong(0L);
final SegmentMapFunction segmentMapFn = ev.createSegmentMapFunction(policyEnforcer);
final QueryRunnerFactory<T, Query<T>> queryRunnerFactory = conglomerate.findFactory(query);
final QueryRunner<T> baseRunner = queryRunnerFactory.mergeRunners(
DirectQueryProcessingPool.INSTANCE,
() -> StreamSupport.stream(segments.spliterator(), false)
.map(s -> segmentMapFn.apply(s).orElseThrow())
.map(queryRunnerFactory::createRunner).iterator()View on GitHub (pinned to 9b90983fd2)