@Override
  public void analyze(ASTNode tree, Table targetTable, ASTNode targetNameNode) throws SemanticException {
    quotedIdentifierHelper = new IdentifierQuoter(ctx.getTokenRewriteStream());

    /*
     * See org.apache.hadoop.hive.ql.parse.TestMergeStatement for some examples of the merge AST
      For example, given:
      MERGE INTO acidTbl USING nonAcidPart2 source ON acidTbl.a = source.a2
      WHEN MATCHED THEN UPDATE SET b = source.b2
      WHEN NOT MATCHED THEN INSERT VALUES (source.a2, source.b2)

      We get AST like this:
      "(tok_merge " +
        "(tok_tabname acidtbl) (tok_tabref (tok_tabname nonacidpart2) source) " +
        "(= (. (tok_table_or_col acidtbl) a) (. (tok_table_or_col source) a2)) " +
        "(tok_matched " +
        "(tok_update " +
        "(tok_set_columns_clause (= (tok_table_or_col b) (. (tok_table_or_col source) b2))))) " +
        "(tok_not_matched " +
        "tok_insert " +
        "(tok_value_row (. (tok_table_or_col source) a2) (. (tok_table_or_col source) b2))))");

        And need to produce a multi-insert like this to execute:
        FROM acidTbl RIGHT OUTER JOIN nonAcidPart2 ON acidTbl.a = source.a2
        INSERT INTO TABLE acidTbl SELECT nonAcidPart2.a2, nonAcidPart2.b2 WHERE acidTbl.a IS null
        INSERT INTO TABLE acidTbl SELECT target.ROW__ID, nonAcidPart2.a2, nonAcidPart2.b2
        WHERE nonAcidPart2.a2=acidTbl.a SORT BY acidTbl.ROW__ID
    */
    /*todo: we need some sort of validation phase over original AST to make things user friendly; for example, if
     original command refers to a column that doesn't exist, this will be caught when processing the rewritten query but
     the errors will point at locations that the user can't map to anything
     - VALUES clause must have the same number of values as target table (including partition cols).  Part cols go last
     in Select clause of Insert as Select
     todo: do we care to preserve comments in original SQL?
     todo: check if identifiers are properly escaped/quoted in the generated SQL - it's currently inconsistent
      Look at UnparseTranslator.addIdentifierTranslation() - it does unescape + unparse...
     todo: consider "WHEN NOT MATCHED BY SOURCE THEN UPDATE SET TargetTable.Col1 = SourceTable.Col1 "; what happens when
     source is empty?  This should be a runtime error - maybe not the outer side of ROJ is empty => the join produces 0
     rows. If supporting WHEN NOT MATCHED BY SOURCE, then this should be a runtime error
    */
    if (tree.getToken().getType() != HiveParser.TOK_MERGE) {
      throw new RuntimeException("Asked to parse token " + tree.getName() + " in " +
              "MergeSemanticAnalyzer");
    }

    ASTNode source = (ASTNode)tree.getChild(1);
    String targetAlias = getSimpleTableName(targetNameNode);
    String sourceName = getSimpleTableName(source);
    ASTNode onClause = (ASTNode) tree.getChild(2);
    String onClauseAsText = getMatchedText(onClause);
    
    MergeStatement.MergeStatementBuilder mergeStatementBuilder = MergeStatement
        .withTarget(targetTable, getFullTableNameForSQL(targetNameNode), targetAlias)
        .sourceName(sourceName)
        .sourceAlias(getSourceAlias(source, sourceName))
        .onClauseAsText(onClauseAsText);

    int whenClauseBegins = 3;
    boolean hasHint = false;
    // query hint
    ASTNode qHint = (ASTNode) tree.getChild(3);
    if (qHint.getType() == HiveParser.QUERY_HINT) {
      hasHint = true;
      whenClauseBegins++;
    }
    List<ASTNode> whenClauses = findWhenClauses(tree, whenClauseBegins);

    // Add the hint if any
    if (hasHint) {
      mergeStatementBuilder.hintStr(String.format(" /*+ %s */ ", qHint.getText()));
    }

    /*
     * We allow at most 2 WHEN MATCHED clause, in which case 1 must be Update the other Delete
     * If we have both update and delete, the 1st one (in SQL code) must have "AND <extra predicate>"
     * so that the 2nd can ensure not to process the same rows.
     * Update and Delete may be in any order.  (Insert is always last)
     */
    String extraPredicate = null;
    int numInsertClauses = 0;
    numWhenMatchedUpdateClauses = 0;
    numWhenMatchedDeleteClauses = 0;
    for (ASTNode whenClause : whenClauses) {
      switch (getWhenClauseOperation(whenClause).getType()) {
      case HiveParser.TOK_INSERT:
        numInsertClauses++;

        OnClauseAnalyzer oca = new OnClauseAnalyzer(onClause, targetTable, targetAlias,
          conf, onClauseAsText);
        oca.analyze();
        
        mergeStatementBuilder.addWhenClause(
            handleInsert(whenClause, oca.getPredicate(), targetTable))
          .onClausePredicate(oca.getPredicate());
        break;
      case HiveParser.TOK_UPDATE:
        numWhenMatchedUpdateClauses++;
        MergeStatement.UpdateClause updateClause = handleUpdate(whenClause, targetTable, extraPredicate);
        mergeStatementBuilder.addWhenClause(updateClause);
        if (numWhenMatchedUpdateClauses + numWhenMatchedDeleteClauses == 1) {
          extraPredicate = updateClause.getExtraPredicate(); //i.e. it's the 1st WHEN MATCHED
        }
        break;
      case HiveParser.TOK_DELETE:
        numWhenMatchedDeleteClauses++;
        MergeStatement.DeleteClause deleteClause = handleDelete(whenClause, extraPredicate);
        mergeStatementBuilder.addWhenClause(deleteClause);
        if (numWhenMatchedUpdateClauses + numWhenMatchedDeleteClauses == 1) {
          extraPredicate = deleteClause.getExtraPredicate(); //i.e. it's the 1st WHEN MATCHED
        }
        break;
      default:
        throw new IllegalStateException("Unexpected WHEN clause type: " + whenClause.getType() +
            addParseInfo(whenClause));
      }
      if (numWhenMatchedDeleteClauses > 1) {
        throw new SemanticException(ErrorMsg.MERGE_TOO_MANY_DELETE, ctx.getCmd());
      }
      if (numWhenMatchedUpdateClauses > 1) {
        throw new SemanticException(ErrorMsg.MERGE_TOO_MANY_UPDATE, ctx.getCmd());
      }
      assert numInsertClauses < 2: "too many Insert clauses";
    }
    if (numWhenMatchedDeleteClauses + numWhenMatchedUpdateClauses == 2 && extraPredicate == null) {
      throw new SemanticException(ErrorMsg.MERGE_PREDIACTE_REQUIRED, ctx.getCmd());
    }

    String subQueryAlias = isAliased(targetNameNode) ? targetAlias : targetTable.getTTable().getTableName();

    rewriteAndAnalyze(mergeStatementBuilder.build(), subQueryAlias);

    updateOutputs(targetTable);
  }
Here we take a Merge statement AST and generate a semantically equivalent multi-insert
statement to execute.  Each Insert leg represents a single WHEN clause.  As much as possible,
the new SQL statement is made to look like the input SQL statement so that it's easier to map
Query Compiler errors from generated SQL to original one this way.
The generated SQL is a complete representation of the original input for the same reason.
In many places SemanticAnalyzer throws exceptions that contain (line, position) coordinates.
If generated SQL doesn't have everything and is patched up later, these coordinates point to
the wrong place.

@throws SemanticException
ql/src/java/org/apache/hadoop/hive/ql/parse/MergeSemanticAnalyzer.java