{"record":{"id":"98b806a4b6542cf4","repo":"prestodb/presto","slug":"druid-pushdown-unsupported-expression","errorCode":"DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION","errorMessage":"Expected string literal but found: ${expression} to pushdown for Druid connector.","messagePattern":"Expected string literal but found: (.+?) to pushdown for Druid connector\\.","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"warning","filePath":"presto-druid/src/main/java/com/facebook/presto/druid/DruidAggregationProjectConverter.java","lineNumber":168,"sourceCode":"    {\n        if (function.getDisplayName().toLowerCase(ENGLISH).equals(DATE_TRUNC)) {\n            return handleDateTruncationViaDateTruncation(function, context);\n        }\n        throw new PrestoException(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION, \"Unsupported function: \" + function.getDisplayName() + \" to pushdown for Druid connector.\");\n    }\n\n    private static String getStringFromConstant(RowExpression expression)\n    {\n        if (expression instanceof ConstantExpression) {\n            Object value = ((ConstantExpression) expression).getValue();\n            if (value instanceof String) {\n                return (String) value;\n            }\n            if (value instanceof Slice) {\n                return ((Slice) value).toStringUtf8();\n            }\n        }\n        throw new PrestoException(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION, \"Expected string literal but found: \" + expression + \" to pushdown for Druid connector.\");\n    }\n\n    private CallExpression getExpressionAsFunction(\n            RowExpression originalExpression,\n            RowExpression expression)\n    {\n        if (expression instanceof CallExpression) {\n            CallExpression call = (CallExpression) expression;\n            if (standardFunctionResolution.isCastFunction(call.getFunctionHandle())) {\n                if (isImplicitCast(call.getArguments().get(0).getType(), call.getType())) {\n                    return getExpressionAsFunction(originalExpression, call.getArguments().get(0));\n                }\n            }\n            else {\n                return call;\n            }\n        }\n        throw new PrestoException(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION, \"Could not dig function out of expression: \" + originalExpression + \", inside of: \" + expression + \" to pushdown for Druid connector.\");","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-druid/src/main/java/com/facebook/presto/druid/DruidAggregationProjectConverter.java#L150-L186","documentation":"DruidAggregationProjectConverter.getStringFromConstant converts a constant RowExpression into a Java string so it can be pushed down into Druid SQL functions (e.g. as a date unit for DATE_TRUNC). Only VARCHAR literals (String or Slice) are supported; any other constant kind causes a PrestoException with DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION.","triggerScenarios":"handleDateTruncationViaDateTruncation pushes a DATE_TRUNC-like aggregation whose unit argument (or other string argument) is a constant expression that is not a VARCHAR literal — e.g. a non-literal expression, a cast, or a numeric/timestamp constant — reaching getStringFromConstant and failing the value instanceof String/Slice checks.","commonSituations":"Queries like date_trunc('day', ts) where the unit comes from a bind parameter, a CASE expression, or is written without quotes; queries generated by ORMs/bi tools emitting casts around the literal; pushing down functions with arguments Druid cannot receive as plain strings.","solutions":["Rewrite the query so the string argument is a plain single-quoted literal, e.g. date_trunc('day', ts) instead of date_trunc(unit_col, ts)","Remove implicit/extra casts around the literal (CAST('day' AS varchar) should be fine, but non-VARCHAR constants must be replaced)","If the unit is computed at runtime, compute it in SQL CASE logic instead of relying on pushdown, or run the aggregation without Druid pushdown","Check the EXPLAIN plan to confirm which expression fails pushdown and simplify it"],"exampleFix":"-- before\nSELECT date_trunc(unit, ts), count(*) FROM druid_table GROUP BY 1; -- non-literal unit\n-- after\nSELECT date_trunc('day', ts), count(*) FROM druid_table GROUP BY 1;","handlingStrategy":"validation","validationCode":"// before relying on Druid pushdown for date_trunc, check the unit argument is a plain literal\nRowExpression unitArg = getFirstArgument(callExpression);\nboolean pushable = unitArg instanceof ConstantExpression\n        && ((ConstantExpression) unitArg).getValue() instanceof String;\nif (!pushable) { /* plan without pushdown or rewrite the query */ }","typeGuard":"boolean isStringLiteral(RowExpression e) {\n    return e instanceof ConstantExpression\n        && (((ConstantExpression) e).getValue() instanceof String\n            || ((ConstantExpression) e).getValue() instanceof Slice);\n}","tryCatchPattern":"try {\n    String unit = converter.getStringFromConstant(expression);\n} catch (PrestoException e) {\n    if (e.getCode().equals(DRUID_PUSHDOWN_UNSUPPORTED_EXPRESSION)) {\n        // fall back: evaluate the aggregation without Druid pushdown\n        return evaluateLocally(expression);\n    }\n    throw e;\n}","preventionTips":["Always pass single-quoted string literals (e.g. 'day') to date_trunc in queries targeting Druid pushdown","Avoid bind parameters, CASE expressions, or computed values as the unit argument when pushdown matters","Check EXPLAIN / connector logs to confirm the aggregation was actually pushed down","Keep the timestamp argument as a supported function call so the converter can dig out the call"],"tags":["druid","pushdown","expression","date-trunc"],"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"}