private Operator genJoinOperatorChildren(QBJoinTree join, Operator left,
                                           Operator[] right, Set<Integer> omitOpts, ExprNodeDesc[][] joinKeys) throws SemanticException {

    RowResolver outputRR = new RowResolver();
    List<String> outputColumnNames = new ArrayList<String>();
    // all children are base classes
    Operator<?>[] rightOps = new Operator[right.length];

    Map<String, Byte> reversedExprs = new HashMap<String, Byte>();
    Map<Byte, List<ExprNodeDesc>> exprMap = new HashMap<Byte, List<ExprNodeDesc>>();
    Map<String, ExprNodeDesc> colExprMap = new HashMap<String, ExprNodeDesc>();
    Map<Integer, Set<String>> posToAliasMap = new HashMap<Integer, Set<String>>();
    Map<Byte, List<ExprNodeDesc>> filterMap = new HashMap<Byte, List<ExprNodeDesc>>();

    // Only used for semijoin with residual predicates
    List<ColumnInfo> topSelectInputColumns = new ArrayList<>();

    for (int pos = 0; pos < right.length; ++pos) {
      Operator<?> input = right[pos] == null ? left : right[pos];
      if (input == null) {
        input = left;
      }
      ReduceSinkOperator rs = (ReduceSinkOperator) input;
      if (rs.getNumParent() != 1) {
        throw new SemanticException("RS should have single parent");
      }
      Operator<?> parent = rs.getParentOperators().get(0);
      ReduceSinkDesc rsDesc = (ReduceSinkDesc) (input.getConf());

      int[] index = rs.getValueIndex();

      List<ExprNodeDesc> valueDesc = new ArrayList<ExprNodeDesc>();
      List<ExprNodeDesc> filterDesc = new ArrayList<ExprNodeDesc>();
      Byte tag = (byte) rsDesc.getTag();

      // check whether this input operator produces output
      // If it has residual, we do not skip this output,
      // we will add a Select on top of the join
      if (omitOpts != null && omitOpts.contains(pos)
          && join.getPostJoinFilters().size() == 0) {
        exprMap.put(tag, valueDesc);
        filterMap.put(tag, filterDesc);
        rightOps[pos] = input;
        continue;
      }

      List<String> keyColNames = rsDesc.getOutputKeyColumnNames();
      List<String> valColNames = rsDesc.getOutputValueColumnNames();

      // prepare output descriptors for the input opt
      RowResolver inputRR = opParseCtx.get(input).getRowResolver();
      RowResolver parentRR = opParseCtx.get(parent).getRowResolver();
      posToAliasMap.put(pos, new HashSet<String>(inputRR.getTableNames()));

      List<ColumnInfo> columns = parentRR.getColumnInfos();
      for (int i = 0; i < index.length; i++) {
        ColumnInfo prev = columns.get(i);
        String[] nm = parentRR.reverseLookup(prev.getInternalName());
        String[] nm2 = parentRR.getAlternateMappings(prev.getInternalName());
        if (outputRR.get(nm[0], nm[1]) != null) {
          continue;
        }
        ColumnInfo info = new ColumnInfo(prev);
        String field;
        if (index[i] >= 0) {
          field = Utilities.ReduceField.KEY + "." + keyColNames.get(index[i]);
        } else {
          field = Utilities.ReduceField.VALUE + "." + valColNames.get(-index[i] - 1);
        }
        String internalName = getColumnInternalName(outputColumnNames.size());
        ExprNodeColumnDesc desc = new ExprNodeColumnDesc(info.getType(),
            field, info.getTabAlias(), info.getIsVirtualCol());

        info.setInternalName(internalName);
        colExprMap.put(internalName, desc);
        outputRR.put(nm[0], nm[1], info);
        if (nm2 != null) {
          outputRR.addMappingOnly(nm2[0], nm2[1], info);
        }

        valueDesc.add(desc);
        outputColumnNames.add(internalName);
        reversedExprs.put(internalName, tag);

        // Populate semijoin select if needed
        if (omitOpts == null || !omitOpts.contains(pos)) {
          topSelectInputColumns.add(info);
        }
      }
      for (ASTNode cond : join.getFilters().get(tag)) {
        filterDesc.add(genExprNodeDesc(cond, inputRR));
      }
      exprMap.put(tag, valueDesc);
      filterMap.put(tag, filterDesc);
      rightOps[pos] = input;
    }

    JoinCondDesc[] joinCondns = new JoinCondDesc[join.getJoinCond().length];
    for (int i = 0; i < join.getJoinCond().length; i++) {
      JoinCond condn = join.getJoinCond()[i];
      joinCondns[i] = new JoinCondDesc(condn);
    }

    JoinDesc desc = new JoinDesc(exprMap, outputColumnNames,
        join.getNoOuterJoin(), joinCondns, filterMap, joinKeys, null);
    desc.setReversedExprs(reversedExprs);
    desc.setFilterMap(join.getFilterMap());
    // Add filters that apply to more than one input
    if (join.getPostJoinFilters().size() != 0 &&
        (!join.getNoOuterJoin() || !join.getNoSemiJoin()
            || HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_PUSH_RESIDUAL_INNER))) {
      LOG.debug("Generate JOIN with post-filtering conditions");
      List<ExprNodeDesc> residualFilterExprs = new ArrayList<ExprNodeDesc>();
      for (ASTNode cond : join.getPostJoinFilters()) {
        residualFilterExprs.add(genExprNodeDesc(cond, outputRR, false, isCBOExecuted()));
      }
      desc.setResidualFilterExprs(residualFilterExprs);
      // Clean post-conditions
      join.getPostJoinFilters().clear();
    }

    JoinOperator joinOp = (JoinOperator) OperatorFactory.getAndMakeChild(getOpContext(), desc,
        new RowSchema(outputRR.getColumnInfos()), rightOps);
    joinOp.setColumnExprMap(colExprMap);
    joinOp.setPosToAliasMap(posToAliasMap);

    if (join.getNullSafes() != null) {
      boolean[] nullsafes = new boolean[join.getNullSafes().size()];
      for (int i = 0; i < nullsafes.length; i++) {
        nullsafes[i] = join.getNullSafes().get(i);
      }
      desc.setNullSafes(nullsafes);
    }

    Operator<?> topOp = putOpInsertMap(joinOp, outputRR);
    if (omitOpts != null && !omitOpts.isEmpty()
        && desc.getResidualFilterExprs() != null && !desc.getResidualFilterExprs().isEmpty()) {
      // Adding a select operator to top of semijoin to ensure projection of only correct columns
      final List<ExprNodeDesc> topSelectExprs = new ArrayList<>();
      final List<String> topSelectOutputColNames = new ArrayList<>();
      final RowResolver topSelectRR = new RowResolver();
      final Map<String, ExprNodeDesc> topSelectColExprMap = new HashMap<String, ExprNodeDesc>();
      for (ColumnInfo colInfo : topSelectInputColumns) {
        ExprNodeColumnDesc columnExpr = new ExprNodeColumnDesc(colInfo);
        topSelectExprs.add(columnExpr);
        topSelectOutputColNames.add(colInfo.getInternalName());
        topSelectColExprMap.put(colInfo.getInternalName(), columnExpr);
        String[] nm = outputRR.reverseLookup(columnExpr.getColumn());
        String[] nm2 = outputRR.getAlternateMappings(columnExpr.getColumn());
        topSelectRR.put(nm[0], nm[1], colInfo);
        if (nm2 != null) {
          topSelectRR.addMappingOnly(nm2[0], nm2[1], colInfo);
        }
      }
      final SelectDesc topSelect = new SelectDesc(topSelectExprs, topSelectOutputColNames);
      topOp = putOpInsertMap(OperatorFactory.getAndMakeChild(topSelect,
          new RowSchema(topSelectRR.getColumnInfos()), topOp), topSelectRR);
      topOp.setColumnExprMap(topSelectColExprMap);
    }

    return topOp;
  }
Creates an expression for converting from a table column to a row column. For example:
The table column is int but the query provides a string in the row, then we need to cast automatically.
@param tableField The target table column
@param rowField The source row column
@param conversion The value of this boolean is set to true if we detect that a conversion is needed. This is a
                  hidden return value hidden here, to notify the caller that a cast was needed.
@param dest The destination table for the error message
@param columnNum The destination column id for the error message
@return The Expression describing the selected column. Note that `conversion` can be considered as a return value
        as well
@throws SemanticException If conversion were needed, but automatic conversion is not available
ql/src/java/org/apache/hadoop/hive/ql/parse/SemanticAnalyzer.java