{"record":{"id":"2d17227054ee99aa","repo":"apache/druid","slug":"cannot-handle-equality-condition-involving-left-ha","errorCode":null,"errorMessage":"Cannot handle equality condition involving left-hand expression: %s","messagePattern":"Cannot handle equality condition involving left-hand expression: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinStageProcessor.java","lineNumber":275,"sourceCode":"   * Validates that a join condition can be handled by this processor. Returns the condition if it can be handled.\n   * Throws {@link IllegalArgumentException} if the condition cannot be handled.\n   */\n  public static JoinConditionAnalysis validateCondition(final JoinConditionAnalysis condition)\n  {\n    if (condition.isAlwaysTrue()) {\n      return condition;\n    }\n\n    if (condition.isAlwaysFalse()) {\n      throw new IAE(\"Cannot handle constant condition: %s\", condition.getOriginalExpression());\n    }\n\n    if (condition.getNonEquiConditions().size() > 0) {\n      throw new IAE(\"Cannot handle non-equijoin condition: %s\", condition.getOriginalExpression());\n    }\n\n    if (condition.getEquiConditions().stream().anyMatch(c -> !c.getLeftExpr().isIdentifier())) {\n      throw new IAE(\n          \"Cannot handle equality condition involving left-hand expression: %s\",\n          condition.getOriginalExpression()\n      );\n    }\n\n    return condition;\n  }\n\n  /**\n   * Validates that all signatures from {@link #collectAndReadPartitions(ExecutionContext)} are prefixed by the\n   * provided {@code keyColumns}.\n   */\n  private static Int2ObjectMap<List<ReadableInput>> validateInputFrameSignatures(\n      final Int2ObjectMap<List<ReadableInput>> inputsByPartition,\n      final List<List<KeyColumn>> keyColumns\n  )\n  {\n    for (List<ReadableInput> readableInputs : inputsByPartition.values()) {","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/multi-stage-query/src/main/java/org/apache/druid/msq/querykit/common/SortMergeJoinStageProcessor.java#L257-L293","documentation":"SortMergeJoinStageProcessor.validateCondition requires every equi-join equality to have a plain column identifier on the left-hand side. This IllegalArgumentException is thrown when the left side of an equality in the join condition is an arbitrary expression (e.g. f(x) = y), because the shuffle/partitioning keys can only be direct columns.","triggerScenarios":"An MSQ join whose ON clause contains an equality like ON lower(t1.name) = t2.name or ON t1.a + 1 = t2.a, where the left operand of '=' is an expression rather than a column reference.","commonSituations":"Writing SQL that normalizes a column during the join (functions, casts, arithmetic on the left key); translating hand-written native queries where the left field of an equi-condition is an expression.","solutions":["Rewrite the join so both sides of the equality are plain column identifiers, e.g. ON t1.name = t2.name, and apply the function to a pre-computed column.","Precompute the expression in a subquery/CTE as a projected column, then join on that column.","Apply the transformation on the right-hand side instead if only one side needs it.","Use the native query engine with a join that supports expression keys if rewriting is impossible."],"exampleFix":"// before\nFROM t1 JOIN t2 ON LOWER(t1.name) = t2.name\n// after\nFROM (SELECT LOWER(name) AS name_lower, ... FROM t1) t1 JOIN t2 ON t1.name_lower = t2.name","handlingStrategy":"validation","validationCode":"// Verify each equality's left side is an identifier before running via MSQ\nconst bad = joinCondition.equalities.filter(e => !/^[a-zA-Z_][\\w]*/.test(e.left) || e.left.includes('('));\nif (bad.length) throw new Error('Equi-join left expressions not supported: ' + bad.map(b => b.left));","typeGuard":"const isIdentifierExpr = (e) => e && e.kind === 'identifier';","tryCatchPattern":null,"preventionTips":["Never wrap join-key columns in functions or arithmetic in ON clauses.","Precompute expressions into projected columns before joining.","Keep join keys as raw columns on both sides where possible."],"tags":["sql","join","msq"],"backgroundTag":"unsupported-operation","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}