package com.chenyang.druid.sql;

import com.chenyang.druid.DbType;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLLimit;
import com.chenyang.druid.sql.ast.SQLName;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.SQLReplaceable;
import com.chenyang.druid.sql.ast.SQLStatement;
import com.chenyang.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.chenyang.druid.sql.ast.expr.SQLBinaryOperator;
import com.chenyang.druid.sql.ast.expr.SQLInListExpr;
import com.chenyang.druid.sql.ast.expr.SQLLiteralExpr;
import com.chenyang.druid.sql.ast.expr.SQLTimestampExpr;
import com.chenyang.druid.sql.ast.expr.SQLUnaryExpr;
import com.chenyang.druid.sql.ast.expr.SQLUnaryOperator;
import com.chenyang.druid.sql.ast.expr.SQLVariantRefExpr;
import com.chenyang.druid.sql.ast.statement.SQLCreateTableStatement;
import com.chenyang.druid.sql.ast.statement.SQLDeleteStatement;
import com.chenyang.druid.sql.ast.statement.SQLDumpStatement;
import com.chenyang.druid.sql.ast.statement.SQLInsertStatement;
import com.chenyang.druid.sql.ast.statement.SQLJoinTableSource;
import com.chenyang.druid.sql.ast.statement.SQLSelect;
import com.chenyang.druid.sql.ast.statement.SQLSelectItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectQuery;
import com.chenyang.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.chenyang.druid.sql.ast.statement.SQLSelectStatement;
import com.chenyang.druid.sql.ast.statement.SQLSetStatement;
import com.chenyang.druid.sql.ast.statement.SQLTableSource;
import com.chenyang.druid.sql.ast.statement.SQLUnionQuery;
import com.chenyang.druid.sql.ast.statement.SQLUpdateSetItem;
import com.chenyang.druid.sql.ast.statement.SQLUpdateStatement;
import com.chenyang.druid.sql.dialect.db2.visitor.DB2OutputVisitor;
import com.chenyang.druid.sql.dialect.db2.visitor.DB2SchemaStatVisitor;
import com.chenyang.druid.sql.dialect.dm.visitor.DmOutputVisitor;
import com.chenyang.druid.sql.dialect.gauss.visitor.GaussOutputVisitor;
import com.chenyang.druid.sql.dialect.gauss.visitor.GaussSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.greenplum.visitor.GPOutputVisitor;
import com.chenyang.druid.sql.dialect.greenplum.visitor.GPSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.hive.visitor.HiveOutputVisitor;
import com.chenyang.druid.sql.dialect.hive.visitor.HiveSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.kingbase.visitor.KingbaseOutputVisitor;
import com.chenyang.druid.sql.dialect.kingbase.visitor.KingbaseSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.mysql.ast.MySqlObject;
import com.chenyang.druid.sql.dialect.mysql.ast.clause.MySqlSelectIntoStatement;
import com.chenyang.druid.sql.dialect.mysql.ast.statement.MySqlInsertStatement;
import com.chenyang.druid.sql.dialect.mysql.visitor.MySqlOutputVisitor;
import com.chenyang.druid.sql.dialect.mysql.visitor.MySqlSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.oracle.visitor.OracleOutputVisitor;
import com.chenyang.druid.sql.dialect.oracle.visitor.OracleSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.oracle.visitor.OracleToMySqlOutputVisitor;
import com.chenyang.druid.sql.dialect.postgresql.visitor.PGOutputVisitor;
import com.chenyang.druid.sql.dialect.postgresql.visitor.PGSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.spark.visitor.SparkOutputVisitor;
import com.chenyang.druid.sql.dialect.spark.visitor.SparkSchemaStatVisitor;
import com.chenyang.druid.sql.dialect.sqlserver.visitor.SQLServerOutputVisitor;
import com.chenyang.druid.sql.dialect.sqlserver.visitor.SQLServerSchemaStatVisitor;
import com.chenyang.druid.sql.parser.Lexer;
import com.chenyang.druid.sql.parser.ParserException;
import com.chenyang.druid.sql.parser.SQLExprParser;
import com.chenyang.druid.sql.parser.SQLParserFeature;
import com.chenyang.druid.sql.parser.SQLParserUtils;
import com.chenyang.druid.sql.parser.SQLStatementParser;
import com.chenyang.druid.sql.parser.Token;
import com.chenyang.druid.sql.repository.SchemaRepository;
import com.chenyang.druid.sql.visitor.SQLASTOutputVisitor;
import com.chenyang.druid.sql.visitor.SQLASTVisitor;
import com.chenyang.druid.sql.visitor.SQLASTVisitorAdapter;
import com.chenyang.druid.sql.visitor.SchemaStatVisitor;
import com.chenyang.druid.sql.visitor.VisitorFeature;
import com.chenyang.druid.support.logging.Log;
import com.chenyang.druid.support.logging.LogFactory;
import com.chenyang.druid.util.FnvHash;
import com.chenyang.druid.util.MySqlUtils;
import com.chenyang.druid.util.OracleUtils;
import com.chenyang.druid.util.PGUtils;
import com.chenyang.druid.util.StringUtils;
import com.chenyang.druid.util.Utils;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

public class SQLUtils {
   public static final Charset UTF8 = Charset.forName("UTF-8");
   private static final SQLParserFeature[] FORMAT_DEFAULT_FEATURES;
   public static FormatOption DEFAULT_FORMAT_OPTION;
   public static FormatOption DEFAULT_LCASE_FORMAT_OPTION;
   private static final Log LOG;

   public static String toSQLString(SQLObject sqlObject, String dbType) {
      return toSQLString(sqlObject, DbType.valueOf(dbType));
   }

   public static String toSQLString(SQLObject sqlObject, DbType dbType) {
      return toSQLString(sqlObject, dbType, (FormatOption)null, (VisitorFeature[])null);
   }

   public static String toSQLString(SQLObject sqlObject, DbType dbType, FormatOption option) {
      return toSQLString(sqlObject, dbType, option, (VisitorFeature[])null);
   }

   public static String toSQLString(SQLObject sqlObject, DbType dbType, FormatOption option, VisitorFeature... features) {
      StringBuilder out = new StringBuilder();
      SQLASTOutputVisitor visitor = createOutputVisitor(out, dbType);
      if (option == null) {
         option = DEFAULT_FORMAT_OPTION;
      }

      visitor.setUppCase(option.isUppCase());
      visitor.setPrettyFormat(option.isPrettyFormat());
      visitor.setParameterized(option.isParameterized());
      int featuresValue = option.features;
      if (features != null) {
         for(VisitorFeature feature : features) {
            visitor.config(feature, true);
            featuresValue |= feature.mask;
         }
      }

      visitor.setFeatures(featuresValue);
      sqlObject.accept(visitor);
      String sql = out.toString();
      return sql;
   }

   public static DbType getDbTypteByParent(SQLObject obj) {
      if (obj == null) {
         return null;
      } else if (obj instanceof SQLStatement) {
         SQLStatement stmt = (SQLStatement)obj;
         return stmt.getDbType();
      } else {
         return getDbTypteByParent(obj.getParent());
      }
   }

   public static String toSQLString(SQLObject obj) {
      if (obj instanceof SQLStatement) {
         SQLStatement stmt = (SQLStatement)obj;
         return toSQLString(stmt, (DbType)stmt.getDbType());
      } else if (obj instanceof MySqlObject) {
         return toMySqlString(obj);
      } else if (getDbTypteByParent(obj) == DbType.kingbase) {
         return toKingbaseString(obj);
      } else {
         DbType dbType = getDbTypteByParent(obj);
         StringBuilder out = new StringBuilder();
         SQLASTVisitor visitor = null;
         SQLASTOutputVisitor var6;
         if (dbType != null) {
            var6 = createOutputVisitor(out, dbType);
         } else {
            var6 = new SQLASTOutputVisitor(out);
         }

         obj.accept(var6);
         String sql = out.toString();
         return sql;
      }
   }

   public static String toOdpsString(SQLObject sqlObject) {
      return toOdpsString(sqlObject, (FormatOption)null);
   }

   public static String toHiveString(SQLObject sqlObject) {
      return toSQLString(sqlObject, DbType.odps);
   }

   public static String toOdpsString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.odps, option);
   }

   public static String toAntsparkString(SQLObject sqlObject) {
      return toAntsparkString(sqlObject, (FormatOption)null);
   }

   public static String toAntsparkString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.antspark, option);
   }

   public static String toSparkString(SQLObject sqlObject) {
      return toSparkString(sqlObject, (FormatOption)null);
   }

   public static String toSparkString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.antspark, option);
   }

   public static String toMySqlString(SQLObject sqlObject) {
      return toMySqlString(sqlObject, (FormatOption)null);
   }

   public static String toMySqlStringIfNotNull(SQLObject sqlObject, String defaultStr) {
      return sqlObject == null ? defaultStr : toMySqlString(sqlObject, (FormatOption)null);
   }

   public static String toMySqlString(SQLObject sqlObject, VisitorFeature... features) {
      return toMySqlString(sqlObject, new FormatOption(features));
   }

   public static String toNormalizeMysqlString(SQLObject sqlObject) {
      return sqlObject != null ? normalize(toSQLString(sqlObject, DbType.mysql)) : null;
   }

   public static String toMySqlString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.mysql, option);
   }

   public static SQLExpr toMySqlExpr(String sql) {
      return toSQLExpr(sql, DbType.mysql);
   }

   public static String formatMySql(String sql) {
      return format(sql, DbType.mysql);
   }

   public static String formatMySql(String sql, FormatOption option) {
      return format(sql, DbType.mysql, option);
   }

   public static String formatOracle(String sql) {
      return format(sql, DbType.oracle);
   }

   public static String formatOracle(String sql, FormatOption option) {
      return format(sql, DbType.oracle, option);
   }

   public static String formatOdps(String sql) {
      return format(sql, DbType.odps);
   }

   public static String formatPresto(String sql) {
      return formatPresto(sql, (FormatOption)null);
   }

   public static String formatPresto(String sql, FormatOption option) {
      SQLParserFeature[] features = new SQLParserFeature[]{SQLParserFeature.KeepComments, SQLParserFeature.EnableSQLBinaryOpExprGroup, SQLParserFeature.KeepNameQuotes};
      return format(sql, DbType.mysql, (List)null, option, features);
   }

   public static String formatHive(String sql) {
      return format(sql, DbType.hive);
   }

   public static String formatOdps(String sql, FormatOption option) {
      return format(sql, DbType.odps, option);
   }

   public static String formatHive(String sql, FormatOption option) {
      return format(sql, DbType.hive, option);
   }

   public static String formatSQLServer(String sql) {
      return format(sql, DbType.sqlserver);
   }

   public static String toDmString(SQLObject sqlObject) {
      return toDmString(sqlObject, (FormatOption)null);
   }

   public static String toDmString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.dm, option);
   }

   public static String toOracleString(SQLObject sqlObject) {
      return toOracleString(sqlObject, (FormatOption)null);
   }

   public static String toOracleString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.oracle, option);
   }

   public static String toKingbaseString(SQLObject sqlObject) {
      return toKingbaseString(sqlObject, (FormatOption)null);
   }

   public static String toKingbaseString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.kingbase, option);
   }

   public static String toPGString(SQLObject sqlObject) {
      return toPGString(sqlObject, (FormatOption)null);
   }

   public static String toPGString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.postgresql, option);
   }

   public static String toDB2String(SQLObject sqlObject) {
      return toDB2String(sqlObject, (FormatOption)null);
   }

   public static String toDB2String(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.db2, option);
   }

   public static String toSQLServerString(SQLObject sqlObject) {
      return toSQLServerString(sqlObject, (FormatOption)null);
   }

   public static String toSQLServerString(SQLObject sqlObject, FormatOption option) {
      return toSQLString(sqlObject, DbType.sqlserver, option);
   }

   public static String formatPGSql(String sql, FormatOption option) {
      return format(sql, DbType.postgresql, option);
   }

   public static SQLExpr toSQLExpr(String sql, DbType dbType) {
      SQLExprParser parser = SQLParserUtils.createExprParser(sql, dbType);
      SQLExpr expr = parser.expr();
      if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("illegal sql expr : " + sql + ", " + parser.getLexer().info());
      } else {
         return expr;
      }
   }

   public static SQLSelectOrderByItem toOrderByItem(String sql, DbType dbType) {
      SQLExprParser parser = SQLParserUtils.createExprParser(sql, dbType);
      SQLSelectOrderByItem orderByItem = parser.parseSelectOrderByItem();
      if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("illegal sql expr : " + sql + ", " + parser.getLexer().info());
      } else {
         return orderByItem;
      }
   }

   public static SQLUpdateSetItem toUpdateSetItem(String sql, DbType dbType) {
      SQLExprParser parser = SQLParserUtils.createExprParser(sql, dbType);
      SQLUpdateSetItem updateSetItem = parser.parseUpdateSetItem();
      if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("illegal sql expr : " + sql + ", " + parser.getLexer().info());
      } else {
         return updateSetItem;
      }
   }

   public static SQLSelectItem toSelectItem(String sql, DbType dbType) {
      SQLExprParser parser = SQLParserUtils.createExprParser(sql, dbType);
      SQLSelectItem selectItem = parser.parseSelectItem();
      if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("illegal sql expr : " + sql + ", " + parser.getLexer().info());
      } else {
         return selectItem;
      }
   }

   public static List<SQLStatement> toStatementList(String sql, DbType dbType) {
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType);
      return parser.parseStatementList();
   }

   public static SQLExpr toSQLExpr(String sql) {
      return toSQLExpr(sql, (DbType)null);
   }

   public static String format(String sql, String dbType) {
      return format(sql, DbType.of(dbType));
   }

   public static String format(String sql, DbType dbType) {
      return format(sql, dbType, (List)null, (FormatOption)null);
   }

   public static String format(String sql, DbType dbType, FormatOption option) {
      return format(sql, dbType, (List)null, option);
   }

   public static String format(String sql, DbType dbType, List<Object> parameters) {
      return format(sql, dbType, parameters, (FormatOption)null);
   }

   public static String format(String sql, DbType dbType, List<Object> parameters, FormatOption option) {
      return format(sql, dbType, parameters, option, FORMAT_DEFAULT_FEATURES);
   }

   public static String format(String sql, DbType dbType, List<Object> parameters, FormatOption option, SQLParserFeature[] features) {
      try {
         SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
         List<SQLStatement> statementList = parser.parseStatementList();
         return toSQLString(statementList, dbType, parameters, option);
      } catch (ParserException ex) {
         LOG.warn("rowFormat error", ex);
         return sql;
      }
   }

   public static String toSQLString(List<SQLStatement> statementList, DbType dbType) {
      return toSQLString(statementList, dbType, (List)null);
   }

   public static String toSQLString(List<SQLStatement> statementList, DbType dbType, FormatOption option) {
      return toSQLString((List)statementList, dbType, (List)null, (FormatOption)option);
   }

   public static String toSQLString(List<SQLStatement> statementList, DbType dbType, List<Object> parameters) {
      return toSQLString(statementList, dbType, parameters, (FormatOption)null, (Map)null);
   }

   public static String toSQLString(List<SQLStatement> statementList, DbType dbType, List<Object> parameters, FormatOption option) {
      return toSQLString(statementList, dbType, parameters, option, (Map)null);
   }

   public static String toSQLString(List<SQLStatement> statementList, DbType dbType, List<Object> parameters, FormatOption option, Map<String, String> tableMapping) {
      StringBuilder out = new StringBuilder();
      SQLASTOutputVisitor visitor = createFormatOutputVisitor(out, statementList, dbType);
      if (parameters != null) {
         visitor.setInputParameters(parameters);
      }

      if (option == null) {
         option = DEFAULT_FORMAT_OPTION;
      }

      visitor.setFeatures(option.features);
      if (tableMapping != null) {
         visitor.setTableMapping(tableMapping);
      }

      boolean printStmtSeperator;
      if (DbType.sqlserver == dbType) {
         printStmtSeperator = false;
      } else {
         printStmtSeperator = DbType.oracle != dbType;
      }

      int i = 0;

      for(int size = statementList.size(); i < size; ++i) {
         SQLStatement stmt = (SQLStatement)statementList.get(i);
         if (i > 0) {
            SQLStatement preStmt = (SQLStatement)statementList.get(i - 1);
            if (printStmtSeperator && !preStmt.isAfterSemi()) {
               visitor.print(";");
            }

            List<String> comments = preStmt.getAfterCommentsDirect();
            if (comments != null) {
               for(int j = 0; j < comments.size(); ++j) {
                  String comment = (String)comments.get(j);
                  if (j != 0) {
                     visitor.println();
                  }

                  visitor.printComment(comment);
               }
            }

            if (printStmtSeperator) {
               visitor.println();
            }

            if (!(stmt instanceof SQLSetStatement)) {
               visitor.println();
            }
         }

         stmt.accept(visitor);
         if (i == size - 1) {
            List<String> comments = stmt.getAfterCommentsDirect();
            if (comments != null) {
               for(int j = 0; j < comments.size(); ++j) {
                  String comment = (String)comments.get(j);
                  if (j != 0) {
                     visitor.println();
                  }

                  visitor.printComment(comment);
               }
            }
         }
      }

      return out.toString();
   }

   public static SQLASTOutputVisitor createOutputVisitor(Appendable out, DbType dbType) {
      return createFormatOutputVisitor(out, (List)null, dbType);
   }

   public static SQLASTOutputVisitor createFormatOutputVisitor(Appendable out, List<SQLStatement> statementList, DbType dbType) {
      if (dbType == null) {
         if (statementList != null && statementList.size() > 0) {
            dbType = ((SQLStatement)statementList.get(0)).getDbType();
         }

         if (dbType == null) {
            dbType = DbType.other;
         }
      }

      switch (dbType) {
         case oracle:
         case oceanbase_oracle:
            if (statementList != null && statementList.size() != 1) {
               return new OracleOutputVisitor(out, true);
            }

            return new OracleOutputVisitor(out, false);
         case mysql:
         case mariadb:
            return new MySqlOutputVisitor(out);
         case sqlserver:
         case jtds:
            return new SQLServerOutputVisitor(out);
         case greenplum:
            return new GPOutputVisitor(out);
         case gauss:
            return new GaussOutputVisitor(out);
         case dm:
            return new DmOutputVisitor(out);
         case db2:
            return new DB2OutputVisitor(out);
         case kingbase:
            return new KingbaseOutputVisitor(out);
         case postgresql:
            return new PGOutputVisitor(out);
         case hive:
            return new HiveOutputVisitor(out);
         case spark:
            return new SparkOutputVisitor(out);
         default:
            return new SQLASTOutputVisitor(out, dbType);
      }
   }

   /** @deprecated */
   @Deprecated
   public static SchemaStatVisitor createSchemaStatVisitor(List<SQLStatement> statementList, DbType dbType) {
      return createSchemaStatVisitor(dbType);
   }

   public static SchemaStatVisitor createSchemaStatVisitor(DbType dbType) {
      return createSchemaStatVisitor((SchemaRepository)null, dbType);
   }

   public static SchemaStatVisitor createSchemaStatVisitor(SchemaRepository repository) {
      return createSchemaStatVisitor(repository, repository.getDbType());
   }

   public static SchemaStatVisitor createSchemaStatVisitor(SchemaRepository repository, DbType dbType) {
      if (repository == null) {
         repository = new SchemaRepository(dbType);
      }

      if (dbType == null) {
         return new SchemaStatVisitor(repository);
      } else {
         switch (dbType) {
            case oracle:
               return new OracleSchemaStatVisitor(repository);
            case oceanbase_oracle:
            case dm:
            default:
               return new SchemaStatVisitor(repository);
            case mysql:
            case mariadb:
            case elastic_search:
               return new MySqlSchemaStatVisitor(repository);
            case sqlserver:
            case jtds:
               return new SQLServerSchemaStatVisitor(repository);
            case greenplum:
               return new GPSchemaStatVisitor(repository);
            case gauss:
               return new GaussSchemaStatVisitor(repository);
            case db2:
               return new DB2SchemaStatVisitor(repository);
            case kingbase:
               return new KingbaseSchemaStatVisitor(repository);
            case postgresql:
               return new PGSchemaStatVisitor(repository);
            case hive:
               return new HiveSchemaStatVisitor(repository);
            case spark:
               return new SparkSchemaStatVisitor(repository);
         }
      }
   }

   public static List<SQLStatement> parseStatements(String sql, String dbType, SQLParserFeature... features) {
      return parseStatements(sql, DbType.of(dbType), features);
   }

   public static List<SQLStatement> parseStatements(String sql, DbType dbType, SQLParserFeature... features) {
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
      List<SQLStatement> stmtList = new ArrayList();
      parser.parseStatementList(stmtList, -1, (SQLObject)null);
      if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("syntax error : " + sql);
      } else {
         return stmtList;
      }
   }

   public static List<SQLStatement> parseStatements(String sql, DbType dbType, boolean keepComments) {
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, keepComments);
      List<SQLStatement> stmtList = parser.parseStatementList();
      if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("syntax error. " + sql);
      } else {
         return stmtList;
      }
   }

    public static List<SQLStatement> parseStatements(String sql, String dbType) {
        return parseStatements(sql, dbType, new SQLParserFeature[0]);
    }


    public static List<SQLStatement> parseStatements(String sql, DbType dbType) {
        return parseStatements(sql, dbType, new SQLParserFeature[0]);
    }

   public static SQLStatement parseSingleStatement(String sql, DbType dbType, boolean keepComments) {
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, keepComments);
      List<SQLStatement> stmtList = parser.parseStatementList();
      if (stmtList.size() > 1) {
         throw new ParserException("multi-statement be found.");
      } else if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("syntax error. " + sql);
      } else {
         return (SQLStatement)stmtList.get(0);
      }
   }

   public static SQLStatement parseSingleStatement(String sql, String dbType, SQLParserFeature... features) {
      return parseSingleStatement(sql, DbType.of(dbType), features);
   }

   public static SQLStatement parseSingleStatement(String sql, DbType dbType, SQLParserFeature... features) {
      SQLStatementParser parser = SQLParserUtils.createSQLStatementParser(sql, dbType, features);
      List<SQLStatement> stmtList = parser.parseStatementList();
      if (stmtList.size() > 1) {
         throw new ParserException("multi-statement be found.");
      } else if (parser.getLexer().token() != Token.EOF) {
         throw new ParserException("syntax error. " + sql);
      } else {
         return (SQLStatement)stmtList.get(0);
      }
   }

   public static SQLStatement parseSingleMysqlStatement(String sql) {
      return parseSingleStatement(sql, DbType.mysql, false);
   }

   public static String buildToDate(String columnName, String tableAlias, String pattern, DbType dbType) {
      StringBuilder sql = new StringBuilder();
      if (StringUtils.isEmpty(columnName)) {
         return "";
      } else {
         if (dbType == null) {
            dbType = DbType.mysql;
         }

         String formatMethod = "";
         if (DbType.mysql == dbType) {
            formatMethod = "STR_TO_DATE";
            if (StringUtils.isEmpty(pattern)) {
               pattern = "%Y-%m-%d %H:%i:%s";
            }
         } else {
            if (DbType.oracle != dbType) {
               return "";
            }

            formatMethod = "TO_DATE";
            if (StringUtils.isEmpty(pattern)) {
               pattern = "yyyy-mm-dd hh24:mi:ss";
            }
         }

         sql.append(formatMethod).append("(");
         if (!StringUtils.isEmpty(tableAlias)) {
            sql.append(tableAlias).append(".");
         }

         sql.append(columnName).append(",");
         sql.append("'");
         sql.append(pattern);
         sql.append("')");
         return sql.toString();
      }
   }

   public static List<SQLExpr> split(SQLBinaryOpExpr x) {
      return SQLBinaryOpExpr.split(x);
   }

   public static String translateOracleToMySql(String sql) {
      List<SQLStatement> stmtList = toStatementList(sql, DbType.oracle);
      StringBuilder out = new StringBuilder();
      OracleToMySqlOutputVisitor visitor = new OracleToMySqlOutputVisitor(out, false);

      for(int i = 0; i < stmtList.size(); ++i) {
         ((SQLStatement)stmtList.get(i)).accept(visitor);
      }

      String mysqlSql = out.toString();
      return mysqlSql;
   }

   public static String addCondition(String sql, String condition, DbType dbType) {
      String result = addCondition(sql, condition, SQLBinaryOperator.BooleanAnd, false, dbType);
      return result;
   }

   public static String addCondition(String sql, String condition, SQLBinaryOperator op, boolean left, DbType dbType) {
      if (sql == null) {
         throw new IllegalArgumentException("sql is null");
      } else if (condition == null) {
         return sql;
      } else {
         if (op == null) {
            op = SQLBinaryOperator.BooleanAnd;
         }

         if (op != SQLBinaryOperator.BooleanAnd && op != SQLBinaryOperator.BooleanOr) {
            throw new IllegalArgumentException("add condition not support : " + op);
         } else {
            List<SQLStatement> stmtList = parseStatements(sql, dbType);
            if (stmtList.size() == 0) {
               throw new IllegalArgumentException("not support empty-statement :" + sql);
            } else if (stmtList.size() > 1) {
               throw new IllegalArgumentException("not support multi-statement :" + sql);
            } else {
               SQLStatement stmt = (SQLStatement)stmtList.get(0);
               SQLExpr conditionExpr = toSQLExpr(condition, dbType);
               addCondition(stmt, op, conditionExpr, left);
               return toSQLString(stmt, (DbType)dbType);
            }
         }
      }
   }

   public static void addCondition(SQLStatement stmt, SQLBinaryOperator op, SQLExpr condition, boolean left) {
      if (stmt instanceof SQLSelectStatement) {
         SQLSelectQuery query = ((SQLSelectStatement)stmt).getSelect().getQuery();
         if (query instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)query;
            SQLExpr newCondition = buildCondition(op, condition, left, queryBlock.getWhere());
            queryBlock.setWhere(newCondition);
         } else {
            throw new IllegalArgumentException("add condition not support " + stmt.getClass().getName());
         }
      } else if (stmt instanceof SQLDeleteStatement) {
         SQLDeleteStatement delete = (SQLDeleteStatement)stmt;
         SQLExpr newCondition = buildCondition(op, condition, left, delete.getWhere());
         delete.setWhere(newCondition);
      } else if (stmt instanceof SQLUpdateStatement) {
         SQLUpdateStatement update = (SQLUpdateStatement)stmt;
         SQLExpr newCondition = buildCondition(op, condition, left, update.getWhere());
         update.setWhere(newCondition);
      } else {
         throw new IllegalArgumentException("add condition not support " + stmt.getClass().getName());
      }
   }

   public static SQLExpr buildCondition(SQLBinaryOperator op, SQLExpr condition, boolean left, SQLExpr where) {
      if (where == null) {
         return condition;
      } else {
         SQLBinaryOpExpr newCondition;
         if (left) {
            newCondition = new SQLBinaryOpExpr(condition, op, where);
         } else {
            newCondition = new SQLBinaryOpExpr(where, op, condition);
         }

         return newCondition;
      }
   }

   public static String addSelectItem(String selectSql, String expr, String alias, DbType dbType) {
      return addSelectItem(selectSql, expr, alias, false, dbType);
   }

   public static String addSelectItem(String selectSql, String expr, String alias, boolean first, DbType dbType) {
      List<SQLStatement> stmtList = parseStatements(selectSql, dbType);
      if (stmtList.size() == 0) {
         throw new IllegalArgumentException("not support empty-statement :" + selectSql);
      } else if (stmtList.size() > 1) {
         throw new IllegalArgumentException("not support multi-statement :" + selectSql);
      } else {
         SQLStatement stmt = (SQLStatement)stmtList.get(0);
         SQLExpr columnExpr = toSQLExpr(expr, dbType);
         addSelectItem(stmt, columnExpr, alias, first);
         return toSQLString(stmt, (DbType)dbType);
      }
   }

   public static void addSelectItem(SQLStatement stmt, SQLExpr expr, String alias, boolean first) {
      if (expr != null) {
         if (stmt instanceof SQLSelectStatement) {
            SQLSelectQuery query = ((SQLSelectStatement)stmt).getSelect().getQuery();
            if (query instanceof SQLSelectQueryBlock) {
               SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)query;
               addSelectItem(queryBlock, expr, alias, first);
            } else {
               throw new IllegalArgumentException("add condition not support " + stmt.getClass().getName());
            }
         } else {
            throw new IllegalArgumentException("add selectItem not support " + stmt.getClass().getName());
         }
      }
   }

   public static void addSelectItem(SQLSelectQueryBlock queryBlock, SQLExpr expr, String alias, boolean first) {
      SQLSelectItem selectItem = new SQLSelectItem(expr, alias);
      queryBlock.getSelectList().add(selectItem);
      selectItem.setParent(selectItem);
   }

   public static String refactor(String sql, DbType dbType, Map<String, String> tableMapping) {
      List<SQLStatement> stmtList = parseStatements(sql, dbType);
      return toSQLString(stmtList, dbType, (List)null, (FormatOption)null, tableMapping);
   }

   public static long hash(String sql, DbType dbType) {
      Lexer lexer = SQLParserUtils.createLexer(sql, dbType);
      StringBuilder buf = new StringBuilder(sql.length());

      while(true) {
         lexer.nextToken();
         Token token = lexer.token();
         if (token == Token.EOF) {
            return (long)buf.hashCode();
         }

         if (token == Token.ERROR) {
            return Utils.fnv_64(sql);
         }

         if (buf.length() != 0) {
         }
      }
   }

   public static SQLExpr not(SQLExpr expr) {
      if (expr instanceof SQLBinaryOpExpr) {
         SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr)expr;
         SQLBinaryOperator op = binaryOpExpr.getOperator();
         SQLBinaryOperator notOp = null;
         switch (op) {
            case Equality:
               notOp = SQLBinaryOperator.LessThanOrGreater;
               break;
            case LessThanOrEqualOrGreaterThan:
               notOp = SQLBinaryOperator.Equality;
               break;
            case LessThan:
               notOp = SQLBinaryOperator.GreaterThanOrEqual;
               break;
            case LessThanOrEqual:
               notOp = SQLBinaryOperator.GreaterThan;
               break;
            case GreaterThan:
               notOp = SQLBinaryOperator.LessThanOrEqual;
               break;
            case GreaterThanOrEqual:
               notOp = SQLBinaryOperator.LessThan;
               break;
            case Is:
               notOp = SQLBinaryOperator.IsNot;
               break;
            case IsNot:
               notOp = SQLBinaryOperator.Is;
         }

         if (notOp != null) {
            return new SQLBinaryOpExpr(binaryOpExpr.getLeft(), notOp, binaryOpExpr.getRight());
         }
      }

      if (expr instanceof SQLInListExpr) {
         SQLInListExpr inListExpr = (SQLInListExpr)expr;
         SQLInListExpr newInListExpr = new SQLInListExpr(inListExpr);
         newInListExpr.getTargetList().addAll(inListExpr.getTargetList());
         newInListExpr.setNot(!inListExpr.isNot());
         return newInListExpr;
      } else {
         return new SQLUnaryExpr(SQLUnaryOperator.Not, expr);
      }
   }

   public static String normalize(String name) {
      return normalize(name, (DbType)null);
   }

   public static String normalize(String name, boolean isTrimmed) {
      return _normalize(name, (DbType)null, false, isTrimmed);
   }

   public static String normalize(String name, DbType dbType) {
      return _normalize(name, dbType, false);
   }

   private static String _normalize(String name, DbType dbType, boolean isForced) {
      return _normalize(name, dbType, isForced, true);
   }

   private static String _normalize(String name, DbType dbType, boolean isForced, boolean isTrimmed) {
      if (name == null) {
         return null;
      } else {
         if (name.length() > 2) {
            char c0 = name.charAt(0);
            char x0 = name.charAt(name.length() - 1);
            if (c0 == '"' && x0 == '"' || c0 == '`' && x0 == '`' || c0 == '\'' && x0 == '\'') {
               String normalizeName = name.substring(1, name.length() - 1);
               if (isTrimmed) {
                  normalizeName = normalizeName.trim();
               }

               int dotIndex = normalizeName.indexOf(46);
               if (dotIndex > 0 && c0 == '`') {
                  normalizeName = normalizeName.replaceAll("`\\.`", ".");
               }

               if (!isForced) {
                  if (DbType.oracle == dbType) {
                     if (OracleUtils.isKeyword(normalizeName)) {
                        return name;
                     }
                  } else if (DbType.mysql == dbType) {
                     if (MySqlUtils.isKeyword(normalizeName)) {
                        return name;
                     }
                  } else if ((DbType.postgresql == dbType || DbType.db2 == dbType) && PGUtils.isKeyword(normalizeName)) {
                     return name;
                  }
               }

               return normalizeName;
            }
         }

         return name;
      }
   }

   public static String forcedNormalize(String name, DbType dbType) {
      return _normalize(name, dbType, true);
   }

   public static boolean nameEquals(SQLName a, SQLName b) {
      if (a == b) {
         return true;
      } else if (a != null && b != null) {
         return a.nameHashCode64() == b.nameHashCode64();
      } else {
         return false;
      }
   }

   public static boolean nameEquals(String a, String b) {
      if (a == b) {
         return true;
      } else if (a != null && b != null) {
         if (a.equalsIgnoreCase(b)) {
            return true;
         } else {
            String normalize_a = normalize(a);
            String normalize_b = normalize(b);
            return normalize_a.equalsIgnoreCase(normalize_b);
         }
      } else {
         return false;
      }
   }

   public static boolean isValue(SQLExpr expr) {
      if (expr instanceof SQLLiteralExpr) {
         return true;
      } else if (expr instanceof SQLVariantRefExpr) {
         return true;
      } else {
         if (expr instanceof SQLBinaryOpExpr) {
            SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr)expr;
            SQLBinaryOperator op = binaryOpExpr.getOperator();
            if (op == SQLBinaryOperator.Add || op == SQLBinaryOperator.Subtract || op == SQLBinaryOperator.Multiply) {
               return isValue(binaryOpExpr.getLeft()) && isValue(binaryOpExpr.getRight());
            }
         }

         return false;
      }
   }

   public static boolean replaceInParent(SQLExpr expr, SQLExpr target) {
      if (expr == null) {
         return false;
      } else {
         SQLObject parent = expr.getParent();
         return parent instanceof SQLReplaceable ? ((SQLReplaceable)parent).replace(expr, target) : false;
      }
   }

   public static boolean replaceInParent(SQLTableSource cmp, SQLTableSource dest) {
      if (cmp == null) {
         return false;
      } else {
         SQLObject parent = cmp.getParent();
         if (parent instanceof SQLSelectQueryBlock) {
            SQLSelectQueryBlock queryBlock = (SQLSelectQueryBlock)parent;
            if (queryBlock.getFrom() == cmp) {
               queryBlock.setFrom(dest);
               return true;
            }
         }

         if (parent instanceof SQLJoinTableSource) {
            SQLJoinTableSource join = (SQLJoinTableSource)parent;
            return join.replace(cmp, dest);
         } else {
            return false;
         }
      }
   }

   public static boolean replaceInParent(SQLSelectQuery cmp, SQLSelectQuery dest) {
      if (cmp == null) {
         return false;
      } else {
         SQLObject parent = cmp.getParent();
         if (parent == null) {
            return false;
         } else if (parent instanceof SQLUnionQuery) {
            return ((SQLUnionQuery)parent).replace(cmp, dest);
         } else {
            return parent instanceof SQLSelect ? ((SQLSelect)parent).replace(cmp, dest) : false;
         }
      }
   }

   public static String desensitizeTable(String tableName) {
      if (tableName == null) {
         return null;
      } else {
         tableName = normalize(tableName);
         long hash = FnvHash.hashCode64(tableName);
         return Utils.hex_t(hash);
      }
   }

   public static String sort(String sql, DbType dbType) {
      List stmtList = parseStatements(sql, DbType.oracle);
      SQLCreateTableStatement.sort(stmtList);
      return toSQLString(stmtList, dbType);
   }

   public static Object[] clearLimit(String query, DbType dbType) {
      List stmtList = parseStatements(query, dbType);
      SQLLimit limit = null;
      SQLStatement statement = (SQLStatement)stmtList.get(0);
      if (statement instanceof SQLSelectStatement) {
         SQLSelectStatement selectStatement = (SQLSelectStatement)statement;
         if (selectStatement.getSelect().getQuery() instanceof SQLSelectQueryBlock) {
            limit = clearLimit(selectStatement.getSelect().getQueryBlock());
         }
      }

      if (statement instanceof SQLDumpStatement) {
         SQLDumpStatement dumpStatement = (SQLDumpStatement)statement;
         if (dumpStatement.getSelect().getQuery() instanceof SQLSelectQueryBlock) {
            limit = clearLimit(dumpStatement.getSelect().getQueryBlock());
         }
      }

      if (statement instanceof MySqlSelectIntoStatement) {
         MySqlSelectIntoStatement sqlSelectIntoStatement = (MySqlSelectIntoStatement)statement;
         limit = clearLimit(sqlSelectIntoStatement.getSelect().getQueryBlock());
      }

      if (statement instanceof MySqlInsertStatement) {
         MySqlInsertStatement insertStatement = (MySqlInsertStatement)statement;
         limit = clearLimit(insertStatement.getQuery().getQueryBlock());
      }

      String sql = toSQLString(stmtList, dbType);
      return new Object[]{sql, limit};
   }

   private static SQLLimit clearLimit(SQLSelectQueryBlock queryBlock) {
      if (queryBlock == null) {
         return null;
      } else {
         SQLLimit limit = queryBlock.getLimit();
         queryBlock.setLimit((SQLLimit)null);
         return limit;
      }
   }

   public static SQLLimit getLimit(SQLStatement statement, DbType dbType) {
      if (statement instanceof SQLSelectStatement) {
         SQLSelectQueryBlock queryBlock = ((SQLSelectStatement)statement).getSelect().getQueryBlock();
         return queryBlock == null ? null : queryBlock.getLimit();
      } else if (statement instanceof SQLDumpStatement) {
         SQLSelectQueryBlock queryBlock = ((SQLDumpStatement)statement).getSelect().getQueryBlock();
         return queryBlock == null ? null : queryBlock.getLimit();
      } else if (statement instanceof MySqlSelectIntoStatement) {
         SQLSelectQueryBlock queryBlock = ((MySqlSelectIntoStatement)statement).getSelect().getQueryBlock();
         return queryBlock == null ? null : queryBlock.getLimit();
      } else if (statement instanceof MySqlInsertStatement) {
         SQLSelect select = ((MySqlInsertStatement)statement).getQuery();
         if (select == null) {
            return null;
         } else {
            return select.getQuery() instanceof SQLUnionQuery ? ((SQLUnionQuery)select.getQuery()).getLimit() : select.getQueryBlock().getLimit();
         }
      } else {
         return null;
      }
   }

   public static SQLLimit getLimit(String query, DbType dbType) {
      List stmtList = parseStatements(query, dbType);
      SQLStatement statement = (SQLStatement)stmtList.get(0);
      return getLimit(statement, dbType);
   }

   public static String convertTimeZone(String sql, TimeZone from, TimeZone to) {
      SQLStatement statement = parseSingleMysqlStatement(sql);
      statement.accept(new TimeZoneVisitor(from, to));
      return statement.toString();
   }

   public static SQLStatement convertTimeZone(SQLStatement stmt, TimeZone from, TimeZone to) {
      stmt.accept(new TimeZoneVisitor(from, to));
      return stmt;
   }

   public static List<SQLInsertStatement> splitInsertValues(DbType dbType, String insertSql, int size) {
      SQLStatement statement = (SQLStatement)parseStatements(insertSql, dbType, false).get(0);
      if (!(statement instanceof SQLInsertStatement)) {
         throw new IllegalArgumentException("The SQL must be insert statement.");
      } else {
         List<SQLInsertStatement> insertLists = new ArrayList();
         SQLInsertStatement insertStatement = (SQLInsertStatement)statement;
         List<SQLInsertStatement.ValuesClause> valuesList = insertStatement.getValuesList();
         int totalSize = valuesList.size();
         if (totalSize <= size) {
            insertLists.add(insertStatement);
         } else {
            SQLInsertStatement insertTemplate = new SQLInsertStatement();
            insertStatement.cloneTo(insertTemplate);
            insertTemplate.getValuesList().clear();
            int batchCount = 0;
            if (totalSize % size == 0) {
               batchCount = totalSize / size;
            } else {
               batchCount = totalSize / size + 1;
            }

            for(int i = 0; i < batchCount; ++i) {
               SQLInsertStatement subInsertStatement = new SQLInsertStatement();
               insertTemplate.cloneTo(subInsertStatement);
               int fromIndex = i * size;
               int toIndex = fromIndex + size > totalSize ? totalSize : fromIndex + size;
               List<SQLInsertStatement.ValuesClause> subValuesList = valuesList.subList(fromIndex, toIndex);
               subInsertStatement.getValuesList().addAll(subValuesList);
               insertLists.add(subInsertStatement);
            }
         }

         return insertLists;
      }
   }

   static {
      FORMAT_DEFAULT_FEATURES = new SQLParserFeature[]{SQLParserFeature.KeepComments, SQLParserFeature.EnableSQLBinaryOpExprGroup};
      DEFAULT_FORMAT_OPTION = new FormatOption(true, true);
      DEFAULT_LCASE_FORMAT_OPTION = new FormatOption(false, true);
      LOG = LogFactory.getLog(SQLUtils.class);
   }

   public static class FormatOption {
      private int features;

      public FormatOption() {
         this.features = VisitorFeature.of(VisitorFeature.OutputUCase, VisitorFeature.OutputPrettyFormat);
      }

      public FormatOption(VisitorFeature... features) {
         this.features = VisitorFeature.of(VisitorFeature.OutputUCase, VisitorFeature.OutputPrettyFormat);
         this.features = VisitorFeature.of(features);
      }

      public FormatOption(boolean ucase) {
         this(ucase, true);
      }

      public FormatOption(boolean ucase, boolean prettyFormat) {
         this(ucase, prettyFormat, false);
      }

      public FormatOption(boolean ucase, boolean prettyFormat, boolean parameterized) {
         this.features = VisitorFeature.of(VisitorFeature.OutputUCase, VisitorFeature.OutputPrettyFormat);
         this.features = VisitorFeature.config(this.features, VisitorFeature.OutputUCase, ucase);
         this.features = VisitorFeature.config(this.features, VisitorFeature.OutputPrettyFormat, prettyFormat);
         this.features = VisitorFeature.config(this.features, VisitorFeature.OutputParameterized, parameterized);
      }

      public boolean isDesensitize() {
         return this.isEnabled(VisitorFeature.OutputDesensitize);
      }

      public void setDesensitize(boolean val) {
         this.config(VisitorFeature.OutputDesensitize, val);
      }

      public boolean isUppCase() {
         return this.isEnabled(VisitorFeature.OutputUCase);
      }

      public void setUppCase(boolean val) {
         this.config(VisitorFeature.OutputUCase, val);
      }

      public boolean isPrettyFormat() {
         return this.isEnabled(VisitorFeature.OutputPrettyFormat);
      }

      public void setPrettyFormat(boolean prettyFormat) {
         this.config(VisitorFeature.OutputPrettyFormat, prettyFormat);
      }

      public boolean isParameterized() {
         return this.isEnabled(VisitorFeature.OutputParameterized);
      }

      public void setParameterized(boolean parameterized) {
         this.config(VisitorFeature.OutputParameterized, parameterized);
      }

      public void config(VisitorFeature feature, boolean state) {
         this.features = VisitorFeature.config(this.features, feature, state);
      }

      public final boolean isEnabled(VisitorFeature feature) {
         return VisitorFeature.isEnabled(this.features, feature);
      }
   }

   static class TimeZoneVisitor extends SQLASTVisitorAdapter {
      private TimeZone from;
      private TimeZone to;

      public TimeZoneVisitor(TimeZone from, TimeZone to) {
         this.from = from;
         this.to = to;
      }

      public boolean visit(SQLTimestampExpr x) {
         SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         String newTime = format.format(x.getDate(this.from));
         x.setLiteral(newTime);
         return true;
      }
   }
}
