{"record":{"id":"3472235d9ec86af3","repo":"prestodb/presto","slug":"druid-query-generator-failure","errorCode":"DRUID_QUERY_GENERATOR_FAILURE","errorMessage":"Expected to find a druid table scan node","messagePattern":"Expected to find a druid table scan node","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"error","filePath":"presto-druid/src/main/java/com/facebook/presto/druid/DruidPlanOptimizer.java","lineNumber":155,"sourceCode":"        public Visitor(Map<PlanNodeId, TableScanNode> tableScanNodes, ConnectorSession session, PlanNodeIdAllocator idAllocator)\n        {\n            this.session = session;\n            this.idAllocator = idAllocator;\n            this.tableScanNodes = tableScanNodes;\n            // Just making sure that the table exists\n            tableScanNodes.forEach((key, value) -> getDruidTableHandle(value).get().getTableName());\n        }\n\n        private Optional<PlanNode> tryCreatingNewScanNode(PlanNode plan)\n        {\n            Optional<DruidQueryGenerator.DruidQueryGeneratorResult> dql = druidQueryGenerator.generate(plan, session);\n            if (!dql.isPresent()) {\n                return Optional.empty();\n            }\n            DruidQueryGeneratorContext context = dql.get().getContext();\n            final PlanNodeId tableScanNodeId = context.getTableScanNodeId().orElseThrow(() -> new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, \"Expected to find a druid table scan node id\"));\n            if (!tableScanNodes.containsKey(tableScanNodeId)) {\n                throw new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, \"Expected to find a druid table scan node\");\n            }\n            final TableScanNode tableScanNode = tableScanNodes.get(tableScanNodeId);\n            DruidTableHandle druidTableHandle = getDruidTableHandle(tableScanNode).orElseThrow(() -> new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, \"Expected to find a druid table handle\"));\n            TableHandle oldTableHandle = tableScanNode.getTable();\n            Map<VariableReferenceExpression, DruidColumnHandle> assignments = context.getAssignments();\n            TableHandle newTableHandle = new TableHandle(\n                    oldTableHandle.getConnectorId(),\n                    new DruidTableHandle(druidTableHandle.getSchemaName(), druidTableHandle.getTableName(), Optional.of(dql.get().getGeneratedDql())),\n                    oldTableHandle.getTransaction(),\n                    oldTableHandle.getLayout());\n            return Optional.of(\n                    new TableScanNode(\n                            tableScanNode.getSourceLocation(),\n                            idAllocator.getNextId(),\n                            newTableHandle,\n                            ImmutableList.copyOf(assignments.keySet()),\n                            assignments.entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, (e) -> (ColumnHandle) (e.getValue()))),\n                            tableScanNode.getTableConstraints(),","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-druid/src/main/java/com/facebook/presto/druid/DruidPlanOptimizer.java#L137-L173","documentation":"During the Druid plan optimizer, the query generator tries to replace the original Druid table scan node with a new scan node carrying a pushed-down Druid query. This error means the plan contains the recorded table scan node id, but that id is not present in the set of table scan nodes collected while traversing the plan, so the rewrite cannot proceed. It indicates an internal inconsistency in the plan (the scan node disappeared or was never a Druid scan).","triggerScenarios":"DruidPlanOptimizer.tryCreatingNewScanNode is called with a DruidQueryGeneratorContext whose tableScanNodeId is set, but tableScanNodes (the map of TableScanNode collected from the plan) does not contain that PlanNodeId — e.g. the plan was rewritten/optimized after the id was captured, or the node at that id is not a Druid table scan.","commonSituations":"Plan re-optimization passes that replace or renumber scan nodes before the Druid pushdown runs; mixing plans across connectors so the scan node was pruned; internal plugin bugs after Presto/Druid plugin version upgrades.","solutions":["Verify the plan reaches DruidPlanOptimizer with an intact Druid TableScanNode matching the id stored in DruidQueryGeneratorContext","Check for custom optimizers/rules that rewrite or remove table scan nodes before the Druid plan optimizer runs","Reproduce with the failing SQL and capture the EXPLAIN plan to confirm the Druid scan node id","File/report with the query plan if the plugin loses the scan node during optimization"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// best-effort pre-check: ensure the scan id from the context exists in collected scans\nif (context.getTableScanNodeId().isEmpty() || !tableScanNodes.containsKey(context.getTableScanNodeId().get())) {\n    // skip pushdown / use original plan\n}","typeGuard":null,"tryCatchPattern":"try {\n    return tryCreatingNewScanNode(...);\n} catch (PrestoException e) {\n    if (e.getErrorCode().equals(DRUID_QUERY_GENERATOR_FAILURE.toErrorCode())) {\n        return Optional.empty(); // fall back to original plan\n    }\n    throw e;\n}","preventionTips":["Keep the Druid plan optimizer ordered after passes that mutate scan nodes","Log the plan node id set when this fires to diagnose plan-mutation passes","Pin tested Presto/Druid plugin versions together","Report the query plan to maintainers if it reproduces consistently"],"tags":["druid","query-planning","pushdown"],"backgroundTag":"query-planner-internal-error","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}