@Override
  public void onMatch(RelOptRuleCall call) {
    final Join topJoin = call.rel(0);
    final Join bottomJoin = call.rel(1);
    final RexBuilder rexBuilder = topJoin.getCluster().getRexBuilder();

    // 1) Check whether these joins can be swapped.
    if (topJoin.getJoinType().generatesNullsOnLeft()
        || bottomJoin.getJoinType().generatesNullsOnLeft()
        || bottomJoin.isSemiJoin()) {
      // Nothing to do
      return;
    }

    // 2) Check whether the bottom is a non-filtering column appending join.
    // - If the top one is a non-filtering column appending join, we do not
    // trigger the optimization, since we do not want to swap this type of
    // joins.
    // - If the bottom one is not a non-filtering column appending join,
    // we cannot trigger the optimization.
    RewritablePKFKJoinInfo topInfo = HiveRelOptUtil.isRewritablePKFKJoin(
        topJoin, topJoin.getLeft(), topJoin.getRight(), call.getMetadataQuery());
    RewritablePKFKJoinInfo bottomInfo = HiveRelOptUtil.isRewritablePKFKJoin(
        bottomJoin, bottomJoin.getLeft(), bottomJoin.getRight(), call.getMetadataQuery());
    if (topInfo.rewritable || !bottomInfo.rewritable) {
      // Nothing to do
      return;
    }

    // 3) Rewrite.
    // X is the left child of the join below
    // Y is the right child of the join below
    // Z is the right child of the top join
    int nFieldsX = bottomJoin.getLeft().getRowType().getFieldList().size();
    int nFieldsY = bottomJoin.getRight().getRowType().getFieldList().size();
    int nFieldsZ = topJoin.getRight().getRowType().getFieldList().size();
    int nTotalFields = nFieldsX + nFieldsY + nFieldsZ;
    List<RelDataTypeField> fields = new ArrayList<>();

    // create a list of fields for the full join result; note that
    // we can't simply use the fields because the row-type of a
    // semi-join would only include the left hand side fields
    List<RelDataTypeField> joinFields =
        topJoin.getRowType().getFieldList();
    for (int i = 0; i < (nFieldsX + nFieldsY); i++) {
      fields.add(joinFields.get(i));
    }
    joinFields = topJoin.getRight().getRowType().getFieldList();
    for (int i = 0; i < nFieldsZ; i++) {
      fields.add(joinFields.get(i));
    }

    // determine which operands below the join are the actual
    // rels that participate in it
    final Set<Integer> leftKeys = HiveCalciteUtil.getInputRefs(topJoin.getCondition());
    leftKeys.removeIf(i -> i >= topJoin.getLeft().getRowType().getFieldCount());
    int nKeysFromX = 0;
    for (int leftKey : leftKeys) {
      if (leftKey < nFieldsX) {
        nKeysFromX++;
      }
    }
    // the keys must all originate from the left
    if (nKeysFromX != leftKeys.size()) {
      // Nothing to do
      return;
    }

    // need to convert the conditions
    // (X, Y, Z) --> (X, Z, Y)
    int[] adjustments = new int[nTotalFields];
    setJoinAdjustments(
        adjustments,
        nFieldsX,
        nFieldsY,
        nFieldsZ,
        nFieldsZ,
        -nFieldsY);
    final RexNode newBottomCondition =
        topJoin.getCondition().accept(
            new RelOptUtil.RexInputConverter(
                rexBuilder,
                fields,
                adjustments));
    // create the new joins
    final Join newBottomJoin =
        topJoin.copy(
            topJoin.getTraitSet(),
            newBottomCondition,
            bottomJoin.getLeft(),
            topJoin.getRight(),
            topJoin.getJoinType(),
            topJoin.isSemiJoinDone());
    final RexNode newTopCondition;
    if (newBottomJoin.isSemiJoin()) {
      newTopCondition = bottomJoin.getCondition();
    } else {
      newTopCondition =
          bottomJoin.getCondition().accept(
              new RelOptUtil.RexInputConverter(
                  rexBuilder,
                  fields,
                  adjustments));
    }
    final Join newTopJoin =
        bottomJoin.copy(
            bottomJoin.getTraitSet(),
            newTopCondition,
            newBottomJoin,
            bottomJoin.getRight(),
            bottomJoin.getJoinType(),
            bottomJoin.isSemiJoinDone());

    if (newBottomJoin.isSemiJoin()) {
      call.transformTo(newTopJoin);
    } else {
      // need to swap the columns to match the original join
      // (X, Y, Z) --> (X, Z, Y)
      List<RexNode> exprs = new ArrayList<>();
      for (int i = 0; i < nFieldsX; i++) {
        exprs.add(rexBuilder.makeInputRef(newTopJoin, i));
      }
      for (int i = nFieldsX + nFieldsZ; i < topJoin.getRowType().getFieldCount(); i++) {
        exprs.add(rexBuilder.makeInputRef(newTopJoin, i));
      }
      for (int i = nFieldsX; i < nFieldsX + nFieldsZ; i++) {
        exprs.add(rexBuilder.makeInputRef(newTopJoin, i));
      }
      call.transformTo(
          call.builder()
              .push(newTopJoin)
              .project(exprs)
              .build());
    }
  }
Planner rule that pushes a Join down in a tree past a Join in order
to leave non-filtering column appending joins at the top of the plan.

<p>Join(Join(X, Y), Z) &rarr; Join(Join(X, Z), Y)
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveJoinSwapConstraintsRule.java