content: package com.alibaba.druid.support.calcite;
  
  import com.alibaba.druid.DbType;
  import com.alibaba.druid.FastsqlException;
  import com.alibaba.druid.sql.SQLUtils;
  import com.alibaba.druid.sql.ast.*;
  import com.alibaba.druid.sql.ast.expr.*;
  import com.alibaba.druid.sql.ast.statement.*;
  import com.alibaba.druid.sql.dialect.mysql.ast.statement.*;
  import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
  import com.alibaba.druid.sql.parser.ParserException;
  import com.alibaba.druid.util.FnvHash;
  import org.apache.calcite.avatica.util.TimeUnit;
  import org.apache.calcite.sql.*;
  import org.apache.calcite.sql.fun.*;
  import org.apache.calcite.sql.parser.SqlParserPos;
  import org.apache.calcite.sql.parser.SqlParserUtil;
  import org.apache.calcite.sql.type.SqlTypeName;
  import org.apache.calcite.util.DateString;
  import org.apache.calcite.util.TimeString;
  import org.apache.calcite.util.TimestampString;
  
  import java.util.*;
  
  public class CalciteMySqlNodeVisitor extends MySqlASTVisitorAdapter {
      static Map<Long, SqlOperator> operators = new HashMap<Long, SqlOperator>();
  
      static {
          List<SqlOperator> list = SqlStdOperatorTable.instance().getOperatorList();
          for (SqlOperator op : list) {
              long h = FnvHash.hashCode64(op.getName());
              if (h == FnvHash.Constants.TRIM) {
                  continue;
              }
              operators.put(h, op);
          }
          operators.put(FnvHash.Constants.CEILING, SqlStdOperatorTable.CEIL);
      }
  
      static SqlOperator func(long hash) {
          return operators.get(hash);
      }
  
      private SqlNode sqlNode;
  
      public SqlNode getSqlNode() {
          return sqlNode;
      }
  
      public boolean visit(SQLInsertStatement x) {
          SqlNodeList keywords = new SqlNodeList(new ArrayList<SqlNode>(), SqlParserPos.ZERO);
  
          SQLExprTableSource tableSource = (SQLExprTableSource) x.getTableSource();
          SqlNode targetTable = convertToSqlNode(tableSource.getExpr());
  
          SqlNode source;
  
          SQLSelect query = x.getQuery();
          if (query != null) {
              query.accept(this);
              source = sqlNode;
          } else {
              List<SQLInsertStatement.ValuesClause> valuesList = x.getValuesList();
  
              SqlNode[] rows = new SqlNode[valuesList.size()];
              for (int j = 0; j < valuesList.size(); j++) {
                  List<SQLExpr> values = valuesList.get(j).getValues();
  
                  SqlNode[] valueNodes = new SqlNode[values.size()];
                  for (int i = 0; i < values.size(); i++) {
                      SqlNode valueNode = convertToSqlNode(values.get(i));
                      valueNodes[i] = valueNode;
                  }
                  SqlBasicCall row = new SqlBasicCall(SqlStdOperatorTable.ROW, valueNodes, SqlParserPos.ZERO);
                  rows[j] = row;
              }
              source = new SqlBasicCall(SqlStdOperatorTable.VALUES, rows, SqlParserPos.ZERO);
          }
  
          SqlNodeList columnList = x.getColumns().size() > 0
                  ? convertToSqlNodeList(x.getColumns())
                  : null;
  
          this.sqlNode = new SqlInsert(SqlParserPos.ZERO, keywords, targetTable, source, columnList);
          return false;
      }
  
      public boolean visit(MySqlInsertStatement x) {
          return visit((SQLInsertStatement) x);
      }
  
      private boolean visit(List<SQLInsertStatement.ValuesClause> valuesList) {
          boolean isBatch = false;
          List<SQLInsertStatement.ValuesClause> newValuesList = convertToSingleValuesIfNeed(valuesList);
          if (newValuesList.size() < valuesList.size()) {
              isBatch = true;
              valuesList = newValuesList;
          }
  
          SqlNode[] rows = new SqlNode[valuesList.size()];
          for (int j = 0; j < valuesList.size(); j++) {
              List<SQLExpr> values = valuesList.get(j).getValues();
  
              SqlNode[] valueNodes = new SqlNode[values.size()];
              for (int i = 0; i < values.size(); i++) {
                  SqlNode valueNode = convertToSqlNode(values.get(i));
                  valueNodes[i] = valueNode;
              }
              SqlBasicCall row = new SqlBasicCall(SqlStdOperatorTable.ROW, valueNodes, SqlParserPos.ZERO);
              rows[j] = row;
          }
  
          this.sqlNode = new SqlBasicCall(SqlStdOperatorTable.VALUES, rows, SqlParserPos.ZERO);
  
          return isBatch;
      }
  
      public boolean visit(MySqlUpdateStatement x) {
          if (x.getTableSource().getClass() != SQLExprTableSource.class) {
              throw new UnsupportedOperationException("Support single table only for SqlUpdate statement of calcite.");
          }
          SQLExprTableSource tableSource = (SQLExprTableSource) x.getTableSource();
          SqlNode targetTable = convertToSqlNode(tableSource.getExpr());
  
          List<SqlNode> columns = new ArrayList<SqlNode>();
          List<SqlNode> values = new ArrayList<SqlNode>();
  
          for (SQLUpdateSetItem item : x.getItems()) {
              columns.add(convertToSqlNode(item.getColumn()));
              values.add(convertToSqlNode(item.getValue()));
          }
          SqlNodeList targetColumnList = new SqlNodeList(columns, SqlParserPos.ZERO);
          SqlNodeList sourceExpressList = new SqlNodeList(values, SqlParserPos.ZERO);
  
          SqlNode condition = convertToSqlNode(x.getWhere());
  
          SqlIdentifier alias = null;
          if (x.getTableSource().getAlias() != null) {
              alias = new SqlIdentifier(tableSource.getAlias(), SqlParserPos.ZERO);
          }
  
          sqlNode = new SqlUpdate(SqlParserPos.ZERO, targetTable, targetColumnList, sourceExpressList, condition, null, alias);
  
          return false;
      }
  
      public boolean visit(MySqlDeleteStatement x) {
          SQLExprTableSource tableSource = (SQLExprTableSource) x.getTableSource();
          SqlNode targetTable = convertToSqlNode(tableSource.getExpr());
  
          SqlNode condition = convertToSqlNode(x.getWhere());
  
          SqlIdentifier alias = null;
          if (x.getTableSource().getAlias() != null) {
              alias = new SqlIdentifier(tableSource.getAlias(), SqlParserPos.ZERO);
          }
  
          sqlNode = new SqlDelete(SqlParserPos.ZERO, targetTable, condition, null, alias);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLUnionQuery x) {
          SqlNode[] nodes;
          if (x.getRelations().size() > 2) {
              nodes = new SqlNode[x.getRelations().size()];
              for (int i = 0; i < x.getRelations().size(); i++) {
                  nodes[i] = convertToSqlNode(x.getRelations().get(i));
              }
          } else {
              SqlNode left = convertToSqlNode(x.getLeft());
              SqlNode right = convertToSqlNode(x.getRight());
  
              nodes = new SqlNode[]{left, right};
          }
  
          //order by
          SqlNodeList orderBySqlNode = null;
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              orderBySqlNode = convertOrderby(orderBy);
          }
  
          //limit
          SqlNode offset = null;
          SqlNode fetch = null;
          SQLLimit limit = x.getLimit();
          if (limit != null) {
              offset = convertToSqlNode(limit.getOffset());
              fetch = convertToSqlNode(limit.getRowCount());
          }
  
          SQLUnionOperator operator = x.getOperator();
  
          SqlNode union = null;
          switch (operator) {
              case UNION_ALL:
                  union = new SqlBasicCall(SqlStdOperatorTable.UNION_ALL,
                          nodes,
                          SqlParserPos.ZERO);
                  break;
              case UNION:
              case DISTINCT:
                  union = new SqlBasicCall(SqlStdOperatorTable.UNION,
                          nodes,
                          SqlParserPos.ZERO);
                  break;
              case INTERSECT:
                  union = new SqlBasicCall(SqlStdOperatorTable.INTERSECT,
                          nodes,
                          SqlParserPos.ZERO);
                  break;
              case EXCEPT:
                  union = new SqlBasicCall(SqlStdOperatorTable.EXCEPT,
                          nodes,
                          SqlParserPos.ZERO);
                  break;
              default:
                  throw new FastsqlException("unsupported join type: " + operator);
          }
  
          if (null == orderBy && null == offset && null == fetch) {
              sqlNode = union;
          } else {
              if (orderBySqlNode == null) {
                  orderBySqlNode = SqlNodeList.EMPTY;
              }
              sqlNode = new SqlOrderBy(SqlParserPos.ZERO, union, orderBySqlNode, offset, fetch);
          }
  
          return false;
  
      }
  
      public boolean visit(MySqlSelectQueryBlock x) {
          return visit((SQLSelectQueryBlock) x);
      }
  
      private static Map<Long, SqlTypeName> nameHashCode64SqlTypeNameMapping = new HashMap<>();
  
      public boolean visit(SQLTableSource x) {
          Class<?> clazz = x.getClass();
          if (clazz == SQLJoinTableSource.class) {
              visit((SQLJoinTableSource) x);
          } else if (clazz == SQLExprTableSource.class) {
              visit((SQLExprTableSource) x);
          } else if (clazz == SQLSubqueryTableSource.class) {
              visit((SQLSubqueryTableSource) x);
          } else {
              x.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLExprTableSource x) {
          SqlIdentifier table;
          SQLExpr expr = x.getExpr();
          if (expr instanceof SQLIdentifierExpr) {
              table = buildIdentifier((SQLIdentifierExpr) expr);
          } else if (expr instanceof SQLPropertyExpr) {
              table = buildIdentifier((SQLPropertyExpr) expr);
          } else {
              throw new FastsqlException("not support : " + expr);
          }
  
          if (x.getAlias() != null) {
              SqlIdentifier alias = new SqlIdentifier(x.computeAlias(), SqlParserPos.ZERO);
              SqlBasicCall as = new SqlBasicCall(SqlStdOperatorTable.AS, new SqlNode[]{table, alias},
                      SqlParserPos.ZERO);
              sqlNode = as;
          } else {
              sqlNode = table;
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLJoinTableSource x) {
          SQLJoinTableSource.JoinType joinType = x.getJoinType();
  
          SqlNode left = convertToSqlNode(x.getLeft());
          SqlNode right = convertToSqlNode(x.getRight());
          SqlNode condition = convertToSqlNode(x.getCondition());
  
          SqlLiteral conditionType = condition == null
                  ? JoinConditionType.NONE.symbol(SqlParserPos.ZERO)
                  : JoinConditionType.ON.symbol(SqlParserPos.ZERO);
  
          if (condition == null && !x.getUsing().isEmpty()) {
              List<SQLExpr> using = x.getUsing();
              conditionType = JoinConditionType.USING.symbol(SqlParserPos.ZERO);
              condition = convertToSqlNodeList(x.getUsing());
          }
  
          switch (joinType) {
              case COMMA:
                  this.sqlNode = new SqlJoin(SqlParserPos.ZERO, left,
                          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                          JoinType.COMMA.symbol(SqlParserPos.ZERO), right,
                          JoinConditionType.NONE.symbol(SqlParserPos.ZERO),
                          null);
                  break;
              case JOIN:
              case INNER_JOIN:
                  if (condition == null) {
                      this.sqlNode = new SqlJoin(
                              SqlParserPos.ZERO,
                              left,
                              SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                              JoinType.COMMA.symbol(SqlParserPos.ZERO),
                              right,
                              conditionType,
                              null
                      );
                  } else {
                      this.sqlNode = new SqlJoin(
                              SqlParserPos.ZERO,
                              left,
                              SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                              JoinType.INNER.symbol(SqlParserPos.ZERO),
                              right,
                              conditionType,
                              condition
                      );
                  }
                  break;
              case LEFT_OUTER_JOIN:
                  this.sqlNode = new SqlJoin(SqlParserPos.ZERO,
                          left,
                          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                          JoinType.LEFT.symbol(SqlParserPos.ZERO),
                          right,
                          conditionType,
                          condition);
                  break;
              case RIGHT_OUTER_JOIN:
                  this.sqlNode = new SqlJoin(SqlParserPos.ZERO,
                          left,
                          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                          JoinType.RIGHT.symbol(SqlParserPos.ZERO),
                          right,
                          conditionType,
                          condition);
                  break;
              case NATURAL_JOIN:
                  this.sqlNode = new SqlJoin(
                          SqlParserPos.ZERO,
                          left,
                          SqlLiteral.createBoolean(true, SqlParserPos.ZERO),
                          JoinType.COMMA.symbol(SqlParserPos.ZERO),
                          right,
                          JoinConditionType.NONE.symbol(SqlParserPos.ZERO),
                          null);
                  break;
              case CROSS_JOIN:
                  this.sqlNode = new SqlJoin(
                          SqlParserPos.ZERO,
                          left,
                          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                          JoinType.CROSS.symbol(SqlParserPos.ZERO),
                          right,
                          JoinConditionType.NONE.symbol(SqlParserPos.ZERO),
                          null);
                  break;
              case NATURAL_CROSS_JOIN:
                  this.sqlNode = new SqlJoin(SqlParserPos.ZERO,
                          left,
                          SqlLiteral.createBoolean(true, SqlParserPos.ZERO),
                          JoinType.CROSS.symbol(SqlParserPos.ZERO),
                          right,
                          JoinConditionType.NONE.symbol(SqlParserPos.ZERO),
                          null);
                  break;
              case FULL_OUTER_JOIN:
                  this.sqlNode = new SqlJoin(
                          SqlParserPos.ZERO,
                          left,
                          SqlLiteral.createBoolean(false, SqlParserPos.ZERO),
                          JoinType.FULL.symbol(SqlParserPos.ZERO),
                          right,
                          condition == null
                                  ? JoinConditionType.NONE.symbol(SqlParserPos.ZERO)
                                  : conditionType,
                          condition
                  );
                  break;
              default:
                  throw new UnsupportedOperationException("unsupported : " + joinType);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSubqueryTableSource x) {
          sqlNode = convertToSqlNode(x.getSelect());
  
          final String alias = x.getAlias();
          if (alias != null) {
              SqlIdentifier aliasIdentifier = new SqlIdentifier(alias, SqlParserPos.ZERO);
  
              List<SQLName> columns = x.getColumns();
  
              SqlNode[] operands;
              if (columns.isEmpty()) {
                  operands = new SqlNode[]{sqlNode, aliasIdentifier};
              } else {
                  operands = new SqlNode[columns.size() + 2];
                  operands[0] = sqlNode;
                  operands[1] = aliasIdentifier;
                  for (int i = 0; i < columns.size(); i++) {
                      SQLName column = columns.get(i);
                      operands[i + 2] = new SqlIdentifier(
                              SQLUtils.normalize(column.getSimpleName()), SqlParserPos.ZERO);
                  }
              }
              sqlNode = new SqlBasicCall(SqlStdOperatorTable.AS, operands, SqlParserPos.ZERO);
          }
  
          return false;
      }
  
      public boolean visit(SQLUnionQueryTableSource x) {
          x.getUnion().accept(this);
  
          final String alias = x.getAlias();
          if (alias != null) {
              SqlIdentifier aliasIdentifier = new SqlIdentifier(alias, SqlParserPos.ZERO);
              sqlNode = new SqlBasicCall(SqlStdOperatorTable.AS,
                      new SqlNode[]{sqlNode, aliasIdentifier},
                      SqlParserPos.ZERO);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLInSubQueryExpr x) {
          SqlNode left = convertToSqlNode(x.getExpr());
          SqlBinaryOperator subOperator = SqlStdOperatorTable.IN;
          if (x.isNot()) {
              subOperator = SqlStdOperatorTable.NOT_IN;
          }
          SqlNode right = convertToSqlNode(x.subQuery);
  
          sqlNode = new SqlBasicCall(subOperator, new SqlNode[]{left, right}, SqlParserPos.ZERO);
          return false;
      }
  
      static {
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.BIT, SqlTypeName.BOOLEAN);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.BOOLEAN, SqlTypeName.BOOLEAN);
  
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.TINYINT, SqlTypeName.TINYINT);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.SMALLINT, SqlTypeName.SMALLINT);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.INT, SqlTypeName.INTEGER);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.INTEGER, SqlTypeName.INTEGER);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.BIGINT, SqlTypeName.BIGINT);
  
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.DECIMAL, SqlTypeName.DECIMAL);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.FLOAT, SqlTypeName.FLOAT);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.REAL, SqlTypeName.REAL);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.DOUBLE, SqlTypeName.DOUBLE);
  
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.DATE, SqlTypeName.DATE);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.TIME, SqlTypeName.TIME);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.TIMESTAMP, SqlTypeName.TIMESTAMP);
  
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.CHAR, SqlTypeName.CHAR);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.VARCHAR, SqlTypeName.VARCHAR);
  
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.BINARY, SqlTypeName.BINARY);
          nameHashCode64SqlTypeNameMapping.put(FnvHash.Constants.VARBINARY, SqlTypeName.VARBINARY);
      }
  
      public boolean visit(SQLSelectQueryBlock x) {
          SqlNodeList keywordList = null;
          List<SqlNode> keywordNodes = new ArrayList<SqlNode>(5);
          int option = x.getDistionOption();
          if (option != 0) {
              if (option == SQLSetQuantifier.DISTINCT
                      || option == SQLSetQuantifier.DISTINCTROW) {
                  keywordNodes.add(SqlSelectKeyword.DISTINCT.symbol(SqlParserPos.ZERO));
              } else if (option == SQLSetQuantifier.ALL) {
                  keywordNodes.add(SqlSelectKeyword.ALL.symbol(SqlParserPos.ZERO));
              }
  
              keywordList = new SqlNodeList(keywordNodes, SqlParserPos.ZERO);
          }
  
          // select list
          List<SqlNode> columnNodes = new ArrayList<SqlNode>(x.getSelectList().size());
          for (SQLSelectItem selectItem : x.getSelectList()) {
              SqlNode column = convertToSqlNode(selectItem);
              columnNodes.add(column);
          }
  
          //select item
          SqlNodeList selectList = new SqlNodeList(columnNodes, SqlParserPos.ZERO);
  
          //from
          SqlNode from = null;
  
          SQLTableSource tableSource = x.getFrom();
          if (tableSource != null) {
              from = convertToSqlNode(tableSource);
          }
  
          //where
          SqlNode where = convertToSqlNode(x.getWhere());
  
          //order by
          SqlNodeList orderBySqlNode = null;
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              orderBySqlNode = convertOrderby(orderBy);
          }
  
          //group by
          SqlNodeList groupBySqlNode = null;
          SqlNode having = null;
          SQLSelectGroupByClause groupBys = x.getGroupBy();
  
          if (groupBys != null) {
              if (groupBys.getHaving() != null) {
                  having = convertToSqlNode(groupBys.getHaving());
              }
  
              if (groupBys.getItems().size() > 0) {
                  List<SqlNode> groupByNodes = new ArrayList<SqlNode>(groupBys.getItems().size());
  
                  for (SQLExpr groupBy : groupBys.getItems()) {
                      SqlNode groupByNode = convertToSqlNode(groupBy);
                      groupByNodes.add(groupByNode);
                  }
                  groupBySqlNode = new SqlNodeList(groupByNodes, SqlParserPos.ZERO);
              }
  
              SqlInternalOperator op = null;
              if (groupBys.isWithRollUp()) {
                  op = SqlStdOperatorTable.ROLLUP;
              } else if (groupBys.isWithCube()) {
                  op = SqlStdOperatorTable.CUBE;
              }
  
              if (op != null) {
                  List<SqlNode> rollupNodes = new ArrayList<SqlNode>(1);
  
                  boolean isRow = false;
                  for (SqlNode node : groupBySqlNode.getList()) {
                      if (node instanceof SqlBasicCall && ((SqlBasicCall) node).getOperator() == SqlStdOperatorTable.ROW) {
                          isRow = true;
                          break;
                      }
                  }
  
                  if (isRow) {
                      rollupNodes.add(op.createCall(SqlParserPos.ZERO, groupBySqlNode.toArray()));
                      groupBySqlNode = new SqlNodeList(rollupNodes, SqlParserPos.ZERO);
                  } else {
                      rollupNodes.add(op.createCall(SqlParserPos.ZERO, groupBySqlNode));
                      groupBySqlNode = new SqlNodeList(rollupNodes, SqlParserPos.ZERO);
                  }
  
              }
          }
  
          //limit
          SqlNode offset = null;
          SqlNode fetch = null;
          SQLLimit limit = x.getLimit();
          if (limit != null) {
              offset = convertToSqlNode(limit.getOffset());
              fetch = convertToSqlNode(limit.getRowCount());
          }
  
          //hints
          SqlNodeList hints = convertHints(x.getHints());
  
          if (orderBy != null && x.getParent() instanceof SQLUnionQuery) {
              this.sqlNode = new com.alibaba.druid.support.calcite.TDDLSqlSelect(
                      SqlParserPos.ZERO,
                      keywordList,
                      selectList,
                      from,
                      where,
                      groupBySqlNode,
                      having,
                      null,
                      null,
                      offset,
                      fetch,
                      hints,
                      null
              );
              sqlNode = new SqlOrderBy(
                      SqlParserPos.ZERO,
                      sqlNode,
                      orderBySqlNode,
                      null,
                      fetch
              );
          } else {
              if (orderBySqlNode == null) {
                  orderBySqlNode = SqlNodeList.EMPTY;
              }
              if (hints == null || SqlNodeList.isEmptyList(hints)) {
                  this.sqlNode = new SqlSelect(
                          SqlParserPos.ZERO,
                          keywordList,
                          selectList,
                          from,
                          where,
                          groupBySqlNode,
                          having,
                          null,
                          SqlNodeList.EMPTY,
                          null,
                          null,
                          null
                  );
  
                  if ((!SqlNodeList.isEmptyList(orderBySqlNode))
                          || offset != null
                          || fetch != null
                  ) {
                      sqlNode = new SqlOrderBy(
                              SqlParserPos.ZERO,
                              sqlNode,
                              orderBySqlNode,
                              offset,
                              fetch
                      );
                  }
              } else {
                  this.sqlNode = new com.alibaba.druid.support.calcite.TDDLSqlSelect(
                          SqlParserPos.ZERO,
                          keywordList,
                          selectList,
                          from,
                          where,
                          groupBySqlNode,
                          having,
                          null,
                          orderBySqlNode,
                          offset,
                          fetch,
                          hints,
                          null
                  );
              }
          }
  
          return false;
      }
  
      private SqlTypeName toSqlTypeName(SQLDataType dataType) {
          long nameHashCode64 = dataType.nameHashCode64();
          SqlTypeName sqlTypeName = nameHashCode64SqlTypeNameMapping.get(nameHashCode64);
          if (sqlTypeName != null) {
              return sqlTypeName;
          }
  
          throw new FastsqlException("TODO");
      }
  
      public boolean visit(SQLCastExpr x) {
          SqlLiteral functionQualifier = null;
  
          SqlNode sqlNode = convertToSqlNode(x.getExpr());
  
          SQLDataType dataType = x.getDataType();
          String typeName = dataType.getName().toUpperCase();
          if (dataType.nameHashCode64() == FnvHash.Constants.INT) {
              typeName = "INTEGER";
          } else if (dataType.nameHashCode64() == FnvHash.Constants.NUMERIC) {
              typeName = "DECIMAL";
          }
  
          SqlIdentifier dataTypeNode = (SqlIdentifier) convertToSqlNode(
                  new SQLIdentifierExpr(typeName));
  
          int scale = -1;
          int precision = -1;
  
          List<SQLExpr> arguments = dataType.getArguments();
          if (arguments != null && !arguments.isEmpty()) {
              scale = ((SQLNumericLiteralExpr) arguments.get(0)).getNumber().intValue();
              if (arguments.size() > 1) {
                  precision = ((SQLNumericLiteralExpr) arguments.get(1)).getNumber().intValue();
              }
          }
  
          SqlDataTypeSpec sqlDataTypeSpec = new SqlDataTypeSpec(
                  new SqlBasicTypeNameSpec(
                          toSqlTypeName(dataType),
                          scale,
                          precision,
                          SqlParserPos.ZERO
                  ),
                  SqlParserPos.ZERO
          );
  
          SqlOperator sqlOperator = new SqlCastFunction();
  
          this.sqlNode = new com.alibaba.druid.support.calcite.CalciteSqlBasicCall(
                  sqlOperator,
                  new SqlNode[]{sqlNode, sqlDataTypeSpec},
                  SqlParserPos.ZERO,
                  false,
                  functionQualifier
          );
          return false;
      }
  
      public boolean visit(SQLCaseExpr x) {
          SQLExpr valueExpr = x.getValueExpr();
          SqlNode nodeValue = null;
          SqlNodeList nodeWhen = new SqlNodeList(SqlParserPos.ZERO);
          SqlNodeList nodeThen = new SqlNodeList(SqlParserPos.ZERO);
          if (valueExpr != null) {
              nodeValue = convertToSqlNode(valueExpr);
          }
  
          List items = x.getItems();
          int elExpr = 0;
  
          for (int size = items.size(); elExpr < size; ++elExpr) {
              this.visit((SQLCaseExpr.Item) items.get(elExpr));
              if (this.sqlNode != null && this.sqlNode instanceof SqlNodeList) {
                  SqlNodeList nodeListTemp = (SqlNodeList) this.sqlNode;
                  nodeWhen.add(nodeListTemp.get(0));
                  nodeThen.add(nodeListTemp.get(1));
              }
          }
          SQLExpr elseExpr = x.getElseExpr();
          SqlNode nodeElse = convertToSqlNode(elseExpr);
          SqlNodeList sqlNodeList = new SqlNodeList(SqlParserPos.ZERO);
          sqlNodeList.add(nodeValue);
          sqlNodeList.add(nodeWhen);
          sqlNodeList.add(nodeThen);
          sqlNodeList.add(nodeElse);
          sqlNode = SqlCase.createSwitched(SqlParserPos.ZERO, nodeValue, nodeWhen, nodeThen, nodeElse);
          return false;
      }
  
      public boolean visit(SQLCaseExpr.Item x) {
          SQLExpr conditionExpr = x.getConditionExpr();
          SqlNode sqlNode1 = convertToSqlNode(conditionExpr);
          SQLExpr valueExpr = x.getValueExpr();
          SqlNode sqlNode2 = convertToSqlNode(valueExpr);
          SqlNodeList sqlNodeList = new SqlNodeList(SqlParserPos.ZERO);
          sqlNodeList.add(sqlNode1);
          sqlNodeList.add(sqlNode2);
          sqlNode = sqlNodeList;
          return false;
      }
  
      public boolean visit(SQLListExpr x) {
          List<SQLExpr> items = x.getItems();
          List<SqlNode> objects = new ArrayList<SqlNode>();
          for (int i = 0; i < items.size(); i++) {
              SQLExpr sqlExpr = items.get(i);
              SqlNode sqlNode = convertToSqlNode(sqlExpr);
              objects.add(sqlNode);
          }
          sqlNode = SqlStdOperatorTable.ROW.createCall(SqlParserPos.ZERO, objects);
          return false;
      }
  
      @Override
      public boolean visit(SQLSelect x) {
          SQLWithSubqueryClause with = x.getWithSubQuery();
          if (with != null) {
              SqlNodeList withList = new SqlNodeList(SqlParserPos.ZERO);
              final List<SQLWithSubqueryClause.Entry> entries = with.getEntries();
              for (SQLWithSubqueryClause.Entry entry : entries) {
                  visit(entry);
                  withList.add(sqlNode);
              }
              SqlNode query = convertToSqlNode(x.getQuery());
  
              if (query instanceof SqlOrderBy) {
                  SqlOrderBy orderBy = (SqlOrderBy) query;
  
                  SqlWith w = new SqlWith(SqlParserPos.ZERO, withList, orderBy.query);
                  sqlNode = new SqlOrderBy(
                          SqlParserPos.ZERO,
                          w,
                          orderBy.orderList,
                          orderBy.offset,
                          orderBy.fetch
                  );
              } else {
                  sqlNode = new SqlWith(SqlParserPos.ZERO, withList, query);
              }
  
              if (query instanceof SqlSelect) {
                  SqlSelect select = (SqlSelect) query;
                  SqlNode fetch = select.getFetch();
                  SqlNodeList orderList = select.getOrderList();
  
                  if (fetch != null
                          || (orderList != null && orderList.size() > 0)) {
                      SqlNodeList orderByList = null;
                      if (orderList != null) {
                          orderByList = new SqlNodeList(orderList.getList(), SqlParserPos.ZERO);
                          orderList.getList().clear();
                      } else {
                          orderByList = SqlNodeList.EMPTY;
                      }
  
                      sqlNode = new SqlOrderBy(
                              SqlParserPos.ZERO,
                              sqlNode,
                              orderByList,
                              null,
                              fetch
                      );
  
                      if (fetch != null) {
                          select.setFetch(null);
                      }
                  }
              }
  
          } else {
              sqlNode = convertToSqlNode(x.getQuery());
          }
  
          return false;
      }
  
      public boolean visit(SQLWithSubqueryClause.Entry x) {
          SqlNodeList columnList = null;
          final List<SQLName> columns = x.getColumns();
          if (columns.size() > 0) {
              columnList = new SqlNodeList(SqlParserPos.ZERO);
              for (SQLName column : columns) {
                  columnList.add(new SqlIdentifier(column.getSimpleName(), SqlParserPos.ZERO));
              }
          }
          SqlNode query = convertToSqlNode(x.getSubQuery());
          SqlIdentifier name = new SqlIdentifier(x.getAlias(), SqlParserPos.ZERO);
          sqlNode = new SqlWithItem(SqlParserPos.ZERO, name, columnList, query);
          return false;
      }
  
      @Override
      public boolean visit(SQLSelectStatement x) {
          SqlNode sqlNode = convertToSqlNode(x.getSelect());
  
          if (sqlNode instanceof com.alibaba.druid.support.calcite.TDDLSqlSelect) {
              com.alibaba.druid.support.calcite.TDDLSqlSelect select = (com.alibaba.druid.support.calcite.TDDLSqlSelect) sqlNode;
  
              SqlNodeList headHints = convertHints(x.getHeadHintsDirect());
              select.setHeadHints(headHints);
              this.sqlNode = select;
          } else {
              this.sqlNode = sqlNode;
          }
  
          return false;
      }
  
      protected void visit(SQLSelectQuery x) {
          Class<?> clazz = x.getClass();
          if (clazz == MySqlSelectQueryBlock.class) {
              visit((MySqlSelectQueryBlock) x);
          } else if (clazz == SQLUnionQuery.class) {
              visit((SQLUnionQuery) x);
          } else {
              x.accept(this);
          }
      }
  
      public boolean visit(SQLAllExpr x) {
          sqlNode = convertToSqlNode(x.getSubQuery());
          return false;
      }
  
      public boolean visit(SQLAnyExpr x) {
          sqlNode = convertToSqlNode(x.getSubQuery());
          return false;
      }
  
      private boolean isSqlAllExpr(SQLExpr x) {
          return x.getClass() == SQLAllExpr.class;
      }
  
      private boolean isAnyOrSomeExpr(SQLExpr x) {
          return x.getClass() == SQLAnyExpr.class || x.getClass() == SQLSomeExpr.class;
      }
  
      public boolean visit(SQLSelectItem x) {
          SQLExpr expr = x.getExpr();
  
          if (expr instanceof SQLIdentifierExpr) {
              visit((SQLIdentifierExpr) expr);
          } else if (expr instanceof SQLPropertyExpr) {
              visit((SQLPropertyExpr) expr);
          } else if (expr instanceof SQLAggregateExpr) {
              visit((SQLAggregateExpr) expr);
          } else {
              expr.accept(this);
          } // select a + (select count(1) from b) as mm from c;
          // select a + (select COUNT(1) from b) as 'a + (select count(1) as
          // 'count(1)' from b)' from c;
          String alias = x.getAlias();
          if (alias != null && alias.length() > 0) {
              String alias2 = x.getAlias2();
              sqlNode = new SqlBasicCall(SqlStdOperatorTable.AS,
                      new SqlNode[]{sqlNode, new SqlIdentifier(SQLUtils.normalize(alias2, DbType.mysql), SqlParserPos.ZERO)},
                      SqlParserPos.ZERO);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLIdentifierExpr x) {
          if (x.getName().equalsIgnoreCase("unknown")) {
              sqlNode = SqlLiteral.createUnknown(SqlParserPos.ZERO);
              return false;
          }
          sqlNode = buildIdentifier(x);
          return false;
      }
  
      public boolean visit(SQLPropertyExpr x) {
          sqlNode = buildIdentifier(x);
          return false;
      }
  
      SqlIdentifier buildIdentifier(SQLIdentifierExpr x) {
          return new SqlIdentifier(SQLUtils.normalize(x.getName()), SqlParserPos.ZERO);
      }
  
      SqlIdentifier buildIdentifier(SQLPropertyExpr x) {
          String name = SQLUtils.normalize(x.getName());
          if ("*".equals(name)) {
              name = "";
          }
  
          SQLExpr owner = x.getOwner();
  
          List<String> names;
          if (owner instanceof SQLIdentifierExpr) {
              names = Arrays.asList(((SQLIdentifierExpr) owner).normalizedName(), name);
          } else if (owner instanceof SQLPropertyExpr) {
              names = new ArrayList<String>();
              buildIdentifier((SQLPropertyExpr) owner, names);
              names.add(name);
          } else {
              throw new FastsqlException("not support : " + owner);
          }
  
          return new SqlIdentifier(names, SqlParserPos.ZERO);
      }
  
      void buildIdentifier(SQLPropertyExpr x, List<String> names) {
          String name = SQLUtils.normalize(x.getName());
  
          SQLExpr owner = x.getOwner();
          if (owner instanceof SQLIdentifierExpr) {
              names.add(((SQLIdentifierExpr) owner).normalizedName());
          } else if (owner instanceof SQLPropertyExpr) {
              buildIdentifier((SQLPropertyExpr) owner, names);
          } else {
              throw new FastsqlException("not support : " + owner);
          }
  
          names.add(name);
      }
  
      public boolean visit(SQLBinaryOpExprGroup x) {
          SqlOperator operator = null;
          switch (x.getOperator()) {
              case BooleanAnd:
                  operator = SqlStdOperatorTable.AND;
                  break;
              case BooleanOr:
                  operator = SqlStdOperatorTable.OR;
                  break;
              default:
                  break;
          }
  
          final List<SQLExpr> items = x.getItems();
          SqlNode group = null;
          for (int i = 0; i < items.size(); i++) {
              SQLExpr item = items.get(i);
              final SqlNode calciteNode = convertToSqlNode(item);
              if (group == null) {
                  group = calciteNode;
              } else {
                  group = new SqlBasicCall(operator, new SqlNode[]{group, calciteNode}, SqlParserPos.ZERO);
              }
          }
          this.sqlNode = group;
          return false;
      }
  
      public boolean visit(SQLBinaryOpExpr x) {
          SqlOperator operator = null;
  
          SqlQuantifyOperator someOrAllOperator = null;
  
          SqlNode left = convertToSqlNode(x.getLeft());
  
          SQLExpr rightExpr = x.getRight();
          SqlNode right = convertToSqlNode(rightExpr);
  
          switch (x.getOperator()) {
              case Equality:
                  if (isSqlAllExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.ALL_EQ;
                  } else if (isAnyOrSomeExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.SOME_EQ;
                  } else {
                      operator = SqlStdOperatorTable.EQUALS;
                  }
                  break;
              case GreaterThan:
                  if (isSqlAllExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.ALL_GT;
                  } else if (isAnyOrSomeExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.SOME_GT;
                  } else {
                      operator = SqlStdOperatorTable.GREATER_THAN;
                  }
                  break;
              case GreaterThanOrEqual:
                  if (isSqlAllExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.ALL_GE;
                  } else if (isAnyOrSomeExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.SOME_GE;
                  } else {
                      operator = SqlStdOperatorTable.GREATER_THAN_OR_EQUAL;
                  }
                  break;
              case LessThan:
                  if (isSqlAllExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.ALL_LT;
                  } else if (isAnyOrSomeExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.SOME_LT;
                  } else {
                      operator = SqlStdOperatorTable.LESS_THAN;
                  }
                  break;
              case LessThanOrEqual:
                  if (isSqlAllExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.ALL_LE;
                  } else if (isAnyOrSomeExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.SOME_LE;
                  } else {
                      operator = SqlStdOperatorTable.LESS_THAN_OR_EQUAL;
                  }
                  break;
              case NotEqual:
              case LessThanOrGreater:
                  if (isSqlAllExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.ALL_NE;
                  } else if (isAnyOrSomeExpr(rightExpr)) {
                      someOrAllOperator = SqlStdOperatorTable.SOME_NE;
                  } else {
                      operator = SqlStdOperatorTable.NOT_EQUALS;
                  }
                  break;
              case Add:
                  operator = SqlStdOperatorTable.PLUS;
                  break;
              case Subtract:
                  operator = SqlStdOperatorTable.MINUS;
                  break;
              case Multiply:
                  operator = SqlStdOperatorTable.MULTIPLY;
                  break;
              case Divide:
                  operator = SqlStdOperatorTable.DIVIDE;
                  break;
              case Modulus:
                  operator = SqlStdOperatorTable.MOD;
                  break;
              case Like:
                  operator = SqlStdOperatorTable.LIKE;
                  break;
              case NotLike:
                  operator = SqlStdOperatorTable.NOT_LIKE;
                  break;
              case BooleanAnd:
                  operator = SqlStdOperatorTable.AND;
                  break;
              case BooleanOr:
                  operator = SqlStdOperatorTable.OR;
                  break;
              case Concat:
                  operator = SqlStdOperatorTable.CONCAT;
                  break;
              case Is: {
                  if (rightExpr instanceof SQLNullExpr) {
                      operator = SqlStdOperatorTable.IS_NULL;
                  } else if (rightExpr instanceof SQLIdentifierExpr) {
                      long hashCode64 = ((SQLIdentifierExpr) rightExpr).nameHashCode64();
                      if (hashCode64 == FnvHash.Constants.JSON
                              || hashCode64 == JSON_VALUE) {
                          operator = SqlStdOperatorTable.IS_JSON_VALUE;
                      } else if (hashCode64 == JSON_OBJECT) {
                          operator = SqlStdOperatorTable.IS_JSON_OBJECT;
                      } else if (hashCode64 == JSON_ARRAY) {
                          operator = SqlStdOperatorTable.IS_JSON_ARRAY;
                      } else if (hashCode64 == JSON_SCALAR) {
                          operator = SqlStdOperatorTable.IS_JSON_SCALAR;
                      } else if (hashCode64 == FnvHash.Constants.UNKNOWN) {
                          operator = SqlStdOperatorTable.IS_UNKNOWN;
                      }
                  } else if (rightExpr instanceof SQLBooleanExpr) {
                      if (((SQLBooleanExpr) rightExpr).getValue()) {
                          operator = SqlStdOperatorTable.IS_TRUE;
                      } else {
                          operator = SqlStdOperatorTable.IS_FALSE;
                      }
                  }
              }
              break;
              case IsNot:
                  if (rightExpr instanceof SQLNullExpr) {
                      operator = SqlStdOperatorTable.IS_NOT_NULL;
                  } else if (rightExpr instanceof SQLIdentifierExpr) {
                      long hashCode64 = ((SQLIdentifierExpr) rightExpr).nameHashCode64();
                      if (hashCode64 == FnvHash.Constants.JSON
                              || hashCode64 == JSON_VALUE) {
                          operator = SqlStdOperatorTable.IS_NOT_JSON_VALUE;
                      } else if (hashCode64 == JSON_OBJECT) {
                          operator = SqlStdOperatorTable.IS_NOT_JSON_OBJECT;
                      } else if (hashCode64 == JSON_ARRAY) {
                          operator = SqlStdOperatorTable.IS_NOT_JSON_ARRAY;
                      } else if (hashCode64 == JSON_SCALAR) {
                          operator = SqlStdOperatorTable.IS_NOT_JSON_SCALAR;
                      } else if (hashCode64 == FnvHash.Constants.UNKNOWN) {
                          operator = SqlStdOperatorTable.IS_NOT_UNKNOWN;
                      }
                  } else if (rightExpr instanceof SQLBooleanExpr) {
                      if (((SQLBooleanExpr) rightExpr).getValue()) {
                          operator = SqlStdOperatorTable.IS_NOT_TRUE;
                      } else {
                          operator = SqlStdOperatorTable.IS_NOT_FALSE;
                      }
                  }
                  break;
              case Escape: {
                  SqlBasicCall like = (SqlBasicCall) left;
                  sqlNode = new SqlBasicCall(like.getOperator(), new SqlNode[]{like.operands[0], like.operands[1], right},
                          SqlParserPos.ZERO);
                  return false;
              }
              default:
                  throw new FastsqlException("not support " + x.getOperator());
  
          }
  
          if (someOrAllOperator != null) {
              this.sqlNode = new SqlBasicCall(someOrAllOperator, new SqlNode[]{left, right},
                      SqlParserPos.ZERO);
          } else {
              if (operator == SqlStdOperatorTable.IS_NULL
                      || operator == SqlStdOperatorTable.IS_NOT_NULL
                      || operator == SqlStdOperatorTable.IS_TRUE
                      || operator == SqlStdOperatorTable.IS_NOT_TRUE) {
                  this.sqlNode = new SqlBasicCall(operator,
                          new SqlNode[]{left},
                          SqlParserPos.ZERO);
              } else {
                  this.sqlNode = new SqlBasicCall(operator,
                          new SqlNode[]{left, right},
                          SqlParserPos.ZERO);
              }
          }
          return false;
      }
  
      public boolean visit(SQLBetweenExpr x) {
          SQLExpr testExpr = x.getTestExpr();
          SqlOperator sqlOperator = SqlStdOperatorTable.BETWEEN;
          if (x.isNot()) {
              sqlOperator = SqlStdOperatorTable.NOT_BETWEEN;
          }
          SqlNode sqlNode = convertToSqlNode(testExpr);
          SqlNode sqlNodeBegin = convertToSqlNode(x.getBeginExpr());
          SqlNode sqlNodeEnd = convertToSqlNode(x.getEndExpr());
          ArrayList<SqlNode> sqlNodes = new ArrayList<SqlNode>(3);
          sqlNodes.add(sqlNode);
          sqlNodes.add(sqlNodeBegin);
          sqlNodes.add(sqlNodeEnd);
          this.sqlNode = new SqlBasicCall(sqlOperator, SqlParserUtil.toNodeArray(sqlNodes), SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLExistsExpr x) {
          SqlOperator sqlOperator = SqlStdOperatorTable.EXISTS;
          SqlNode sqlNode = sqlOperator.createCall(SqlParserPos.ZERO,
                  convertToSqlNode(x.getSubQuery()));
          if (x.isNot()) {
              sqlNode = SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, sqlNode);
          }
          this.sqlNode = sqlNode;
          return false;
      }
  
      public boolean visit(SQLAllColumnExpr x) {
          sqlNode = new SqlIdentifier(Arrays.asList(""), SqlParserPos.ZERO);
  
          return false;
      }
  
      public boolean visit(SQLCharExpr x) {
          String text = x.getText();
          text = text.replaceAll("\\\\", "\\\\\\\\");
          sqlNode = SqlLiteral.createCharString(text, SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLNCharExpr x) {
          String text = x.getText();
          text = text.replaceAll("\\\\", "\\\\\\\\");
          sqlNode = SqlLiteral.createCharString(text, SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLNullExpr x) {
          sqlNode = SqlLiteral.createNull(SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLIntegerExpr x) {
          sqlNode = SqlLiteral.createExactNumeric(x.getNumber().toString(), SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLBooleanExpr x) {
          sqlNode = SqlLiteral.createBoolean(x.getBooleanValue(), SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLNumberExpr x) {
          String str = x.toString();
          if (str.indexOf('E') > 0 || str.indexOf('e') > 0) {
              sqlNode = SqlLiteral.createApproxNumeric(str, SqlParserPos.ZERO);
          } else {
              sqlNode = SqlLiteral.createExactNumeric(str, SqlParserPos.ZERO);
          }
          return false;
      }
  
      public boolean visit(SQLTimestampExpr x) {
          String literal = x.getValue();
          int precision = 0;
          if (literal.endsWith("00")) {
              char c3 = literal.charAt(literal.length() - 3);
              if (c3 >= '0' && c3 <= '9') {
                  literal = literal.substring(0, literal.length() - 2);
                  precision = 3;
              }
          }
          TimestampString ts = new TimestampString(literal);
          sqlNode = SqlLiteral.createTimestamp(ts, precision, SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLDateExpr x) {
          String literal = x.getValue();
          DateString ds = new DateString(literal);
          sqlNode = SqlLiteral.createDate(ds, SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLTimeExpr x) {
          String literal = x.getValue();
          TimeString ds = new TimeString(literal);
          sqlNode = SqlLiteral.createTime(ds, 0, SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLCurrentTimeExpr x) {
          sqlNode = new SqlIdentifier(x.getType().name, SqlParserPos.ZERO);
          return false;
      }
  
      public boolean visit(SQLAggregateExpr x) {
          SqlOperator functionOperator;
  
          String methodName = x.getMethodName();
  
          long hashCode64 = x.methodNameHashCode64();
  
          functionOperator = func(hashCode64);
  
          if (functionOperator == null) {
              functionOperator = new SqlUnresolvedFunction(new SqlIdentifier(methodName, SqlParserPos.ZERO),
                      null,
                      null,
                      null,
                      null,
                      SqlFunctionCategory.USER_DEFINED_FUNCTION);
          }
  
          SqlLiteral functionQualifier = null;
  
          if (x.getOption() == SQLAggregateOption.DISTINCT) {
              functionQualifier = SqlSelectKeyword.DISTINCT.symbol(SqlParserPos.ZERO);
          }
          List<SQLExpr> arguments = x.getArguments();
          List<SqlNode> argNodes = new ArrayList<SqlNode>(arguments.size());
          for (int i = 0, size = arguments.size(); i < size; ++i) {
              argNodes.add(convertToSqlNode(arguments.get(i)));
          }
          this.sqlNode = functionOperator.createCall(functionQualifier,
                  SqlParserPos.ZERO,
                  SqlParserUtil.toNodeArray(argNodes)
          );
  
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              SqlNodeList orderByItems = convertOrderby(orderBy);
  
              this.sqlNode = SqlStdOperatorTable.WITHIN_GROUP
                      .createCall(SqlParserPos.ZERO, this.sqlNode, orderByItems);
          }
  
          SQLOver over = x.getOver();
          if (over != null) {
              SqlNode aggNode = this.sqlNode;
              SQLOver.WindowingBound windowingBetweenBeginBound = over.getWindowingBetweenBeginBound();
              SQLOver.WindowingBound windowingBetweenEndBound = over.getWindowingBetweenEndBound();
  
              boolean isRow = over.getWindowingType() != SQLOver.WindowingType.RANGE;
              SqlNode lowerBound;
              if (over.getWindowingBetweenBegin() != null) {
                  over.getWindowingBetweenBegin().accept(this);
                  lowerBound = SqlWindow.createPreceding(sqlNode, SqlParserPos.ZERO);
              } else {
                  lowerBound = createSymbol(windowingBetweenBeginBound);
              }
              SqlNode upperBound = createSymbol(windowingBetweenEndBound);
  
              SqlWindow window = new SqlWindow(
                      SqlParserPos.ZERO,
                      null,
                      null,
                      convertToSqlNodeList(over.getPartitionBy()),
                      convertOrderby(over.getOrderBy()),
                      SqlLiteral.createBoolean(isRow, SqlParserPos.ZERO),
                      lowerBound,
                      upperBound,
                      null
              );
              sqlNode = SqlStdOperatorTable.OVER.createCall(
                      SqlParserPos.ZERO,
                      aggNode,
                      window);
          }
  
          SQLExpr filter = x.getFilter();
          if (filter != null) {
              SqlNode aggNode = this.sqlNode;
  
              filter.accept(this);
              sqlNode = SqlStdOperatorTable.FILTER.createCall(
                      SqlParserPos.ZERO,
                      aggNode,
                      sqlNode);
          }
  
          return false;
      }
  
      protected static SqlNode createSymbol(SQLOver.WindowingBound bound) {
          if (bound == null) {
              return null;
          }
  
          switch (bound) {
              case CURRENT_ROW:
                  return SqlWindow.createCurrentRow(SqlParserPos.ZERO);
              case UNBOUNDED_FOLLOWING:
                  return SqlWindow.createUnboundedFollowing(SqlParserPos.ZERO);
              case UNBOUNDED_PRECEDING:
                  return SqlWindow.createUnboundedPreceding(SqlParserPos.ZERO);
              default:
                  return null;
          }
      }
  
      public boolean visit(SQLMethodInvokeExpr x) {
          List<SQLExpr> arguments = x.getArguments();
          List<SqlNode> argNodes = new ArrayList<SqlNode>(arguments.size());
  
          long nameHashCode64 = x.methodNameHashCode64();
  
          SqlOperator functionOperator = func(nameHashCode64);
  
          String methodName = x.getMethodName();
  
          if (functionOperator == null) {
              if (nameHashCode64 == FnvHash.Constants.TRIM) {
                  functionOperator = SqlStdOperatorTable.TRIM;
                  if (arguments.size() == 1) {
                      SqlNode sqlNode = convertToSqlNode(arguments.get(0));
  
                      this.sqlNode = new com.alibaba.druid.support.calcite.CalciteSqlBasicCall(functionOperator,
                              new SqlNode[]{
                                      SqlLiteral.createSymbol(SqlTrimFunction.Flag.BOTH, SqlParserPos.ZERO),
                                      SqlCharStringLiteral.createCharString(" ", SqlParserPos.ZERO),
                                      sqlNode
                              },
                              SqlParserPos.ZERO,
                              false,
                              null);
                      return false;
                  }
  
              } else {
                  functionOperator = new SqlUnresolvedFunction(
                          new SqlIdentifier(methodName, SqlParserPos.ZERO),
                          null,
                          null,
                          null,
                          null,
                          SqlFunctionCategory.USER_DEFINED_FUNCTION);
              }
          }
  
          SqlLiteral functionQualifier = null;
  
          for (SQLExpr exp : arguments) {
              argNodes.add(convertToSqlNode(exp));
          }
  
          if ((nameHashCode64 == FnvHash.Constants.TIMESTAMPDIFF || nameHashCode64 == FnvHash.Constants.TIMESTAMPADD)
                  && argNodes.size() > 0
                  && argNodes.get(0) instanceof SqlIdentifier
          ) {
              SqlIdentifier arg0 = (SqlIdentifier) argNodes.get(0);
              TimeUnit timeUnit = TimeUnit.valueOf(arg0.toString().toUpperCase());
              argNodes.set(0, SqlLiteral.createSymbol(timeUnit, SqlParserPos.ZERO));
          }
  
          this.sqlNode = new com.alibaba.druid.support.calcite.CalciteSqlBasicCall(functionOperator,
                  SqlParserUtil.toNodeArray(argNodes),
                  SqlParserPos.ZERO,
                  false,
                  functionQualifier);
          return false;
      }
  
      public boolean visit(SQLInListExpr x) {
          SqlNodeList sqlNodes = convertToSqlNodeList(x.getTargetList());
          SqlOperator sqlOperator = x.isNot() ? SqlStdOperatorTable.NOT_IN : SqlStdOperatorTable.IN;
          sqlNode = new SqlBasicCall(sqlOperator, new SqlNode[]{convertToSqlNode(x.getExpr()), sqlNodes},
                  SqlParserPos.ZERO);
  
          return false;
      }
  
      public boolean visit(SQLVariantRefExpr x) {
          if ("?".equals(x.getName())) {
              this.sqlNode = new SqlDynamicParam(x.getIndex(),
                      SqlParserPos.ZERO);
              return false;
          } else {
              System.out.println("end");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLUnaryExpr x) {
          SQLUnaryOperator operator = x.getOperator();
          switch (operator) {
              case NOT:
                  this.sqlNode = SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO,
                          convertToSqlNode(x.getExpr()));
                  break;
              case Negative:
                  this.sqlNode = SqlStdOperatorTable.UNARY_MINUS.createCall(SqlParserPos.ZERO,
                          convertToSqlNode(x.getExpr()));
                  break;
              case Not:
              case Compl:
              case BINARY:
              default:
                  super.visit(x);
          }
          return false;
      }
  
      protected SqlNodeList convertToSqlNodeList(SQLExpr expr) {
          if (expr instanceof SQLListExpr) {
              return convertToSqlNodeList(((SQLListExpr) expr).getItems());
          } else {
              List<SqlNode> nodes = new ArrayList<SqlNode>(1);
              return new SqlNodeList(nodes, SqlParserPos.ZERO);
          }
      }
  
      protected SqlNodeList convertToSqlNodeList(List<? extends SQLExpr> exprList) {
          final int size = exprList.size();
  
          List<SqlNode> nodes = new ArrayList<SqlNode>(size);
          for (int i = 0; i < size; ++i) {
              SQLExpr expr = exprList.get(i);
              SqlNode node;
              if (expr instanceof SQLListExpr) {
                  node = convertToSqlNodeList(((SQLListExpr) expr).getItems());
              } else {
                  node = convertToSqlNode(expr);
              }
              nodes.add(node);
          }
  
          return new SqlNodeList(nodes, SqlParserPos.ZERO);
      }
  
      protected SqlNode convertToSqlNode(SQLObject ast) {
          if (ast == null) {
              return null;
          }
          CalciteMySqlNodeVisitor visitor = new CalciteMySqlNodeVisitor();
          ast.accept(visitor);
          return visitor.getSqlNode();
      }
  
      private SqlNodeList convertOrderby(SQLOrderBy orderBy) {
          if (orderBy == null) {
              return new SqlNodeList(new ArrayList(), SqlParserPos.ZERO);
          }
  
          List<SQLSelectOrderByItem> items = orderBy.getItems();
          List<SqlNode> orderByNodes = new ArrayList<SqlNode>(items.size());
  
          for (SQLSelectOrderByItem item : items) {
              SqlNode node = convertToSqlNode(item.getExpr());
              if (item.getType() == SQLOrderingSpecification.DESC) {
                  node = new SqlBasicCall(SqlStdOperatorTable.DESC, new SqlNode[]{node}, SqlParserPos.ZERO);
              }
              SQLSelectOrderByItem.NullsOrderType nullsOrderType = item.getNullsOrderType();
              if (nullsOrderType != null) {
                  switch (nullsOrderType) {
                      case NullsFirst:
                          node = new SqlBasicCall(SqlStdOperatorTable.NULLS_FIRST, new SqlNode[]{node}, SqlParserPos.ZERO);
                          break;
                      case NullsLast:
                          node = new SqlBasicCall(SqlStdOperatorTable.NULLS_LAST, new SqlNode[]{node}, SqlParserPos.ZERO);
                          break;
                      default:
                          break;
                  }
              }
              orderByNodes.add(node);
          }
  
          return new SqlNodeList(orderByNodes, SqlParserPos.ZERO);
      }
  
      private SqlNodeList convertHints(List<SQLCommentHint> hints) {
          if (hints == null) {
              return null;
          }
  
          List<SqlNode> nodes = new ArrayList<SqlNode>(hints.size());
  
          for (SQLCommentHint hint : hints) {
              if (hint instanceof TDDLHint) {
                  nodes.add(convertTDDLHint((TDDLHint) hint));
              }
          }
  
          return new SqlNodeList(nodes, SqlParserPos.ZERO);
  
      }
  
      private SqlNodeList convertTDDLHint(TDDLHint hint) {
          List<TDDLHint.Function> functions = hint.getFunctions();
          List<SqlNode> funNodes = new ArrayList<SqlNode>(functions.size());
  
          for (TDDLHint.Function function : functions) {
              String functionName = function.getName();
  
              List<TDDLHint.Argument> arguments = function.getArguments();
  
              SqlNode[] argNodes = new SqlNode[arguments.size()];
              for (int i = 0; i < arguments.size(); i++) {
                  TDDLHint.Argument argument = arguments.get(i);
                  SqlNode argName = convertToSqlNode(argument.getName());
                  SqlNode argValue = convertToSqlNode(argument.getValue());
  
                  List<SqlNode> arg = new ArrayList<SqlNode>();
                  if (argName != null) {
                      arg.add(argName);
                  }
                  if (argValue != null) {
                      arg.add(argValue);
                  }
  
                  SqlNode argNode = null;
                  if (arg.size() == 2) {
                      argNode = SqlStdOperatorTable.EQUALS.createCall(SqlParserPos.ZERO, arg);
                  } else if (arg.size() == 1) {
                      argNode = argName;
                  }
  
                  argNodes[i] = argNode;
              }
  
              SqlNode funNode = new SqlBasicCall(
                      new SqlUnresolvedFunction(new SqlIdentifier(functionName, SqlParserPos.ZERO), null, null,
                              null, null, SqlFunctionCategory.USER_DEFINED_FUNCTION), argNodes,
                      SqlParserPos.ZERO);
  
              funNodes.add(funNode);
          }
  
          return new SqlNodeList(funNodes, SqlParserPos.ZERO);
      }
  
      /**
       * Converts a list of SQLInsertStatement.ValuesClause objects to a single values clause if necessary.
       *
       * @param valuesClauseList the list of SQLInsertStatement.ValuesClause objects to be converted
       * @return a list containing a single SQLInsertStatement.ValuesClause object if the input list contains more than one clause and they are all literals; otherwise, the original list is returned
       */
      public static List<SQLInsertStatement.ValuesClause> convertToSingleValuesIfNeed(List<SQLInsertStatement.ValuesClause> valuesClauseList) {
          if (valuesClauseList.size() <= 1) {
              return valuesClauseList;
          }
  
          // If they are all literals
          for (SQLInsertStatement.ValuesClause clause : valuesClauseList) {
              for (SQLExpr expr : clause.getValues()) {
                  if (expr instanceof SQLVariantRefExpr) {
                      if (((SQLVariantRefExpr) expr).getName().equals("?")) {
                          continue;
                      }
                  }
                  return valuesClauseList;
              }
          }
  
          // Return only the first values clause.
          return Arrays.asList(valuesClauseList.get(0));
      }
  
      public boolean visit(SQLIntervalExpr x) {
          TimeUnit[] timeUnits = getTimeUnit(x.getUnit());
          List<SqlNode> convertedArgs = new ArrayList<SqlNode>(2);
          SqlIntervalQualifier unitNode = new SqlIntervalQualifier(timeUnits[0], timeUnits[1], SqlParserPos.ZERO);
          SqlLiteral valueNode = (SqlLiteral) convertToSqlNode(x.getValue());
          sqlNode = SqlIntervalLiteral.createInterval(1, valueNode.toValue(), unitNode, SqlParserPos.ZERO);
          return false;
      }
  
      public static TimeUnit[] getTimeUnit(SQLIntervalUnit unit) {
          TimeUnit[] timeUnits = new TimeUnit[2];
          switch (unit) {
              // case MICROSECOND:
              // timeUnits[0] = TimeUnit.MICROSECOND;
              // timeUnits[1] = TimeUnit.MICROSECOND;
              // break;
              case SECOND:
                  timeUnits[0] = TimeUnit.SECOND;
                  timeUnits[1] = TimeUnit.SECOND;
                  break;
              case MINUTE:
                  timeUnits[0] = TimeUnit.MINUTE;
                  timeUnits[1] = TimeUnit.MINUTE;
                  break;
              case HOUR:
                  timeUnits[0] = TimeUnit.HOUR;
                  timeUnits[1] = TimeUnit.HOUR;
                  break;
              case DAY:
                  timeUnits[0] = TimeUnit.DAY;
                  timeUnits[1] = TimeUnit.DAY;
                  break;
              case WEEK:
                  timeUnits[0] = TimeUnit.WEEK;
                  timeUnits[1] = TimeUnit.WEEK;
                  break;
              case MONTH:
                  timeUnits[0] = TimeUnit.MONTH;
                  timeUnits[1] = TimeUnit.MONTH;
                  break;
              case QUARTER:
                  timeUnits[0] = TimeUnit.QUARTER;
                  timeUnits[1] = TimeUnit.QUARTER;
                  break;
              case YEAR:
                  timeUnits[0] = TimeUnit.YEAR;
                  timeUnits[1] = TimeUnit.YEAR;
                  break;
              // case MINUTE_MICROSECOND:
              // timeUnits[0] = TimeUnit.MINUTE;
              // timeUnits[1] = TimeUnit.MICROSECOND;
              // break;
              case MINUTE_SECOND:
                  timeUnits[0] = TimeUnit.MINUTE;
                  timeUnits[1] = TimeUnit.SECOND;
                  break;
              // case HOUR_MICROSECOND:
              // timeUnits[0] = TimeUnit.HOUR;
              // timeUnits[1] = TimeUnit.MICROSECOND;
              // break;
              case HOUR_SECOND:
                  timeUnits[0] = TimeUnit.HOUR;
                  timeUnits[1] = TimeUnit.SECOND;
                  break;
              case HOUR_MINUTE:
                  timeUnits[0] = TimeUnit.HOUR;
                  timeUnits[1] = TimeUnit.MINUTE;
                  break;
              // case DAY_MICROSECOND:
              // timeUnits[0] = TimeUnit.DAY;
              // timeUnits[1] = TimeUnit.MICROSECOND;
              // break;
              case DAY_SECOND:
                  timeUnits[0] = TimeUnit.DAY;
                  timeUnits[1] = TimeUnit.SECOND;
                  break;
              case DAY_MINUTE:
                  timeUnits[0] = TimeUnit.DAY;
                  timeUnits[1] = TimeUnit.MINUTE;
                  break;
              case DAY_HOUR:
                  timeUnits[0] = TimeUnit.DAY;
                  timeUnits[1] = TimeUnit.HOUR;
                  break;
              case YEAR_MONTH:
                  timeUnits[0] = TimeUnit.YEAR;
                  timeUnits[1] = TimeUnit.MONTH;
                  break;
              default:
                  throw new ParserException("Unsupported time unit");
          }
          return timeUnits;
      }
  
      public boolean visit(SQLNotExpr x) {
          SQLExpr expr = x.getExpr();
          if (expr instanceof SQLIdentifierExpr) {
              long hashCode64 = ((SQLIdentifierExpr) expr).nameHashCode64();
              if (hashCode64 == FnvHash.Constants.UNKNOWN) {
                  sqlNode = SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, SqlLiteral.createUnknown(SqlParserPos.ZERO));
                  return false;
              }
          }
          expr.accept(this);
          sqlNode = SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO, sqlNode);
          return false;
      }
  
      @Override
      public boolean visit(SQLExtractExpr x) {
          x.getValue().accept(this);
          TimeUnit[] timeUnits = getTimeUnit(x.getUnit());
  
          sqlNode = SqlStdOperatorTable.EXTRACT
                  .createCall(
                          SqlParserPos.ZERO,
                          new SqlIntervalQualifier(timeUnits[0], timeUnits[1], SqlParserPos.ZERO),
                          sqlNode
                  );
          return false;
      }
  
      @Override
      public boolean visit(SQLGroupingSetExpr x) {
          sqlNode = SqlStdOperatorTable.GROUPING_SETS.createCall(
                  SqlParserPos.ZERO,
                  convertToSqlNodeList(x.getParameters())
          );
          return false;
      }
  
      @Override
      public boolean visit(SQLValuesQuery x) {
          List<SqlNode> valuesNodes = new ArrayList<SqlNode>();
          for (SQLExpr value : x.getValues()) {
              valuesNodes.add(
                      SqlStdOperatorTable.ROW.createCall(SqlParserPos.ZERO, convertToSqlNodeList(value)));
          }
  
          sqlNode = SqlStdOperatorTable.VALUES.createCall(SqlParserPos.ZERO, valuesNodes);
          return false;
      }
  
      @Override
      public boolean visit(SQLUnnestTableSource x) {
          sqlNode = SqlStdOperatorTable.UNNEST
                  .createCall(
                          SqlParserPos.ZERO,
                          convertToSqlNodeList(x.getItems())
                  );
  
          String alias = x.getAlias();
          if (alias != null) {
              sqlNode = new SqlBasicCall(
                      SqlStdOperatorTable.AS,
                      new SqlNode[]{sqlNode, new SqlIdentifier(alias, SqlParserPos.ZERO)},
                      SqlParserPos.ZERO
              );
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLDefaultExpr x) {
          sqlNode = SqlStdOperatorTable.DEFAULT.createCall(SqlParserPos.ZERO);
          return false;
      }
  
      @Override
      public boolean visit(MySqlExplainStatement x) {
          x.getStatement().accept(this);
          SqlNode explicandum = this.sqlNode;
          sqlNode = new SqlExplain(
                  SqlParserPos.ZERO,
                  explicandum,
                  SqlLiteral.createSymbol(SqlExplainLevel.EXPPLAN_ATTRIBUTES, SqlParserPos.ZERO),
                  SqlLiteral.createSymbol(SqlExplain.Depth.PHYSICAL, SqlParserPos.ZERO),
                  SqlLiteral.createSymbol(SqlExplainFormat.TEXT, SqlParserPos.ZERO),
                  0
          );
          return false;
      }
  
      static long JSON_VALUE = FnvHash.fnv1a_64_lower("JSON VALUE");
      static long JSON_OBJECT = FnvHash.fnv1a_64_lower("JSON OBJECT");
      static long JSON_ARRAY = FnvHash.fnv1a_64_lower("JSON ARRAY");
      static long JSON_SCALAR = FnvHash.fnv1a_64_lower("JSON SCALAR");
  }
  