apache/druid · error · IllegalArgumentException

Cannot build hash-join matcher on non-equi-join condition

Error message

Cannot build hash-join matcher on non-equi-join condition: %s

What it means

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.

Solutions

  1. Rewrite the join to use a simple equality on columns: ON t1.col = t2.col
  2. Move the non-equi predicate into a WHERE clause applied after the join
  3. Index the table's key columns so the equality can be matched (ColumnIndex for the join keys)
  4. Use a different join type (e.g. a subquery or sort-merge style join) instead of broadcast hash join

Example fix

// before
// ON t1.startTime < t2.startTime
// after
// ON t1.id = t2.id WHERE t1.startTime < t2.startTime
Defensive patterns

Strategy: validation

Validate before calling

// ensure join condition is Equality before building the matcher
if (!(condition instanceof Equality)) { throw new IllegalArgumentException("hash join requires equi-join"); }

Try / catch

try { new IndexedTableJoinMatcher(...); } catch (IAE e) { /* fall back to nested-loop/filter join */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of apache/druid@9b90983fd2 (2026-09-07). Data as JSON: /api/errors/3e9cbb14d3b49221. Report an issue: GitHub.

Appendix: source

Thrown at processing/src/main/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcher.java:123

      this.singleRowMatching = false;
    } else if (condition.isAlwaysFalse()) {
      this.conditionMatchers = Collections.singletonList(() -> IntSortedSets.EMPTY_SET);
      this.singleRowMatching = false;
    } else if (condition.getNonEquiConditions().isEmpty()) {
      final List<Pair<IndexedTable.Index, Equality>> indexes =
          condition.getEquiConditions()
                   .stream()
                   .map(eq -> Pair.of(getIndex(table, eq), eq))
                   .collect(Collectors.toCollection(ArrayList::new));

      this.conditionMatchers =
          indexes.stream()
                 .map(pair -> makeConditionMatcher(pair.lhs, leftSelectorFactory, pair.rhs))
                 .collect(Collectors.toList());

      this.singleRowMatching = indexes.stream().allMatch(pair -> pair.lhs.areKeysUnique(pair.rhs.isIncludeNull()));
    } else {
      throw new IAE(
          "Cannot build hash-join matcher on non-equi-join condition: %s",
          condition.getOriginalExpression()
      );
    }

    ColumnSelectorFactory selectorFactory = table.makeColumnSelectorFactory(joinableOffset, closer);
    this.selectorFactory = selectorFactory != null
                           ? selectorFactory
                           : new IndexedTableColumnSelectorFactory(table, () -> currentRow, closer);

    if (remainderNeeded) {
      this.matchedRows = new IntRBTreeSet();
    } else {
      this.matchedRows = null;
    }

  }

View on GitHub (pinned to 9b90983fd2)