public ParseContext transform(ParseContext parseContext) throws SemanticException {
    Map<ReduceSinkOperator, SemiJoinBranchInfo> allSemijoins = parseContext.getRsToSemiJoinBranchInfo();
    if (allSemijoins.isEmpty()) {
      return parseContext;
    }
    HiveConf hiveConf = parseContext.getConf();

    for (Entry<SJSourceTarget, List<ReduceSinkOperator>> sjMergeCandidate : createMergeCandidates(allSemijoins)) {
      final List<ReduceSinkOperator> sjBranches = sjMergeCandidate.getValue();
      if (sjBranches.size() < 2) {
        continue;
      }
      List<SelectOperator> selOps = new ArrayList<>(sjBranches.size());
      for (ReduceSinkOperator rs : sjBranches) {
        selOps.add(OperatorUtils.ancestor(rs, SelectOperator.class, 0, 0, 0, 0));
      }

      long sjEntriesHint = extractBloomEntriesHint(sjBranches);

      SelectOperator selectOp = mergeSelectOps(sjMergeCandidate.getKey().source, selOps);

      GroupByOperator gbPartialOp = createGroupBy(selectOp, selectOp, GroupByDesc.Mode.HASH, sjEntriesHint, hiveConf);

      ReduceSinkOperator rsPartialOp = createReduceSink(gbPartialOp, NullOrdering.defaultNullOrder(hiveConf));
      rsPartialOp.getConf().setReducerTraits(EnumSet.of(ReduceSinkDesc.ReducerTraits.QUICKSTART));

      GroupByOperator gbCompleteOp =
          createGroupBy(selectOp, rsPartialOp, GroupByDesc.Mode.FINAL, sjEntriesHint, hiveConf);

      ReduceSinkOperator rsCompleteOp = createReduceSink(gbCompleteOp, NullOrdering.defaultNullOrder(hiveConf));

      final TableScanOperator sjTargetTable = sjMergeCandidate.getKey().target;
      SemiJoinBranchInfo sjInfo = new SemiJoinBranchInfo(sjTargetTable, false);
      parseContext.getRsToSemiJoinBranchInfo().put(rsCompleteOp, sjInfo);

      // Save the info that is required at query time to resolve dynamic/runtime values.
      RuntimeValuesInfo valuesInfo = createRuntimeValuesInfo(rsCompleteOp, sjBranches, parseContext);
      parseContext.getRsToRuntimeValuesInfoMap().put(rsCompleteOp, valuesInfo);

      ExprNodeGenericFuncDesc sjPredicate = createSemiJoinPredicate(sjBranches, valuesInfo, parseContext);

      // Update filter operators with the new semi-join predicate
      for (Operator<?> op : sjTargetTable.getChildOperators()) {
        if (op instanceof FilterOperator) {
          FilterDesc filter = ((FilterOperator) op).getConf();
          filter.setPredicate(and(filter.getPredicate(), sjPredicate));
        }
      }
      // Update tableScan with the new semi-join predicate
      sjTargetTable.getConf().setFilterExpr(and(sjTargetTable.getConf().getFilterExpr(), sjPredicate));

      for (ReduceSinkOperator rs : sjBranches) {
        GenTezUtils.removeSemiJoinOperator(parseContext, rs, sjTargetTable);
        GenTezUtils.removeBranch(rs);
      }
    }
    return parseContext;
  }
A transformation that merges multiple single-column semi join reducers to one multi-column semi join reducer.
<p>
Semi join reducers are comprised from two parts:
<ul>
  <li>Filter creation from the source relation and broadcast: SOURCE - SEL - GB1 - RS1 - GB2 - RS2</li>
  <li>Filter application on the target relation: TS - FIL[in_bloom(col)]</li>
</ul>
<p>
An example of the transformation on three single column semi join reducers is shown below. The plan is simplified for
presentation purposes.
<h2>BEFORE:</h2>
<pre>
       / SEL[fname] - GB1 - RS1 - GB2 - RS2  \
SOURCE - SEL[lname] - GB1 - RS1 - GB2 - RS2  -&gt; TS[Author] - FIL[in_bloom(fname) ^ in_bloom(lname) ^ in_bloom(age)]
       \ SEL[age]   - GB1 - RS1 - GB2 - RS2  /
</pre>
<h3>AFTER:</h3>
<pre>
SOURCE - SEL[fname, lname, age] - GB1 - RS1 - GB2 - RS2 -&gt; TS[Author] - FIL[in_bloom(hash(fname,lname,age)]
</pre>
ql/src/java/org/apache/hadoop/hive/ql/optimizer/SemiJoinReductionMerge.java