{"record":{"id":"e7627cb37a033547","repo":"apache/druid","slug":"got-a-s-which-isn-t-a-s","errorCode":null,"errorMessage":"Got a [%s] which isn't a %s","messagePattern":"Got a \\[(.+?)\\] which isn't a (.+?)","errorType":"exception","errorClass":"IllegalStateException (ISE)","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java","lineNumber":366,"sourceCode":"  }\n\n  private static class ScanQueryRunner implements QueryRunner<ScanResultValue>\n  {\n    private final ScanQueryEngine engine;\n    private final Segment segment;\n\n    public ScanQueryRunner(ScanQueryEngine engine, Segment segment)\n    {\n      this.engine = engine;\n      this.segment = segment;\n    }\n\n    @Override\n    public Sequence<ScanResultValue> run(QueryPlus<ScanResultValue> queryPlus, ResponseContext responseContext)\n    {\n      Query<ScanResultValue> query = queryPlus.getQuery();\n      if (!(query instanceof ScanQuery)) {\n        throw new ISE(\"Got a [%s] which isn't a %s\", query.getClass(), ScanQuery.class);\n      }\n\n      ScanQuery.verifyOrderByForNativeExecution((ScanQuery) query);\n\n      // it happens in unit tests\n      final Long timeoutAt = responseContext.getTimeoutTime();\n      if (timeoutAt == null || timeoutAt == 0L) {\n        responseContext.putTimeoutTime(JodaUtils.MAX_INSTANT);\n      }\n      return engine.process((ScanQuery) query, segment, responseContext, queryPlus.getQueryMetrics());\n    }\n  }\n}\n","sourceCodeStart":348,"sourceCodeEnd":380,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/scan/ScanQueryRunnerFactory.java#L348-L380","documentation":"The scan query runner's run() only accepts ScanQuery instances; it is invoked with the generic Query interface. A non-ScanQuery reaching it means the query was misrouted or mistyped, so it throws an IllegalStateException naming the actual class.","triggerScenarios":"Calling ScanQueryRunnerFactory.QueryRunner.run(queryPlus, responseContext) with a QueryPlus whose query is not a ScanQuery — usually a unit-test wiring mistake or a custom tool executing a query against the scan runner directly.","commonSituations":"Unit tests registering the scan runner factory for the wrong query type; generic execution harnesses dispatching all query types to one runner.","solutions":["Route the query through the proper QueryToolChest/factory for its type (ScanQueryToolChest for ScanQuery).","In tests, construct QueryPlus.scanQuery(...) or assert the query type before invoking the scan runner.","Check custom dispatch/serialization code that may deserialize queries as the wrong concrete class."],"exampleFix":"// before\nQueryPlus<ScanResultValue> qp = QueryPlus.wrap(someTimeseriesQuery);\nscanRunner.run(qp, responseContext);\n// after\nif (query instanceof ScanQuery) {\n  scanRunner.run(QueryPlus.wrap(query), responseContext);\n}","handlingStrategy":"type-guard","validationCode":"if (!(query instanceof ScanQuery)) throw new IllegalArgumentException(\"scan runner requires ScanQuery\");","typeGuard":"boolean isScanQuery(Query<?> q) { return q instanceof ScanQuery; }","tryCatchPattern":"try {\n  seq = scanRunner.run(queryPlus, responseContext);\n} catch (IllegalStateException e) {\n  if (e.getMessage().startsWith(\"Got a [\")) { /* dispatch to the correct runner factory */ }\n  else throw e;\n}","preventionTips":["Dispatch queries by type via QueryToolChest.getRunnerFactory.","In tests, wrap ScanQuery in QueryPlus.scanQuery.","Avoid generic execution harnesses that reuse one runner for all query types."],"tags":["druid","scan-query","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}