{"record":{"id":"259cdf39ace91436","repo":"prestodb/presto","slug":"pinot-unsupported-expression-259cdf","errorCode":"PINOT_UNSUPPORTED_EXPRESSION","errorMessage":"'%s' is not supported in filter","messagePattern":"'(.+?)' is not supported in filter","errorType":"error_code","errorClass":"PinotException","httpStatus":null,"severity":"error","filePath":"presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/query/PinotFilterExpressionConverter.java","lineNumber":109,"sourceCode":"    }\n\n    private PinotExpression handleIsNull(\n            SpecialFormExpression specialForm,\n            boolean isWhitelist,\n            Function<VariableReferenceExpression, Selection> context)\n    {\n        return derived(format(\"(%s %s)\",\n                specialForm.getArguments().get(0).accept(this, context).getDefinition(),\n                isWhitelist ? \"IS NULL\" : \"IS NOT NULL\"));\n    }\n\n    private PinotExpression handleLogicalBinary(\n            String operator,\n            CallExpression call,\n            Function<VariableReferenceExpression, Selection> context)\n    {\n        if (!LOGICAL_BINARY_OPS_FILTER.contains(operator)) {\n            throw new PinotException(PINOT_UNSUPPORTED_EXPRESSION, Optional.empty(), format(\"'%s' is not supported in filter\", operator));\n        }\n        List<RowExpression> arguments = call.getArguments();\n        if (arguments.size() == 2) {\n            // Check if call compares a date/time column with a date/time constant. Otherwise just treat it like a regular binary operator.\n            return handleDateOrTimestampBinaryExpression(operator, arguments, context).orElseGet(\n                    () -> derived(format(\n                            \"(%s %s %s)\",\n                            arguments.get(0).accept(this, context).getDefinition(),\n                            operator,\n                            arguments.get(1).accept(this, context).getDefinition())));\n        }\n        throw new PinotException(PINOT_UNSUPPORTED_EXPRESSION, Optional.empty(), format(\"Unknown logical binary: '%s'\", call));\n    }\n\n    private Optional<PinotExpression> handleDateOrTimestampBinaryExpression(String operator, List<RowExpression> arguments, Function<VariableReferenceExpression, Selection> context)\n    {\n        Optional<String> left = handleTimeValueCast(context, arguments.get(1), arguments.get(0));\n        Optional<String> right = handleTimeValueCast(context, arguments.get(0), arguments.get(1));","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-pinot-toolkit/src/main/java/com/facebook/presto/pinot/query/PinotFilterExpressionConverter.java#L91-L127","documentation":"PinotFilterExpressionConverter.handleLogicalBinary translates logical operators in WHERE clauses to Pinot filter syntax. Only operators in LOGICAL_BINARY_OPS_FILTER (AND/OR family) are pushable; any other logical binary operator reaching this method is rejected with ''%s' is not supported in filter'.","triggerScenarios":"A CallExpression in a filter (WHERE/HAVING) resolves to a logical binary function not in LOGICAL_BINARY_OPS_FILTER — e.g. XOR, or a non-standard logical function the planner represents as a call with operator name outside the allowlist.","commonSituations":"Queries using XOR or exotic boolean operators in WHERE clauses, new Presto versions introducing different function names for logical ops, and third-party SQL generators emitting logical operators the connector allowlist does not cover.","solutions":["Rewrite the predicate using AND/OR/NOT: XOR(a, b) becomes (a AND NOT b) OR (NOT a AND b)","Split the query so unsupported boolean logic is applied outside the Pinot-pushed filter (outer query over a subquery)","Inspect LOGICAL_BINARY_OPS_FILTER in your connector version and restrict queries to those operators","Upgrade the connector if newer versions added the operator to the filter allowlist"],"exampleFix":"// before\nSELECT * FROM t WHERE a XOR b;\n// after\nSELECT * FROM t WHERE (a AND NOT b) OR (NOT a AND b);","handlingStrategy":"validation","validationCode":"// Check WHERE-clause logical operators against the connector allowlist\nprivate static final Set<String> FILTER_LOGICAL_OPS = Set.of(\"AND\", \"OR\");\nif (!FILTER_LOGICAL_OPS.contains(op.toUpperCase(Locale.ROOT))) {\n    throw new IllegalArgumentException(\n        \"Logical operator not pushable to Pinot filter: \" + op);\n}","typeGuard":"private static boolean isFilterPushableLogical(String operator) {\n    return operator != null\n        && Set.of(\"AND\", \"OR\").contains(operator.toUpperCase(Locale.ROOT));\n}","tryCatchPattern":"try {\n    PinotExpression filter = filterConverter.visitCall(call, context);\n} catch (PinotException ex) {\n    if (ex.getMessage() != null && ex.getMessage().endsWith(\"is not supported in filter\")) {\n        // keep this predicate for Presto-side filtering\n    } else throw ex;\n}","preventionTips":["Restrict WHERE clauses to AND/OR; express XOR and exotic boolean logic manually","Put unsupported boolean expressions in an outer query over a Pinot subquery","Review LOGICAL_BINARY_OPS_FILTER in your connector version before using new operators","Favor simple conjunctive/disjunctive filter forms for best pushdown coverage"],"tags":["pinot","filter","pushdown","logical-operators"],"backgroundTag":"pinot-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"}