package com.alibaba.druid.sql.dialect.sqlserver.parser;

import com.alibaba.druid.sql.ast.SQLDeclareItem;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLQueryExpr;
import com.alibaba.druid.sql.ast.statement.SQLBlockStatement;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLCommitStatement;
import com.alibaba.druid.sql.ast.statement.SQLConstraint;
import com.alibaba.druid.sql.ast.statement.SQLDeclareStatement;
import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprHint;
import com.alibaba.druid.sql.ast.statement.SQLIfStatement;
import com.alibaba.druid.sql.ast.statement.SQLInsertInto;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLScriptCommitStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLSetStatement;
import com.alibaba.druid.sql.ast.statement.SQLStartTransactionStatement;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerOutput;
import com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerTop;
import com.alibaba.druid.sql.dialect.sqlserver.ast.expr.SQLServerCurrentOfCursorExpr;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerDeleteStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerExecStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerInsertStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerMergeStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerRollbackStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerSetTransactionIsolationLevelStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerUpdateStatement;
import com.alibaba.druid.sql.dialect.sqlserver.ast.stmt.SQLServerWaitForStatement;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.Collection;
import java.util.List;

public class SQLServerStatementParser extends SQLStatementParser {
   public SQLServerStatementParser(String sql) {
      super((SQLExprParser)(new SQLServerExprParser(sql)));
   }

   public SQLServerStatementParser(String sql, SQLParserFeature... features) {
      super((SQLExprParser)(new SQLServerExprParser(sql, features)));
   }

   public SQLSelectParser createSQLSelectParser() {
      return new SQLServerSelectParser(this.exprParser, this.selectListCache);
   }

   public SQLServerStatementParser(Lexer lexer) {
      super((SQLExprParser)(new SQLServerExprParser(lexer)));
   }

   public boolean parseStatementListDialect(List<SQLStatement> statementList) {
      if (this.lexer.token() == Token.WITH) {
         SQLStatement stmt = this.parseSelect();
         statementList.add(stmt);
         return true;
      } else if (!this.lexer.identifierEquals(FnvHash.Constants.EXEC) && !this.lexer.identifierEquals(FnvHash.Constants.EXECUTE)) {
         if (this.lexer.token() == Token.DECLARE) {
            statementList.add(this.parseDeclare());
            return true;
         } else if (this.lexer.token() == Token.IF) {
            statementList.add(this.parseIf());
            return true;
         } else if (this.lexer.token() == Token.BEGIN) {
            statementList.add(this.parseBlock());
            return true;
         } else if (this.lexer.token() == Token.COMMIT) {
            statementList.add(this.parseCommit());
            return true;
         } else if (this.lexer.identifierEquals(FnvHash.Constants.WAITFOR)) {
            statementList.add(this.parseWaitFor());
            return true;
         } else if (this.lexer.identifierEquals(FnvHash.Constants.GO)) {
            this.lexer.nextToken();
            SQLStatement stmt = new SQLScriptCommitStatement();
            statementList.add(stmt);
            return true;
         } else {
            return false;
         }
      } else {
         this.lexer.nextToken();
         SQLServerExecStatement execStmt = new SQLServerExecStatement();
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            this.parseExecParameter(execStmt.getParameters(), execStmt);
            this.accept(Token.RPAREN);
         } else {
            SQLName sqlNameName = this.exprParser.name();
            if (this.lexer.token() == Token.EQ) {
               this.lexer.nextToken();
               execStmt.setReturnStatus(sqlNameName);
               execStmt.setModuleName(this.exprParser.name());
            } else {
               execStmt.setModuleName(sqlNameName);
            }

            if (this.lexer.token() != Token.SEMI && this.lexer.token() != Token.EOF) {
               this.parseExecParameter(execStmt.getParameters(), execStmt);
            }
         }

         statementList.add(execStmt);
         return true;
      }
   }

   public void parseExecParameter(Collection<SQLServerExecStatement.SQLServerParameter> exprCol, SQLObject parent) {
      if (this.lexer.token() != Token.RPAREN && this.lexer.token() != Token.RBRACKET) {
         if (this.lexer.token() != Token.EOF) {
            SQLServerExecStatement.SQLServerParameter param = new SQLServerExecStatement.SQLServerParameter();
            SQLExpr expr = this.exprParser.expr();
            expr.setParent(parent);
            param.setExpr(expr);
            if (this.lexer.token() == Token.OUT) {
               param.setType(true);
               this.accept(Token.OUT);
            }

            exprCol.add(param);

            for(; this.lexer.token() == Token.COMMA; exprCol.add(param)) {
               this.lexer.nextToken();
               param = new SQLServerExecStatement.SQLServerParameter();
               expr = this.exprParser.expr();
               expr.setParent(parent);
               param.setExpr(expr);
               if (this.lexer.token() == Token.OUT) {
                  param.setType(true);
                  this.accept(Token.OUT);
               }
            }

         }
      }
   }

   public SQLStatement parseDeclare() {
      this.accept(Token.DECLARE);
      SQLDeclareStatement declareStatement = new SQLDeclareStatement();

      while(true) {
         SQLDeclareItem item = new SQLDeclareItem();
         declareStatement.addItem(item);
         item.setName(this.exprParser.name());
         if (this.lexer.token() == Token.AS) {
            this.lexer.nextToken();
         }

         if (this.lexer.token() == Token.TABLE) {
            this.lexer.nextToken();
            item.setType(SQLDeclareItem.Type.TABLE);
            if (this.lexer.token() == Token.LPAREN) {
               this.lexer.nextToken();

               do {
                  if (this.lexer.token() != Token.IDENTIFIER && this.lexer.token() != Token.LITERAL_ALIAS) {
                     if (this.lexer.token() != Token.PRIMARY && this.lexer.token() != Token.UNIQUE && this.lexer.token() != Token.CHECK && this.lexer.token() != Token.CONSTRAINT) {
                        if (this.lexer.token() == Token.TABLESPACE) {
                           throw new ParserException("TODO " + this.lexer.info());
                        }

                        SQLColumnDefinition column = this.exprParser.parseColumn();
                        item.getTableElementList().add(column);
                     } else {
                        SQLConstraint constraint = this.exprParser.parseConstaint();
                        constraint.setParent(item);
                        item.getTableElementList().add((SQLTableElement)constraint);
                     }
                  } else {
                     SQLColumnDefinition column = this.exprParser.parseColumn();
                     item.getTableElementList().add(column);
                  }

                  if (this.lexer.token() != Token.COMMA) {
                     break;
                  }

                  this.lexer.nextToken();
               } while(this.lexer.token() != Token.RPAREN);

               this.accept(Token.RPAREN);
            }
            break;
         }

         if (this.lexer.token() == Token.CURSOR) {
            item.setType(SQLDeclareItem.Type.CURSOR);
            this.lexer.nextToken();
         } else {
            item.setType(SQLDeclareItem.Type.LOCAL);
            item.setDataType(this.exprParser.parseDataType());
            if (this.lexer.token() == Token.EQ) {
               this.lexer.nextToken();
               item.setValue(this.exprParser.expr());
            }
         }

         if (this.lexer.token() != Token.COMMA) {
            break;
         }

         this.lexer.nextToken();
      }

      return declareStatement;
   }

   public SQLStatement parseInsert() {
      SQLServerInsertStatement insertStatement = new SQLServerInsertStatement();
      if (this.lexer.token() == Token.INSERT) {
         this.accept(Token.INSERT);
      }

      this.parseInsert0(insertStatement);
      return insertStatement;
   }

   protected void parseInsert0(SQLInsertInto insert, boolean acceptSubQuery) {
      SQLServerInsertStatement insertStatement = (SQLServerInsertStatement)insert;
      SQLServerTop top = this.getExprParser().parseTop();
      if (top != null) {
         insertStatement.setTop(top);
      }

      if (this.lexer.token() == Token.INTO) {
         this.lexer.nextToken();
      }

      SQLName tableName = this.exprParser.name();
      insertStatement.setTableName(tableName);
      if (this.lexer.token() == Token.LITERAL_ALIAS) {
         insertStatement.setAlias(this.tableAlias());
      }

      this.parseWithHint(insertStatement.getTableSource());
      this.parseInsert0_hinits(insertStatement);
      if (this.lexer.token() == Token.IDENTIFIER && !this.lexer.stringVal().equalsIgnoreCase("OUTPUT")) {
         insertStatement.setAlias(this.lexer.stringVal());
         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         this.exprParser.exprList(insertStatement.getColumns(), insertStatement);
         this.accept(Token.RPAREN);
      }

      SQLServerOutput output = this.getExprParser().parserOutput();
      if (output != null) {
         insertStatement.setOutput(output);
      }

      if (this.lexer.token() == Token.VALUES) {
         this.lexer.nextToken();

         while(true) {
            this.accept(Token.LPAREN);
            SQLInsertStatement.ValuesClause values = new SQLInsertStatement.ValuesClause();
            this.exprParser.exprList(values.getValues(), values);
            insertStatement.addValueCause(values);
            this.accept(Token.RPAREN);
            if (!this.parseCompleteValues && insertStatement.getValuesList().size() >= this.parseValuesSize) {
               this.lexer.skipToEOF();
               break;
            }

            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         }
      } else if (!acceptSubQuery || this.lexer.token() != Token.SELECT && this.lexer.token() != Token.LPAREN) {
         if (this.lexer.token() == Token.DEFAULT) {
            this.lexer.nextToken();
            this.accept(Token.VALUES);
            insertStatement.setDefaultValues(true);
         }
      } else {
         SQLQueryExpr queryExpr = (SQLQueryExpr)this.exprParser.expr();
         insertStatement.setQuery(queryExpr.getSubQuery());
      }

   }

   private void parseWithHint(SQLTableSource tableSource) {
      if (this.lexer.token() == Token.WITH) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);

         while(true) {
            SQLExpr expr = this.exprParser.expr();
            SQLExprHint hint = new SQLExprHint(expr);
            hint.setParent(tableSource);
            tableSource.getHints().add(hint);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      }

   }

   protected SQLServerUpdateStatement createUpdateStatement() {
      return new SQLServerUpdateStatement();
   }

   public SQLUpdateStatement parseUpdateStatement() {
      SQLServerUpdateStatement udpateStatement = this.createUpdateStatement();
      this.accept(Token.UPDATE);
      SQLServerTop top = this.getExprParser().parseTop();
      if (top != null) {
         udpateStatement.setTop(top);
      }

      SQLTableSource tableSource = this.exprParser.createSelectParser().parseTableSource();
      udpateStatement.setTableSource(tableSource);
      this.parseUpdateSet(udpateStatement);
      SQLServerOutput output = this.getExprParser().parserOutput();
      if (output != null) {
         udpateStatement.setOutput(output);
      }

      if (this.lexer.token() == Token.FROM) {
         this.lexer.nextToken();
         SQLTableSource from = this.exprParser.createSelectParser().parseTableSource();
         udpateStatement.setFrom(from);
      }

      if (this.lexer.token() == Token.WHERE) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.CURRENT) {
            this.lexer.nextToken();
            this.accept(Token.OF);
            SQLServerCurrentOfCursorExpr cursorExpr = new SQLServerCurrentOfCursorExpr();
            if (this.lexer.token() == Token.GLOBAL) {
               cursorExpr.setGlobal(true);
               this.lexer.nextToken();
            }

            cursorExpr.setCursorName(this.exprParser.name());
            udpateStatement.setWhere(cursorExpr);
         } else {
            udpateStatement.setWhere(this.exprParser.expr());
         }
      }

      return udpateStatement;
   }

   public SQLServerExprParser getExprParser() {
      return (SQLServerExprParser)this.exprParser;
   }

   public SQLStatement parseSet() {
      this.accept(Token.SET);
      if (this.lexer.identifierEquals(FnvHash.Constants.TRANSACTION)) {
         this.lexer.nextToken();
         this.acceptIdentifier("ISOLATION");
         this.acceptIdentifier("LEVEL");
         SQLServerSetTransactionIsolationLevelStatement stmt = new SQLServerSetTransactionIsolationLevelStatement();
         if (this.lexer.identifierEquals("READ")) {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals("UNCOMMITTED")) {
               stmt.setLevel("READ UNCOMMITTED");
               this.lexer.nextToken();
            } else {
               if (!this.lexer.identifierEquals("COMMITTED")) {
                  throw new ParserException("UNKOWN TRANSACTION LEVEL : " + this.lexer.stringVal() + ", " + this.lexer.info());
               }

               stmt.setLevel("READ COMMITTED");
               this.lexer.nextToken();
            }
         } else if (this.lexer.identifierEquals("SERIALIZABLE")) {
            stmt.setLevel("SERIALIZABLE");
            this.lexer.nextToken();
         } else if (this.lexer.identifierEquals("SNAPSHOT")) {
            stmt.setLevel("SNAPSHOT");
            this.lexer.nextToken();
         } else {
            if (!this.lexer.identifierEquals("REPEATABLE")) {
               throw new ParserException("UNKOWN TRANSACTION LEVEL : " + this.lexer.stringVal() + ", " + this.lexer.info());
            }

            this.lexer.nextToken();
            if (!this.lexer.identifierEquals("READ")) {
               throw new ParserException("UNKOWN TRANSACTION LEVEL : " + this.lexer.stringVal() + ", " + this.lexer.info());
            }

            stmt.setLevel("REPEATABLE READ");
            this.lexer.nextToken();
         }

         return stmt;
      } else if (!this.lexer.identifierEquals(FnvHash.Constants.STATISTICS)) {
         if (this.lexer.identifierEquals(FnvHash.Constants.IDENTITY_INSERT)) {
            SQLSetStatement stmt = new SQLSetStatement();
            stmt.setOption(SQLSetStatement.Option.IDENTITY_INSERT);
            this.lexer.nextToken();
            SQLName table = this.exprParser.name();
            if (this.lexer.token() == Token.ON) {
               SQLExpr value = new SQLIdentifierExpr("ON");
               stmt.set(table, value);
               this.lexer.nextToken();
            } else if (this.lexer.identifierEquals(FnvHash.Constants.OFF)) {
               SQLExpr value = new SQLIdentifierExpr("OFF");
               stmt.set(table, value);
               this.lexer.nextToken();
            }

            return stmt;
         } else if (this.lexer.token() == Token.VARIANT) {
            SQLSetStatement stmt = new SQLSetStatement(this.getDbType());
            this.parseAssignItems(stmt.getItems(), stmt);
            return stmt;
         } else {
            SQLSetStatement stmt = new SQLSetStatement();
            SQLExpr target = this.exprParser.expr();
            if (this.lexer.token() == Token.ON) {
               stmt.set(target, new SQLIdentifierExpr("ON"));
               this.lexer.nextToken();
            } else if (this.lexer.identifierEquals("OFF")) {
               stmt.set(target, new SQLIdentifierExpr("OFF"));
               this.lexer.nextToken();
            } else {
               stmt.set(target, this.exprParser.expr());
            }

            return stmt;
         }
      } else {
         this.lexer.nextToken();
         SQLSetStatement stmt = new SQLSetStatement();
         if (this.lexer.identifierEquals("IO") || this.lexer.identifierEquals("XML") || this.lexer.identifierEquals("PROFILE") || this.lexer.identifierEquals("TIME")) {
            SQLExpr target = new SQLIdentifierExpr("STATISTICS " + this.lexer.stringVal().toUpperCase());
            this.lexer.nextToken();
            if (this.lexer.token() == Token.ON) {
               stmt.set(target, new SQLIdentifierExpr("ON"));
               this.lexer.nextToken();
            } else if (this.lexer.identifierEquals(FnvHash.Constants.OFF)) {
               stmt.set(target, new SQLIdentifierExpr("OFF"));
               this.lexer.nextToken();
            }
         }

         return stmt;
      }
   }

   public SQLIfStatement parseIf() {
      this.accept(Token.IF);
      SQLIfStatement stmt = new SQLIfStatement();
      stmt.setCondition(this.exprParser.expr());
      this.parseStatementList(stmt.getStatements(), 1, stmt);
      if (this.lexer.token() == Token.SEMI) {
         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.ELSE) {
         this.lexer.nextToken();
         SQLIfStatement.Else elseItem = new SQLIfStatement.Else();
         this.parseStatementList(elseItem.getStatements(), 1, elseItem);
         stmt.setElseItem(elseItem);
      }

      return stmt;
   }

   public SQLStatement parseBlock() {
      this.accept(Token.BEGIN);
      if (!this.lexer.identifierEquals("TRANSACTION") && !this.lexer.identifierEquals("TRAN")) {
         SQLBlockStatement block = new SQLBlockStatement();
         this.parseStatementList(block.getStatementList());
         this.accept(Token.END);
         return block;
      } else {
         this.lexer.nextToken();
         SQLStartTransactionStatement startTrans = new SQLStartTransactionStatement(this.dbType);
         if (this.lexer.token() == Token.IDENTIFIER) {
            SQLName name = this.exprParser.name();
            startTrans.setName(name);
         }

         return startTrans;
      }
   }

   public SQLStatement parseCommit() {
      this.acceptIdentifier("COMMIT");
      SQLCommitStatement stmt = new SQLCommitStatement();
      if (this.lexer.identifierEquals("WORK")) {
         this.lexer.nextToken();
         stmt.setWork(true);
      }

      if (this.lexer.identifierEquals("TRAN") || this.lexer.identifierEquals("TRANSACTION")) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.IDENTIFIER || this.lexer.token() == Token.VARIANT) {
            stmt.setTransactionName(this.exprParser.expr());
         }

         if (this.lexer.token() == Token.WITH) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            this.acceptIdentifier("DELAYED_DURABILITY");
            this.accept(Token.EQ);
            stmt.setDelayedDurability(this.exprParser.expr());
            this.accept(Token.RPAREN);
         }
      }

      return stmt;
   }

   public SQLServerRollbackStatement parseRollback() {
      this.acceptIdentifier("ROLLBACK");
      SQLServerRollbackStatement stmt = new SQLServerRollbackStatement();
      if (this.lexer.identifierEquals("WORK")) {
         this.lexer.nextToken();
         stmt.setWork(true);
      }

      if (this.lexer.identifierEquals("TRAN") || this.lexer.identifierEquals("TRANSACTION")) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.IDENTIFIER || this.lexer.token() == Token.VARIANT) {
            stmt.setName(this.exprParser.expr());
         }
      }

      return stmt;
   }

   public SQLServerWaitForStatement parseWaitFor() {
      this.acceptIdentifier("WAITFOR");
      SQLServerWaitForStatement stmt = new SQLServerWaitForStatement();
      if (this.lexer.identifierEquals("DELAY")) {
         this.lexer.nextToken();
         stmt.setDelay(this.exprParser.expr());
      }

      if (this.lexer.identifierEquals("TIME")) {
         this.lexer.nextToken();
         stmt.setTime(this.exprParser.expr());
      }

      if (this.lexer.token() == Token.COMMA) {
         this.lexer.nextToken();
         if (this.lexer.identifierEquals("TIMEOUT")) {
            this.lexer.nextToken();
            stmt.setTimeout(this.exprParser.expr());
         }
      }

      return stmt;
   }

   public SQLDeleteStatement parseDeleteStatement() {
      SQLServerDeleteStatement deleteStatement = new SQLServerDeleteStatement(this.getDbType());
      if (this.lexer.token() == Token.DELETE) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.TOP) {
            SQLServerTop top = this.getExprParser().parseTop();
            deleteStatement.setTop(top);
         }

         if (this.lexer.token() == Token.FROM) {
            this.lexer.nextToken();
         }

         if (this.lexer.token() == Token.COMMENT) {
            this.lexer.nextToken();
         }

         SQLName tableName = this.exprParser.name();
         deleteStatement.setTableName(tableName);
         this.parseWithHint(deleteStatement.getExprTableSource());
         SQLServerOutput output = this.getExprParser().parserOutput();
         if (output != null) {
            deleteStatement.setOutput(output);
         }

         if (this.lexer.token() == Token.FROM) {
            this.lexer.nextToken();
            SQLTableSource tableSource = this.createSQLSelectParser().parseTableSource();
            deleteStatement.setFrom(tableSource);
         }
      }

      if (this.lexer.token() == Token.WHERE) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.CURRENT) {
            this.lexer.nextToken();
            this.accept(Token.OF);
            SQLServerCurrentOfCursorExpr cursorExpr = new SQLServerCurrentOfCursorExpr();
            if (this.lexer.token() == Token.GLOBAL) {
               cursorExpr.setGlobal(true);
               this.lexer.nextToken();
            }

            cursorExpr.setCursorName(this.exprParser.name());
            deleteStatement.setWhere(cursorExpr);
         } else {
            SQLExpr where = this.exprParser.expr();
            deleteStatement.setWhere(where);
         }
      }

      return deleteStatement;
   }

   public SQLStatement parseWith() {
      SQLWithSubqueryClause with = this.parseWithQuery();
      if (this.lexer.token() == Token.INSERT) {
         SQLServerInsertStatement stmt = (SQLServerInsertStatement)this.parseInsert();
         stmt.setWith(with);
         return stmt;
      } else if (this.lexer.token() == Token.SELECT) {
         SQLSelectStatement stmt = (SQLSelectStatement)this.parseSelect();
         stmt.getSelect().setWithSubQuery(with);
         return stmt;
      } else if (this.lexer.token() == Token.DELETE) {
         SQLServerDeleteStatement stmt = (SQLServerDeleteStatement)this.parseDeleteStatement();
         stmt.setWith(with);
         return stmt;
      } else if (this.lexer.token() == Token.UPDATE) {
         SQLServerUpdateStatement stmt = (SQLServerUpdateStatement)this.parseUpdateStatement();
         stmt.setWith(with);
         return stmt;
      } else if (this.lexer.token() == Token.MERGE) {
         SQLServerMergeStatement stmt = (SQLServerMergeStatement)this.parseMerge();
         stmt.setWith(with);
         return stmt;
      } else {
         throw new ParserException("TODO. " + this.lexer.info());
      }
   }

   public SQLStatement parseMerge() {
      this.accept(Token.MERGE);
      SQLServerMergeStatement stmt = new SQLServerMergeStatement();
      stmt.setDbType(this.dbType);
      this.parseHints(stmt.getHints());
      if (this.lexer.token() == Token.TOP) {
         SQLServerTop top = this.getExprParser().parseTop();
         stmt.setTop(top);
      }

      if (this.lexer.token() == Token.INTO) {
         this.accept(Token.INTO);
      }

      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         SQLSelect select = this.createSQLSelectParser().select();
         SQLSubqueryTableSource tableSource = new SQLSubqueryTableSource(select);
         stmt.setInto(tableSource);
         this.accept(Token.RPAREN);
      } else {
         stmt.setInto(this.exprParser.name());
      }

      this.parseWithHint(stmt.getInto());
      stmt.getInto().setAlias(this.tableAlias());
      this.accept(Token.USING);
      SQLTableSource using = this.createSQLSelectParser().parseTableSource();
      stmt.setUsing(using);
      this.accept(Token.ON);
      stmt.setOn(this.exprParser.expr());

      while(this.lexer.token() == Token.WHEN) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.MATCHED) {
            this.lexer.nextToken();
            this.parseMergeMatchedClause(stmt, false);
         } else if (this.lexer.token() == Token.NOT) {
            this.lexer.nextToken();
            boolean isByTarget = false;
            this.accept(Token.MATCHED);
            if (this.lexer.token() == Token.BY) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals("TARGET")) {
                  this.acceptIdentifier("TARGET");
                  isByTarget = true;
                  this.parseMergeNotMatched(stmt, isByTarget);
               } else {
                  this.acceptIdentifier("SOURCE");
                  this.parseMergeMatchedClause(stmt, true);
               }
            } else {
               this.parseMergeNotMatched(stmt, isByTarget);
            }
         }
      }

      SQLServerOutput output = this.getExprParser().parserOutput();
      if (output != null) {
         stmt.setOutput(output);
      }

      return stmt;
   }

   private void parseMergeMatchedClause(SQLServerMergeStatement stmt, boolean isBySource) {
      SQLExpr where = null;
      if (this.lexer.token() == Token.AND) {
         this.lexer.nextToken();
         where = this.exprParser.expr();
      }

      this.accept(Token.THEN);
      if (this.lexer.token() == Token.UPDATE) {
         this.parseMergeUpdateCause(stmt, where, isBySource);
      } else {
         this.accept(Token.DELETE);
         SQLServerMergeStatement.MergeDeleteClause deleteClause = new SQLServerMergeStatement.MergeDeleteClause();
         deleteClause.setWhere(where);
         deleteClause.setBySourse(isBySource);
         stmt.getClauses().add(deleteClause);
      }

   }

   private void parseMergeNotMatched(SQLServerMergeStatement stmt, boolean isByTarget) {
      SQLServerMergeStatement.SQLServerMergeInsertClause insertClause = new SQLServerMergeStatement.SQLServerMergeInsertClause();
      insertClause.setByTarget(isByTarget);
      if (this.lexer.token() == Token.AND) {
         this.lexer.nextToken();
         insertClause.setWhere(this.exprParser.expr());
      }

      this.accept(Token.THEN);
      this.accept(Token.INSERT);
      if (this.lexer.token() == Token.LPAREN) {
         this.accept(Token.LPAREN);
         this.exprParser.exprList(insertClause.getColumns(), insertClause);
         this.accept(Token.RPAREN);
      }

      if (this.lexer.token() == Token.DEFAULT) {
         this.accept(Token.DEFAULT);
         this.accept(Token.VALUES);
         insertClause.setDefaultValues(true);
      } else {
         this.accept(Token.VALUES);
         this.accept(Token.LPAREN);
         this.exprParser.exprList(insertClause.getValues(), insertClause);
         this.accept(Token.RPAREN);
      }

      stmt.getClauses().add(insertClause);
   }

   private void parseMergeUpdateCause(SQLServerMergeStatement stmt, SQLExpr where, boolean isBySource) {
      this.accept(Token.UPDATE);
      SQLServerMergeStatement.SQLServerMergeUpdateClause updateClause = new SQLServerMergeStatement.SQLServerMergeUpdateClause();
      updateClause.setWhere(where);
      updateClause.setBySourse(isBySource);
      this.accept(Token.SET);

      while(true) {
         SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
         updateClause.addItem(item);
         item.setParent(updateClause);
         if (this.lexer.token() != Token.COMMA) {
            stmt.getClauses().add(updateClause);
            return;
         }

         this.lexer.nextToken();
      }
   }
}
