{"record":{"id":"a7861f6e05269e65","repo":"prestodb/presto","slug":"clickhouse-pushdown-unsupported-expression-a7861f","errorCode":"CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION","errorMessage":"ClickHouse does not support filter on top of AggregationNode.","messagePattern":"ClickHouse does not support filter on top of AggregationNode\\.","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"warning","filePath":"presto-clickhouse/src/main/java/com/facebook/presto/plugin/clickhouse/optimization/ClickHouseQueryGeneratorContext.java","lineNumber":120,"sourceCode":"            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.schema = requireNonNull(schema, \"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 ClickHouseQueryGeneratorContext withFilter(String filter)\n    {\n        if (hasAggregation()) {\n            throw new PrestoException(CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION, \"ClickHouse does not support filter on top of AggregationNode.\");\n        }\n        checkState(!hasFilter(), \"ClickHouse doesn't support filters at multiple levels under AggregationNode\");\n        return new ClickHouseQueryGeneratorContext(\n                selections,\n                from,\n                schema,\n                Optional.of(filter),\n                limit,\n                aggregations,\n                groupByColumns,\n                variablesInAggregation,\n                hiddenColumnSet,\n                tableScanNodeId);\n    }\n\n    public ClickHouseQueryGeneratorContext withProject(Map<VariableReferenceExpression, Selection> newSelections)\n    {\n        return new ClickHouseQueryGeneratorContext(","sourceCodeStart":102,"sourceCodeEnd":138,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-clickhouse/src/main/java/com/facebook/presto/plugin/clickhouse/optimization/ClickHouseQueryGeneratorContext.java#L102-L138","documentation":"The ClickHouse pushdown optimizer only supports pushing a filter below or beside an aggregation (e.g. HAVING-style is not modeled here); it cannot represent a filter applied on top of an AggregationNode in the generated SQL. When withFilter is invoked on a context that already contains an aggregation, the pushdown is aborted so the query falls back to non-pushed execution. This is a deliberate capability limit of the generator, not a ClickHouse server error.","triggerScenarios":"visitFilter calls withFilter(String) while the ClickHouseQueryGeneratorContext already hasAggregation() == true — i.e. the plan has FilterNode -> AggregationNode -> TableScan (or filter otherwise lands above the aggregation during pushdown).","commonSituations":"Queries like SELECT ... FROM t GROUP BY k HAVING agg ... where the planner builds a filter node on top of the aggregation, or views/subqueries with WHERE clauses wrapped around a GROUP BY, when ClickHouse pushdown is enabled.","solutions":["Rewrite the query so the filter sits below the aggregation (filter raw rows in a WHERE before grouping) rather than above it.","Disable ClickHouse pushdown for such queries so Presto executes the filter locally after the aggregation.","If HAVING is needed, express it in a form the connector supports, or upgrade the connector — check whether your version added HAVING pushdown support.","File/patch the connector to translate top-of-aggregation filters into HAVING clauses."],"exampleFix":"// before (filter above aggregation)\nSELECT k, count(*) FROM t GROUP BY k HAVING count(*) > 5;\n// after (filter below aggregation where possible)\nSELECT k, count(*) FROM t WHERE valid = true GROUP BY k;","handlingStrategy":"validation","validationCode":"// Detect filter-on-top-of-aggregation shape before expecting pushdown\nboolean filterAboveAggregation = planContains(FilterNode.class, node ->\n    childOf(node, AggregationNode.class) && beneathAggregationIsClickHouseScan(node));\n// if true, don't rely on pushdown; rewrite or run locally","typeGuard":null,"tryCatchPattern":"try {\n    execute(query);\n} catch (PrestoException e) {\n    if (\"CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION\".equals(e.getErrorCode().getName())) {\n        executeWithoutPushdown(query); // session-level pushdown disabled fallback\n    } else {\n        throw e;\n    }\n}","preventionTips":["Place row-filtering WHERE clauses below GROUP BY, not above it","Prefer pushing filters on raw columns; treat HAVING as not pushable for this connector","Verify plan shapes with EXPLAIN before benchmarking pushdown","Keep connector updated in case HAVING pushdown is added"],"tags":["clickhouse","pushdown","filter","aggregation"],"backgroundTag":"pushdown-unsupported-expression","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"}