apache/beam · error · java.lang.UnsupportedOperationException

Only support column reference or struct field access in conj

Error message

Only support column reference or struct field access in conjunction clause

What it means

After confirming an '=' conjunct, extractJoinPairOfRexNodes checks that both operands are legal join keys: plain column references (RexInputRef) or struct field accesses. Anything else (literals, function calls, casts) in the join clause is rejected with UnsupportedOperationException.

Source

Thrown at sdks/java/extensions/sql/src/main/java/org/apache/beam/sdk/extensions/sql/impl/rel/BeamJoinRel.java:195

        pairs.add(pair);
      }
    } else if ("=".equals(call.getOperator().getName())) {
      pairs.add(extractJoinPairOfRexNodes(call));
    } else {
      throw new UnsupportedOperationException(
          "Operator " + call.getOperator().getName() + " is not supported in join condition");
    }

    return pairs;
  }

  private static Pair<RexNode, RexNode> extractJoinPairOfRexNodes(RexCall rexCall) {
    if (!rexCall.getOperator().getName().equals("=")) {
      throw new UnsupportedOperationException("Non equi-join is not supported");
    }

    if (isIllegalJoinConjunctionClause(rexCall)) {
      throw new UnsupportedOperationException(
          "Only support column reference or struct field access in conjunction clause");
    }

    int leftIndex = getColumnIndex(rexCall.getOperands().get(0));
    int rightIndex = getColumnIndex(rexCall.getOperands().get(1));
    if (leftIndex < rightIndex) {
      return new Pair<>(rexCall.getOperands().get(0), rexCall.getOperands().get(1));
    } else {
      return new Pair<>(rexCall.getOperands().get(1), rexCall.getOperands().get(0));
    }
  }

  // Only support {RexInputRef | RexFieldAccess} = {RexInputRef | RexFieldAccess}
  private static boolean isIllegalJoinConjunctionClause(RexCall rexCall) {
    return (!(rexCall.getOperands().get(0) instanceof RexInputRef)
            && !(rexCall.getOperands().get(0) instanceof RexFieldAccess))
        || (!(rexCall.getOperands().get(1) instanceof RexInputRef)
            && !(rexCall.getOperands().get(1) instanceof RexFieldAccess));

View on GitHub (pinned to 12126d8942)

Solutions

  1. Join on raw columns only; apply functions/casts in a subquery or CTE before the join (e.g. project UPPER(name) AS name_norm then join on name_norm)
  2. Remove literals from the join clause and express them as WHERE filters
  3. Cast one of the source tables (via a subquery) so both key columns share the same type without in-condition casts

Example fix

// before
SELECT * FROM a JOIN b ON UPPER(a.name) = b.name;
// after
SELECT * FROM (SELECT UPPER(name) AS name_norm, * FROM a) a JOIN b ON a.name_norm = b.name;
Defensive patterns

Strategy: validation

Validate before calling

// Every join operand must be a column ref or struct field access
for (RexNode operand : joinKeyOperands) {
  if (!(operand instanceof RexInputRef || operand instanceof RexFieldAccess)) {
    throw new IllegalArgumentException("Join keys must be plain columns or struct fields");
  }
}

Try / catch

try {
  result = sqlEnv.sqlQuery(q).evaluate();
} catch (UnsupportedOperationException e) {
  if (e.getMessage().contains("Only support column reference or struct field access")) {
    q = precomputeJoinKeysInSubquery(q);
  } else throw e;
}

Prevention

When it happens

Trigger: Join conditions containing expressions rather than bare columns, e.g. ON UPPER(a.name) = b.name or ON a.id = CAST(b.id AS BIGINT) or a constant on one side.

Common situations: Joining on computed/normalized values; comparing a join key to a literal; applying type casts inside the join condition.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/750a5ab09e83463f. Report an issue: GitHub.