prestodb/presto · warning · PinotException
PINOT_UNSUPPORTED_EXPRESSION
PINOT_UNSUPPORTED_EXPRESSION
Error message
Unsupported function in pinot aggregation:
What it means
During aggregation pushdown, PinotAggregationProjectConverter.visitCall converts call expressions to Pinot SQL. Casts, NOT, and BETWEEN have dedicated handling; any other non-operator function encountered in an aggregation projection is rejected with PINOT_UNSUPPORTED_EXPRESSION because Pinot cannot execute it server-side.
Source
Thrown at presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/query/PinotAggregationProjectConverter.java:78
}
public PinotAggregationProjectConverter(TypeManager typeManager, FunctionMetadataManager functionMetadataManager, StandardFunctionResolution standardFunctionResolution, ConnectorSession session, VariableReferenceExpression arrayVariableHint)
{
super(typeManager, functionMetadataManager, standardFunctionResolution, session);
this.arrayVariableHint = arrayVariableHint;
}
@Override
public PinotExpression visitCall(
CallExpression call,
Map<VariableReferenceExpression, PinotQueryGeneratorContext.Selection> context)
{
FunctionHandle functionHandle = call.getFunctionHandle();
if (standardFunctionResolution.isCastFunction(functionHandle)) {
return handleCast(call, context);
}
if (standardFunctionResolution.isNotFunction(functionHandle) || standardFunctionResolution.isBetweenFunction(functionHandle)) {
throw new PinotException(PINOT_UNSUPPORTED_EXPRESSION, Optional.empty(), "Unsupported function in pinot aggregation: " + functionHandle);
}
FunctionMetadata functionMetadata = functionMetadataManager.getFunctionMetadata(functionHandle);
Optional<OperatorType> operatorTypeOptional = functionMetadata.getOperatorType();
if (operatorTypeOptional.isPresent()) {
OperatorType operatorType = operatorTypeOptional.get();
if (operatorType.isArithmeticOperator()) {
return handleArithmeticExpression(call, operatorType, context);
}
if (operatorType.isComparisonOperator()) {
throw new PinotException(PINOT_UNSUPPORTED_EXPRESSION, Optional.empty(), "Comparison operator not supported: " + call);
}
}
return handleFunction(call, context);
}
@Override
public PinotExpression visitConstant(View on GitHub (pinned to 55bb57d202)
Solutions
- Rewrite the query to move the unsupported function into an outer projection and keep only supported expressions in the aggregation/inner projection
- Register/implement the equivalent function in Pinot so pushdown is valid
- Cast inputs so the expression reduces to a supported operator (e.g. use || or a supported date function)
- If the function should be pushable, add handling in PinotAggregationProjectConverter/PinotExpressionFormatter
Example fix
// before SELECT concat(region, '_x'), count(*) FROM pinot_table GROUP BY 1 // after SELECT region || '_x', count(*) FROM pinot_table GROUP BY 1 -- or keep concat outside the pushed-down aggregation
Defensive patterns
Strategy: try-catch
Validate before calling
// pre-check: keep pushed-down projections to supported functions only
List<String> allowed = List.of("cast", "not", "between", "and", "or", "+", "-", "*", "/", "%", "=", "<", ">", "<=", ">=", "<>");
// during query construction, reject scalar function calls (concat, udfs) in grouped/projected expressions
if (callOutsideAllowedSet(expr, allowed)) {
log.warn("Expression will not push down to pinot: " + expr);
} Try / catch
try {
result = session.execute(query);
} catch (PinotException e) {
if (PinotErrorCode.PINOT_UNSUPPORTED_EXPRESSION.toErrorCodeObject().equals(e.getErrorCode())) {
log.warn("Rewriting query to avoid pushdown: %s", e.getMessage());
result = session.execute(restrictPushdown(query)); // wrap unsupported fn in an outer projection
} else throw e;
} Prevention
- Use only arithmetic/comparison/cast operators in aggregation projections
- Move string/date formatting functions into outer SELECT layers
- Avoid UDFs in queries intended for full pushdown
- Test new dashboards against pinot to catch pushdown rejections early
When it happens
Trigger: visitCall sees a Call expression inside an aggregation whose function is neither a cast nor NOT/BETWEEN and whose FunctionMetadata has no OperatorType — e.g. user-defined scalar functions, string functions like concat/substr used in the projection, lambda expressions, or try_cast.
Common situations: Writing aggregations with computed columns using functions Pinot doesn't support (e.g. concat(), date_format() in the projection); UDFs registered in Presto but not Pinot; planner pushing projections down that should stay in Presto.
Related errors
- PINOT_UNSUPPORTED_EXPRESSION
- PINOT_UNSUPPORTED_EXPRESSION
- PINOT_UNSUPPORTED_EXPRESSION
- CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION
- CLICKHOUSE_PUSHDOWN_UNSUPPORTED_EXPRESSION
AI-assisted analysis of prestodb/presto@55bb57d202 (2026-09-04).
Data as JSON: /api/errors/0cb39054da86a21d.
Report an issue: GitHub.