prestodb/presto · error · PrestoException

DRUID_QUERY_GENERATOR_FAILURE

DRUID_QUERY_GENERATOR_FAILURE

Error message

Expected to find a druid table scan node

What it means

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).

Source

Thrown at presto-druid/src/main/java/com/facebook/presto/druid/DruidPlanOptimizer.java:155

        public Visitor(Map<PlanNodeId, TableScanNode> tableScanNodes, ConnectorSession session, PlanNodeIdAllocator idAllocator)
        {
            this.session = session;
            this.idAllocator = idAllocator;
            this.tableScanNodes = tableScanNodes;
            // Just making sure that the table exists
            tableScanNodes.forEach((key, value) -> getDruidTableHandle(value).get().getTableName());
        }

        private Optional<PlanNode> tryCreatingNewScanNode(PlanNode plan)
        {
            Optional<DruidQueryGenerator.DruidQueryGeneratorResult> dql = druidQueryGenerator.generate(plan, session);
            if (!dql.isPresent()) {
                return Optional.empty();
            }
            DruidQueryGeneratorContext context = dql.get().getContext();
            final PlanNodeId tableScanNodeId = context.getTableScanNodeId().orElseThrow(() -> new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, "Expected to find a druid table scan node id"));
            if (!tableScanNodes.containsKey(tableScanNodeId)) {
                throw new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, "Expected to find a druid table scan node");
            }
            final TableScanNode tableScanNode = tableScanNodes.get(tableScanNodeId);
            DruidTableHandle druidTableHandle = getDruidTableHandle(tableScanNode).orElseThrow(() -> new PrestoException(DRUID_QUERY_GENERATOR_FAILURE, "Expected to find a druid table handle"));
            TableHandle oldTableHandle = tableScanNode.getTable();
            Map<VariableReferenceExpression, DruidColumnHandle> assignments = context.getAssignments();
            TableHandle newTableHandle = new TableHandle(
                    oldTableHandle.getConnectorId(),
                    new DruidTableHandle(druidTableHandle.getSchemaName(), druidTableHandle.getTableName(), Optional.of(dql.get().getGeneratedDql())),
                    oldTableHandle.getTransaction(),
                    oldTableHandle.getLayout());
            return Optional.of(
                    new TableScanNode(
                            tableScanNode.getSourceLocation(),
                            idAllocator.getNextId(),
                            newTableHandle,
                            ImmutableList.copyOf(assignments.keySet()),
                            assignments.entrySet().stream().collect(toImmutableMap(Map.Entry::getKey, (e) -> (ColumnHandle) (e.getValue()))),
                            tableScanNode.getTableConstraints(),

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Verify the plan reaches DruidPlanOptimizer with an intact Druid TableScanNode matching the id stored in DruidQueryGeneratorContext
  2. Check for custom optimizers/rules that rewrite or remove table scan nodes before the Druid plan optimizer runs
  3. Reproduce with the failing SQL and capture the EXPLAIN plan to confirm the Druid scan node id
  4. File/report with the query plan if the plugin loses the scan node during optimization
Defensive patterns

Strategy: fallback

Validate before calling

// best-effort pre-check: ensure the scan id from the context exists in collected scans
if (context.getTableScanNodeId().isEmpty() || !tableScanNodes.containsKey(context.getTableScanNodeId().get())) {
    // skip pushdown / use original plan
}

Try / catch

try {
    return tryCreatingNewScanNode(...);
} catch (PrestoException e) {
    if (e.getErrorCode().equals(DRUID_QUERY_GENERATOR_FAILURE.toErrorCode())) {
        return Optional.empty(); // fall back to original plan
    }
    throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04). Data as JSON: /api/errors/3472235d9ec86af3. Report an issue: GitHub.