@Override
    public void onMatch(RelOptRuleCall call) {
      final T topOperator = call.rel(0);
      final Join join = call.rel(1);

      if (join.isSemiJoin()) {
        // bail out, nothing to do
        return;
      }

      final Aggregate aggregate = call.rel(2);
      final RelNode aggregateInput = call.rel(3);
      final RelNode right = call.rel(4);

      // make sure the following conditions are met
      //  Join is INNER
      // Join keys are same as gb keys
      //  project above is referring to inputs only from non-aggregate side
      final JoinInfo joinInfo = join.analyzeCondition();
      if(!joinInfo.isEqui()) {
        return;
      }
      if (!joinInfo.leftSet().equals(
          ImmutableBitSet.range(aggregate.getGroupCount()))) {
        // Rule requires that aggregate key to be the same as the join key.
        // By the way, neither a super-set nor a sub-set would work.
        return;
      }
      final ImmutableBitSet topRefs = extractUsedFields(topOperator);

      final ImmutableBitSet leftBits =
          ImmutableBitSet.range(0, join.getLeft().getRowType().getFieldCount());

      if (topRefs.intersects(leftBits)) {
        return;
      }
      // it is safe to swap inputs
      final T swappedTopOperator = swapInputs(join, topOperator, call.builder());
      final Join swappedJoin = (Join) swappedTopOperator.getInput(0);

      final ImmutableBitSet swappedTopRefs = extractUsedFields(swappedTopOperator);

      perform(call, swappedTopRefs, swappedTopOperator, swappedJoin, right, aggregate, aggregateInput);
    }
Rule that swaps Join inputs if they can potentially lead to SemiJoin conversion.
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveSemiJoinRule.java