private static ExprNodeGenericFuncDesc createSemiJoinPredicate(List<ReduceSinkOperator> sjBranches,
      RuntimeValuesInfo sjValueInfo, ParseContext context) {
    // Performance note: To speed-up evaluation 'BETWEEN' predicates should come before the 'IN_BLOOM_FILTER'
    Deque<String> dynamicIds = new ArrayDeque<>(sjValueInfo.getDynamicValueIDs());
    List<ExprNodeDesc> sjPredicates = new ArrayList<>();
    List<ExprNodeDesc> hashArgs = new ArrayList<>();
    for (ReduceSinkOperator rs : sjBranches) {
      RuntimeValuesInfo info = context.getRsToRuntimeValuesInfoMap().get(rs);
      checkState(info.getTargetColumns().size() == 1, "Cannot handle multi-column semijoin branches.");
      final ExprNodeDesc targetColumn = info.getTargetColumns().get(0);
      TypeInfo typeInfo = targetColumn.getTypeInfo();
      DynamicValue minDynamic = new DynamicValue(dynamicIds.poll(), typeInfo);
      DynamicValue maxDynamic = new DynamicValue(dynamicIds.poll(), typeInfo);

      List<ExprNodeDesc> betweenArgs = Arrays.asList(
          // Use false to not invert between result
          new ExprNodeConstantDesc(Boolean.FALSE),
          targetColumn,
          new ExprNodeDynamicValueDesc(minDynamic),
          new ExprNodeDynamicValueDesc(maxDynamic));
      ExprNodeDesc betweenExp =
          new ExprNodeGenericFuncDesc(TypeInfoFactory.booleanTypeInfo, new GenericUDFBetween(), "between", betweenArgs);
      sjPredicates.add(betweenExp);
      hashArgs.add(targetColumn);
    }

    ExprNodeDesc hashExp = ExprNodeDescUtils.murmurHash(hashArgs);

    assert dynamicIds.size() == 1 : "There should be one column left untreated the one with the bloom filter";
    DynamicValue bloomDynamic = new DynamicValue(dynamicIds.poll(), TypeInfoFactory.binaryTypeInfo);
    sjPredicates.add(
        new ExprNodeGenericFuncDesc(TypeInfoFactory.booleanTypeInfo, new GenericUDFInBloomFilter(), "in_bloom_filter",
            Arrays.asList(hashExp, new ExprNodeDynamicValueDesc(bloomDynamic))));
    return and(sjPredicates);
  }
Creates the multi-column semi-join predicate that is applied on the target relation.

Assuming that the target columns of the semi-join are fname, lname, and age, the generated predicates is:
<pre>
  fname BETWEEN ?min_fname AND ?max_fname and
  lname BETWEEN ?min_lname AND ?max_lname and
  age   BETWEEN ?min_age   AND ?max_age and
  IN_BLOOM_FILTER(HASH(fname,lname,age),?bloom_filter)
</pre>
where the question mark (?) indicates dynamic values bound at runtime.
ql/src/java/org/apache/hadoop/hive/ql/optimizer/SemiJoinReductionMerge.java