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 output

View on GitHub (pinned to 55bb57d202)

Solutions

  1. Remove FILTER/ORDER BY/DISTINCT from aggregate functions in the query.
  2. For count(DISTINCT col), keep the simple form (the connector handles count + standard distinct mask via distinctCount); exotic distinct masks are not supported.
  3. 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.
  4. 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

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


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