private static HiveLoptJoinTree createReplacementJoin(
          RelBuilder relBuilder,
          HiveLoptMultiJoin multiJoin,
          HiveLoptSemiJoinOptimizer semiJoinOpt,
          HiveLoptJoinTree currJoinTree,
          int leftIdx,
          int factorToAdd,
          ImmutableIntList newKeys,
          Integer @Nullable [] replacementKeys,
          List<RexNode> filtersToAdd) {
    // create a projection, projecting the fields from the join tree
    // containing the current joinRel and the new factor; for fields
    // corresponding to join keys, replace them with the corresponding key
    // from the replacementKeys passed in; for other fields, just create a
    // null expression as a placeholder for the column; this is done so we
    // don't have to adjust the offsets of other expressions that reference
    // the new factor; the placeholder expression values should never be
    // referenced, so that's why it's ok to create these possibly invalid
    // expressions
    RelNode currJoinRel = currJoinTree.getJoinTree();
    List<RelDataTypeField> currFields = currJoinRel.getRowType().getFieldList();
    final int nCurrFields = currFields.size();
    List<RelDataTypeField> newFields =
            multiJoin.getJoinFactor(factorToAdd).getRowType().getFieldList();
    final int nNewFields = newFields.size();
    List<Pair<RexNode, String>> projects = new ArrayList<>();
    RexBuilder rexBuilder = currJoinRel.getCluster().getRexBuilder();
    RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();

    for (int i = 0; i < nCurrFields; i++) {
      projects.add(
              Pair.of(rexBuilder.makeInputRef(currFields.get(i).getType(), i),
                      currFields.get(i).getName()));
    }
    for (int i = 0; i < nNewFields; i++) {
      RexNode projExpr;
      RelDataType newType = newFields.get(i).getType();
      if (!newKeys.contains(i)) {
        if (replacementKeys == null) {
          // null generating factor in an outer join; so make the
          // type nullable
          newType =
                  typeFactory.createTypeWithNullability(newType, true);
        }
        projExpr = rexBuilder.makeNullLiteral(newType);
      } else {
        // TODO: is the above if (replacementKeys==null) check placed properly?
        requireNonNull(replacementKeys, "replacementKeys");
        RelDataTypeField mappedField = currFields.get(replacementKeys[i]);
        RexNode mappedInput =
                rexBuilder.makeInputRef(
                        mappedField.getType(),
                        replacementKeys[i]);

        // if the types aren't the same, create a cast
        if (mappedField.getType() == newType) {
          projExpr = mappedInput;
        } else {
          projExpr =
                  rexBuilder.makeCast(
                          newFields.get(i).getType(),
                          mappedInput);
        }
      }
      projects.add(Pair.of(projExpr, newFields.get(i).getName()));
    }

    relBuilder.push(currJoinRel);
    relBuilder.project(Pair.left(projects), Pair.right(projects));

    // remove the join conditions corresponding to the join we're removing;
    // we don't actually need to use them, but we need to remove them
    // from the list since they're no longer needed
    HiveLoptJoinTree newTree =
            new HiveLoptJoinTree(
                    semiJoinOpt.getChosenSemiJoin(factorToAdd),
                    factorToAdd);
    addFilters(
            multiJoin,
            currJoinTree,
            leftIdx,
            newTree,
            filtersToAdd,
            false);

    // Filters referencing factors other than leftIdx and factorToAdd
    // still do need to be applied.  So, add them into a separate
    // LogicalFilter placed on top off the projection created above.
    if (leftIdx >= 0) {
      addAdditionalFilters(
              relBuilder,
              multiJoin,
              currJoinTree,
              newTree,
              filtersToAdd);
    }

    // finally, create a join tree consisting of the current join's join
    // tree with the newly created projection; note that in the factor
    // tree, we act as if we're joining in the new factor, even
    // though we really aren't; this is needed so we can map the columns
    // from the new factor as we go up in the join tree
    return new HiveLoptJoinTree(
            relBuilder.build(),
            currJoinTree.getFactorTree(),
            newTree.getFactorTree());
  }
Creates a replacement join, projecting either dummy columns or
replacement keys from the factor that doesn't actually need to be joined.

@param multiJoin join factors being optimized
@param semiJoinOpt optimal semijoins for each factor
@param currJoinTree current join tree being added to
@param leftIdx if &ge; 0, when creating the replacement join, only consider
filters that reference leftIdx in currJoinTree; otherwise, consider all
filters that reference any factor in currJoinTree
@param factorToAdd new factor whose join can be removed
@param newKeys join keys that need to be replaced
@param replacementKeys the keys that replace the join keys; null if we're
removing the null generating factor in an outer join
@param filtersToAdd filters remaining to be added; filters added to the
new join tree are removed from the list

@return created join tree with an appropriate projection for the factor
that can be removed
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveLoptOptimizeJoinRule.java