{"record":{"id":"3e9cbb14d3b49221","repo":"apache/druid","slug":"cannot-build-hash-join-matcher-on-non-equi-join-co","errorCode":null,"errorMessage":"Cannot build hash-join matcher on non-equi-join condition: %s","messagePattern":"Cannot build hash-join matcher on non-equi-join condition: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcher.java","lineNumber":123,"sourceCode":"      this.singleRowMatching = false;\n    } else if (condition.isAlwaysFalse()) {\n      this.conditionMatchers = Collections.singletonList(() -> IntSortedSets.EMPTY_SET);\n      this.singleRowMatching = false;\n    } else if (condition.getNonEquiConditions().isEmpty()) {\n      final List<Pair<IndexedTable.Index, Equality>> indexes =\n          condition.getEquiConditions()\n                   .stream()\n                   .map(eq -> Pair.of(getIndex(table, eq), eq))\n                   .collect(Collectors.toCollection(ArrayList::new));\n\n      this.conditionMatchers =\n          indexes.stream()\n                 .map(pair -> makeConditionMatcher(pair.lhs, leftSelectorFactory, pair.rhs))\n                 .collect(Collectors.toList());\n\n      this.singleRowMatching = indexes.stream().allMatch(pair -> pair.lhs.areKeysUnique(pair.rhs.isIncludeNull()));\n    } else {\n      throw new IAE(\n          \"Cannot build hash-join matcher on non-equi-join condition: %s\",\n          condition.getOriginalExpression()\n      );\n    }\n\n    ColumnSelectorFactory selectorFactory = table.makeColumnSelectorFactory(joinableOffset, closer);\n    this.selectorFactory = selectorFactory != null\n                           ? selectorFactory\n                           : new IndexedTableColumnSelectorFactory(table, () -> currentRow, closer);\n\n    if (remainderNeeded) {\n      this.matchedRows = new IntRBTreeSet();\n    } else {\n      this.matchedRows = null;\n    }\n\n  }\n","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcher.java#L105-L141","documentation":"The hash-join matcher can only evaluate equi-join conditions (Equality clauses on key columns). If the analysis of the join condition fails to extract at least one equality, building an IndexedTableJoinMatcher is impossible and the constructor throws IAE with the original expression.","triggerScenarios":"Passing a Joinable with a non-equi condition (e.g. inequality like t1.x < t2.y, or a complex expression such as lower(t1.a) = t2.b) into IndexedTableJoinMatcher construction via a hash join plan.","commonSituations":"SQL queries with inequality or expression-based join predicates against broadcast tables; Druid planner choosing a hash join when the predicate is not a simple column equality; version changes that tightened equi-join analysis.","solutions":["Rewrite the join to use a simple equality on columns: ON t1.col = t2.col","Move the non-equi predicate into a WHERE clause applied after the join","Index the table's key columns so the equality can be matched (ColumnIndex for the join keys)","Use a different join type (e.g. a subquery or sort-merge style join) instead of broadcast hash join"],"exampleFix":"// before\n// ON t1.startTime < t2.startTime\n// after\n// ON t1.id = t2.id WHERE t1.startTime < t2.startTime","handlingStrategy":"validation","validationCode":"// ensure join condition is Equality before building the matcher\nif (!(condition instanceof Equality)) { throw new IllegalArgumentException(\"hash join requires equi-join\"); }","typeGuard":null,"tryCatchPattern":"try { new IndexedTableJoinMatcher(...); } catch (IAE e) { /* fall back to nested-loop/filter join */ }","preventionTips":["Write equi-join ON clauses (col = col) for broadcast joins","Move inequality predicates to WHERE after the join","Plan joins on indexed key columns"],"tags":["java","join","hash-join","non-equi-join"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}