prestodb/presto · error · PrestoException
DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION
DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION
Error message
Unsupported aggregation node ${aggregationNode} What it means
DruidPushdownUtils.computeAggregationNodes throws this when an AggregationNode contains an aggregation that has a filter, is DISTINCT, or has an ORDER BY — none of which Druid pushdown supports in the general case. Pushdown of that aggregation plan is refused with DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION.
Source
Thrown at presto-druid/src/main/java/com/facebook/presto/druid/DruidPushdownUtils.java:77
public static final String DRUID_COUNT_DISTINCT_FUNCTION_NAME = "distinctCount";
private static final String COUNT_FUNCTION_NAME = "count";
private static final String DISTINCT_MASK = "$distinct";
private DruidPushdownUtils() {}
public static List<DruidAggregationColumnNode> computeAggregationNodes(AggregationNode aggregationNode)
{
int groupByKeyIndex = 0;
ImmutableList.Builder<DruidAggregationColumnNode> nodeBuilder = ImmutableList.builder();
for (VariableReferenceExpression outputColumn : aggregationNode.getOutputVariables()) {
AggregationNode.Aggregation aggregation = aggregationNode.getAggregations().get(outputColumn);
if (aggregation != null) {
if (aggregation.getFilter().isPresent()
|| aggregation.isDistinct()
|| aggregation.getOrderBy().isPresent()) {
throw new PrestoException(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION, "Unsupported aggregation node " + aggregationNode);
}
if (aggregation.getMask().isPresent()) {
// This block handles the case when a distinct aggregation is present in addition to another aggregation function.
// E.g. `SELECT count(distinct COL_A), sum(COL_B) FROM myTable` to Druid as `SELECT distinctCount(COL_A), sum(COL_B) FROM myTable`
if (aggregation.getCall().getDisplayName().equalsIgnoreCase(COUNT_FUNCTION_NAME) && aggregation.getMask().get().getName().equalsIgnoreCase(aggregation.getArguments().get(0) + DISTINCT_MASK)) {
nodeBuilder.add(new AggregationFunctionColumnNode(outputColumn, new CallExpression(aggregation.getCall().getSourceLocation(), DRUID_COUNT_DISTINCT_FUNCTION_NAME, aggregation.getCall().getFunctionHandle(), aggregation.getCall().getType(), aggregation.getCall().getArguments())));
continue;
}
// Druid doesn't support push down aggregation functions other than count on top of distinct function.
throw new PrestoException(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION, "Unsupported aggregation node with mask " + aggregationNode);
}
if (handlePushDownSingleDistinctCount(nodeBuilder, aggregationNode, outputColumn, aggregation)) {
continue;
}
nodeBuilder.add(new AggregationFunctionColumnNode(outputColumn, aggregation.getCall()));
}
else {
// group by outputView on GitHub (pinned to 55bb57d202)
Solutions
- Remove FILTER/ORDER BY/DISTINCT from aggregate functions in the query.
- For count(DISTINCT col), keep the simple form (the connector handles count + standard distinct mask via distinctCount); exotic distinct masks are not supported.
- Split the query: push down the simple aggregations to Druid and compute the filtered/ordered aggregates client-side or in a separate non-pushed-down pass.
- Rewrite filtered aggregates as CASE-based sums if a newer connector supports CASE pushdown, or upgrade Presto.
Example fix
// before SELECT sum(x) FILTER (WHERE y > 0) FROM druid_table; // after SELECT sum(CASE WHEN y > 0 THEN x END) FROM druid_table; -- or compute client-side
Defensive patterns
Strategy: fallback
Validate before calling
boolean pushable(AggregationNode agg) {
return agg.getAggregations().values().stream().noneMatch(a ->
a.getFilter().isPresent() || a.isDistinct() || a.getOrderBy().isPresent());
} Try / catch
try { druidQuery = DruidPushdownUtils.computeAggregationNodes(agg, builder); }
catch (PrestoException e) {
if (e.getErrorCode().getName().equals("DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION")) { runAggregationInPresto(); }
else throw e;
} Prevention
- Avoid FILTER and ORDER BY inside Druid aggregate calls.
- Restrict DISTINCT to count(DISTINCT col) over Druid tables.
- Split complex aggregates into separate queries.
- Verify aggregation plan shape with EXPLAIN.
When it happens
Trigger: Computing aggregation nodes for pushdown when aggregation.getFilter().isPresent() || aggregation.isDistinct() || aggregation.getOrderBy().isPresent() — e.g. FILTER clauses, generic DISTINCT aggregations, or ORDER BY inside aggregates.
Common situations: Queries like SELECT sum(x ORDER BY y), avg(x) FILTER (WHERE z), or count(DISTINCT col) with an unrecognized mask over Druid tables.
Related errors
- DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION
- DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION
- CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION
- CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION
- DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/91b47e1d16efdb29.
Report an issue: GitHub.