{"record":{"id":"24bd190cf51ae63f","repo":"prestodb/presto","slug":"not-supported-24bd19","errorCode":"NOT_SUPPORTED","errorMessage":"External function in join filter is not supported: %s","messagePattern":"External function in join filter is not supported: (.+?)","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"error","filePath":"presto-main-base/src/main/java/com/facebook/presto/sql/planner/sanity/CheckUnsupportedExternalFunctions.java","lineNumber":81,"sourceCode":"        @Override\n        public Void visitAggregation(AggregationNode node, Void context)\n        {\n            checkState(\n                    node.getAggregations().values().stream()\n                            .noneMatch(\n                                    aggregation -> aggregation.getFilter().isPresent() ||\n                                            aggregation.getCall().accept(externalCallExpressionChecker, null)),\n                    \"Expect aggregation to be local\");\n\n            node.getSource().accept(this, context);\n            return null;\n        }\n\n        @Override\n        public Void visitJoin(JoinNode node, Void context)\n        {\n            if (node.getFilter().isPresent() && node.getFilter().get().accept(externalCallExpressionChecker, null)) {\n                throw new PrestoException(NOT_SUPPORTED, format(\"External function in join filter is not supported: %s\", node.getFilter().get()));\n            }\n\n            node.getSources().forEach(child -> child.accept(this, context));\n            return null;\n        }\n\n        @Override\n        public Void visitApply(ApplyNode node, Void context)\n        {\n            throw new IllegalStateException(\"Do not expect ApplyNode\");\n        }\n\n        @Override\n        public Void visitLateralJoin(LateralJoinNode node, Void context)\n        {\n            throw new IllegalStateException(\"Do not expect ApplyNode\");\n        }\n    }","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-main-base/src/main/java/com/facebook/presto/sql/planner/sanity/CheckUnsupportedExternalFunctions.java#L63-L99","documentation":"Presto's planner refuses to plan join queries whose join filter contains a call to an external (connector/plugin-provided) function. External functions cannot be safely evaluated in the distributed join-filter context, so CheckUnsupportedExternalFunctions walks the plan and rejects any JoinNode whose filter expression contains such a call.","triggerScenarios":"Running a query with an explicit join condition (e.g. `... JOIN t ON a.x = b.y AND ext_func(a.z)`) where `ext_func` is registered by a connector as an external function, so the filter expression passed the externalCallExpressionChecker during plan sanity validation in visitJoin.","commonSituations":"Queries mixing built-in and connector-provided UDFs in JOIN ON clauses; migrations after a connector adds external function support; users assuming external functions behave like ordinary scalar functions everywhere.","solutions":["Move the external function call out of the join filter into a subquery/CTE that computes it before the join (e.g. precompute in a SELECT, then join on the result).","Replace the external function with an equivalent built-in Presto function if one exists.","If the function must be used in the join predicate, check whether the connector offers a native (non-external) variant or pushdown of the predicate.","As a last resort, restructure as a lateral/cross join plus WHERE if the planner supports it for that function."],"exampleFix":"// before\nSELECT * FROM a JOIN b ON a.id = b.id AND my_ext_func(a.ts) > 10\n// after\nWITH pre AS (SELECT *, my_ext_func(ts) AS f FROM a)\nSELECT * FROM pre JOIN b ON pre.id = b.id AND pre.f > 10","handlingStrategy":"validation","validationCode":"// Inspect join filters for external functions before submitting\nboolean hasExternalCallInJoin(PlanNode joinNode, Metadata metadata) {\n    return joinNode.getFilter()\n        .map(f -> f.accept(new ExternalCallExpressionChecker(metadata), null))\n        .orElse(false);\n}\nif (hasExternalCallInJoin(node, metadata)) { /* rewrite query to precompute */ }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep external/connector functions out of JOIN ON clauses; precompute them in subqueries","Prefer built-in scalar functions in join predicates","Wrap external function calls in CTEs before joining","Add query linting in your SQL-generation layer to flag external functions inside join conditions"],"tags":["planner","join","external-function","not-supported"],"backgroundTag":"unsupported-function-in-join-filter","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"}