private static @Nullable HiveLoptJoinTree pushDownFactor(
          RelMetadataQuery mq,
          RelBuilder relBuilder,
          HiveLoptMultiJoin multiJoin,
          HiveLoptSemiJoinOptimizer semiJoinOpt,
          HiveLoptJoinTree joinTree,
          int factorToAdd,
          BitSet factorsNeeded,
          List<RexNode> filtersToAdd,
          boolean selfJoin) {
    // pushdown option only works if we already have a join tree
    if (!isJoinTree(joinTree.getJoinTree())) {
      return null;
    }
    int childNo = -1;
    HiveLoptJoinTree left = joinTree.getLeft();
    HiveLoptJoinTree right = joinTree.getRight();
    Join joinRel = (Join) joinTree.getJoinTree();
    JoinRelType joinType = joinRel.getJoinType();

    // can't push factors pass self-joins because in order to later remove
    // them, we need to keep the factors together
    if (joinTree.isRemovableSelfJoin()) {
      return null;
    }

    // If there are no constraints as to which side the factor must
    // be pushed, arbitrarily push to the left.  In the case of a
    // self-join, always push to the input that contains the other
    // half of the self-join.
    if (selfJoin) {
      BitSet selfJoinFactor = new BitSet(multiJoin.getNumJoinFactors());
      Integer factor = requireNonNull(multiJoin.getOtherSelfJoinFactor(factorToAdd),
              () -> "multiJoin.getOtherSelfJoinFactor(" + factorToAdd + ") is null");
      selfJoinFactor.set(factor);
      if (multiJoin.hasAllFactors(left, selfJoinFactor)) {
        childNo = 0;
      } else {
        assert multiJoin.hasAllFactors(right, selfJoinFactor);
        childNo = 1;
      }
    } else if (
            (factorsNeeded.cardinality() == 0)
                    && !joinType.generatesNullsOnLeft()) {
      childNo = 0;
    } else {
      // push to the left if the LHS contains all factors that the
      // current factor needs and that side is not null-generating;
      // same check for RHS
      if (multiJoin.hasAllFactors(left, factorsNeeded)
              && !joinType.generatesNullsOnLeft()) {
        childNo = 0;
      } else if (
              multiJoin.hasAllFactors(right, factorsNeeded)
                      && !joinType.generatesNullsOnRight()) {
        childNo = 1;
      }
      // if it couldn't be pushed down to either side, then it can
      // only be put on top
    }
    if (childNo == -1) {
      return null;
    }

    // remember the original join order before the pushdown so we can
    // appropriately adjust any filters already attached to the join
    // node
    final List<Integer> origJoinOrder = joinTree.getTreeOrder();

    // recursively pushdown the factor
    HiveLoptJoinTree subTree = (childNo == 0) ? left : right;
    subTree =
            addFactorToTree(
                    mq,
                    relBuilder,
                    multiJoin,
                    semiJoinOpt,
                    subTree,
                    factorToAdd,
                    factorsNeeded,
                    filtersToAdd,
                    selfJoin);

    if (childNo == 0) {
      left = subTree;
    } else {
      right = subTree;
    }

    // adjust the join condition from the original join tree to reflect
    // pushdown of the new factor as well as any swapping that may have
    // been done during the pushdown
    RexNode newCondition =
            ((Join) joinTree.getJoinTree()).getCondition();
    newCondition =
            adjustFilter(
                    multiJoin,
                    requireNonNull(left, "left"),
                    requireNonNull(right, "right"),
                    newCondition,
                    factorToAdd,
                    origJoinOrder,
                    joinTree.getJoinTree().getRowType().getFieldList());

    // determine if additional filters apply as a result of adding the
    // new factor, provided this isn't a left or right outer join; for
    // those cases, the additional filters will be added on top of the
    // join in createJoinSubtree
    if ((joinType != JoinRelType.LEFT) && (joinType != JoinRelType.RIGHT)) {
      RexNode condition =
              addFilters(
                      multiJoin,
                      left,
                      -1,
                      right,
                      filtersToAdd,
                      true);
      RexBuilder rexBuilder =
              multiJoin.getMultiJoinRel().getCluster().getRexBuilder();
      newCondition =
              RelOptUtil.andJoinFilters(
                      rexBuilder,
                      newCondition,
                      condition);
    }

    // create the new join tree with the factor pushed down
    return createJoinSubtree(
            mq,
            relBuilder,
            multiJoin,
            left,
            right,
            newCondition,
            joinType,
            filtersToAdd,
            false,
            false);
  }
Creates a join tree where the new factor is pushed down one of the
operands of the current join tree.

@param multiJoin join factors being optimized
@param semiJoinOpt optimal semijoins for each factor
@param joinTree current join tree
@param factorToAdd new factor to be added
@param factorsNeeded factors that must precede the factor to be added
@param filtersToAdd filters remaining to be added; filters that are added
to the join tree are removed from the list
@param selfJoin true if the factor being added is part of a removable
self-join

@return optimal join tree with the new factor pushed down the current
join tree if it is possible to do the pushdown; otherwise, null is
returned
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveLoptOptimizeJoinRule.java