{"record":{"id":"c2faeb85adcd0540","repo":"prestodb/presto","slug":"druid-pushdown-unsupported-expression-c2faeb","errorCode":"DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION","errorMessage":"Druid does not support filter on top of AggregationNode.","messagePattern":"Druid does not support filter on top of AggregationNode\\.","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"error","filePath":"presto-druid/src/main/java/com/facebook/presto/druid/DruidQueryGeneratorContext.java","lineNumber":115,"sourceCode":"            Set<VariableReferenceExpression> variablesInAggregation,\n            Set<VariableReferenceExpression> hiddenColumnSet,\n            Optional<PlanNodeId> tableScanNodeId)\n    {\n        this.selections = new LinkedHashMap<>(requireNonNull(selections, \"selections can't be null\"));\n        this.from = requireNonNull(from, \"source can't be null\");\n        this.filter = requireNonNull(filter, \"filter is null\");\n        this.limit = requireNonNull(limit, \"limit is null\");\n        this.aggregations = aggregations;\n        this.groupByColumns = new LinkedHashMap<>(requireNonNull(groupByColumns, \"groupByColumns can't be null. It could be empty if not available\"));\n        this.hiddenColumnSet = requireNonNull(hiddenColumnSet, \"hidden column set is null\");\n        this.variablesInAggregation = requireNonNull(variablesInAggregation, \"variables in aggregation is null\");\n        this.tableScanNodeId = requireNonNull(tableScanNodeId, \"tableScanNodeId can't be null\");\n    }\n\n    public DruidQueryGeneratorContext withFilter(String filter)\n    {\n        if (hasAggregation()) {\n            throw new PrestoException(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION, \"Druid does not support filter on top of AggregationNode.\");\n        }\n        checkState(!hasFilter(), \"Druid doesn't support filters at multiple levels under AggregationNode\");\n        return new DruidQueryGeneratorContext(\n                selections,\n                from,\n                Optional.of(filter),\n                limit,\n                aggregations,\n                groupByColumns,\n                variablesInAggregation,\n                hiddenColumnSet,\n                tableScanNodeId);\n    }\n\n    public DruidQueryGeneratorContext withProject(Map<VariableReferenceExpression, Selection> newSelections)\n    {\n        return new DruidQueryGeneratorContext(\n                newSelections,","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-druid/src/main/java/com/facebook/presto/druid/DruidQueryGeneratorContext.java#L97-L133","documentation":"Druid's native groupBy/topN query model does not allow an outer filter applied above the aggregation result the way Presto plans it (filter on top of AggregationNode). The context builder refuses to attach a filter once aggregations exist, since it cannot express the plan faithfully in a single Druid query.","triggerScenarios":"A query plan reaches visitFilter after aggregation was recorded, e.g. SELECT ... FROM (SELECT k, sum(v) AS s FROM t GROUP BY k) WHERE s > 10 — a HAVING-like/outer filter over an aggregate — and the connector tries to push the filter into the Druid context.","commonSituations":"Filtering on the result of an aggregate (HAVING with complex expressions) or filtering a derived table built on top of a Druid aggregation; typically surfaces when the planner attempts predicate pushdown past an AggregationNode.","solutions":["Rewrite as a HAVING clause (SELECT k, sum(v) FROM t GROUP BY k HAVING sum(v) > 10) so the filter folds into the aggregation","Disable pushdown (connector-level or session property) so the filter is evaluated in Presto","Restructure the query to filter base columns before aggregation (push filters below the GROUP BY)","Modify DruidQueryGeneratorContext/visitor to convert qualifying post-aggregation filters into Druid having specs"],"exampleFix":"// before\nSELECT * FROM (SELECT k, sum(v) s FROM t GROUP BY k) WHERE s > 10; -- throws\n// after\nSELECT k, sum(v) FROM t GROUP BY k HAVING sum(v) > 10;","handlingStrategy":"try-catch","validationCode":"boolean isFilterAboveAggregationSafe(PlanNode filterNode) {\n    return !(filterNode.getSource() instanceof AggregationNode);\n}","typeGuard":null,"tryCatchPattern":"try { return context.withFilter(filterSql); }\ncatch (PrestoException e) {\n    if (DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION.getCode().equals(e.getErrorCode().getCode())) {\n        return skipPushdownAndEvaluateFilterInPresto();\n    }\n    throw e;\n}","preventionTips":["Express post-aggregation conditions as HAVING, not as outer WHERE on derived tables","Filter raw columns before GROUP BY, not aggregate results after","Avoid chaining aggregation under aggregation or filter-over-subquery patterns on Druid tables","Test plan pushdown with EXPLAIN before deploying report queries"],"tags":["druid","pushdown","filter","aggregation","presto"],"backgroundTag":"aggregation-pushdown-unsupported","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}