public static void removeUnionOperators(GenTezProcContext context, BaseWork work, int indexForTezUnion)
    throws SemanticException {

    List<Operator<?>> roots = new ArrayList<Operator<?>>();
    roots.addAll(work.getAllRootOperators());
    if (work.getDummyOps() != null) {
      roots.addAll(work.getDummyOps());
    }
    roots.addAll(context.eventOperatorSet);

    // need to clone the plan.
    List<Operator<?>> newRoots;
    try (TruncatedOperatorTree truncator = new TruncatedOperatorTree(roots)) {
      newRoots = SerializationUtilities.cloneOperatorTree(roots);
    }

    // we're cloning the operator plan but we're retaining the original work. That means
    // that root operators have to be replaced with the cloned ops. The replacement map
    // tells you what that mapping is.
    BiMap<Operator<?>, Operator<?>> replacementMap = HashBiMap.create();

    // there's some special handling for dummyOps required. Mapjoins won't be properly
    // initialized if their dummy parents aren't initialized. Since we cloned the plan
    // we need to replace the dummy operators in the work with the cloned ones.
    List<HashTableDummyOperator> dummyOps = new LinkedList<HashTableDummyOperator>();

    Iterator<Operator<?>> it = newRoots.iterator();
    for (Operator<?> orig: roots) {
      Set<FileSinkOperator> fsOpSet = OperatorUtils.findOperators(orig, FileSinkOperator.class);
      for (FileSinkOperator fsOp : fsOpSet) {
        context.fileSinkSet.remove(fsOp);
      }

      Operator<?> newRoot = it.next();

      replacementMap.put(orig, newRoot);

      if (newRoot instanceof HashTableDummyOperator) {
        // dummy ops need to be updated to the cloned ones.
        dummyOps.add((HashTableDummyOperator) newRoot);
        it.remove();
      } else if (newRoot instanceof AppMasterEventOperator) {
        // event operators point to table scan operators. When cloning these we
        // need to restore the original scan.
        if (newRoot.getConf() instanceof DynamicPruningEventDesc) {
          TableScanOperator ts = ((DynamicPruningEventDesc) orig.getConf()).getTableScan();
          if (ts == null) {
            throw new AssertionError("No table scan associated with dynamic event pruning. " + orig);
          }
          ((DynamicPruningEventDesc) newRoot.getConf()).setTableScan(ts);
        }
        it.remove();
      } else {
        if (newRoot instanceof TableScanOperator) {
          if (context.tsToEventMap.containsKey(orig)) {
            // we need to update event operators with the cloned table scan
            for (AppMasterEventOperator event : context.tsToEventMap.get(orig)) {
              ((DynamicPruningEventDesc) event.getConf()).setTableScan((TableScanOperator) newRoot);
            }
          }
          // This TableScanOperator could be part of semijoin optimization.
          Map<ReduceSinkOperator, SemiJoinBranchInfo> rsToSemiJoinBranchInfo =
                  context.parseContext.getRsToSemiJoinBranchInfo();
          for (ReduceSinkOperator rs : rsToSemiJoinBranchInfo.keySet()) {
            SemiJoinBranchInfo sjInfo = rsToSemiJoinBranchInfo.get(rs);
            if (sjInfo.getTsOp() == orig) {
              SemiJoinBranchInfo newSJInfo = new SemiJoinBranchInfo(
                      (TableScanOperator) newRoot, sjInfo.getIsHint());
              rsToSemiJoinBranchInfo.put(rs, newSJInfo);
            }
          }
          // This TableScanOperator could also be part of other events in eventOperatorSet.
          for(AppMasterEventOperator event: context.eventOperatorSet) {
            if(event.getConf() instanceof DynamicPruningEventDesc) {
              TableScanOperator ts = ((DynamicPruningEventDesc) event.getConf()).getTableScan();
              if(ts.equals(orig)){
                ((DynamicPruningEventDesc) event.getConf()).setTableScan((TableScanOperator) newRoot);
              }
            }
          }
        }
        context.rootToWorkMap.remove(orig);
        context.rootToWorkMap.put(newRoot, work);
      }
    }

    // now we remove all the unions. we throw away any branch that's not reachable from
    // the current set of roots. The reason is that those branches will be handled in
    // different tasks.
    Deque<Operator<?>> operators = new LinkedList<Operator<?>>();
    operators.addAll(newRoots);

    Set<Operator<?>> seen = new HashSet<Operator<?>>();

    Set<FileStatus> fileStatusesToFetch = null;
    if(context.parseContext.getFetchTask() != null) {
      // File sink operator keeps a reference to a list of files. This reference needs to be passed on
      // to other file sink operators which could have been added by removal of Union Operator
      fileStatusesToFetch = context.parseContext.getFetchTask().getWork().getFilesToFetch();
    }

    while(!operators.isEmpty()) {
      Operator<?> current = operators.pop();

      if (seen.add(current) && current instanceof FileSinkOperator) {
        FileSinkOperator fileSink = (FileSinkOperator)current;

        // remember it for additional processing later
        if (context.fileSinkSet.contains(fileSink)) {
          continue;
        } else {
          context.fileSinkSet.add(fileSink);
        }

        FileSinkDesc desc = fileSink.getConf();
        Path path = desc.getDirName();
        List<FileSinkDesc> linked;

        if (!context.linkedFileSinks.containsKey(path)) {
          linked = new ArrayList<FileSinkDesc>();
          context.linkedFileSinks.put(path, linked);
        }
        linked = context.linkedFileSinks.get(path);
        linked.add(desc);

        desc.setDirName(new Path(path, AbstractFileMergeOperator.UNION_SUDBIR_PREFIX + linked.size()));
        Utilities.FILE_OP_LOGGER.debug("removing union - new desc with "
            + desc.getDirName() + "; parent " + path);
        desc.setLinkedFileSink(true);
        desc.setLinkedFileSinkDesc(linked);
        desc.setFilesToFetch(fileStatusesToFetch);
      }

      if (current instanceof AppMasterEventOperator) {
        // remember for additional processing later
        context.eventOperatorSet.add((AppMasterEventOperator) current);

        // mark the original as abandoned. Don't need it anymore.
        context.abandonedEventOperatorSet.add((AppMasterEventOperator) replacementMap.inverse()
            .get(current));
      }

      if (current instanceof UnionOperator) {
        Operator<?> parent = null;
        int count = 0;

        for (Operator<?> op: current.getParentOperators()) {
          if (seen.contains(op)) {
            ++count;
            parent = op;
          }
        }

        // we should have been able to reach the union from only one side.
        assert count <= 1;

        if (parent == null) {
          // root operator is union (can happen in reducers)
          replacementMap.put(current, current.getChildOperators().get(0));
        } else {
          parent.removeChildAndAdoptItsChildren(current);
          operators.remove(current);
        }
      }

      if (current instanceof FileSinkOperator
          || current instanceof ReduceSinkOperator) {
        current.setChildOperators(null);
      } else {
        operators.addAll(current.getChildOperators());
      }
    }
    LOG.debug("Setting dummy ops for work " + work.getName() + ": " + dummyOps);
    work.setDummyOps(dummyOps);
    work.replaceRoots(replacementMap);
  }
Temporarily truncates the operator tree to avoid full cloning of the tree.

It does the following simplifications:
<ul>
 <li> hides UNION operator parents which are unrelated to the current set of roots
 <li> hides childs from RS and FS operators
<ul>
ql/src/java/org/apache/hadoop/hive/ql/parse/GenTezUtils.java