private @Nullable LogicalJoin findSemiJoinIndexByCost(
      HiveLoptMultiJoin multiJoin,
      List<RexNode> joinFilters,
      int factIdx,
      int dimIdx) {
    // create a SemiJoin with the semi-join condition and keys
    RexNode semiJoinCondition =
        RexUtil.composeConjunction(rexBuilder, joinFilters);

    int leftAdjustment = 0;
    for (int i = 0; i < factIdx; i++) {
      leftAdjustment -= multiJoin.getNumFieldsInJoinFactor(i);
    }

    semiJoinCondition =
        adjustSemiJoinCondition(
            multiJoin,
            leftAdjustment,
            semiJoinCondition,
            factIdx,
            dimIdx);

    RelNode factRel = multiJoin.getJoinFactor(factIdx);
    RelNode dimRel = multiJoin.getJoinFactor(dimIdx);
    final JoinInfo joinInfo = JoinInfo.of(factRel, dimRel, semiJoinCondition);
    assert joinInfo.leftKeys.size() > 0;

    // mutable copies
    final List<Integer> leftKeys = Lists.newArrayList(joinInfo.leftKeys);
    final List<Integer> rightKeys = Lists.newArrayList(joinInfo.rightKeys);

    // make sure all the fact table keys originate from the same table
    // and are simple column references
    final List<Integer> actualLeftKeys = new ArrayList<>();
    LcsTable factTable =
        validateKeys(
            factRel,
            leftKeys,
            rightKeys,
            actualLeftKeys);
    if (factTable == null) {
      return null;
    }

    // find the best index
    final List<Integer> bestKeyOrder = new ArrayList<>();
    LcsTableScan tmpFactRel =
        (LcsTableScan) factTable.toRel(
            ViewExpanders.simpleContext(factRel.getCluster()));

    LcsIndexOptimizer indexOptimizer = new LcsIndexOptimizer(tmpFactRel);
    FemLocalIndex bestIndex =
        indexOptimizer.findSemiJoinIndexByCost(
            dimRel,
            actualLeftKeys,
            rightKeys,
            bestKeyOrder);

    if (bestIndex == null) {
      return null;
    }

    // if necessary, truncate the keys to reflect the ones that match
    // the index and remove the corresponding, unnecessary filters from
    // the condition; note that we don't save the actual keys here because
    // later when the semijoin is pushed past other RelNodes, the keys will
    // be converted
    final List<Integer> truncatedLeftKeys;
    final List<Integer> truncatedRightKeys;
    if (actualLeftKeys.size() == bestKeyOrder.size()) {
      truncatedLeftKeys = leftKeys;
      truncatedRightKeys = rightKeys;
    } else {
      truncatedLeftKeys = new ArrayList<>();
      truncatedRightKeys = new ArrayList<>();
      for (int key : bestKeyOrder) {
        truncatedLeftKeys.add(leftKeys.get(key));
        truncatedRightKeys.add(rightKeys.get(key));
      }
      semiJoinCondition =
          removeExtraFilters(
              truncatedLeftKeys,
              multiJoin.getNumFieldsInJoinFactor(factIdx),
              semiJoinCondition);
    }
    return LogicalJoin.create(factRel, dimRel, ImmutableList.of(),
        requireNonNull(semiJoinCondition, "semiJoinCondition"),
        ImmutableSet.of(), JoinRelType.SEMI);
  }
Given a list of possible filters on a fact table, determine if there is
an index that can be used, provided all the fact table keys originate
from the same underlying table.

@param multiJoin join factors being optimized
@param joinFilters filters to be used on the fact table
@param factIdx index in join factors corresponding to the fact table
@param dimIdx index in join factors corresponding to the dimension table

@return SemiJoin containing information regarding the semijoin that
can be used to filter the fact table
ql/src/java/org/apache/hadoop/hive/ql/optimizer/calcite/rules/HiveLoptSemiJoinOptimizer.java