{"record":{"id":"b8cb853a6f456583","repo":"apache/druid","slug":"cannot-build-hash-join-matcher-on-non-key-based-co","errorCode":null,"errorMessage":"Cannot build hash-join matcher on non-key-based condition: %s","messagePattern":"Cannot build hash-join matcher on non-key-based condition: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcher.java","lineNumber":148,"sourceCode":"    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\n  private static IndexedTable.Index getIndex(\n      final IndexedTable table,\n      final Equality condition\n  )\n  {\n    if (!table.keyColumns().contains(condition.getRightColumn())) {\n      throw new IAE(\"Cannot build hash-join matcher on non-key-based condition: %s\", condition);\n    }\n\n    final int keyColumnNumber = table.rowSignature().indexOf(condition.getRightColumn());\n\n    return table.columnIndex(keyColumnNumber);\n  }\n\n  private static ConditionMatcher makeConditionMatcher(\n      final IndexedTable.Index index,\n      final ColumnSelectorFactory selectorFactory,\n      final Equality condition\n  )\n  {\n    return ColumnProcessors.makeProcessor(\n        condition.getLeftExpr(),\n        index.keyType(),\n        new ConditionMatcherFactory(index, condition.isIncludeNull()),\n        selectorFactory","sourceCodeStart":130,"sourceCodeEnd":166,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/segment/join/table/IndexedTableJoinMatcher.java#L130-L166","documentation":"Hash-join matching relies on the right-hand join column being one of the table's key columns, because only key columns have usable indexes for lookups. getIndex() validates the condition's right column is a key column and throws IAE otherwise.","triggerScenarios":"Building an IndexedTableJoinMatcher where the Equality condition's right column (the table-side column) is not in table.keyColumns(), e.g. joining ON t1.a = table.nonKeyColumn.","commonSituations":"Query referencing a broadcast table column that was not declared as a key column when the IndexedTable was built; tables built without indexes on the join key; schema drift between the table definition and the query.","solutions":["Include the join column in the keyColumns list when building the IndexedTable (e.g. RowBasedIndexedTable builder .keyColumns(...))","Rewrite the query so the join condition uses a column that is a declared key","Verify table.keyColumns() covers all columns used on the right side of ON equalities"],"exampleFix":"// before\nRowBasedIndexedTable.Builder.create(adapter, signature, \"v1\")\n    .keyColumns(Set.of(\"id\")).build(rows, keyColumnsCacheKey);\n// after\nRowBasedIndexedTable.Builder.create(adapter, signature, \"v1\")\n    .keyColumns(Set.of(\"id\", \"joinKey\")).build(rows, keyColumnsCacheKey);","handlingStrategy":"validation","validationCode":"if (!table.keyColumns().contains(rightColumn)) { throw new IllegalArgumentException(\"join column must be a key column: \" + rightColumn); }","typeGuard":null,"tryCatchPattern":"try { matcher = new IndexedTableJoinMatcher(...); } catch (IAE e) { /* re-plan or add key column */ }","preventionTips":["Declare every expected join column as a key column at table build time","Check table.keyColumns() coverage before planning joins"],"tags":["java","join","key-columns","index"],"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-14T05:17:10.506Z"}