content: /*
   * Copyright 1999-2017 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alibaba.druid.sql.visitor;
  
  import com.alibaba.druid.DbType;
  import com.alibaba.druid.sql.SQLDialect;
  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.ast.statement.SQLCreateTriggerStatement.TriggerType;
  import com.alibaba.druid.sql.ast.statement.SQLInsertStatement.ValuesClause;
  import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource.JoinType;
  import com.alibaba.druid.sql.ast.statement.SQLMergeStatement.WhenInsert;
  import com.alibaba.druid.sql.ast.statement.SQLMergeStatement.WhenUpdate;
  import com.alibaba.druid.sql.dialect.hive.ast.HiveInputOutputFormat;
  import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey;
  import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlOrderingExpr;
  import com.alibaba.druid.sql.dialect.mysql.ast.statement.*;
  import com.alibaba.druid.sql.dialect.odps.ast.OdpsNewExpr;
  import com.alibaba.druid.sql.dialect.oracle.ast.OracleSegmentAttributes;
  import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleCursorExpr;
  import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleDatetimeExpr;
  import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleCreatePackageStatement;
  import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleForStatement;
  import com.alibaba.druid.sql.dialect.oracle.parser.OracleFunctionDataType;
  import com.alibaba.druid.sql.dialect.oracle.parser.OracleProcedureDataType;
  import com.alibaba.druid.sql.parser.CharTypes;
  import com.alibaba.druid.sql.template.SQLSelectQueryTemplate;
  import com.alibaba.druid.util.FnvHash;
  import com.alibaba.druid.util.JdbcUtils;
  
  import java.io.InputStream;
  import java.io.Reader;
  import java.math.BigDecimal;
  import java.math.BigInteger;
  import java.sql.Blob;
  import java.sql.Clob;
  import java.sql.NClob;
  import java.text.SimpleDateFormat;
  import java.time.Instant;
  import java.time.LocalDate;
  import java.time.LocalDateTime;
  import java.time.LocalTime;
  import java.time.OffsetDateTime;
  import java.time.OffsetTime;
  import java.time.ZonedDateTime;
  import java.time.format.DateTimeFormatter;
  import java.time.temporal.TemporalAmount;
  import java.util.*;
  
  import static com.alibaba.druid.util.Utils.getBoolean;
  
  public class SQLASTOutputVisitor extends SQLASTVisitorAdapter implements ParameterizedVisitor, PrintableVisitor {
      public static Boolean defaultPrintStatementAfterSemi;
  
      static {
          try {
              defaultPrintStatementAfterSemi = getBoolean(System.getProperties(), "fastsql.sql.output.printStatementAfterSemi"); // compatible for early versions
          } catch (SecurityException ex) {
              // skip
          }
      }
  
      protected final StringBuilder appender;
      protected int indentCount;
      protected boolean ucase = true;
      protected int selectListNumberOfLine = 5;
  
      protected boolean groupItemSingleLine;
  
      protected List<Object> parameters;
      protected List<Object> inputParameters;
      protected Set<String> tables;
      protected String table; // for improved
  
      protected boolean exportTables;
  
      protected DbType dbType;
  
      protected Map<String, String> tableMapping;
  
      protected int replaceCount;
  
      protected boolean parameterizedMergeInList;
      protected boolean parameterizedQuesUnMergeInList;
      protected boolean parameterizedQuesUnMergeValuesList;
      protected boolean printNameQuote;
  
      protected boolean parameterized;
      protected boolean shardingSupport;
  
      protected transient int lines;
      private TimeZone timeZone;
      protected boolean endLineComment;
  
      protected SQLDialect dialect;
  
      protected Boolean printStatementAfterSemi = defaultPrintStatementAfterSemi;
  
      {
          features |= VisitorFeature.OutputPrettyFormat.mask;
      }
  
      public SQLASTOutputVisitor(StringBuilder appender) {
          this.appender = appender;
      }
  
      public SQLASTOutputVisitor(StringBuilder appender, DbType dbType) {
          this(appender, dbType, SQLDialect.of(dbType));
      }
  
      public SQLASTOutputVisitor(StringBuilder appender, DbType dbType, SQLDialect dialect) {
          this.appender = appender;
          this.dbType = dbType;
          this.dialect = dialect;
      }
  
      public SQLASTOutputVisitor(StringBuilder appender, boolean parameterized) {
          this.appender = appender;
          this.dialect = SQLDialect.of(dbType);
          this.config(VisitorFeature.OutputParameterized, parameterized);
      }
  
      public SQLASTOutputVisitor(StringBuilder appender, DbType dbType, boolean parameterized) {
          this(appender, dbType, SQLDialect.of(dbType), parameterized);
      }
  
      public SQLASTOutputVisitor(StringBuilder appender, DbType dbType, SQLDialect dialect, boolean parameterized) {
          this.appender = appender;
          this.dbType = dbType;
          this.dialect = dialect;
          this.config(VisitorFeature.OutputParameterized, parameterized);
      }
  
      public int getReplaceCount() {
          return this.replaceCount;
      }
  
      public void incrementReplaceCunt() {
          replaceCount++;
      }
  
      public TimeZone getTimeZone() {
          return timeZone;
      }
  
      public void setTimeZone(TimeZone timeZone) {
          this.timeZone = timeZone;
      }
  
      public void addTableMapping(String srcTable, String destTable) {
          if (tableMapping == null) {
              tableMapping = new HashMap<String, String>();
          }
  
          if (srcTable.indexOf('.') >= 0) {
              SQLExpr expr = SQLUtils.toSQLExpr(srcTable, dbType);
              if (expr instanceof SQLPropertyExpr) {
                  srcTable = ((SQLPropertyExpr) expr).simplify().toString();
              }
          } else {
              srcTable = SQLUtils.normalize(srcTable);
          }
          tableMapping.put(srcTable, destTable);
      }
  
      public void setTableMapping(Map<String, String> tableMapping) {
          this.tableMapping = tableMapping;
      }
  
      public List<Object> getParameters() {
          if (parameters == null) {
              parameters = new ArrayList<Object>();
          }
  
          return parameters;
      }
  
      public boolean isDesensitize() {
          return isEnabled(VisitorFeature.OutputDesensitize);
      }
  
      public void setDesensitize(boolean desensitize) {
          config(VisitorFeature.OutputDesensitize, desensitize);
      }
  
      public Set<String> getTables() {
          if (this.table != null && this.tables == null) {
              return Collections.singleton(this.table);
          }
          return this.tables;
      }
  
      @Deprecated
      public void setParameters(List<Object> parameters) {
          if (parameters != null && parameters.size() > 0) {
              this.inputParameters = parameters;
          } else {
              this.parameters = parameters;
          }
      }
  
      public void setInputParameters(List<Object> parameters) {
          this.inputParameters = parameters;
      }
  
      /**
       * @since 1.1.5
       */
      public void setOutputParameters(List<Object> parameters) {
          this.parameters = parameters;
      }
  
      public int getIndentCount() {
          return indentCount;
      }
  
      public StringBuilder getAppender() {
          return appender;
      }
  
      public boolean isPrettyFormat() {
          return isEnabled(VisitorFeature.OutputPrettyFormat);
      }
  
      public void setPrettyFormat(boolean prettyFormat) {
          config(VisitorFeature.OutputPrettyFormat, prettyFormat);
      }
  
      public void decrementIndent() {
          this.indentCount--;
      }
  
      public void incrementIndent() {
          this.indentCount++;
      }
  
      public boolean isParameterized() {
          return isEnabled(VisitorFeature.OutputParameterized);
      }
  
      public void setParameterized(boolean parameterized) {
          config(VisitorFeature.OutputParameterized, parameterized);
      }
  
      public boolean isParameterizedMergeInList() {
          return parameterizedMergeInList;
      }
  
      public void setParameterizedMergeInList(boolean parameterizedMergeInList) {
          this.parameterizedMergeInList = parameterizedMergeInList;
      }
  
      public boolean isParameterizedQuesUnMergeInList() {
          return parameterizedQuesUnMergeInList;
      }
  
      public void setParameterizedQuesUnMergeInList(boolean parameterizedQuesUnMergeInList) {
          config(VisitorFeature.OutputParameterizedQuesUnMergeInList, parameterizedQuesUnMergeInList);
      }
  
      public boolean isExportTables() {
          return exportTables;
      }
  
      public void setExportTables(boolean exportTables) {
          this.exportTables = exportTables;
      }
  
      public final void print(char value) {
          if (endLineComment) {
              println();
          }
  
          print0(value);
      }
  
      protected final void print0(char value) {
          if (this.appender == null) {
              return;
          }
  
          this.appender.append(value);
      }
  
      public void print(int value) {
          if (this.appender == null) {
              return;
          }
  
          appender.append(value);
      }
  
      public void print(long value) {
          if (this.appender == null) {
              return;
          }
  
          appender.append(value);
      }
  
      public void print(float value) {
          if (this.appender == null) {
              return;
          }
  
          appender.append(value);
      }
  
      public void print(double value) {
          if (this.appender == null) {
              return;
          }
  
          appender.append(value);
      }
  
      public void print(Date date) {
          if (this.appender == null) {
              return;
          }
  
          SimpleDateFormat dateFormat;
          if (date instanceof java.sql.Date) {
              dateFormat = new SimpleDateFormat("yyyy-MM-dd");
              print0("DATE ");
          } else if (date instanceof java.sql.Time) {
              dateFormat = new SimpleDateFormat("HH:mm:ss");
              print0("TIME ");
          } else {
              dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
              print0("TIMESTAMP ");
          }
  
          if (timeZone != null) {
              dateFormat.setTimeZone(timeZone);
          }
  
          print0("'" + dateFormat.format(date) + "'");
      }
  
      public void print(String text) {
          if (this.appender == null) {
              return;
          }
          print0(text);
      }
  
      protected void print0(String text) {
          if (appender == null) {
              return;
          }
  
          this.appender.append(text);
      }
  
      protected void printUcase(String text) {
          print0(ucase ? text.toUpperCase() : text.toLowerCase());
      }
      protected void printName0(String name, boolean needQuote) {
          StringBuilder appender = this.appender;
          if (appender == null || name.isEmpty()) {
              return;
          }
  
          SQLDialect dialect = this.dialect;
          char quote = '"';
          boolean keyword = false;
          if (isEnabled(VisitorFeature.OutputNameQuote) && dialect != null && dialect.getQuoteChars() != SQLDialect.DEFAULT_QUOTE_INT && needQuote) {
              keyword = dialect.isKeyword(name);
              quote = SQLDialect.Quote.getQuote(dialect.getQuoteChars());
          }
  
          if (keyword) {
              appender.append(quote);
          }
          appender.append(name);
          if (keyword) {
              appender.append(quote);
          }
      }
  
      protected void printName0(String name) {
          printName0(name, true);
      }
  
      protected boolean nameHasSpecial(String alias) {
          int len = alias.length();
          if (len == 0) {
              return false;
          }
          char c0 = alias.charAt(0);
          if (c0 != '"' && c0 != '\'' && c0 != '`' && c0 != '[') {
              for (int i = 1; i < len; ++i) {
                  char ch = alias.charAt(i);
                  if (ch < 256 &&
                          !((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || ch == '$')) {
                      return true;
                  }
              }
          }
          return false;
      }
  
      protected void printAlias0(String alias) {
          boolean special = nameHasSpecial(alias);
          StringBuilder appender = this.appender;
          SQLDialect dialect = this.dialect;
          char quote = dialect != null ? SQLDialect.Quote.getQuote(dialect.getQuoteChars()) : '"';
          boolean needQuote = special;
          if (!needQuote && isEnabled(VisitorFeature.OutputNameQuote) && dialect != null) {
              needQuote = dialect.isAliasKeyword(alias);
          }
  
          if (needQuote) {
              appender.append(quote);
          }
          appender.append(alias);
          if (needQuote) {
              appender.append(quote);
          }
      }
  
      protected void printAlias(String alias) {
          if (alias == null || alias.length() == 0) {
              return;
          }
  
          print(' ');
  
          printAlias0(alias);
      }
  
      protected void printPrefix(boolean predicate, String ucaseMessage, String lcaseMessage) {
          if (!predicate) {
              return;
          }
          print0(ucase ? ucaseMessage : lcaseMessage);
      }
  
      protected void printAndAccept(List<? extends SQLObject> nodes, String seperator) {
          for (int i = 0, size = nodes.size(); i < size; ++i) {
              if (i != 0) {
                  print0(seperator);
              }
              nodes.get(i).accept(this);
          }
      }
  
      protected void printlnAndAccept(List<? extends SQLObject> nodes, boolean needPrintLine) {
          if (needPrintLine) {
              incrementIndent();
              println();
          }
          for (int i = 0; i < nodes.size(); i++) {
              if (i != 0) {
                  if (needPrintLine) {
                      print0(',');
                      println();
                  } else {
                      print0(", ");
                  }
              }
              nodes.get(i)
                      .accept(this);
          }
          if (needPrintLine) {
              decrementIndent();
              println();
          }
      }
  
      protected void printAndAccept(
              String prefix,
              String suffix,
              List<? extends SQLObject> nodes,
              String separator,
              int lineItems
      ) {
          int size = nodes.size();
          print(prefix);
          incrementIndent();
          if (size > lineItems) {
              println();
          }
          for (int i = 0; i < size; ++i) {
              if (i != 0) {
                  if (i % lineItems == 0) {
                      println(separator.trim());
                  } else {
                      print0(separator);
                  }
              }
              nodes.get(i).accept(this);
          }
          decrementIndent();
          if (size > lineItems) {
              println();
          }
          print(suffix);
      }
  
      protected void printAndAccept(List<? extends SQLExpr> nodes, String seperator, boolean parameterized) {
          for (int i = 0, size = nodes.size(); i < size; ++i) {
              if (i != 0) {
                  print0(seperator);
              }
              printExpr(nodes.get(i), parameterized);
          }
      }
  
      private static int paramCount(SQLExpr x) {
          if (x instanceof SQLName) {
              return 1;
          }
  
          if (x instanceof SQLAggregateExpr) {
              SQLAggregateExpr aggregateExpr = (SQLAggregateExpr) x;
              List<SQLExpr> args = aggregateExpr.getArguments();
              int paramCount = 1;
              for (SQLExpr arg : args) {
                  paramCount += paramCount(arg);
              }
              if (aggregateExpr.getOver() != null) {
                  paramCount += 1;
              }
              return paramCount;
          }
  
          if (x instanceof SQLMethodInvokeExpr) {
              List<SQLExpr> params = ((SQLMethodInvokeExpr) x).getArguments();
              int paramCount = 1;
              for (SQLExpr param : params) {
                  paramCount += paramCount(param);
              }
              return paramCount;
          }
  
          if (x instanceof SQLBinaryOpExpr) {
              return paramCount(((SQLBinaryOpExpr) x).getLeft())
                      + paramCount(((SQLBinaryOpExpr) x).getRight());
          }
  
          if (x instanceof SQLCaseExpr) {
              return 10;
          }
  
          return 1;
      }
  
      protected void printSelectList(List<SQLSelectItem> selectList) {
          this.indentCount++;
          for (int i = 0, lineItemCount = 0, size = selectList.size(); i < size; ++i, ++lineItemCount) {
              SQLSelectItem selectItem = selectList.get(i);
              if (selectItem.hasBeforeComment()) {
                  printlnComments(selectItem.getBeforeCommentsDirect());
              }
              SQLExpr selectItemExpr = selectItem.getExpr();
  
              int paramCount = paramCount(selectItemExpr);
  
              boolean methodOrBinary = (!(selectItemExpr instanceof SQLName))
                      && (selectItemExpr instanceof SQLMethodInvokeExpr
                      || selectItemExpr instanceof SQLAggregateExpr
                      || selectItemExpr instanceof SQLBinaryOpExpr);
  
              if (methodOrBinary) {
                  lineItemCount += (paramCount - 1);
              }
  
              if (i != 0) {
                  SQLSelectItem preSelectItem = selectList.get(i - 1);
                  if (preSelectItem.getAfterCommentsDirect() != null) {
                      lineItemCount = 0;
                      println();
                  } else if (methodOrBinary) {
                      if (lineItemCount >= selectListNumberOfLine) {
                          lineItemCount = paramCount;
                          println();
                      }
                  } else if (lineItemCount >= selectListNumberOfLine
                          || selectItemExpr instanceof SQLQueryExpr
                          || selectItemExpr instanceof SQLCaseExpr
                          || (selectItemExpr instanceof SQLCharExpr && ((SQLCharExpr) selectItemExpr).getText().length() > 20)) {
                      lineItemCount = 0;
                      println();
                  }
                  if (selectItem.getExpr() instanceof SQLVariantRefExpr && !((SQLVariantRefExpr) selectItem.getExpr()).isHasPrefixComma()) {
                      print0(" ");
                  } else {
                      print0(", ");
                  }
              }
  
              if (selectItem.getClass() == SQLSelectItem.class) {
                  this.visit(selectItem);
              } else {
                  selectItem.accept(this);
              }
  
              if (selectItem.hasAfterComment()) {
                  print(' ');
                  printlnComment(selectItem.getAfterCommentsDirect());
              }
          }
          this.indentCount--;
      }
  
      protected void printlnAndAccept(List<? extends SQLObject> nodes, String seperator) {
          for (int i = 0, size = nodes.size(); i < size; ++i) {
              if (i != 0) {
                  println(seperator);
              }
  
              ((SQLObject) nodes.get(i)).accept(this);
          }
      }
  
      protected void printIndent() {
          if (this.appender == null) {
              return;
          }
  
          for (int i = 0; i < this.indentCount; ++i) {
              this.appender.append('\t');
          }
      }
  
      public void println() {
          if (!isPrettyFormat() && !endLineComment) {
              print(' ');
              return;
          }
  
          print0('\n');
          lines++;
          printIndent();
          if (endLineComment) {
              endLineComment = false;
          }
      }
  
      public void println(String text) {
          print(text);
          println();
      }
  
      public void println(char ch) {
          print(ch);
          println();
      }
  
      // ////////////////////
  
      public boolean visit(SQLBetweenExpr x) {
          final SQLExpr testExpr = x.getTestExpr();
          final SQLExpr beginExpr = x.getBeginExpr();
          final SQLExpr endExpr = x.getEndExpr();
          if (x.isParenthesized()) {
              print('(');
          }
          if (testExpr != null) {
              printExpr(testExpr, parameterized);
          }
  
          if (x.isNot()) {
              print0(ucase ? " NOT BETWEEN " : " not between ");
          } else {
              print0(ucase ? " BETWEEN " : " between ");
          }
  
          int lines = this.lines;
          if (beginExpr instanceof SQLBinaryOpExpr) {
              SQLBinaryOpExpr binaryOpBegin = (SQLBinaryOpExpr) beginExpr;
              incrementIndent();
              printExpr(beginExpr, parameterized);
              decrementIndent();
          } else {
              printExpr(beginExpr, parameterized);
          }
  
          if (lines != this.lines) {
              println();
              print0(ucase ? "AND " : "and ");
          } else {
              print0(ucase ? " AND " : " and ");
          }
  
          if (endExpr instanceof SQLBinaryOpExpr) {
              SQLBinaryOpExpr binaryOpEnd = (SQLBinaryOpExpr) endExpr;
              incrementIndent();
              printExpr(endExpr, parameterized);
              decrementIndent();
          } else {
              printExpr(endExpr, parameterized);
          }
  
          if (x.getHint() != null) {
              x.getHint().accept(this);
          }
          if (x.isParenthesized()) {
              print(')');
          }
          return false;
      }
  
      public boolean visit(SQLBinaryOpExprGroup x) {
          SQLObject parent = x.getParent();
          SQLBinaryOperator operator = x.getOperator();
  
          boolean isRoot = parent instanceof SQLSelectQueryBlock || parent instanceof SQLBinaryOpExprGroup;
  
          List<SQLExpr> items = x.getItems();
          if (items.isEmpty()) {
              print("true");
              return false;
          }
  
          if (isRoot) {
              this.indentCount++;
          }
  
          if (this.parameterized) {
              SQLExpr firstLeft = null;
              SQLBinaryOperator firstOp = null;
              List<Object> parameters = new ArrayList<Object>(items.size());
  
              List<SQLBinaryOpExpr> literalItems = null;
  
              if ((operator != SQLBinaryOperator.BooleanOr || !isEnabled(VisitorFeature.OutputParameterizedQuesUnMergeOr)) &&
                      (operator != SQLBinaryOperator.BooleanAnd || !isEnabled(VisitorFeature.OutputParameterizedQuesUnMergeAnd))) {
                  for (int i = 0; i < items.size(); i++) {
                      SQLExpr item = items.get(i);
                      if (item instanceof SQLBinaryOpExpr) {
                          SQLBinaryOpExpr binaryItem = (SQLBinaryOpExpr) item;
                          SQLExpr left = binaryItem.getLeft();
                          SQLExpr right = binaryItem.getRight();
  
                          if (right instanceof SQLLiteralExpr && !(right instanceof SQLNullExpr)) {
                              if (left instanceof SQLLiteralExpr) {
                                  if (literalItems == null) {
                                      literalItems = new ArrayList<SQLBinaryOpExpr>();
                                  }
                                  literalItems.add(binaryItem);
                                  continue;
                              }
  
                              if (this.parameters != null) {
                                  ExportParameterVisitorUtils.exportParameter(parameters, right);
                              }
                          } else if (right instanceof SQLVariantRefExpr) {
                              // skip
                          } else {
                              firstLeft = null;
                              break;
                          }
  
                          if (firstLeft == null) {
                              firstLeft = binaryItem.getLeft();
                              firstOp = binaryItem.getOperator();
                          } else {
                              if (firstOp != binaryItem.getOperator() || !SQLExprUtils.equals(firstLeft, left)) {
                                  firstLeft = null;
                                  break;
                              }
                          }
                      } else {
                          firstLeft = null;
                          break;
                      }
                  }
              }
  
              if (firstLeft != null) {
                  if (literalItems != null) {
                      for (SQLBinaryOpExpr literalItem : literalItems) {
                          visit(literalItem);
                          println();
                          printOperator(operator);
                          print(' ');
  
                      }
                  }
                  printExpr(firstLeft, parameterized);
                  print(' ');
                  printOperator(firstOp);
                  print0(" ?");
  
                  if (this.parameters != null) {
                      if (parameters.size() > 0) {
                          this.parameters.addAll(parameters);
                      }
                  }
  
                  incrementReplaceCunt();
                  if (isRoot) {
                      this.indentCount--;
                  }
                  return false;
              }
          }
  
          for (int i = 0; i < items.size(); i++) {
              SQLExpr item = items.get(i);
  
              if (i != 0) {
                  println();
                  printOperator(operator);
                  print(' ');
              }
  
              if (item.hasBeforeComment()) {
                  printlnComments(item.getBeforeCommentsDirect());
              }
  
              if (item instanceof SQLBinaryOpExpr) {
                  SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) item;
                  SQLExpr binaryOpExprRight = binaryOpExpr.getRight();
                  SQLBinaryOperator itemOp = binaryOpExpr.getOperator();
  
                  boolean isLogic = itemOp.isLogical();
                  if (isLogic) {
                      indentCount++;
                  }
  
                  boolean bracket = false;
                  if (itemOp.priority > operator.priority) {
                      bracket = true;
                  } else {
                      //bracket = !parameterized && !((SQLBinaryOpExpr) item).isParenthesized();
                  }
                  visit(binaryOpExpr);
  //
  //                if (item.hasAfterComment() && !parameterized) {
  //                    print(' ');
  //                    printlnComment(item.getAfterCommentsDirect());
  //                }
  
                  if (isLogic) {
                      indentCount--;
                  }
              } else if (item instanceof SQLBinaryOpExprGroup) {
                  print('(');
                  visit((SQLBinaryOpExprGroup) item);
                  print(')');
              } else {
                  printExpr(item, parameterized);
              }
          }
          List<String> afterComments = x.getAfterCommentsDirect();
          if (!parameterized) {
              if (afterComments != null && !afterComments.isEmpty() && isPrettyFormat()) {
                  print(' ');
              }
              printlnComment(afterComments);
          }
          if (isRoot) {
              this.indentCount--;
          }
          return false;
      }
  
      public boolean visit(SQLBinaryOpExpr x) {
          if (x.isParenthesized()) {
              print('(');
          }
          boolean rs = visitInternal(x);
          if (x.isParenthesized()) {
              print(')');
          }
          List<String> afterComments = x.getAfterCommentsDirect();
          if (!parameterized) {
              if (afterComments != null && !afterComments.isEmpty() && isPrettyFormat()) {
                  print(' ');
              }
              printlnComment(afterComments);
          }
          return rs;
      }
  
      public boolean visit(SQLTypeExpr x) {
          boolean needPrefixDataType = ! (x instanceof SQLBooleanExpr);
          boolean needQuota = ! (x instanceof SQLBooleanExpr);
          if (needPrefixDataType) {
              String dataType = x.getDataType().getName();
              print(ucase ? dataType.toUpperCase() : dataType.toLowerCase());
              print(' ');
          }
          if (parameterized) {
              print('?');
          } else {
              if (needQuota) {
                  print("'");
              }
              print(x.getValue().toString());
              if (needQuota) {
                  print("'");
              }
          }
          return false;
      }
      public boolean visitInternal(SQLBinaryOpExpr x) {
          SQLBinaryOperator operator = x.getOperator();
          if (this.parameterized
                  && operator == SQLBinaryOperator.BooleanOr
                  && !isEnabled(VisitorFeature.OutputParameterizedQuesUnMergeOr)) {
              x = SQLBinaryOpExpr.merge(this, x);
  
              operator = x.getOperator();
          }
  
          if (inputParameters != null
                  && inputParameters.size() > 0
                  && operator == SQLBinaryOperator.Equality
                  && x.getRight() instanceof SQLVariantRefExpr
          ) {
              SQLVariantRefExpr right = (SQLVariantRefExpr) x.getRight();
              int index = right.getIndex();
              if (index >= 0 && index < inputParameters.size()) {
                  Object param = inputParameters.get(index);
                  if (param instanceof Collection) {
                      x.getLeft().accept(this);
                      print0(" IN (");
                      right.accept(this);
                      print(')');
                      return false;
                  }
              }
          }
  
          SQLObject parent = x.getParent();
          boolean isRoot = parent instanceof SQLSelectQueryBlock;
          boolean relational = operator == SQLBinaryOperator.BooleanAnd
                  || operator == SQLBinaryOperator.BooleanOr;
          if (isRoot && relational) {
              this.indentCount++;
          }
  
          List<SQLExpr> groupList = new ArrayList<SQLExpr>();
          SQLExpr left = x.getLeft();
          SQLExpr right = x.getRight();
  
          if (inputParameters != null
                  && operator != SQLBinaryOperator.Equality) {
              int varIndex = -1;
              if (right instanceof SQLVariantRefExpr) {
                  varIndex = ((SQLVariantRefExpr) right).getIndex();
              }
  
              Object param = null;
              if (varIndex >= 0 && varIndex < inputParameters.size()) {
                  param = inputParameters.get(varIndex);
              }
  
              if (param instanceof Collection) {
                  Collection values = (Collection) param;
  
                  if (values.size() > 0) {
                      print('(');
                      int valIndex = 0;
                      for (Object value : values) {
                          if (valIndex++ != 0) {
                              print0(ucase ? " OR " : " or ");
                          }
                          printExpr(left, parameterized);
                          print(' ');
                          if (operator == SQLBinaryOperator.Is) {
                              print('=');
                          } else {
                              printOperator(operator);
                          }
                          print(' ');
                          printParameter(value);
                      }
                      print(')');
                      return false;
                  }
              }
          }
  
          if (operator.isRelational()
                  && left instanceof SQLIntegerExpr
                  && right instanceof SQLIntegerExpr) {
              print(((SQLIntegerExpr) left).getNumber().longValue());
              print(' ');
              printOperator(operator);
              print(' ');
              Number number = ((SQLIntegerExpr) right).getNumber();
              if (right.hasBeforeComment()) {
                  printlnComments(right.getBeforeCommentsDirect());
              }
              if (number instanceof BigInteger) {
                  print0(((BigInteger) number).toString());
              } else {
                  print(number.longValue());
              }
              if (right.hasAfterComment()) {
                  printAfterComments(right.getAfterCommentsDirect());
              }
              return false;
          }
  
          for (; ; ) {
              if (left instanceof SQLBinaryOpExpr && ((SQLBinaryOpExpr) left).getOperator() == operator
                  && operator != SQLBinaryOperator.IsNot && operator != SQLBinaryOperator.Is && operator != SQLBinaryOperator.Modulus) {
                  SQLBinaryOpExpr binaryLeft = (SQLBinaryOpExpr) left;
                  groupList.add(binaryLeft.getRight());
                  left = binaryLeft.getLeft();
              } else {
                  groupList.add(left);
                  break;
              }
          }
  
          for (int i = groupList.size() - 1; i >= 0; --i) {
              SQLExpr item = groupList.get(i);
  
              if (relational) {
                  if (isPrettyFormat() && item.hasBeforeComment() && !parameterized) {
                      printlnComments(item.getBeforeCommentsDirect());
                  }
              }
  
              visitBinaryLeft(item, operator);
  
  //            if (isPrettyFormat() && item.hasAfterComment()) {
  //                print(' ');
  //                printlnComment(item.getAfterCommentsDirect());
  //            }
  
              if (i != groupList.size() - 1
                      && isPrettyFormat()
                      && item.getParent() != null
                      && item.getParent().hasAfterComment()
                      && !parameterized) {
                  print(' ');
                  printlnComment(item.getParent().getAfterCommentsDirect());
              }
  
              boolean printOpSpace = true;
              if (relational) {
                  if ((DbType.hive == dbType || DbType.spark == dbType) && x.getParent() instanceof SQLMethodInvokeExpr) {
                      print(' ');
                  } else {
                      println();
                  }
              } else {
                  if (operator == SQLBinaryOperator.Modulus
                          && DbType.oracle == dbType
                          && left instanceof SQLIdentifierExpr
                          && right instanceof SQLIdentifierExpr
                          && ((SQLIdentifierExpr) right).getName().equalsIgnoreCase("NOTFOUND")) {
                      printOpSpace = false;
                  }
                  if (printOpSpace && operator != SQLBinaryOperator.Blank) {
                      print(' ');
                  }
              }
              printOperator(operator);
              if (printOpSpace) {
                  print(' ');
              }
          }
          visitorBinaryRight(x);
  
          if (isRoot && relational) {
              this.indentCount--;
          }
  
          return false;
      }
  
      protected void printOperator(SQLBinaryOperator operator) {
          print0(ucase ? operator.name : operator.nameLCase);
      }
  
      private void visitorBinaryRight(SQLBinaryOpExpr x) {
          SQLExpr right = x.getRight();
          SQLBinaryOperator op = x.getOperator();
          if (right instanceof SQLBinaryOpExpr) {
              SQLBinaryOpExpr binaryRight = (SQLBinaryOpExpr) right;
              SQLBinaryOperator rightOp = binaryRight.getOperator();
  
              boolean rightRational = rightOp == SQLBinaryOperator.BooleanAnd
                      || rightOp == SQLBinaryOperator.BooleanOr;
  
              if (rightOp.priority >= op.priority
                      || (rightOp != op
                      && rightOp.isLogical()
                      && op.isLogical()
              )) {
                  if (rightRational) { //这里需要验证
                      this.indentCount++;
                  }
                  //print('(');
                  printExpr(binaryRight, parameterized);
                  //print(')');
  
                  if (rightRational) {
                      this.indentCount--;
                  }
              } else {
                  printExpr(binaryRight, parameterized);
              }
          } else if (right instanceof SQLBinaryOpExprGroup) {
              SQLBinaryOpExprGroup group = (SQLBinaryOpExprGroup) right;
              if (group.getOperator() == x.getOperator()) {
                  visit(group);
              } else {
                  indentCount++;
                  print('(');
                  visit(group);
                  print(')');
                  indentCount--;
              }
          } else if (SQLBinaryOperator.Equality.priority >= op.priority
                  && (right instanceof SQLNotExpr)) {
              indentCount++;
              print('(');
              printExpr(right, parameterized);
              print(')');
              indentCount--;
          } else {
              printExpr(right, parameterized);
          }
  
          if (x.getHint() != null) {
              x.getHint().accept(this);
          }
      }
  
      private void visitBinaryLeft(SQLExpr left, SQLBinaryOperator op) {
          if (left instanceof SQLBinaryOpExpr) {
              SQLBinaryOpExpr binaryLeft = (SQLBinaryOpExpr) left;
              SQLBinaryOperator leftOp = binaryLeft.getOperator();
              SQLObject parent = left.getParent();
              boolean leftRational = leftOp == SQLBinaryOperator.BooleanAnd
                      || leftOp == SQLBinaryOperator.BooleanOr;
  
              final boolean bracket;
              if (leftOp.priority > op.priority) {
                  bracket = true;
              } else if (leftOp.priority == op.priority
                      && parent instanceof SQLBinaryOpExpr
                      && parent.getParent() instanceof SQLBinaryOpExpr
                      && leftOp.priority == ((SQLBinaryOpExpr) parent.getParent()).getOperator().priority
                      && left == ((SQLBinaryOpExpr) parent).getRight()) {
                  bracket = true;
              } else if ((leftOp == SQLBinaryOperator.Is || leftOp == SQLBinaryOperator.IsNot) && !op.isLogical()) {
                  bracket = true;
              } else if (binaryLeft.isParenthesized()) {
                  if (leftOp != op && ((leftOp.isLogical() && op.isLogical()) || op == SQLBinaryOperator.Is)) {
                      bracket = true;
                  } else {
                      bracket = false;
                  }
              } else {
                  bracket = false;
              }
  
              if (bracket) {
                  if (leftRational) {
                      this.indentCount++;
                  }
                  //print('(');
                  printExpr(left, parameterized);
                 // print(')');
  
                  if (leftRational) {
                      this.indentCount--;
                  }
              } else {
                  printExpr(left, parameterized);
              }
          } else if (left instanceof SQLBinaryOpExprGroup) {
              SQLBinaryOpExprGroup group = (SQLBinaryOpExprGroup) left;
              if (group.getOperator() == op) {
                  visit(group);
              } else {
                  indentCount++;
                  print('(');
                  visit(group);
                  print(')');
                  indentCount--;
              }
          } else if (left instanceof SQLInListExpr) {
              SQLInListExpr inListExpr = (SQLInListExpr) left;
              boolean quote;
              if (inListExpr.isNot()) {
                  quote = op.priority <= SQLBinaryOperator.Equality.priority;
              } else {
                  quote = op.priority < SQLBinaryOperator.Equality.priority;
              }
              if (inListExpr.isParenthesized()) {
                  quote = false;
              }
              if (quote) {
                  print('(');
              }
              visit(inListExpr);
              if (quote) {
                  print(')');
              }
          } else if (left instanceof SQLBetweenExpr) {
              SQLBetweenExpr betweenExpr = (SQLBetweenExpr) left;
              visit(betweenExpr);
          } else if (left instanceof SQLNotExpr) {
              printExpr(left);
          } else if (left instanceof SQLUnaryExpr) {
              SQLUnaryExpr unary = (SQLUnaryExpr) left;
  
              boolean quote = true;
              switch (unary.getOperator()) {
                  case BINARY:
                      quote = false;
                      break;
                  case Plus:
                  case Negative:
                      quote = op.priority < SQLBinaryOperator.Add.priority;
                      break;
                  default:
                      break;
              }
              if (((SQLUnaryExpr) left).isParenthesized()) {
                  quote = false;
              }
              if (quote) {
                  //print('(');
                  printExpr(left);
                 //print(')');
              } else {
                  printExpr(left);
              }
          } else {
              printExpr(left, parameterized);
          }
      }
  
      protected void printTableSource(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);
          }
      }
  
      protected void printQuery(SQLSelectQuery x) {
          Class<?> clazz = x.getClass();
          if (clazz == SQLSelectQueryBlock.class) {
              visit((SQLSelectQueryBlock) x);
          } else if (clazz == SQLUnionQuery.class) {
              visit((SQLUnionQuery) x);
          } else {
              x.accept(this);
          }
      }
  
      protected final void printExpr(SQLExpr x) {
          printExpr(x, parameterized);
      }
  
      protected final void printExpr(SQLExpr x, boolean parameterized) {
          Class<?> clazz = x.getClass();
          if (clazz == SQLIdentifierExpr.class) {
              visit((SQLIdentifierExpr) x);
          } else if (clazz == SQLPropertyExpr.class) {
              visit((SQLPropertyExpr) x);
          } else if (clazz == SQLAllColumnExpr.class) {
              visit((SQLAllColumnExpr) x);
          } else if (clazz == SQLAggregateExpr.class) {
              visit((SQLAggregateExpr) x);
          } else if (clazz == SQLBinaryOpExpr.class) {
              visit((SQLBinaryOpExpr) x);
          } else if (clazz == SQLCharExpr.class) {
              visit((SQLCharExpr) x, parameterized);
          } else if (clazz == SQLNullExpr.class) {
              visit((SQLNullExpr) x);
          } else if (clazz == SQLIntegerExpr.class) {
              printInteger((SQLIntegerExpr) x, parameterized);
          } else if (clazz == SQLNumberExpr.class) {
              visit((SQLNumberExpr) x);
          } else if (clazz == SQLMethodInvokeExpr.class) {
              visit((SQLMethodInvokeExpr) x);
          } else if (clazz == SQLVariantRefExpr.class) {
              visit((SQLVariantRefExpr) x);
          } else if (clazz == SQLBinaryOpExprGroup.class) {
              visit((SQLBinaryOpExprGroup) x);
          } else if (clazz == SQLCaseExpr.class) {
              visit((SQLCaseExpr) x);
          } else if (clazz == SQLInListExpr.class) {
              visit((SQLInListExpr) x);
          } else if (clazz == SQLNotExpr.class) {
              visit((SQLNotExpr) x);
          } else {
              x.accept(this);
          }
      }
      public boolean visit(SQLCaseExpr x) {
          if (x.isParenthesized()) {
              print('(');
          }
          this.indentCount++;
          print0(ucase ? "CASE" : "case");
  
          SQLExpr valueExpr = x.getValueExpr();
          if (valueExpr != null) {
              print(' ');
              printExpr(valueExpr, parameterized);
          }
  
          List<SQLCaseExpr.Item> items = x.getItems();
          for (int i = 0, size = items.size(); i < size; ++i) {
              println();
              visit(items.get(i));
          }
  
          SQLExpr elExpr = x.getElseExpr();
          if (elExpr != null) {
              println();
              print0(ucase ? "ELSE " : "else ");
              if (elExpr instanceof SQLCaseExpr) {
                  this.indentCount++;
                  println();
                  visit((SQLCaseExpr) elExpr);
                  this.indentCount--;
              } else {
                  printExpr(elExpr, parameterized);
              }
          }
  
          this.indentCount--;
          println();
          print0(ucase ? "END" : "end");
          if (x.isParenthesized()) {
              print(')');
          }
          return false;
      }
  
      public boolean visit(SQLCaseExpr.Item x) {
          print0(ucase ? "WHEN " : "when ");
          SQLExpr conditionExpr = x.getConditionExpr();
          int lines = this.lines;
          this.indentCount++;
          printExpr(conditionExpr, parameterized);
          this.indentCount--;
  
          boolean println = lines != this.lines;
          if (isPrettyFormat() && !println) {
              List<String> afterComments = conditionExpr.getAfterCommentsDirect();
              if (afterComments != null && !afterComments.isEmpty()) {
                  println = true;
              }
          }
  
          if (println) {
              println();
          } else {
              print(' ');
          }
  
          print0(ucase ? "THEN " : "then ");
          SQLExpr valueExpr = x.getValueExpr();
          if (valueExpr instanceof SQLCaseExpr) {
              this.indentCount++;
              println();
              visit((SQLCaseExpr) valueExpr);
              this.indentCount--;
          } else {
              this.indentCount++;
              printExpr(valueExpr, parameterized);
              this.indentCount--;
          }
  
          return false;
      }
  
      public boolean visit(SQLCaseStatement x) {
          print0(ucase ? "CASE" : "case");
          SQLExpr valueExpr = x.getValueExpr();
          if (valueExpr != null) {
              print(' ');
              printExpr(valueExpr, parameterized);
          }
          this.indentCount++;
          println();
          printlnAndAccept(x.getItems(), " ");
  
          if (x.getElseStatements().size() > 0) {
              println();
              print0(ucase ? "ELSE " : "else ");
              printlnAndAccept(x.getElseStatements(), "");
          }
  
          this.indentCount--;
  
          println();
          print0(ucase ? "END CASE" : "end case");
          if (DbType.oracle == dbType) {
              print(';');
          }
          return false;
      }
  
      public boolean visit(SQLCaseStatement.Item x) {
          print0(ucase ? "WHEN " : "when ");
          printExpr(x.getConditionExpr(), parameterized);
          print0(ucase ? " THEN " : " then ");
  
          SQLStatement stmt = x.getStatement();
          if (stmt != null) {
              stmt.accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLGetDiagnosticsStatement x) {
          print(ucase ? "GET DIAGNOSTICS " : "get diagnostics ");
  
          x.getExpr().accept(this);
          return false;
      }
  
      public boolean visit(SQLCastExpr x) {
          tryPrintLparen(x);
          if (x.isTry()) {
              print0(ucase ? "TRY_CAST(" : "try_cast(");
          } else {
              print0(ucase ? "CAST(" : "cast(");
          }
          x.getExpr().accept(this);
          print0(ucase ? " AS " : " as ");
          x.getDataType().accept(this);
          print0(")");
          tryPrintRparen(x);
          return false;
      }
  
      public boolean visit(SQLCharExpr x) {
          return visit(x, this.parameterized);
      }
  
      public boolean visit(SQLCharExpr x, boolean parameterized) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          if (x.isParenthesized()) {
              print('(');
          }
          if (parameterized) {
              print('?');
              incrementReplaceCunt();
              if (this.parameters != null) {
                  ExportParameterVisitorUtils.exportParameter(this.parameters, x);
              }
              return false;
          }
  
          printChars(x.getText());
          if (x.isParenthesized()) {
              print(')');
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      protected void printChars(String text) {
          if (text == null) {
              print0(ucase ? "NULL" : "null");
          } else {
              print('\'');
              int index = text.indexOf('\'');
              if (index >= 0) {
                  text = text.replaceAll("'", "''");
              }
              print0(text);
              print('\'');
          }
      }
  
      public boolean visit(SQLDataType x) {
          printDataType(x);
  
          return false;
      }
  
      protected void printDataType(SQLDataType x) {
          boolean parameterized = this.parameterized;
          this.parameterized = false;
  
          print0(x.getName());
          final List<SQLExpr> arguments = x.getArguments();
          if (arguments.size() > 0) {
              print('(');
              for (int i = 0, size = arguments.size(); i < size; ++i) {
                  if (i != 0) {
                      print0(", ");
                  }
                  printExpr(arguments.get(i), false);
              }
              print(')');
          }
  
          Boolean withTimeZone = x.getWithTimeZone();
          if (withTimeZone != null) {
              if (withTimeZone) {
                  if (x.isWithLocalTimeZone()) {
                      print0(ucase ? " WITH LOCAL TIME ZONE" : " with local time zone");
                  } else {
                      print0(ucase ? " WITH TIME ZONE" : " with time zone");
                  }
              } else {
                  print0(ucase ? " WITHOUT TIME ZONE" : " without time zone");
              }
          }
  
          if (x instanceof SQLDataTypeImpl) {
              SQLExpr indexBy = ((SQLDataTypeImpl) x).getIndexBy();
              if (indexBy != null) {
                  print0(ucase ? " INDEX BY " : " index by ");
                  indexBy.accept(this);
              }
          }
  
          this.parameterized = parameterized;
      }
  
      public boolean visit(SQLCharacterDataType x) {
          visit((SQLDataType) x);
  
          List<SQLCommentHint> hints = ((SQLCharacterDataType) x).hints;
          if (hints != null) {
              print(' ');
              for (SQLCommentHint hint : hints) {
                  hint.accept(this);
              }
          }
  
          return false;
      }
  
      public boolean visit(SQLExistsExpr x) {
          if (x.isNot()) {
              print0(ucase ? "NOT EXISTS (" : "not exists (");
          } else {
              print0(ucase ? "EXISTS (" : "exists (");
          }
          this.indentCount++;
          println();
          visit(x.getSubQuery());
          this.indentCount--;
          println();
          print(')');
  
          SQLCommentHint hint = x.getHint();
          if (hint != null) {
              print(' ');
              hint.accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLIdentifierExpr x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          if (x.isParenthesized()) {
              print('(');
          }
          String replacedName = x.getName();
          if (x.getParent() instanceof SQLBinaryOpExpr
          || x.getParent() instanceof SQLSelectItem) {
              replacedName = replaceQuota(x.getName());
          }
          printName0(replacedName);
          if (x.getCollate() != null) {
              String collate = x.getCollate();
              print(" COLLATE ");
              if (collate.startsWith("'") || collate.endsWith("\"")) {
                  print(collate);
              } else {
                  print('\'');
                  print(collate);
                  print('\'');
              }
          }
          if (x.isParenthesized()) {
              print(')');
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      private boolean printName(SQLName x, String name) {
          boolean shardingSupport = this.shardingSupport
                  && this.parameterized;
          return printName(x, name, shardingSupport);
      }
  
      public String unwrapShardingTable(String name) {
          char c0 = name.charAt(0);
          char c_last = name.charAt(name.length() - 1);
          final boolean quote = (c0 == '`' && c_last == '`') || (c0 == '"' && c_last == '"');
  
          int end = name.length();
          if (quote) {
              end--;
          }
  
          int num_cnt = 0, postfixed_cnt = 0;
          for (int i = end - 1; i > 0; --i, postfixed_cnt++) {
              char ch = name.charAt(i);
              if (ch >= '0' && ch <= '9') {
                  num_cnt++;
              }
  
              if (ch != '_' && (ch < '0' || ch > '9')) {
                  break;
              }
          }
          if (num_cnt < 1 || postfixed_cnt < 2) {
              return name;
          }
  
          int start = end - postfixed_cnt;
          if (start < 1) {
              return name;
          }
  
          String realName = name.substring(quote ? 1 : 0, start);
          return realName;
      }
  
      private boolean printName(SQLName x, String name, boolean shardingSupport) {
          if (shardingSupport) {
              SQLObject parent = x.getParent();
              shardingSupport = parent instanceof SQLExprTableSource || parent instanceof SQLPropertyExpr;
  
              if (parent instanceof SQLPropertyExpr && parent.getParent() instanceof SQLExprTableSource) {
                  shardingSupport = false;
              }
          }
  
          if (shardingSupport) {
              final boolean quote = name.charAt(0) == '`' && name.charAt(name.length() - 1) == '`';
  
              String unwrappedName = unwrapShardingTable(name);
              if (unwrappedName != name) {
                  boolean isAlias = false;
                  for (SQLObject parent = x.getParent(); parent != null; parent = parent.getParent()) {
                      if (parent instanceof SQLSelectQueryBlock) {
                          SQLTableSource from = ((SQLSelectQueryBlock) parent).getFrom();
                          if (quote) {
                              String name2 = name.substring(1, name.length() - 1);
                              if (isTableSourceAlias(from, name, name2)) {
                                  isAlias = true;
                              }
                          } else {
                              if (isTableSourceAlias(from, name)) {
                                  isAlias = true;
                              }
                          }
                          break;
                      }
                  }
  
                  if (!isAlias) {
                      print0(unwrappedName);
                      incrementReplaceCunt();
                      return false;
                  } else {
                      printName0(name);
                      return false;
                  }
              }
          }
  
          printName0(name);
          return false;
      }
  
      public boolean visit(SQLInListExpr x) {
          if (x.isParenthesized()) {
              print('(');
          }
          final SQLExpr expr = x.getExpr();
  
          boolean quote = false;
          if (expr instanceof SQLBinaryOpExpr) {
              SQLBinaryOperator operator = ((SQLBinaryOpExpr) expr).getOperator();
              switch (operator) {
                  case BooleanAnd:
                  case BooleanOr:
                  case BooleanXor:
                  case Assignment:
                      quote = true;
                      break;
                  default:
                      quote = false;
                      break;
              }
          } else if (expr instanceof SQLNotExpr
                  || expr instanceof SQLBetweenExpr
                  || expr instanceof SQLInListExpr
                  || expr instanceof SQLUnaryExpr
                  || expr instanceof SQLBinaryOpExprGroup) {
              quote = true;
          }
  
          if (this.parameterized) {
              List<SQLExpr> targetList = x.getTargetList();
  
              boolean allLiteral = true;
              for (SQLExpr item : targetList) {
                  if (!((item instanceof SQLUnaryExpr && ((SQLUnaryExpr) item).getExpr() instanceof SQLLiteralExpr) || item instanceof SQLLiteralExpr
                      || item instanceof SQLVariantRefExpr)) {
                      if (item instanceof SQLListExpr) {
                          SQLListExpr list = (SQLListExpr) item;
                          for (SQLExpr listItem : list.getItems()) {
                              if (!(listItem instanceof SQLLiteralExpr
                                      || listItem instanceof SQLVariantRefExpr)) {
                                  allLiteral = false;
                                  break;
                              }
                          }
                          if (allLiteral) {
                              break;
                          }
                          continue;
                      }
                      allLiteral = false;
                      break;
                  }
              }
  
              if (allLiteral) {
                  boolean changed = true;
                  if (targetList.size() == 1 && targetList.get(0) instanceof SQLVariantRefExpr) {
                      changed = false;
                  }
  
                  if (quote) {
                      print('(');
                  }
                  printExpr(expr, parameterized);
                  if (quote) {
                      print(')');
                  }
  
                  if (x.isNot()) {
                      print(ucase ? " NOT IN" : " not in");
                  } else {
                      print(ucase ? " IN" : " in");
                  }
  
                  if ((!parameterizedQuesUnMergeInList) || (targetList.size() == 1 && !(targetList.get(0) instanceof SQLListExpr))) {
  //                    if (parameters != null) {
  //                        print(" (");
  //                        for (int i = 0; i < targetList.size(); i++) {
  //                            if(i != 0) {
  //                                print(", ");
  //                            }
  //                            SQLExpr item = targetList.get(i);
  //                            printExpr(item);
  //                        }
  //                        print(')');
  //                        return false;
  //                    } else {
  //
  //                    }
                      print(" (?)");
                  } else {
                      print(" (");
                      for (int i = 0; i < targetList.size(); i++) {
                          if (i != 0) {
                              print(", ");
                          }
                          SQLExpr item = targetList.get(i);
                          if (item instanceof SQLListExpr) {
                              visit((SQLListExpr) item);
                              changed = false;
                          } else {
                              print("?");
                          }
                      }
                      print(")");
                  }
  
                  if (changed) {
                      incrementReplaceCunt();
                      if (this.parameters != null) {
                          if (parameterizedMergeInList) {
                              List<Object> subList = new ArrayList<Object>(x.getTargetList().size());
                              for (SQLExpr target : x.getTargetList()) {
                                  ExportParameterVisitorUtils.exportParameter(subList, target);
                              }
                              if (subList != null) {
                                  parameters.add(subList);
                              }
                          } else {
                              for (SQLExpr target : x.getTargetList()) {
                                  ExportParameterVisitorUtils.exportParameter(this.parameters, target);
                              }
                          }
                      }
                  }
  
                  if (x.getHint() != null) {
                      x.getHint().accept(this);
                  }
                  if (x.isParenthesized()) {
                      print(')');
                  }
                  return false;
              }
          }
  
          //if (quote) {
              //print('(');
          //}
          printExpr(expr, parameterized);
          //if (quote) {
              //print(')');
          //}
  
          if (x.isNot()) {
              print0(ucase ? " NOT IN " : " not in ");
          } else {
              print0(ucase ? " IN " : " in ");
          }
  
          boolean quoteValue = !((x.getParent() instanceof SQLMethodInvokeExpr
                  && ((SQLMethodInvokeExpr) x.getParent()).getArguments().size() == 1)
                  && ((SQLMethodInvokeExpr) x.getParent()).methodNameHashCode64() == FnvHash.Constants.POSITION);
          if (quoteValue) {
              print('(');
          }
  
          final List<SQLExpr> list = x.getTargetList();
  
          boolean printLn = false;
          if (list.size() > 5) {
              printLn = true;
              for (int i = 0, size = list.size(); i < size; ++i) {
                  if (!(list.get(i) instanceof SQLCharExpr)) {
                      printLn = false;
                      break;
                  }
              }
          }
  
          if (printLn) {
              this.indentCount++;
              println();
              for (int i = 0, size = list.size(); i < size; ++i) {
                  if (i != 0) {
                      print0(", ");
                      println();
                  }
                  SQLExpr item = list.get(i);
                  printExpr(item, parameterized);
              }
              this.indentCount--;
              println();
          } else {
              List<SQLExpr> targetList = x.getTargetList();
              for (int i = 0; i < targetList.size(); i++) {
                  if (i != 0) {
                      print0(", ");
                  }
                  printExpr(targetList.get(i), parameterized);
              }
          }
  
          if (quoteValue) {
              print(')');
          }
  
          List<String> afterComments = x.getAfterCommentsDirect();
          if (afterComments != null && !afterComments.isEmpty() && afterComments.get(0).startsWith("--")) {
              print(' ');
          }
          printlnComment(afterComments);
  
          if (x.getHint() != null) {
              x.getHint().accept(this);
          }
          if (x.isParenthesized()) {
              print(')');
          }
          return false;
      }
  
      public boolean visit(SQLContainsExpr x) {
          SQLExpr expr = x.getExpr();
          if (expr != null) {
              printExpr(expr, parameterized);
              print(' ');
          }
  
          if (x.isNot()) {
              print0(ucase ? "NOT CONTAINS (" : " not contains (");
          } else {
              print0(ucase ? "CONTAINS (" : " contains (");
          }
  
          final List<SQLExpr> list = x.getTargetList();
  
          boolean printLn = false;
          if (list.size() > 5) {
              printLn = true;
              for (int i = 0, size = list.size(); i < size; ++i) {
                  if (!(list.get(i) instanceof SQLCharExpr)) {
                      printLn = false;
                      break;
                  }
              }
          }
  
          if (printLn) {
              this.indentCount++;
              println();
              for (int i = 0, size = list.size(); i < size; ++i) {
                  if (i != 0) {
                      print0(", ");
                      println();
                  }
                  SQLExpr item = list.get(i);
                  printExpr(item, parameterized);
              }
              this.indentCount--;
              println();
          } else {
              List<SQLExpr> targetList = x.getTargetList();
              for (int i = 0; i < targetList.size(); i++) {
                  if (i != 0) {
                      print0(", ");
                  }
                  printExpr(targetList.get(i), parameterized);
              }
          }
  
          print(')');
          return false;
      }
  
      public boolean visit(SQLIntegerExpr x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          boolean parameterized = this.parameterized;
          if (x.isParenthesized() && !parameterized) {
              print('(');
          }
          printInteger(x, parameterized);
          if (x.isParenthesized() && !parameterized) {
              print(')');
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      private static final Integer ONE = Integer.valueOf(1);
  
      protected void printInteger(SQLIntegerExpr x, boolean parameterized) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          Number number = x.getNumber();
          if (number.equals(ONE)) {
              if (DbType.oracle.equals(dbType)) {
                  SQLObject parent = x.getParent();
                  if (parent instanceof SQLBinaryOpExpr) {
                      SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) parent;
                      SQLExpr left = binaryOpExpr.getLeft();
                      SQLBinaryOperator op = binaryOpExpr.getOperator();
                      if (left instanceof SQLIdentifierExpr
                              && op == SQLBinaryOperator.Equality) {
                          String name = ((SQLIdentifierExpr) left).getName();
                          if ("rownum".equals(name)) {
                              print(1);
                              if (x.hasAfterComment()) {
                                  printAfterComments(x.getAfterCommentsDirect());
                              }
                              return;
                          }
                      }
                  }
              }
          }
  
          if (parameterized) {
              if (isEnabled(VisitorFeature.OutputParameterizedUnMergeZero)
                      && (number instanceof Integer
                      || number instanceof Long) && number.longValue() == 0) {
                  parameterized = false;
              }
              if (isEnabled(VisitorFeature.OutputParameterizedUnMergeOne)
                      && (number instanceof Integer
                      || number instanceof Long) && number.longValue() == 1) {
                  parameterized = false;
              }
          }
  
          if (parameterized) {
              print('?');
              incrementReplaceCunt();
  
              if (this.parameters != null) {
                  ExportParameterVisitorUtils.exportParameter(this.parameters, x);
              }
              if (x.hasAfterComment()) {
                  printAfterComments(x.getAfterCommentsDirect());
              }
              return;
          }
  
          if (number instanceof BigDecimal || number instanceof BigInteger) {
              print(number.toString());
          } else {
              print(number.longValue());
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
      }
  
      public boolean visit(SQLMethodInvokeExpr x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          if (x.isParenthesized()) {
              print('(');
          }
          SQLExpr owner = x.getOwner();
          if (owner != null) {
              printMethodOwner(owner);
          }
  
          if (parameterized) {
              List<SQLExpr> arguments = x.getArguments();
              if (x.methodNameHashCode64() == FnvHash.Constants.TRIM
                      && arguments.size() == 1
                      && arguments.get(0) instanceof SQLCharExpr && x.getTrimOption() == null && x.getFrom() == null) {
                  print('?');
  
                  if (parameters != null) {
                      SQLCharExpr charExpr = (SQLCharExpr) arguments.get(0);
                      parameters.add(charExpr.getText().trim());
                  }
  
                  replaceCount++;
                  if (x.hasAfterComment()) {
                      printAfterComments(x.getAfterCommentsDirect());
                  }
                  return false;
              }
          }
  
          String function = x.getMethodName();
          if (function != null) {
              printFunctionName(function);
          }
          printMethodParameters(x);
          if (x.isParenthesized()) {
              print(')');
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      protected void printMethodParameters(SQLMethodInvokeExpr x, List<SQLExpr> parameters) {
          String function = x.getMethodName();
          long nameHashCode64 = x.methodNameHashCode64();
          if (!x.isRemoveBrackets()) {
              print('(');
          }
  
          String trimOption = x.getTrimOption();
          if (trimOption != null) {
              print0(trimOption);
  
              if (parameters.size() > 0) {
                  print(' ');
              }
          }
          final SQLExpr contentExpr = x.getContent();
          if (contentExpr != null) {
              print(contentExpr.toString());
              print(' ');
          }
  
          for (int i = 0, size = parameters.size(); i < size; ++i) {
              if (i != 0) {
                  print0(", ");
              }
              SQLExpr param = parameters.get(i);
  
              if (this.parameterized) {
                  if (size == 2 && i == 1 && param instanceof SQLCharExpr) {
                      if (DbType.oracle == dbType) {
                          if ("TO_CHAR".equalsIgnoreCase(function)
                                  || "TO_DATE".equalsIgnoreCase(function)) {
                              printChars(((SQLCharExpr) param).getText());
                              continue;
                          }
                      } else if (DbType.mysql == dbType) {
                          if ("DATE_FORMAT".equalsIgnoreCase(function)) {
                              printChars(((SQLCharExpr) param).getText());
                              continue;
                          }
                      }
                  }
  
              }
  
              if (param instanceof SQLBinaryOpExpr) {
                  SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) param;
                  SQLBinaryOperator op = binaryOpExpr.getOperator();
                  if (op == SQLBinaryOperator.BooleanAnd || op == SQLBinaryOperator.BooleanOr) {
                      this.indentCount++;
                      printExpr(param, parameterized);
                      this.indentCount--;
                      continue;
                  }
              }
  
              if (i == 0 && (nameHashCode64 == FnvHash.Constants.TIMESTAMPDIFF || nameHashCode64 == FnvHash.Constants.TIMESTAMPADD)
                      && param instanceof SQLIdentifierExpr) {
                  if (DbType.mysql == dbType) {
                      print(((SQLIdentifierExpr) param).getName());
                      continue;
                  }
              }
  
              printExpr(param, parameterized);
          }
      }
      protected void printMethodParameters(SQLMethodInvokeExpr x) {
          List<SQLExpr> parameters = x.getArguments();
  
          printMethodParameters(x, parameters);
          if (x instanceof SQLParametricMethodInvokeExpr) {
              print(')');
              printMethodParameters(x, ((SQLParametricMethodInvokeExpr) x).getSecondArguments());
          }
  
          SQLExpr from = x.getFrom();
          if (from != null) {
              print0(ucase ? " FROM " : " from ");
              printExpr(from, parameterized);
  
              SQLExpr _for = x.getFor();
              if (_for != null) {
                  print0(ucase ? " FOR " : " for ");
                  printExpr(_for, parameterized);
              }
          }
  
          SQLExpr using = x.getUsing();
  
          boolean odpsTransformUsing = false;
          if (using != null) {
              odpsTransformUsing = x.methodNameHashCode64() == FnvHash.Constants.TRANSFORM;
              if (!odpsTransformUsing) {
                  print0(ucase ? " USING " : " using ");
                  printExpr(using, parameterized);
              }
          }
  
          if (x.methodNameHashCode64() == FnvHash.Constants.WEIGHT_STRING) {
              SQLDataType as = (SQLDataType) x.getAttribute("as");
              if (as != null) {
                  print0(ucase ? " AS " : " as ");
                  as.accept(this);
              }
  
              List<SQLSelectOrderByItem> levels = (List<SQLSelectOrderByItem>) x.getAttribute("levels");
              if (levels != null && levels.size() > 0) {
                  print0(ucase ? " LEVEL " : " level ");
                  printAndAccept(levels, ", ");
              }
  
              Boolean reverse = (Boolean) x.getAttribute("reverse");
              if (reverse != null) {
                  print0(ucase ? " REVERSE" : " reverse");
              }
          }
  
          final SQLExpr asExpr = x.getAs();
          if (asExpr != null) {
              print(" as ");
              print(asExpr.toString());
          }
          if (!x.isRemoveBrackets()) {
              print(')');
          }
  
          if (odpsTransformUsing) {
              print0(ucase ? " USING " : " using ");
              printExpr(using, parameterized);
          }
      }
  
      protected void printMethodOwner(SQLExpr owner) {
          printExpr(owner, parameterized);
          print('.');
      }
  
      protected void printFunctionName(String name) {
          printName0(name, false);
      }
  
      public boolean visit(SQLAggregateExpr x) {
          boolean parameterized = this.parameterized;
          if (x.methodNameHashCode64() == FnvHash.Constants.GROUP_CONCAT) {
              this.parameterized = false;
          }
          if (x.methodNameHashCode64() == FnvHash.Constants.COUNT) {
              List<SQLExpr> arguments = x.getArguments();
              if (arguments.size() == 1) {
                  SQLExpr arg0 = arguments.get(0);
                  if (arg0 instanceof SQLLiteralExpr) {
                      this.parameterized = false;
                  }
              }
          }
  
          if (x.getOwner() != null) {
              printExpr(x.getOwner());
              print(".");
          }
  
          String methodName = x.getMethodName();
          print0(ucase ? methodName : methodName.toLowerCase());
          print('(');
  
          SQLAggregateOption option = x.getOption();
          if (option != null) {
              print0(option.toString());
              print(' ');
          }
  
          List<SQLExpr> arguments = x.getArguments();
          for (int i = 0, size = arguments.size(); i < size; ++i) {
              if (i != 0) {
                  print0(", ");
              }
              printExpr(arguments.get(i), false);
          }
  
          visitAggregateRest(x);
  
          print(')');
  
          if (x.isIgnoreNulls()) {
              print0(ucase ? " IGNORE NULLS" : " ignore nulls");
          }
  
          SQLKeep keep = x.getKeep();
          if (keep != null) {
              print(' ');
              visit(keep);
          }
  
          SQLOver over = x.getOver();
          if (over != null) {
              print0(ucase ? " OVER " : " over ");
              over.accept(this);
          }
  
          final SQLName overRef = x.getOverRef();
          if (overRef != null) {
              print0(ucase ? " OVER " : " over ");
              overRef.accept(this);
          }
  
          final SQLExpr filter = x.getFilter();
          if (filter != null) {
              print0(ucase ? " FILTER (WHERE " : " filter (where ");
              printExpr(filter);
              print(')');
          }
  
          this.parameterized = parameterized;
          return false;
      }
  
      protected void visitAggregateRest(SQLAggregateExpr x) {
          boolean withGroup = x.isWithinGroup();
          if (withGroup) {
              print0(ucase ? ") WITHIN GROUP (" : ") within group (");
          }
  
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              if (!x.isWithinGroup()) {
                  print(' ');
              }
              orderBy.accept(this);
          }
          SQLExpr limit = x.getLimit();
          if (limit != null) {
              print0(ucase ? " LIMIT " : " limit ");
              limit.accept(this);
          }
      }
  
      public boolean visit(SQLAllColumnExpr x) {
          SQLExpr owner = x.getOwner();
          if (owner != null) {
              printExpr(owner);
              print('.');
          }
  
          print('*');
  
          List<SQLExpr> except = x.getExcept();
          if (except != null) {
              print(ucase ? " EXCEPT(" : "except(");
              printAndAccept(except, ", ");
              print(')');
          }
  
          List<SQLAliasedExpr> replace = x.getReplace();
          if (!replace.isEmpty()) {
              print(ucase ? " REPLACE(" : "replace(");
              printAndAccept(replace, ", ");
              print(')');
          }
  
          return true;
      }
  
      public boolean visit(SQLNCharExpr x) {
          if (this.parameterized) {
              print('?');
              incrementReplaceCunt();
  
              if (this.parameters != null) {
                  ExportParameterVisitorUtils.exportParameter(this.parameters, x);
              }
              return false;
          }
  
          if ((x.getText() == null) || (x.getText().length() == 0)) {
              print0(ucase ? "NULL" : "null");
          } else {
              print0(ucase ? "N'" : "n'");
              print0(x.getText().replace("'", "''"));
              print('\'');
          }
          return false;
      }
  
      public boolean visit(SQLNotExpr x) {
          print0(ucase ? "NOT " : "not ");
          SQLExpr expr = x.getExpr();
  
          boolean needParentheses = false;
  
          if (expr instanceof SQLBinaryOpExpr) {
              SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) expr;
              needParentheses = binaryOpExpr.getOperator().isLogical();
              if (binaryOpExpr.isParenthesized()) {
                  needParentheses = false;
              }
          } else if (expr instanceof SQLInListExpr || expr instanceof SQLNotExpr
                  || expr instanceof SQLBinaryOpExprGroup || expr instanceof SQLQueryExpr) {
              needParentheses = true;
          }
  
          if (needParentheses) {
              print('(');
          }
          printExpr(expr, parameterized);
  
          if (needParentheses) {
              print(')');
          }
  
          SQLCommentHint hint = x.getHint();
          if (hint != null) {
              hint.accept(this);
          }
  
          return false;
      }
  
      public boolean visit(SQLNullExpr x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          SQLObject parent = x.getParent();
          if (this.parameterized
                  && (parent instanceof ValuesClause || parent instanceof SQLInListExpr ||
                  (parent instanceof SQLBinaryOpExpr && ((SQLBinaryOpExpr) parent).getOperator() == SQLBinaryOperator.Equality))) {
              print('?');
              incrementReplaceCunt();
  
              if (this.parameters != null) {
                  if (parent instanceof SQLBinaryOpExpr) {
                      ExportParameterVisitorUtils.exportParameter((this).getParameters(), x);
                  } else {
                      this.getParameters().add(null);
                  }
              }
              if (x.hasAfterComment()) {
                  printAfterComments(x.getAfterCommentsDirect());
              }
              return false;
          }
  
          print0(ucase ? "NULL" : "null");
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      public boolean visit(SQLNumberExpr x) {
          if (this.parameterized) {
              print('?');
              incrementReplaceCunt();
  
              if (this.parameters != null) {
                  ExportParameterVisitorUtils.exportParameter((this).getParameters(), x);
              }
              return false;
          }
  
          x.output(appender);
          return false;
      }
  
      public boolean visit(SQLPropertyExpr x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          SQLExpr owner = x.getOwner();
  
          String mapTableName = null, ownerName = null;
          if (owner instanceof SQLIdentifierExpr) {
              ownerName = ((SQLIdentifierExpr) owner).getName();
              if (tableMapping != null) {
                  mapTableName = tableMapping.get(ownerName);
  
                  if (mapTableName == null
                          && ownerName.length() > 2
                          && ownerName.charAt(0) == '`'
                          && ownerName.charAt(ownerName.length() - 1) == '`') {
                      ownerName = ownerName.substring(1, ownerName.length() - 1);
                      mapTableName = tableMapping.get(ownerName);
                  }
              }
          }
  
          if (mapTableName != null) {
              for (SQLObject parent = x.getParent(); parent != null; parent = parent.getParent()) {
                  if (parent instanceof SQLSelectQueryBlock) {
                      SQLTableSource from = ((SQLSelectQueryBlock) parent).getFrom();
                      if (isTableSourceAlias(from, mapTableName, ownerName)) {
                          mapTableName = null;
                      }
                      break;
                  }
              }
          }
  
          if (mapTableName != null) {
              printName0(mapTableName);
          } else {
              if (owner instanceof SQLIdentifierExpr) {
                  SQLIdentifierExpr ownerIdent = (SQLIdentifierExpr) owner;
                  printName(
                          ownerIdent,
                          replaceQuota(ownerIdent.getName()),
                          this.shardingSupport && this.parameterized
                  );
              } else if (owner != null) {
                  printExpr(owner, parameterized);
              }
          }
          if (x.getSplitString() != null) {
              print(x.getSplitString());
          } else {
              print('.');
          }
          String name = replaceQuota(x.getName());
          if ("*".equals(name)) {
              print0(name);
          } else {
              printName0(name);
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      protected boolean isTableSourceAlias(SQLTableSource from, String... tableNames) {
          String alias = from.getAlias();
  
          if (alias != null) {
              for (String tableName : tableNames) {
                  if (alias.equalsIgnoreCase(tableName)) {
                      return true;
                  }
              }
  
              if (alias.length() > 2 && alias.charAt(0) == '`' && alias.charAt(alias.length() - 1) == '`') {
                  alias = alias.substring(1, alias.length() - 1);
                  for (String tableName : tableNames) {
                      if (alias.equalsIgnoreCase(tableName)) {
                          return true;
                      }
                  }
              }
          }
          if (from instanceof SQLJoinTableSource) {
              SQLJoinTableSource join = (SQLJoinTableSource) from;
              return isTableSourceAlias(join.getLeft(), tableNames)
                      || isTableSourceAlias(join.getRight(), tableNames);
          }
          return false;
      }
  
      public boolean visit(SQLQueryExpr x) {
          SQLObject parent = x.getParent();
          if (parent instanceof SQLSelect) {
              parent = parent.getParent();
          }
          if (x.isParenthesized()) {
              print('(');
          }
          SQLSelect subQuery = x.getSubQuery();
          if (parent instanceof ValuesClause) {
              println();
              visit(subQuery);
              println();
          } else if ((parent instanceof SQLStatement
                  && !(parent instanceof OracleForStatement))
                  || (parent instanceof SQLSelectItem
                  && (parent.getParent() instanceof SQLPivot || parent.getParent() instanceof SQLUnpivot))
          ) {
              this.indentCount++;
  
              println();
              visit(subQuery);
  
              this.indentCount--;
          } else if (parent instanceof SQLOpenStatement) {
              visit(subQuery);
          } else if (parent instanceof SQLMethodInvokeExpr
                  && ((SQLMethodInvokeExpr) parent).getArguments().size() == 1
                  && (((SQLMethodInvokeExpr) parent).methodNameHashCode64() == FnvHash.Constants.LATERAL
                  || ((SQLMethodInvokeExpr) parent).methodNameHashCode64() == FnvHash.Constants.ARRAY)
          ) {
              this.indentCount++;
              println();
              visit(subQuery);
              this.indentCount--;
              println();
          } else if (parent instanceof SQLMethodInvokeExpr && ((SQLMethodInvokeExpr) parent).getArguments().size() == 1) {
              this.indentCount++;
              println();
              visit(subQuery);
              this.indentCount--;
              println();
          } else {
              this.indentCount++;
              println();
              visit(subQuery);
              this.indentCount--;
              println();
          }
          if (x.isParenthesized()) {
              print(')');
          }
          return false;
      }
  
      protected boolean legacyCube() {
          return dbType == DbType.oracle;
      }
  
      public boolean visit(SQLSelectGroupByClause x) {
          boolean paren = legacyCube() || x.isParen();
          boolean rollup = x.isWithRollUp();
          boolean cube = x.isWithCube();
  
          List<SQLExpr> items = x.getItems();
          int itemSize = items.size();
          if (itemSize > 0) {
              print0(ucase ? "GROUP BY " : "group by ");
  
              if (x.isDistinct()) {
                  print0(ucase ? "DISTINCT " : "distinct ");
              }
  
              if (paren && rollup) {
                  print0(ucase ? "ROLLUP (" : "rollup (");
              } else if (paren && cube) {
                  print0(ucase ? "CUBE (" : "cube (");
              }
              this.indentCount++;
              for (int i = 0; i < itemSize; ++i) {
                  SQLExpr item = items.get(i);
                  if (isPrettyFormat() && item.hasBeforeComment()) {
                      if (i != 0) {
                          print(' ');
                      }
                      printlnComments(item.getBeforeCommentsDirect());
                  }
  
                  if (i != 0) {
                      if (groupItemSingleLine) {
                          if (item instanceof SQLGroupingSetExpr) {
                              if (!item.hasBeforeComment()) {
                                  println();
                              }
                          } else {
                              println(',');
                          }
                      } else {
                          if (item instanceof SQLGroupingSetExpr) {
                              println();
                          } else {
                              print(", ");
                          }
                      }
                  }
                  if (item instanceof SQLIntegerExpr) {
                      printInteger((SQLIntegerExpr) item, false);
                  } else if (item instanceof MySqlOrderingExpr && ((MySqlOrderingExpr) item).getExpr() instanceof SQLIntegerExpr) {
                      MySqlOrderingExpr orderingExpr = (MySqlOrderingExpr) item;
                      printInteger((SQLIntegerExpr) orderingExpr.getExpr(), false);
                      print(' ' + orderingExpr.getType().name);
                  } else {
                      item.accept(this);
                  }
  
                  SQLCommentHint hint = null;
                  if (item instanceof SQLExprImpl) {
                      hint = ((SQLExprImpl) item).getHint();
                  }
                  if (hint != null) {
                      hint.accept(this);
                  }
              }
              if (paren && (rollup || cube)) {
                  print(')');
              }
              this.indentCount--;
          }
  
          SQLExpr having = x.getHaving();
          if (having != null) {
              printHaving(having);
          }
  
          if (x.isWithRollUp() && !paren) {
              print0(ucase ? " WITH ROLLUP" : " with rollup");
          }
  
          if (x.isWithCube() && !paren) {
              print0(ucase ? " WITH CUBE" : " with cube");
          }
  
          return false;
      }
  
      protected void printHaving(SQLExpr having) {
          println();
          print0(ucase ? "HAVING " : "having ");
          having.accept(this);
      }
  
      public boolean visit(SQLSelect x) {
          SQLHint headHint = x.getHeadHint();
          if (headHint != null) {
              headHint.accept(this);
              println();
          }
  
          SQLWithSubqueryClause withSubQuery = x.getWithSubQuery();
          if (withSubQuery != null) {
              withSubQuery.accept(this);
              println();
          }
  
          printQuery(x.getQuery());
  
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              println();
              orderBy.accept(this);
          }
  
          final SQLLimit limit = x.getLimit();
          if (limit != null) {
              println();
              limit.accept(this);
          }
  
          if (x.getHintsSize() > 0) {
              printAndAccept(x.getHints(), "");
          }
  
          return false;
      }
  
      protected void printSelectListBefore(SQLSelectQueryBlock x) {
          print(' ');
      }
  
      public void printSqlSetQuantifier(SQLSelectQueryBlock x) {
          final int distinctOption = x.getDistionOption();
          if (SQLSetQuantifier.ALL == distinctOption) {
              print0(ucase ? "ALL " : "all ");
          } else if (SQLSetQuantifier.DISTINCT == distinctOption) {
              print0(ucase ? "DISTINCT " : "distinct ");
          } else if (SQLSetQuantifier.UNIQUE == distinctOption) {
              print0(ucase ? "UNIQUE " : "unique ");
          }
      }
  
      public void printTop(SQLSelectQueryBlock x) {
      }
      public boolean visit(SQLSelectQueryBlock x) {
          if (x.isParenthesized()) {
              print('(');
          }
          if (isPrettyFormat() && x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          SQLWithSubqueryClause with = x.getWith();
          if (with != null) {
              with.accept(this);
              println();
          }
  
          print0(ucase ? "SELECT" : "select");
  
          if (x.getHintsSize() > 0) {
              printAndAccept(x.getHints(), ", ");
              print(' ');
          }
  
          printTop(x);
          printSelectListBefore(x);
          printSqlSetQuantifier(x);
  
          printSelectList(
                  x.getSelectList());
  
          printInto(x);
          printFrom(x);
          printWhere(x);
  
          printHierarchical(x);
  
          printGroupBy(x);
  
          printQualify(x);
          printWindow(x);
  
          printOrderBy(x);
  
          final List<SQLSelectOrderByItem> distributeBy = x.getDistributeByDirect();
          if (distributeBy != null && distributeBy.size() > 0) {
              println();
              print0(ucase ? "DISTRIBUTE BY " : "distribute by ");
              printAndAccept(distributeBy, ", ");
          }
  
          List<SQLSelectOrderByItem> sortBy = x.getSortByDirect();
          if (sortBy != null && sortBy.size() > 0) {
              println();
              print0(ucase ? "SORT BY " : "sort by ");
              printAndAccept(sortBy, ", ");
          }
  
          final List<SQLSelectOrderByItem> clusterBy = x.getClusterByDirect();
          if (clusterBy != null && clusterBy.size() > 0) {
              println();
              print0(ucase ? "CLUSTER BY " : "cluster by ");
              printAndAccept(clusterBy, ", ");
          }
  
          printFetchFirst(x);
          printAfterFetch(x);
  
          if (x.isForUpdate()) {
              println();
              print0(ucase ? "FOR UPDATE" : "for update");
          }
          if (x.isParenthesized()) {
              print(')');
          }
          return false;
      }
  
      protected void printInto(SQLSelectQueryBlock x) {
          SQLExprTableSource into = x.getInto();
          if (into != null) {
              println();
              print0(ucase ? "INTO " : "into ");
              into.accept(this);
          }
      }
  
      protected void printQualify(SQLSelectQueryBlock x) {
          SQLExpr qualify = x.getQualify();
          if (qualify == null) {
              return;
          }
          println();
          print0(ucase ? "QUALIFY " : "qualify ");
          qualify.accept(this);
      }
  
      protected void printOrderBy(SQLSelectQueryBlock x) {
          SQLOrderBy orderBy = x.getOrderBy();
          printOrderBy(orderBy);
      }
  
      protected void printOrderBy(SQLOrderBy orderBy) {
          if (orderBy == null) {
              return;
          }
          println();
          orderBy.accept(this);
      }
  
      protected void printFrom(SQLSelectQueryBlock x) {
          SQLTableSource from = x.getFrom();
          if (from == null) {
              return;
          }
  
          println();
          boolean printFrom = from instanceof SQLLateralViewTableSource
                  && ((SQLLateralViewTableSource) from).getTableSource() == null;
          if (!printFrom) {
              print0(ucase ? "FROM " : "from ");
              if (x.getCommentsAfterFrom() != null) {
                  printAfterComments(x.getCommentsAfterFrom());
                  println();
              }
          }
          printTableSource(from);
      }
  
      protected void printGroupBy(SQLSelectQueryBlock x) {
          SQLSelectGroupByClause groupBy = x.getGroupBy();
          if (groupBy == null) {
              return;
          }
          println();
          visit(groupBy);
      }
  
      protected void printLimit(SQLSelectQueryBlock x) {
          SQLLimit limit = x.getLimit();
          if (limit == null) {
              return;
          }
          println();
          limit.accept(this);
      }
  
      protected void printWindow(SQLSelectQueryBlock x) {
          List<SQLWindow> windows = x.getWindows();
          if (windows == null || windows.isEmpty()) {
              return;
          }
          println();
          print0(ucase ? "WINDOW " : "window ");
          printAndAccept(windows, ", ");
      }
  
      protected void printWhere(SQLSelectQueryBlock queryBlock) {
          SQLExpr where = queryBlock.getWhere();
          if (where == null) {
              return;
          }
  
          println();
          print0(ucase ? "WHERE " : "where ");
  
          List<String> beforeComments = where.getBeforeCommentsDirect();
          if (beforeComments != null && !beforeComments.isEmpty() && isPrettyFormat()) {
              printlnComments(beforeComments);
          }
          printExpr(where, parameterized);
      }
  
      protected void printAfterFetch(SQLSelectQueryBlock x) {}
      protected void printFetchFirst(SQLSelectQueryBlock x) {
          SQLLimit limit = x.getLimit();
          if (limit == null) {
              return;
          }
  
          SQLExpr offset = limit.getOffset();
          SQLExpr first = limit.getRowCount();
  
          if (DbType.db2 == dbType
                  || DbType.oracle == dbType
                  || DbType.sqlserver == dbType
          ) {
              //order by 语句必须在FETCH FIRST ROWS ONLY之前
              SQLObject parent = x.getParent();
              if (parent instanceof SQLSelect) {
                  SQLOrderBy orderBy = ((SQLSelect) parent).getOrderBy();
                  if (orderBy != null && orderBy.getItems().size() > 0) {
                      println();
                      print0(ucase ? "ORDER BY " : "order by ");
                      printAndAccept(orderBy.getItems(), ", ");
                  }
              }
  
              println();
  
              if (offset != null) {
                  print0(ucase ? "OFFSET " : "offset ");
                  offset.accept(this);
                  print0(ucase ? " ROWS" : " rows");
              }
  
              if (first != null) {
                  if (offset != null) {
                      print(' ');
                  }
                  if (DbType.sqlserver == dbType && offset != null) {
                      print0(ucase ? "FETCH NEXT " : "fetch next ");
                  } else {
                      print0(ucase ? "FETCH FIRST " : "fetch first ");
                  }
                  first.accept(this);
                  print0(ucase ? " ROWS ONLY" : " rows only");
              }
          } else {
              printLimit(x);
          }
      }
  
      public boolean visit(SQLStructExpr x) {
          SQLStructDataType dataType = x.getDataType();
          if (dataType != null) {
              dataType.accept(this);
              print('(');
          } else {
              print0(ucase ? "STRUCT(" : "struct(");
          }
          printlnAndAccept(
                  x.getItems(),
                  needPrintLine(x));
          print(')');
          return false;
      }
  
      protected boolean needPrintLine(SQLStructExpr x) {
          List<SQLAliasedExpr> fields = x.getItems();
          boolean needPrintLine = false;
          if (fields.size() > 5) {
              needPrintLine = true;
          } else {
              for (SQLAliasedExpr field : fields) {
                  SQLExpr fieldDataType = field.getExpr();
                  if (fieldDataType instanceof SQLArrayDataType || fieldDataType instanceof SQLStructExpr) {
                      needPrintLine = true;
                      break;
                  }
              }
          }
          return needPrintLine;
      }
  
      public boolean visit(SQLAliasedExpr x) {
          SQLExpr expr = x.getExpr();
          if (expr == null) {
              return false;
          }
          expr.accept(this);
  
          String alias = x.getAlias();
          if (alias != null && alias.length() > 0) {
              printExprAlias(alias);
  
              return false;
          }
          return false;
      }
  
      public boolean visit(SQLSelectItem x) {
          if (x.isConnectByRoot()) {
              print0(ucase ? "CONNECT_BY_ROOT " : "connect_by_root ");
          }
  
          SQLExpr expr = x.getExpr();
  
          if (expr instanceof SQLIdentifierExpr) {
              String name = replaceQuota(((SQLIdentifierExpr) expr).getName());
              printName0(name);
          } else if (expr instanceof SQLPropertyExpr) {
              visit((SQLPropertyExpr) expr);
          } else if (expr != null) {
              printExpr(expr, parameterized);
          }
  
          String alias = x.getAlias();
          if (alias != null && alias.length() > 0) {
              printExprAlias(alias);
  
              return false;
          }
  
          List<String> aliasList = x.getAliasList();
          if (aliasList == null) {
              return false;
          }
  
          println();
          print0(ucase ? "AS (" : "as (");
  
          int aliasSize = aliasList.size();
          if (aliasSize > 5) {
              this.indentCount++;
              println();
          }
  
          for (int i = 0; i < aliasSize; ++i) {
              if (i != 0) {
                  if (aliasSize > 5) {
                      println(",");
                  } else {
                      print0(", ");
                  }
              }
              printAlias0(aliasList.get(i));
          }
  
          if (aliasSize > 5) {
              this.indentCount--;
              println();
          }
          print(')');
  
          return false;
      }
  
      protected void printExprAlias(String alias) {
          alias = replaceQuota(alias);
          print0(ucase ? " AS " : " as ");
          printAlias0(alias);
      }
  
      public boolean visit(SQLOrderBy x) {
          List<SQLSelectOrderByItem> items = x.getItems();
  
          if (items.size() > 0) {
              if (x.isSiblings()) {
                  print0(ucase ? "ORDER SIBLINGS BY " : "order siblings by ");
              } else {
                  print0(ucase ? "ORDER BY " : "order by ");
              }
  
              for (int i = 0, size = items.size(); i < size; ++i) {
                  if (i != 0) {
                      print0(", ");
                  }
                  SQLSelectOrderByItem item = items.get(i);
                  visit(item);
              }
          }
          return false;
      }
  
      public boolean visit(SQLSelectOrderByItem x) {
          SQLExpr expr = x.getExpr();
  
          if (expr instanceof SQLIntegerExpr) {
              print(((SQLIntegerExpr) expr).getNumber().longValue());
          } else if (expr instanceof SQLCharExpr) {
              if (dialect != null) {
                  String name = "'" + ((SQLCharExpr) expr).getText() + "'";
                  printName0(replaceQuota(name));
              } else {
                  printExpr(expr, parameterized);
              }
          } else if (expr instanceof SQLIdentifierExpr) {
              ((SQLIdentifierExpr) expr).setName(replaceQuota(((SQLIdentifierExpr) expr).getName()));
              printExpr(expr, parameterized);
          } else {
              printExpr(expr, parameterized);
          }
  
          String collate = x.getCollate();
          if (collate != null) {
              print0(ucase ? " COLLATE " : " collate ");
              print0(collate);
          }
          if (x.getOpclass() != null) {
              print(' ');
              x.getOpclass().accept(this);
          }
          SQLOrderingSpecification type = x.getType();
          if (type != null) {
              print(' ');
              print0(ucase ? type.name : type.nameLCase);
          }
          SQLSelectOrderByItem.NullsOrderType nullsOrderType = x.getNullsOrderType();
          if (nullsOrderType != null) {
              print(' ');
              print0(nullsOrderType.toFormalString());
          }
  
          SQLCommentHint hint = x.getHint();
          if (hint != null) {
              visit(hint);
          }
  
          return false;
      }
  
      protected void addTable(String table) {
          if (tables == null) {
              if (this.table == null) {
                  this.table = table;
                  return;
              } else {
                  tables = new LinkedHashSet<String>();
                  tables.add(this.table);
              }
          }
          this.tables.add(table);
      }
  
      protected void printTableSourceExpr(SQLExpr expr) {
          if (exportTables) {
              addTable(expr.toString());
          }
  
          if (isEnabled(VisitorFeature.OutputDesensitize)) {
              String ident = null;
              if (expr instanceof SQLIdentifierExpr) {
                  ident = ((SQLIdentifierExpr) expr).getName();
              } else if (expr instanceof SQLPropertyExpr) {
                  SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr;
                  propertyExpr.getOwner().accept(this);
                  print('.');
  
                  ident = propertyExpr.getName();
              }
  
              if (ident != null) {
                  String desensitizeTable = SQLUtils.desensitizeTable(ident);
                  print0(desensitizeTable);
                  return;
              }
          }
  
          if (tableMapping != null && expr instanceof SQLName) {
              String tableName;
              if (expr instanceof SQLIdentifierExpr) {
                  tableName = ((SQLIdentifierExpr) expr).normalizedName();
              } else if (expr instanceof SQLPropertyExpr) {
                  tableName = ((SQLPropertyExpr) expr).normalizedName();
              } else {
                  tableName = expr.toString();
              }
  
              String destTableName = tableMapping.get(tableName);
              if (destTableName == null) {
                  if (expr instanceof SQLPropertyExpr) {
                      SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr;
                      String propName = propertyExpr.getName();
                      destTableName = tableMapping.get(propName);
                      if (destTableName == null
                              && propName.length() > 2 && propName.charAt(0) == '`' && propName.charAt(propName.length() - 1) == '`') {
                          destTableName = tableMapping.get(propName.substring(1, propName.length() - 1));
                      }
  
                      if (destTableName != null) {
                          propertyExpr.getOwner().accept(this);
                          print('.');
                          print(destTableName);
                          return;
                      }
                  } else if (expr instanceof SQLIdentifierExpr) {
                      boolean quote = tableName.length() > 2 && tableName.charAt(0) == '`' && tableName.charAt(tableName.length() - 1) == '`';
                      if (quote) {
                          destTableName = tableMapping.get(tableName.substring(1, tableName.length() - 1));
                      }
                  }
              }
              if (destTableName != null) {
                  tableName = destTableName;
                  printName0(tableName);
                  return;
              }
          }
  
          if (expr instanceof SQLIdentifierExpr) {
              SQLIdentifierExpr identifierExpr = (SQLIdentifierExpr) expr;
              final String name = identifierExpr.getName();
              if (!this.parameterized) {
                  printName0(name, false);
                  if (identifierExpr.hasAfterComment()) {
                      printAfterComments(identifierExpr.getAfterCommentsDirect());
                  }
                  return;
              }
  
              boolean shardingSupport = this.shardingSupport
                      && this.parameterized;
  
              if (shardingSupport) {
                  String nameUnwrappe = unwrapShardingTable(name);
  
                  if (!name.equals(nameUnwrappe)) {
                      incrementReplaceCunt();
                  }
  
                  printName0(nameUnwrappe);
              } else {
                  printName0(name);
              }
              if (identifierExpr.hasAfterComment()) {
                  printAfterComments(identifierExpr.getAfterCommentsDirect());
              }
          } else if (expr instanceof SQLPropertyExpr) {
              SQLPropertyExpr propertyExpr = (SQLPropertyExpr) expr;
              SQLExpr owner = propertyExpr.getOwner();
  
              if (owner instanceof SQLIdentifierExpr) {
                  SQLIdentifierExpr identOwner = (SQLIdentifierExpr) owner;
  
                  String ownerName = replaceQuota(identOwner.getName());
                  if (!this.parameterized) {
                      printName0(ownerName);
                  } else {
                      if (shardingSupport) {
                          ownerName = unwrapShardingTable(ownerName);
                      }
                      printName0(ownerName);
                  }
              } else {
                  printExpr(owner);
              }
              print('.');
  
              final String name = replaceQuota(propertyExpr.getName());
              if (!this.parameterized) {
                  printName0(name);
                  return;
              }
  
              boolean shardingSupport = this.shardingSupport
                      && this.parameterized;
  
              if (shardingSupport) {
                  String nameUnwrappe = unwrapShardingTable(name);
  
                  if (!name.equals(nameUnwrappe)) {
                      incrementReplaceCunt();
                  }
  
                  printName0(nameUnwrappe);
              } else {
                  printName0(name);
              }
          } else if (expr instanceof SQLMethodInvokeExpr) {
              visit((SQLMethodInvokeExpr) expr);
          } else {
              expr.accept(this);
          }
  
      }
  
      public String replaceQuota(String name) {
          if (dialect != null && name.length() >= 2 && dialect.getQuoteChars() != SQLDialect.DEFAULT_QUOTE_INT) {
              if ((name.charAt(0) == '`' && name.charAt(name.length() - 1) == '`')
                      || (name.charAt(0) == '"' && name.charAt(name.length() - 1) == '"')
                      || (name.charAt(0) == '\'' && name.charAt(name.length() - 1) == '\'')
                      || (name.charAt(0) == '[' && name.charAt(name.length() - 1) == ']')) {
                  if (!SQLDialect.Quote.isValidQuota(dialect.getQuoteChars(), SQLDialect.Quote.of(name.charAt(0)))) {
                      char quote = SQLDialect.Quote.getQuote(dialect.getQuoteChars());
                      if (quote == '[') {
                          name = '[' + name.substring(1, name.length() - 1) + ']';
                      } else {
                          name = quote + name.substring(1, name.length() - 1) + quote;
                      }
                  }
              }
          }
          return name;
      }
      public boolean visit(SQLExprTableSource x) {
          printTableSourceExpr(x.getExpr());
  
          final SQLTableSampling sampling = x.getSampling();
          if (sampling != null) {
              print(' ');
              sampling.accept(this);
          }
  
          String alias = x.getAlias();
          List<SQLName> columns = x.getColumnsDirect();
          if (alias != null) {
              print(' ');
              if (columns != null && columns.size() > 0) {
                  print0(ucase ? " AS " : " as ");
              } else if (x.isNeedAsTokenForAlias()) {
                  print0(ucase ? "AS " : "as ");
              }
              printAlias0(alias);
          }
  
          if (columns != null && columns.size() > 0) {
              print(" (");
              printAndAccept(columns, ", ");
              print(')');
          }
  
          printPivot(x.getPivot());
  
          printUnpivot(x.getUnpivot());
  
          if (isPrettyFormat() && x.hasAfterComment()) {
              print(' ');
              printlnComment(x.getAfterCommentsDirect());
          }
  
          return false;
      }
  
      protected void printPivot(SQLPivot pivot) {
          if (pivot != null) {
              println();
              pivot.accept(this);
          }
      }
  
      protected void printUnpivot(SQLUnpivot unpivot) {
          if (unpivot != null) {
              println();
              unpivot.accept(this);
          }
      }
  
      public boolean visit(SQLSelectStatement stmt) {
          List<SQLCommentHint> headHints = stmt.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  visit((SQLCommentHint) hint);
                  println();
              }
          }
  
          if (stmt.hasBeforeComment()) {
              printlnComments(stmt.getBeforeCommentsDirect());
          }
  
          SQLSelect select = stmt.getSelect();
          if (select != null) {
              this.visit(select);
          }
  
          return false;
      }
  
      public boolean visit(SQLVariantRefExpr x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          int index = x.getIndex();
  
          if (index < 0 || inputParameters == null || index >= inputParameters.size()) {
              print0(x.getName());
              if (x.hasAfterComment()) {
                  printAfterComments(x.getAfterCommentsDirect());
              }
              return false;
          }
  
          Object param = inputParameters.get(index);
  
          SQLObject parent = x.getParent();
  
          boolean in;
          if (parent instanceof SQLInListExpr) {
              in = true;
          } else if (parent instanceof SQLBinaryOpExpr) {
              SQLBinaryOpExpr binaryOpExpr = (SQLBinaryOpExpr) parent;
              if (binaryOpExpr.getOperator() == SQLBinaryOperator.Equality) {
                  in = true;
              } else {
                  in = false;
              }
          } else {
              in = false;
          }
  
          if (in && param instanceof Collection) {
              boolean first = true;
              for (Object item : (Collection) param) {
                  if (!first) {
                      print0(", ");
                  }
                  printParameter(item);
                  first = false;
              }
          } else {
              printParameter(param);
          }
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      private void printParameterWithSingleQuot(Object param) {
          print0("'");
          print(String.valueOf(param));
          print0("'");
      }
      public void printParameter(Object param) {
          if (param == null) {
              print0(ucase ? "NULL" : "null");
              return;
          }
  
          if (param instanceof Double) {
              param = BigDecimal.valueOf((double) param);
          }
          if (param instanceof BigDecimal) { // 不需要科学计数法输出
              print0(((BigDecimal) param).toPlainString());
              return;
          }
          if (param instanceof Calendar) {
              printParameterWithSingleQuot(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new java.sql.Timestamp(((Calendar) param).getTimeInMillis())));
              return;
          }
          if (param instanceof LocalDateTime) {
              printParameterWithSingleQuot(((LocalDateTime) param).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
              return;
          }
          if (param instanceof ZonedDateTime) {
              printParameterWithSingleQuot(((ZonedDateTime) param).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
              return;
          }
          if (param instanceof OffsetDateTime) {
              printParameterWithSingleQuot(((OffsetDateTime) param).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS")));
              return;
          }
          if (param instanceof OffsetTime || param instanceof LocalTime || param instanceof LocalDate || param instanceof Instant) {
              printParameterWithSingleQuot(param.toString());
              return;
          }
          if (param instanceof TemporalAmount) {
              printParameterWithSingleQuot(param.toString());
              return;
          }
          if (param instanceof Number //
                  || param instanceof Boolean || param instanceof java.time.temporal.Temporal) {
              print0(param.toString());
              return;
          }
  
          if (param instanceof String) {
              SQLCharExpr charExpr = new SQLCharExpr((String) param);
              visit(charExpr);
              return;
          }
  
          if (param instanceof Date) {
              print((Date) param);
              return;
          }
  
          if (param instanceof InputStream) {
              print0("'<InputStream>'");
              return;
          }
  
          if (param instanceof Reader) {
              print0("'<Reader>'");
              return;
          }
  
          if (param instanceof Blob) {
              print0("'<Blob>'");
              return;
          }
  
          if (param instanceof NClob) {
              print0("'<NClob>'");
              return;
          }
  
          if (param instanceof Clob) {
              print0("'<Clob>'");
              return;
          }
  
          if (param instanceof byte[]) {
              byte[] bytes = (byte[]) param;
              int bytesLen = bytes.length;
              char[] chars = new char[bytesLen * 2 + 3];
              chars[0] = 'x';
              chars[1] = '\'';
              for (int i = 0; i < bytes.length; i++) {
                  int a = bytes[i] & 0xFF;
                  int b0 = a >> 4;
                  int b1 = a & 0xf;
  
                  chars[i * 2 + 2] = (char) (b0 + (b0 < 10 ? 48 : 55)); //hexChars[b0];
                  chars[i * 2 + 3] = (char) (b1 + (b1 < 10 ? 48 : 55));
              }
              chars[chars.length - 1] = '\'';
              print0(new String(chars));
              return;
          }
  
          if (param instanceof Character) {
              print('\'');
              print((Character) param);
              print('\'');
              return;
          }
  
          print0("'" + param.getClass().getName() + "'");
      }
  
      public boolean visit(SQLDropTableStatement x) {
          List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  visit((SQLCommentHint) hint);
                  println();
              }
          }
  
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          print0(ucase ? "DROP " : "drop ");
          List<SQLCommentHint> hints = x.getHints();
          if (hints != null) {
              printAndAccept(hints, " ");
              print(' ');
          }
  
          if (x.isExternal()) {
              print0(ucase ? "EXTERNAL " : "external ");
          }
  
          if (x.isDropPartition()) {
              print0(ucase ? "PARTITIONED " : "partitioned ");
          }
  
          if (x.isTemporary()) {
              print0(ucase ? "TEMPORARY TABLE " : "temporary table ");
          } else {
              print0(ucase ? "TABLE " : "table ");
          }
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printAndAccept(x.getTableSources(), ", ");
  
          if (x.isCascade()) {
              printCascade();
          }
  
          if (x.isRestrict()) {
              print0(ucase ? " RESTRICT" : " restrict");
          }
  
          if (x.isPurge()) {
              print0(ucase ? " PURGE" : " purge");
          }
  
          if (x.getWhere() != null) {
              print0(ucase ? " WHERE " : " where ");
              x.getWhere().accept(this);
          }
  
          return false;
      }
  
      protected void printCascade() {
          print0(ucase ? " CASCADE" : " cascade");
      }
  
      public boolean visit(SQLDropViewStatement x) {
          List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  visit((SQLCommentHint) hint);
                  println();
              }
          }
  
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          print0(ucase ? "DROP VIEW " : "drop view ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printAndAccept(x.getTableSources(), ", ");
  
          if (x.isCascade()) {
              printCascade();
          }
          return false;
      }
  
      public boolean visit(SQLDropEventStatement x) {
          print0(ucase ? "DROP EVENT " : "drop event ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printExpr(x.getName(), parameterized);
          return false;
      }
  
      public boolean visit(SQLDropResourceStatement x) {
          print0(ucase ? "DROP RESOURCE " : "drop resource ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printExpr(x.getName(), parameterized);
          return false;
      }
  
      public boolean visit(SQLDropResourceGroupStatement x) {
          print0(ucase ? "DROP RESOURCE GROUP " : "drop resource group ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printExpr(x.getName(), parameterized);
          return false;
      }
  
      public boolean visit(SQLListResourceGroupStatement x) {
          print0(ucase ? "LIST RESOURCE GROUP" : "list resource group");
  
          return false;
      }
  
      public boolean visit(SQLColumnDefinition x) {
          boolean parameterized = this.parameterized;
          this.parameterized = false;
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
          String columnName = replaceQuota(x.getName().getSimpleName());
          printName0(columnName);
          final SQLDataType dataType = x.getDataType();
          if (dataType != null) {
              if (JdbcUtils.isPgsqlDbType(dbType) && x.getParent() instanceof SQLAlterTableAlterColumn) {
                  print0(ucase ? " TYPE " : " type ");
              } else {
                  print(' ');
              }
              dataType.accept(this);
          }
  
          if (x.getAggType() != null) {
              visitAggType(x);
          }
  
          if (x.getDefaultExpr() != null) {
              visitColumnDefault(x);
          }
  
          if (x.isAutoIncrement()) {
              printAutoIncrement();
          }
  
          final AutoIncrementType sequenceType = x.getSequenceType();
          if (sequenceType != null) {
              print0(" ");
              print0(ucase ? sequenceType.getKeyword() : sequenceType.getKeyword().toLowerCase());
          }
  
          for (SQLColumnConstraint item : x.getConstraints()) {
              boolean newLine = item instanceof SQLForeignKeyConstraint //
                      || item instanceof SQLPrimaryKey //
                      || item instanceof SQLColumnCheck //
                      || item.getName() != null;
              if (newLine) {
                  this.indentCount++;
                  println();
              } else {
                  print(' ');
              }
  
              item.accept(this);
  
              if (newLine) {
                  this.indentCount--;
              }
          }
  
          if (x.getCollateExpr() != null) {
              print0(ucase ? " COLLATE " : " collate ");
              x.getCollateExpr().accept(this);
          }
  
          if (x.isDisableNovalidate()) {
              print0(ucase ? " DISABLE NOVALIDATE" : " disable novalidate");
          }
  
          printGeneratedAlways(x, parameterized);
  
          if (x.isVirtual()) {
              print0(ucase ? " VIRTUAL" : " virtual");
          }
  
          if (x.isVisible()) {
              print0(ucase ? " VISIBLE" : " visible");
          }
  
          Boolean enable = x.getEnable();
          if (enable != null) {
              if (enable.booleanValue()) {
                  print0(ucase ? " ENABLE" : " enable");
              }
          }
  
          if (x.getComment() != null) {
              print0(ucase ? " COMMENT " : " comment ");
              x.getComment().accept(this);
          }
  
          printMappedBy(x.getMappedByDirect());
  
          printColumnProperties(x);
  
          printEncoding(x);
  
          printCompression(x);
  
          printBlockSize(x);
  
          this.parameterized = parameterized;
  
          return false;
      }
  
      protected void printEncoding(SQLColumnDefinition x) {
          if (x.getEncode() != null) {
              print0(ucase ? " ENCODE=" : " encode=");
              x.getEncode().accept(this);
          }
      }
  
      protected void printCompression(SQLColumnDefinition x) {
          if (x.getCompression() != null) {
              print0(ucase ? " COMPRESSION=" : " compression=");
              x.getCompression().accept(this);
          }
      }
  
      protected void printBlockSize(SQLColumnDefinition x) {
          if (x.getBlockSize() != null) {
              print0(ucase ? " BLOCK_SIZE " : " block_size ");
              x.getBlockSize().accept(this);
          }
      }
  
      protected void printColumnProperties(SQLColumnDefinition x) {
          List<SQLAssignItem> colProperties = x.getColPropertiesDirect();
          if (colProperties == null || colProperties.isEmpty()) {
              return;
          }
          print0(ucase ? " COLPROPERTIES (" : " colproperties (");
          printAndAccept(colProperties, ", ");
          print0(ucase ? ")" : ")");
      }
  
      protected void printMappedBy(List<SQLAssignItem> mappedBy) {
          if (mappedBy != null && mappedBy.size() > 0) {
              print0(ucase ? " MAPPED BY (" : " mapped by (");
              printAndAccept(mappedBy, ", ");
              print0(ucase ? ")" : ")");
          }
      }
  
      protected void printGeneratedAlways(SQLColumnDefinition x, boolean parameterized) {
          SQLExpr generatedAlwaysAs = x.getGeneratedAlwaysAs();
          if (generatedAlwaysAs != null) {
              print0(ucase ? " GENERATED ALWAYS AS " : " generated always as ");
              printExpr(generatedAlwaysAs, parameterized);
          }
  
          SQLColumnDefinition.Identity identity = x.getIdentity();
          if (identity != null) {
              if (dbType == DbType.h2) {
                  print0(ucase ? " AS " : " as ");
              } else {
                  print(' ');
              }
              identity.accept(this);
          }
      }
  
      protected void printAutoIncrement() {
          print0(ucase ? " AUTO_INCREMENT" : " auto_increment");
      }
  
      @Override
      public boolean visit(SQLColumnDefinition.Identity x) {
          print0(ucase ? "IDENTITY" : "identity");
          Integer seed = x.getSeed();
          if (seed != null) {
              print0(" (");
              print(seed);
              print0(", ");
              print(x.getIncrement());
              print(')');
          }
          return false;
      }
  
      protected void visitColumnDefault(SQLColumnDefinition x) {
          print0(ucase ? " DEFAULT " : " default ");
          printExpr(x.getDefaultExpr(), false);
      }
  
      protected void visitAggType(SQLColumnDefinition x) {
          print(' ');
          print0(ucase ? x.getAggType().getText().toUpperCase(Locale.ROOT) :
                  x.getAggType().getText().toLowerCase(Locale.ROOT));
      }
  
      public boolean visit(SQLDeleteStatement x) {
          SQLTableSource from = x.getFrom();
          String alias = x.getAlias();
  
          if (from == null) {
              print0(ucase ? "DELETE FROM " : "delete from ");
              printTableSourceExpr(x.getTableName());
  
              if (alias != null) {
                  print(' ');
                  print0(alias);
              }
          } else {
              print0(ucase ? "DELETE " : "delete ");
              printTableSourceExpr(x.getTableName());
              print0(ucase ? " FROM " : " from ");
              from.accept(this);
          }
  
          SQLExpr where = x.getWhere();
          if (where != null) {
              println();
              print0(ucase ? "WHERE " : "where ");
              this.indentCount++;
              where.accept(this);
              this.indentCount--;
          }
  
          return false;
      }
  
      public boolean visit(SQLCurrentOfCursorExpr x) {
          print0(ucase ? "CURRENT OF " : "current of ");
          printExpr(x.getCursorName(), parameterized);
          return false;
      }
  
      public void printInsertOverWrite(SQLInsertStatement x) {
          print0(ucase ? "INSERT OVERWRITE " : "insert overwrite ");
      }
      public boolean visit(SQLInsertStatement x) {
          List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          if (x.getInsertBeforeCommentsDirect() != null) {
              printlnComments(x.getInsertBeforeCommentsDirect());
          }
  
          SQLWithSubqueryClause with = x.getWith();
          if (with != null) {
              visit(with);
              println();
          }
  
          if (x.isUpsert()) {
              print0(ucase ? "UPSERT INTO " : "upsert into ");
          } else {
              if (x.isOverwrite()) {
                  printInsertOverWrite(x);
              } else {
                  print0(ucase ? "INSERT INTO " : "insert into ");
              }
          }
  
          x.getTableSource().accept(this);
  
          if (x.getPartitions() != null && !x.getPartitions().isEmpty()) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartitions(), ", ");
              print(')');
          }
          String columnsString = x.getColumnsString();
          if (columnsString != null) {
              print0(columnsString);
          } else {
              printInsertColumns(x.getColumns());
          }
  
          if (!x.getValuesList().isEmpty()) {
              println();
              print0(ucase ? "VALUES " : "values ");
              printAndAccept(x.getValuesList(), ", ");
          } else {
              if (x.getQuery() != null) {
                  println();
                  x.getQuery().accept(this);
              }
          }
  
          return false;
      }
  
      public void printInsertColumns(List<SQLExpr> columns) {
          final int size = columns.size();
          if (size > 0) {
              if (size > 5) {
                  this.indentCount++;
                  println();
              } else {
                  print(' ');
              }
              print('(');
              for (int i = 0; i < size; ++i) {
                  if (i != 0) {
                      if (i % 5 == 0) {
                          println();
                      }
                      print0(", ");
                  }
  
                  SQLExpr column = columns.get(i);
                  // 打印 前注释
                  if (column.hasBeforeComment()) {
                      printlnComments(column.getBeforeCommentsDirect());
                  }
                  if (column instanceof SQLIdentifierExpr) {
                      visit((SQLIdentifierExpr) column);
                  } else {
                      printExpr(column, parameterized);
                  }
                  // 打印 后注释
                  if (isPrettyFormat() && column.hasAfterComment()) {
                      print(' ');
                      printlnComment(column.getAfterCommentsDirect());
                      println();
                  }
                  String dataType = (String) column.getAttribute("dataType");
                  if (dataType != null) {
                      print(' ');
                      print(dataType);
                  }
              }
              print(')');
              if (size > 5) {
                  this.indentCount--;
              }
          }
      }
  
      public boolean visit(SQLUpdateSetItem x) {
          printExpr(x.getColumn(), parameterized);
          print0(" = ");
          printExpr(x.getValue(), parameterized);
          return false;
      }
  
      public boolean visit(SQLUpdateStatement x) {
          final List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          if (x.getWith() != null) {
              x.getWith().accept(this);
              println();
          }
  
          print0(ucase ? "UPDATE " : "update ");
  
          printTableSource(x.getTableSource());
  
          println();
          print0(ucase ? "SET " : "set ");
          for (int i = 0, size = x.getItems().size(); i < size; ++i) {
              if (i != 0) {
                  print0(", ");
              }
              SQLUpdateSetItem item = (SQLUpdateSetItem) x.getItems().get(i);
              visit(item);
          }
  
          SQLTableSource from = x.getFrom();
          if (from instanceof SQLTableSourceImpl) {
              println();
              indentCount++;
              print0(ucase ? "FROM " : "from ");
              if (from instanceof SQLSubqueryTableSource) {
                  visit((SQLSubqueryTableSource) from);
              } else if (from instanceof SQLExprTableSource) {
                  visit((SQLExprTableSource) from);
              }
              indentCount--;
          }
  
          SQLExpr where = x.getWhere();
          if (where != null) {
              println();
              indentCount++;
              print0(ucase ? "WHERE " : "where ");
              printExpr(where, parameterized);
              indentCount--;
          }
          if (x.getLimit() != null) {
              println();
              x.getLimit().accept(this);
          }
  
          return false;
      }
  
      protected void printTableElements(List<SQLTableElement> tableElementList) {
          int size = tableElementList.size();
          if (size == 0) {
              return;
          }
  
          print0(" (");
  
          this.indentCount++;
          println();
          for (int i = 0; i < size; ++i) {
              printTableElement(tableElementList, i);
          }
          this.indentCount--;
          println();
          print(')');
      }
  
      protected void printTableElement(List<SQLTableElement> tableElementList, int i) {
          int size = tableElementList.size();
          SQLTableElement element = tableElementList.get(i);
          element.accept(this);
  
          if (i != size - 1) {
              print(',');
          }
          if (this.isPrettyFormat() && element.hasAfterComment()) {
              print(' ');
              printlnComment(element.getAfterCommentsDirect());
          }
  
          if (i != size - 1) {
              println();
          }
      }
  
      protected void printEngine(SQLCreateTableStatement x) {
      }
  
      protected void printCreateTableRest(SQLCreateTableStatement x) {
      }
  
      protected void printOrderBy(SQLCreateTableStatement x) {
      }
      public boolean visit(SQLCreateTableStatement x) {
          printCreateTable(x, false);
          printPartitionOf(x);
          printEngine(x);
          printPartitionBy(x);
          printTableOptions(x);
          printCreateTableRest(x);
          printTablespace(x.getTablespace());
          printSelectAs(x, true);
          return false;
      }
  
      protected void printPartitionBy(SQLCreateTableStatement x) {
          SQLPartitionBy partitionBy = x.getPartitioning();
          if (partitionBy == null) {
              return;
          }
          println();
          print0(ucase ? "PARTITION BY " : "partition by ");
          partitionBy.accept(this);
      }
  
      protected void printSortedBy(List<SQLSelectOrderByItem> sortedBy) {
          if (sortedBy.size() > 0) {
              println();
              print0(ucase ? "SORTED BY (" : "sorted by (");
              printAndAccept(sortedBy, ", ");
              print(')');
          }
      }
  
      protected void printClusteredBy(SQLCreateTableStatement x) {
          List<SQLSelectOrderByItem> clusteredBy = x.getClusteredBy();
          if (clusteredBy.isEmpty()) {
              return;
          }
          println();
          if (x.getClusteringType() == ClusteringType.Range) {
              print0(ucase ? "RANGE " : "range ");
          }
          print0(ucase ? "CLUSTERED BY (" : "clustered by (");
          printAndAccept(clusteredBy, ",");
          print(')');
      }
  
      protected void printCreateTableAfterName(SQLCreateTableStatement x) {}
  
      protected void printCreateTable(SQLCreateTableStatement x, boolean printSelect) {
          print0(ucase ? "CREATE " : "create ");
  
          printCreateTableFeatures(x);
  
          print0(ucase ? "TABLE " : "table ");
  
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
  
          printTableSourceExpr(
                  x.getTableSource()
                  .getExpr());
  
          printCreateTableAfterName(x);
  
          printTableElements(x.getTableElementList());
  
          printInherits(x);
  
          printComment(x.getComment());
          printCollate(x);
  
          printPartitionedBy(x);
          printClusteredBy(x);
          printSortedBy(x.getSortedBy());
          printIntoBuckets(x.getBuckets());
          printIntoShards(x.getShards());
  
          printStoredAs(x);
          printStoredBy(x);
          printLifeCycle(x.getLifeCycle());
  
          printCreateTableLike(x);
  
          printSelectAs(x, printSelect);
      }
  
      protected void printInherits(SQLCreateTableStatement x) {
          SQLExprTableSource inherits = x.getInherits();
          if (inherits != null) {
              print0(ucase ? " INHERITS (" : " inherits (");
              inherits.accept(this);
              print(')');
          }
      }
  
      protected void printStoredBy(SQLCreateTableStatement x) {
          SQLExpr storedBy = x.getStoredBy();
          if (storedBy != null) {
              print0(ucase ? " STORE BY " : " store by ");
              printExpr(storedBy, parameterized);
          }
      }
  
      protected void printSelectAs(SQLCreateTableStatement x, boolean printSelect) {
          SQLSelect select = x.getSelect();
          if (printSelect && select != null) {
              println();
              print0(ucase ? "AS" : "as");
  
              println();
              visit(select);
          }
      }
  
      protected void printPartitionOf(SQLCreateTableStatement x) {
          SQLPartitionOf partitionOf = x.getPartitionOf();
          if (partitionOf != null) {
              println();
              print0(ucase ? "PARTITION OF " : "partition of ");
              partitionOf.accept(this);
          }
      }
  
      protected void printTablespace(SQLName tablespace) {
          if (tablespace != null) {
              println();
              print0(ucase ? "TABLESPACE " : "tablespace ");
              tablespace.accept(this);
          }
      }
  
      protected void printComment(SQLExpr comment) {
          if (comment == null) {
              return;
          }
          println();
          print0(ucase ? "COMMENT " : "comment ");
          comment.accept(this);
      }
  
      protected void printCollate(SQLCreateTableStatement x){
      }
  
      protected void printStoredAs(SQLCreateTableStatement x) {
          SQLExpr storedAs = x.getStoredAs();
          if (storedAs == null) {
              return;
          }
          print0(ucase ? " STORE AS " : " store as ");
          printExpr(storedAs, parameterized);
      }
  
      public boolean visit(SQLUniqueConstraint x) {
          if (x.getName() != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              x.getName().accept(this);
              print(' ');
          }
  
          print0(ucase ? "UNIQUE (" : "unique (");
          List<SQLSelectOrderByItem> columns = x.getColumns();
          for (int i = 0, size = columns.size(); i < size; ++i) {
              if (i != 0) {
                  print0(", ");
              }
              visit(columns.get(i));
          }
          print(')');
          return false;
      }
  
      public boolean visit(SQLNotNullConstraint x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
          print0(ucase ? "NOT NULL" : "not null");
  
          List<SQLCommentHint> hints = x.hints;
          if (hints != null) {
              print(' ');
              for (SQLCommentHint hint : hints) {
                  hint.accept(this);
              }
          }
  
          return false;
      }
  
      public boolean visit(SQLNullConstraint x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
          print0(ucase ? "NULL" : "null");
          return false;
      }
  
      @Override
      public boolean visit(SQLUnionQuery x) {
          SQLWithSubqueryClause with = x.getWith();
          if (with != null) {
              with.accept(this);
              println();
          }
          SQLUnionOperator operator = x.getOperator();
  
          List<SQLSelectQuery> relations = x.getRelations();
          if (relations.size() > 2) {
              for (int i = 0; i < relations.size(); i++) {
                  if (i != 0) {
                      println();
                      print0(ucase ? operator.name : operator.nameLCase);
                      println();
                  }
  
                  SQLSelectQuery item = relations.get(i);
                  item.accept(this);
              }
  
              SQLOrderBy orderBy = x.getOrderBy();
              if (orderBy != null) {
                  println();
                  orderBy.accept(this);
              }
  
              SQLLimit limit = x.getLimit();
              if (limit != null) {
                  println();
                  limit.accept(this);
              }
              return false;
          }
  
          SQLSelectQuery left = x.getLeft();
          SQLSelectQuery right = x.getRight();
  
          boolean bracket = x.isParenthesized() && !(x.getParent() instanceof SQLUnionQueryTableSource);
  
          SQLOrderBy orderBy = x.getOrderBy();
          if ((!bracket)
                  && left instanceof SQLUnionQuery
                  && ((SQLUnionQuery) left).getOperator() == operator
                  && !right.isParenthesized()
                  && x.getLimit() == null
                  && orderBy == null) {
              SQLUnionQuery leftUnion = (SQLUnionQuery) left;
  
              if (right instanceof SQLSelectQueryBlock) {
                  SQLSelectQueryBlock rightQueryBlock = (SQLSelectQueryBlock) right;
                  if (rightQueryBlock.getOrderBy() != null || rightQueryBlock.getLimit() != null) {
                      rightQueryBlock.setParenthesized(true);
                  }
              }
  
              List<SQLSelectQuery> rights = new ArrayList<SQLSelectQuery>();
              rights.add(right);
  
              if (leftUnion.getRelations().size() > 2) {
                  rights.addAll(leftUnion.getRelations());
              } else {
                  for (; ; ) {
                      SQLSelectQuery leftLeft = leftUnion.getLeft();
                      SQLSelectQuery leftRight = leftUnion.getRight();
  
                      if (leftRight instanceof SQLSelectQueryBlock) {
                          SQLSelectQueryBlock leftRightQueryBlock = (SQLSelectQueryBlock) leftRight;
                          if (leftRightQueryBlock.getOrderBy() != null || leftRightQueryBlock.getLimit() != null) {
                              leftRightQueryBlock.setParenthesized(true);
                          }
                      }
  
                      if ((!leftUnion.isParenthesized())
                              && leftUnion.getOrderBy() == null
                              && (!leftLeft.isParenthesized())
                              && (!leftRight.isParenthesized())
                              && leftLeft instanceof SQLUnionQuery
                              && ((SQLUnionQuery) leftLeft).getOperator() == operator) {
                          rights.add(leftRight);
                          leftUnion = (SQLUnionQuery) leftLeft;
                          continue;
                      } else {
                          rights.add(leftRight);
                          rights.add(leftLeft);
                      }
                      break;
                  }
              }
  
              for (int i = rights.size() - 1; i >= 0; i--) {
                  SQLSelectQuery item = rights.get(i);
                  item.accept(this);
  
                  if (i > 0) {
                      println();
                      print0(ucase ? operator.name : operator.nameLCase);
                      println();
                  }
              }
              return false;
          }
  
          if (bracket) {
              print('(');
          }
  
          if (left != null) {
              for (; ; ) {
                  if (left.getClass() == SQLUnionQuery.class) {
                      SQLUnionQuery leftUnion = (SQLUnionQuery) left;
                      if (leftUnion.getRelations().size() > 2) {
                          visit(leftUnion);
                      } else {
                          SQLSelectQuery leftLeft = leftUnion.getLeft();
                          SQLSelectQuery leftRigt = leftUnion.getRight();
                          if ((!leftUnion.isParenthesized())
                                  && leftUnion.getRight() instanceof SQLSelectQueryBlock
                                  && leftUnion.getLeft() != null
                                  && leftUnion.getLimit() == null
                                  && leftUnion.getOrderBy() == null) {
                              if (leftLeft.getClass() == SQLUnionQuery.class) {
                                  visit((SQLUnionQuery) leftLeft);
                              } else {
                                  printQuery(leftLeft);
                              }
                              println();
                              print0(ucase ? leftUnion.getOperator().name : leftUnion.getOperator().nameLCase);
                              println();
                              leftRigt.accept(this);
                          } else {
                              visit(leftUnion);
                          }
                      }
                  } else {
                      left.accept(this);
                  }
                  break;
              }
          }
  
          if (right == null) {
              return false;
          }
  
          println();
          print0(ucase ? operator.name : operator.nameLCase);
          println();
  
          boolean needParen = false;
          if ((!right.isParenthesized())
                  && right instanceof SQLSelectQueryBlock) {
              SQLSelectQueryBlock rightQuery = (SQLSelectQueryBlock) right;
              if (rightQuery.getOrderBy() != null || rightQuery.getLimit() != null) {
                  needParen = true;
              }
          }
  
          if (needParen) {
              print('(');
              right.accept(this);
              print(')');
          } else {
              right.accept(this);
          }
  
          if (orderBy != null) {
              println();
              orderBy.accept(this);
          }
  
          SQLLimit limit = x.getLimit();
          if (limit != null) {
              println();
              limit.accept(this);
          }
  
          if (bracket) {
              print(')');
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLUnaryExpr x) {
          SQLUnaryOperator operator = x.getOperator();
          if (x.isParenthesized()) {
              print('(');
          }
          print0(operator.name);
          SQLExpr expr = x.getExpr();
  
          switch (operator) {
              case BINARY:
              case Prior:
              case ConnectByRoot:
              case NOT:
                  print(' ');
                  if (operator != SQLUnaryOperator.Prior && expr instanceof SQLBinaryOpExpr && !((SQLBinaryOpExpr) expr).isParenthesized()) {
                      print('(');
                      expr.accept(this);
                      print(')');
                  } else {
                      expr.accept(this);
                  }
                  return false;
              default:
                  break;
          }
  
          if (expr instanceof SQLBinaryOpExpr
                  || expr instanceof SQLBinaryOpExprGroup
                  || expr instanceof SQLUnaryExpr
                  || expr instanceof SQLNotExpr
                  || expr instanceof SQLBetweenExpr
                  || expr instanceof SQLInListExpr
                  || expr instanceof SQLInSubQueryExpr) {
              incrementIndent();
              //print('(');
              expr.accept(this);
              //print(')');
              decrementIndent();
          } else {
              expr.accept(this);
          }
          if (x.isParenthesized()) {
              print(')');
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLHexExpr x) {
          if (this.parameterized) {
              print('?');
              incrementReplaceCunt();
  
              if (this.parameters != null) {
                  ExportParameterVisitorUtils.exportParameter(this.parameters, x);
              }
              return false;
          }
  
          print0("0x");
          print0(x.getHex());
  
          String charset = (String) x.getAttribute("USING");
          if (charset != null) {
              print0(ucase ? " USING " : " using ");
              print0(charset);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSetStatement x) {
          if (x.getHeadHintsDirect() != null) {
              for (SQLCommentHint hint : x.getHeadHintsDirect()) {
                  hint.accept(this);
              }
          }
  
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          SQLSetStatement.Option option = x.getOption();
  
          boolean printSet = Boolean.TRUE.equals(x.getAttribute("parser.set")) || !JdbcUtils.isOracleDbType(dbType);
          if (printSet) {
              if (option == SQLSetStatement.Option.PROJECT) {
                  print0(ucase ? "SETPROJECT " : "setproject ");
              } else {
                  print0(ucase ? "SET " : "set ");
              }
          }
  
          if (option != null && option != SQLSetStatement.Option.PROJECT) {
              print(option.name());
              print(' ');
          }
  
          printAndAccept(x.getItems(), ", ");
  
          if (x.getHints() != null && x.getHints().size() > 0) {
              print(' ');
              printAndAccept(x.getHints(), " ");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAssignItem x) {
          x.getTarget().accept(this);
  
          SQLExpr value = x.getValue();
          if (value != null) {
              print0(" =");
              if (!(value instanceof SQLQueryExpr)) {
                  print(' ');
              }
              value.accept(this);
          } else {
              if (dbType == DbType.odps) {
                  print0(" = ");
              }
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLCallStatement x) {
          if (x.isBrace()) {
              print('{');
          }
          if (x.getOutParameter() != null) {
              x.getOutParameter().accept(this);
              print0(" = ");
          }
  
          print0(ucase ? "CALL " : "call ");
          x.getProcedureName().accept(this);
          print('(');
  
          printAndAccept(x.getParameters(), ", ");
          print(')');
          if (x.isBrace()) {
              print('}');
          }
          return false;
      }
  
      protected void printJoinHint(SQLJoinTableSource x){}
      @Override
      public boolean visit(SQLJoinTableSource x) {
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
          SQLCommentHint hint = x.getHint();
          if (hint != null) {
              hint.accept(this);
          }
  
          SQLTableSource left = x.getLeft();
  
          String alias = x.getAlias();
          if (alias != null) {
              print('(');
          }
  
          JoinType joinType = x.getJoinType();
          if (left instanceof SQLJoinTableSource
                  && ((SQLJoinTableSource) left).getJoinType() == JoinType.COMMA
                  && joinType != JoinType.COMMA
                  && DbType.postgresql != dbType
                  && ((SQLJoinTableSource) left).getAttribute("ads.comma_join") == null) {
              print('(');
              printTableSource(left);
              print(')');
          } else {
              printTableSource(left);
          }
          this.indentCount++;
  
          if (joinType == JoinType.COMMA) {
              print(',');
          } else {
              println();
  
              boolean asof = x.isAsof();
              if (asof) {
                  print0(ucase ? " ASOF " : " asof ");
              }
  
              if (x.isNatural()) {
                  //print0(ucase ? "NATURAL " : "natural ");
              }
  
              if (x.isGlobal()) {
                  print0(ucase ? "GLOBAL " : "global ");
              }
  
              printJoinType(joinType);
          }
          printJoinHint(x);
          print(' ');
  
          SQLTableSource right = x.getRight();
          if (right instanceof SQLJoinTableSource && ((SQLJoinTableSource) right).getJoinType() == JoinType.COMMA) {
              print('(');
              printTableSource(right);
              print(')');
          } else {
              printTableSource(right);
          }
  
          SQLExpr condition = x.getCondition();
          if (condition != null) {
              boolean newLine = false;
  
              if (right instanceof SQLSubqueryTableSource) {
                  newLine = true;
              } else if (condition instanceof SQLBinaryOpExpr) {
                  SQLBinaryOperator op = ((SQLBinaryOpExpr) condition).getOperator();
                  if (op == SQLBinaryOperator.BooleanAnd || op == SQLBinaryOperator.BooleanOr) {
                      newLine = true;
                  }
              } else if (condition instanceof SQLBinaryOpExprGroup) {
                  newLine = true;
              }
              if (newLine) {
                  println();
              } else {
                  print(' ');
              }
              this.indentCount++;
              print0(ucase ? "ON " : "on ");
              printExpr(condition, parameterized);
              if (x.getAfterCommentsDirect() != null) {
                  printAfterComments(x.getAfterCommentsDirect());
                  println();
              }
              this.indentCount--;
          }
  
          if (x.getUsing().size() > 0) {
              print0(ucase ? " USING (" : " using (");
              printAndAccept(x.getUsing(), ", ");
              print(')');
          }
  
          if (alias != null) {
              print0(ucase ? ") AS " : ") as ");
              print0(alias);
          }
  
          SQLJoinTableSource.UDJ udj = x.getUdj();
          if (udj != null) {
              println();
              udj.accept(this);
          }
  
          this.indentCount--;
  
          printPivot(x.getPivot());
          printUnpivot(x.getUnpivot());
          if (x.hasAfterComment()) {
              printAfterComments(x.getAfterCommentsDirect());
          }
          return false;
      }
  
      public boolean visit(SQLJoinTableSource.UDJ x) {
          print0(ucase ? "USING " : "using ");
          x.getFunction().accept(this);
          print('(');
          printAndAccept(x.getArguments(), ", ");
          print0(") ");
          print0(x.getAlias());
          print0(ucase ? " AS (" : " as (");
          printAndAccept(x.getColumns(), ", ");
          print0(")");
  
          List<SQLAssignItem> properties = x.getProperties();
          if (!properties.isEmpty()) {
              print0(ucase ? " WITH UDFPROPERTIES (" : " with udfproperties (");
              printAndAccept(properties, ", ");
              print(')');
          }
          return false;
      }
  
      protected void printJoinType(JoinType joinType) {
          print0(ucase ? joinType.name : joinType.nameLCase);
      }
  
      static String[] variantValuesCache_1 = new String[64];
      static String[] variantValuesCache = new String[64];
  
      static {
          for (int len = 0; len < variantValuesCache_1.length; ++len) {
              StringBuilder buf = new StringBuilder();
              buf.append('(');
              for (int i = 0; i < len; ++i) {
                  if (i != 0) {
                      if (i % 5 == 0) {
                          buf.append("\n\t");
                      }
                      buf.append(", ");
                  }
                  buf.append('?');
              }
              buf.append(')');
              variantValuesCache_1[len] = buf.toString();
          }
  
          for (int len = 0; len < variantValuesCache.length; ++len) {
              StringBuilder buf = new StringBuilder();
              buf.append('(');
              for (int i = 0; i < len; ++i) {
                  if (i != 0) {
                      if (i % 5 == 0) {
                          buf.append("\n\t\t");
                      }
                      buf.append(", ");
                  }
                  buf.append('?');
              }
              buf.append(')');
              variantValuesCache[len] = buf.toString();
          }
      }
  
      @Override
      public boolean visit(ValuesClause x) {
          return visit(x, parameters);
      }
  
      public boolean visit(ValuesClause x, List<Object> parameters) {
          if ((!this.parameterized)
                  && isEnabled(VisitorFeature.OutputUseInsertValueClauseOriginalString)
                  && x.getOriginalString() != null) {
              print0(x.getOriginalString());
              return false;
          }
  
          int xReplaceCount = x.getReplaceCount();
          final List<SQLExpr> values = x.getValues();
  
          this.replaceCount += xReplaceCount;
  
          if (xReplaceCount == values.size() && xReplaceCount < variantValuesCache.length) {
              String variantValues;
              if (indentCount == 0) {
                  variantValues = variantValuesCache_1[xReplaceCount];
              } else {
                  variantValues = variantValuesCache[xReplaceCount];
              }
              print0(variantValues);
              return false;
          }
  
          if (inputParameters != null
                  && inputParameters.size() > 0
                  && inputParameters.get(0) instanceof Collection
                  && ((Collection) inputParameters.get(0)).size() == x.getValues().size()) {
              incrementIndent();
              for (int i = 0; i < inputParameters.size(); i++) {
                  Collection params = (Collection) inputParameters.get(i);
                  if (i != 0) {
                      print(',');
                      println();
                  }
  
                  print('(');
                  int j = 0;
                  for (Object item : (Collection) params) {
                      if (j != 0) {
                          if (j % 5 == 0) {
                              println();
                          }
                          print0(", ");
                      }
                      printParameter(item);
                      j++;
                  }
                  print(')');
              }
              decrementIndent();
              return false;
          }
  
          print('(');
          this.indentCount++;
  
          for (int i = 0, size = values.size(); i < size; ++i) {
              if (i != 0) {
                  if (i % 5 == 0) {
                      println();
                  }
                  print0(", ");
              }
  
              Object value = values.get(i);
  
              if (value instanceof SQLExpr) {
                  SQLExpr expr = values.get(i);
  
                  if (this.parameterized &&
                          (expr instanceof SQLIntegerExpr
                                  || expr instanceof SQLBooleanExpr
                                  || expr instanceof SQLNumberExpr
                                  || expr instanceof SQLCharExpr
                                  || expr instanceof SQLNCharExpr
                                  || expr instanceof SQLTimestampExpr
                                  || expr instanceof SQLDateExpr
                                  || expr instanceof SQLTimeExpr)) {
                      print('?');
                      incrementReplaceCunt();
                      if (parameters != null) {
                          ExportParameterVisitorUtils.exportParameter(parameters, expr);
                      }
                      continue;
                  } else if (this.parameterized && expr instanceof SQLNullExpr) {
                      print('?');
                      if (parameters != null) {
                          parameters.add(null);
                      }
                      incrementReplaceCunt();
                  } else if (expr instanceof SQLVariantRefExpr) {
                      visit((SQLVariantRefExpr) expr);
                  } else {
                      expr.accept(this);
                  }
              } else if (value instanceof Integer) {
                  print(((Integer) value).intValue());
              } else if (value instanceof Long) {
                  print(((Long) value).longValue());
              } else if (value instanceof String) {
                  printChars((String) value);
              } else if (value instanceof Float) {
                  print(((Float) value).floatValue());
              } else if (value instanceof Double) {
                  print(((Double) value).doubleValue());
              } else if (value instanceof Date) {
                  print((Date) value);
              } else if (value instanceof BigDecimal) {
                  print(((BigDecimal) value).toPlainString());
              } else if (value instanceof BigInteger) {
                  print(value.toString());
              } else if (value == null) {
                  print0(ucase ? "NULL" : "null");
              } else {
                  throw new UnsupportedOperationException();
              }
          }
  
          this.indentCount--;
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLSomeExpr x) {
          print0(ucase ? "SOME (" : "some (");
          this.indentCount++;
          println();
          x.getSubQuery().accept(this);
          this.indentCount--;
          println();
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLAnyExpr x) {
          print0(ucase ? "ANY (" : "any (");
          this.indentCount++;
          println();
          x.getSubQuery().accept(this);
          this.indentCount--;
          println();
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLAllExpr x) {
          print0(ucase ? "ALL (" : "all (");
          this.indentCount++;
          println();
          x.getSubQuery().accept(this);
          this.indentCount--;
          println();
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLInSubQueryExpr x) {
          if (x.getExpr() instanceof SQLIdentifierExpr) {
              String columnName = replaceQuota(((SQLIdentifierExpr) x.getExpr()).getName());
              printName0(columnName);
          } else {
              x.getExpr().accept(this);
          }
          if (x.isNot()) {
              if (x.isGlobal()) {
                  print0(ucase ? " GLOBAL NOT IN (" : " global not in (");
              } else {
                  print0(ucase ? " NOT IN (" : " not in (");
              }
          } else {
              if (x.isGlobal()) {
                  print0(ucase ? " GLOBAL IN (" : " global in (");
              } else {
                  print0(ucase ? " IN (" : " in (");
              }
          }
  
          this.indentCount++;
          println();
          x.getSubQuery().accept(this);
          this.indentCount--;
          println();
          print(')');
  
          if (x.getHint() != null) {
              x.getHint().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLListExpr x) {
          print('(');
          printAndAccept(x.getItems(), ", ");
          print(')');
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSubqueryTableSource x) {
          print('(');
          this.indentCount++;
          println();
  
          if (x.getHintsSize() > 0) {
              printAndAccept(x.getHints(), " ");
              println();
          }
  
          this.visit(x.getSelect());
          this.indentCount--;
          println();
          print(')');
  
          final List<SQLName> columns = x.getColumns();
          final String alias = x.getAlias();
          if (alias != null) {
              if (!columns.isEmpty()) {
                  print0(" AS ");
              } else {
                  print(' ');
              }
              print0(alias);
          }
  
          if (!columns.isEmpty()) {
              print0(" (");
              for (int i = 0; i < columns.size(); i++) {
                  if (i != 0) {
                      print0(", ");
                  }
                  printExpr(columns.get(i));
              }
              print(')');
          }
  
          printPivot(x.getPivot());
  
          printUnpivot(x.getUnpivot());
  
          if (isPrettyFormat() && x.hasAfterComment()) {
              print(' ');
              printlnComment(x.getAfterCommentsDirect());
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLUnnestTableSource x) {
          print0(ucase ? "UNNEST(" : "unnest(");
          incrementIndent();
          List<SQLExpr> items = x.getItems();
          printAndAccept(items, ", ");
          decrementIndent();
          print(')');
  
          if (x.isOrdinality()) {
              print0(ucase ? " WITH ORDINALITY" : " with ordinality");
          }
  
          final List<SQLName> columns = x.getColumns();
          final String alias = x.getAlias();
          if (alias != null) {
              print0(ucase ? " AS " : " as ");
              print0(alias);
          }
  
          if (columns.size() > 0) {
              print0(" (");
              for (int i = 0; i < columns.size(); i++) {
                  if (i != 0) {
                      print0(", ");
                  }
                  printExpr(columns.get(i));
              }
              print(')');
          }
  
          if (x.getOffset() != null) {
              print0(ucase ? " WITH OFFSET AS " : " with offset as ");
              x.getOffset().accept(this);
          }
          printPivot(x.getPivot());
          printUnpivot(x.getUnpivot());
          return false;
      }
  
      @Override
      public boolean visit(SQLGeneratedTableSource x) {
          printExpr(x.getMethodName());
          print('(');
          List<SQLExpr> items = x.getItems();
          printAndAccept(items, ", ");
          print(')');
  
          final List<SQLName> columns = x.getColumns();
          final String alias = x.getAlias();
          if (alias != null) {
              if (columns.size() > 0) {
                  print0(ucase ? " AS " : " as ");
              } else {
                  print(' ');
              }
              print0(alias);
          }
  
          if (columns.size() > 0) {
              print0(" (");
              for (int i = 0; i < columns.size(); i++) {
                  if (i != 0) {
                      print0(", ");
                  }
                  printExpr(columns.get(i));
              }
              print(')');
          }
          printPivot(x.getPivot());
          printUnpivot(x.getUnpivot());
          return false;
      }
  
      @Override
      public boolean visit(SQLTruncateStatement x) {
          List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  visit((SQLCommentHint) hint);
                  println();
              }
          }
  
          print0(ucase ? "TRUNCATE TABLE " : "truncate table ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printAndAccept(x.getTableSources(), ", ");
  
          if (x.getPartitions().size() > 0) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartitions(), ", ");
              print(')');
          }
  
          if (x.isPartitionAll()) {
              print0(ucase ? " PARTITION ALL" : " partition all");
          } else if (x.getPartitionsForADB().size() > 0) {
              print0(ucase ? " PARTITION " : " partition ");
              printAndAccept(x.getPartitionsForADB(), ", ");
          }
  
          if (x.isDropStorage()) {
              print0(ucase ? " DROP STORAGE" : " drop storage");
          }
  
          if (x.isReuseStorage()) {
              print0(ucase ? " REUSE STORAGE" : " reuse storage");
          }
  
          if (x.isIgnoreDeleteTriggers()) {
              print0(ucase ? " IGNORE DELETE TRIGGERS" : " ignore delete triggers");
          }
  
          if (x.isRestrictWhenDeleteTriggers()) {
              print0(ucase ? " RESTRICT WHEN DELETE TRIGGERS" : " restrict when delete triggers");
          }
  
          if (x.isContinueIdentity()) {
              print0(ucase ? " CONTINUE IDENTITY" : " continue identity");
          }
  
          if (x.isImmediate()) {
              print0(ucase ? " IMMEDIATE" : " immediate");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDefaultExpr x) {
          print0(ucase ? "DEFAULT" : "default");
          return false;
      }
  
      @Override
      public void endVisit(SQLCommentStatement x) {
      }
  
      @Override
      public boolean visit(SQLCommentStatement x) {
          print0(ucase ? "COMMENT ON " : "comment on ");
          if (x.getType() != null) {
              print0(x.getType().name());
              print(' ');
          }
          x.getOn().accept(this);
  
          print0(ucase ? " IS " : " is ");
          x.getComment().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLBeginStatement x) {
          print0(ucase ? "BEGIN " : "begin ");
          if (x.getTidbTxnMode() != null) {
              x.getTidbTxnMode().accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLUseStatement x) {
          print0(ucase ? "USE " : "use ");
          x.getDatabase().accept(this);
          return false;
      }
  
      protected boolean isOdps() {
          return DbType.odps == dbType;
      }
  
      @Override
      public boolean visit(SQLAlterTableAddColumn x) {
          print0(ucase ? "ADD" : "add");
  
          if (DbType.odps == dbType || DbType.hive == dbType || DbType.spark == dbType) {
              print0(ucase ? " COLUMNS" : " columns");
          }
  
          if (x.isIfNotExists()) {
              print0(ucase ? " IF NOT EXISTS" : " if not exists");
          }
  
          print0(" (");
          printAndAccept(x.getColumns(), ", ");
          print(')');
  
          final Boolean restrict = x.getRestrict();
          if (restrict != null && restrict.booleanValue()) {
              print0(ucase ? " RESTRICT" : " restrict");
          }
  
          if (x.isCascade()) {
              print0(ucase ? " CASCADE" : " cascade");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableReplaceColumn x) {
          print0(ucase ? "REPLACE COLUMNS (" : "replace columns (");
          printAndAccept(x.getColumns(), ", ");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropColumnItem x) {
          print0(ucase ? "DROP COLUMN " : "drop column ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          this.printAndAccept(x.getColumns(), ", ");
  
          if (x.isRestrict()) {
              print0(ucase ? " RESTRICT" : " restrict");
          }
          if (x.isCascade()) {
              print0(ucase ? " CASCADE" : " cascade");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDeleteByCondition x) {
          printUcase("DELETE WHERE ");
          x.getWhere().accept(this);
  
          return false;
      }
  
      @Override
      public void endVisit(SQLAlterTableAddColumn x) {
      }
  
      @Override
      public boolean visit(SQLDropIndexStatement x) {
          print0(ucase ? "DROP INDEX " : "drop index ");
          x.getIndexName().accept(this);
  
          SQLExprTableSource table = x.getTableName();
          if (table != null) {
              print0(ucase ? " ON " : " on ");
              table.accept(this);
          }
  
          SQLExpr algorithm = x.getAlgorithm();
          if (algorithm != null) {
              print0(ucase ? " ALGORITHM " : " algorithm ");
              algorithm.accept(this);
          }
  
          SQLExpr lockOption = x.getLockOption();
          if (lockOption != null) {
              print0(ucase ? " LOCK " : " lock ");
              lockOption.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropLogFileGroupStatement x) {
          print0(ucase ? "DROP LOGFILE GROUP " : "drop logfile group ");
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropServerStatement x) {
          print0(ucase ? "DROP SERVER " : "drop server ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropTypeStatement x) {
          print0(ucase ? "DROP TYPE " : "drop type ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropSynonymStatement x) {
          if (x.isPublic()) {
              print0(ucase ? "DROP PUBLIC SYNONYM " : "drop public synonym ");
          } else {
              print0(ucase ? "DROP SYNONYM " : "drop synonym ");
          }
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getName().accept(this);
  
          if (x.isForce()) {
              print0(ucase ? " FORCE" : " force");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSavePointStatement x) {
          print0(ucase ? "SAVEPOINT" : "savepoint");
          SQLExpr name = x.getName();
          if (name != null) {
              print(' ');
              name.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLReleaseSavePointStatement x) {
          print0(ucase ? "RELEASE SAVEPOINT " : "release savepoint ");
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLRollbackStatement x) {
          print0(ucase ? "ROLLBACK" : "rollback");
          if (x.getTo() != null) {
              print0(ucase ? " TO " : " to ");
              x.getTo().accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLCommentHint x) {
          if (x.hasBeforeComment() && !parameterized) {
              printlnComment(x.getBeforeCommentsDirect());
              println();
          }
          print0("/*");
          print0(x.getText());
          print0("*/");
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateDatabaseStatement x) {
          if (x.getHeadHintsDirect() != null) {
              for (SQLCommentHint hint : x.getHeadHintsDirect()) {
                  hint.accept(this);
              }
          }
  
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          print0(ucase ? "CREATE " : "create ");
  
          if (x.isPhysical()) {
              print0(ucase ? "PHYSICAL " : "physical ");
          }
  
          print0(ucase ? "DATABASE " : "database ");
  
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
          x.getName().accept(this);
  
          if (x.getCharacterSet() != null) {
              print0(ucase ? " CHARACTER SET " : " character set ");
              print0(x.getCharacterSet());
          }
  
          final String collate = x.getCollate();
          if (collate != null) {
              print0(ucase ? " COLLATE " : " collate ");
              print0(collate);
          }
  
          final SQLExpr comment = x.getComment();
          if (comment != null) {
              print0(ucase ? " COMMENT " : " comment ");
              printExpr(comment, false);
          }
  
          final SQLExpr location = x.getLocation();
          if (location != null) {
              print0(ucase ? " LOCATION " : " location ");
              printExpr(location, false);
          }
  
          if (x.getDbProperties().size() > 0) {
              if (dbType == DbType.mysql
                      || dbType == DbType.ads
                      || dbType == DbType.mariadb) {
                  println();
                  print0(ucase ? "WITH (" : "with (");
              } else if (dbType == DbType.hive || dbType == DbType.presto || dbType == DbType.trino || dbType == DbType.supersql || dbType == DbType.spark) {
                  println();
                  print0(ucase ? "WITH DBPROPERTIES (" : "with dbproperties (");
              } else {
                  println();
                  print0(ucase ? "WITH PROPERTIES (" : "with properties (");
              }
              incrementIndent();
              println();
              printlnAndAccept(x.getDbProperties(), ",");
              decrementIndent();
              println();
              print(')');
          }
  
          if (x.getUser() != null) {
              print0(ucase ? " FOR " : " for ");
              print0("'" + x.getUser() + "'");
          }
  
          if (x.getOptions().size() > 0) {
              print0(ucase ? " OPTIONS (" : " options (");
              for (Map.Entry<String, SQLExpr> option : x.getOptions().entrySet()) {
                  print0(option.getKey());
                  print0("=");
                  printExpr(option.getValue(), false);
                  print0(" ");
              }
              print(')');
          }
  
          final List<List<SQLAssignItem>> storedBy = x.getStoredBy();
          if (!storedBy.isEmpty()) {
              println();
              print0(ucase ? "STORED BY " : "stored by ");
  
              incrementIndent();
              for (int i = 0; i < storedBy.size(); i++) {
                  List<SQLAssignItem> items = storedBy.get(i);
                  if (i != 0) {
                      print(',');
                      println();
                  }
                  print('(');
                  printAndAccept(items, ", ");
                  print(')');
              }
              decrementIndent();
          }
  
          final SQLExpr storedIn = x.getStoredIn();
          if (storedIn != null) {
              println();
              print0(ucase ? "STORED IN " : "stored in ");
              printExpr(storedIn);
  
              final List<SQLAssignItem> storedOn = x.getStoredOn();
              if (storedOn.size() > 0) {
                  print0(ucase ? " ON (" : " on (");
                  printAndAccept(storedOn, ", ");
                  print(')');
              }
          }
  
          final SQLExpr storedAs = x.getStoredAs();
          if (storedAs != null) {
              println();
              print0(ucase ? "STORED AS " : "stored as ");
              printExpr(storedAs);
          }
  
          return false;
      }
  
      public boolean visit(SQLCreateTableGroupStatement x) {
          print0(ucase ? "CREATE TABLEGROUP " : "create tablegroup ");
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
          x.getName().accept(this);
  
          SQLExpr partitionNum = x.getPartitionNum();
          if (partitionNum != null) {
              print0(ucase ? " PARTITION NUM " : " partition num ");
              printExpr(partitionNum);
          }
  
          return false;
      }
  
      public boolean visit(SQLDropTableGroupStatement x) {
          print0(ucase ? "DROP TABLEGROUP " : "drop tablegroup ");
          if (x.isIfExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableGroupStatement x) {
          print0(ucase ? "ALTER TABLEGROUP " : "alter tablegroup ");
          x.getName().accept(this);
          print0(" ");
          printAndAccept(x.getOptions(), " ");
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterSystemGetConfigStatement x) {
          print0(ucase ? "ALTER SYSTEM GET CONFIG " : "alter system get config ");
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterSystemSetConfigStatement x) {
          print0(ucase ? "ALTER SYSTEM SET CONFIG " : "alter system set config ");
  
          printAndAccept(x.getOptions(), " ");
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterViewStatement x) {
          print0(ucase ? "ALTER " : "alter ");
  
          this.indentCount++;
          String algorithm = x.getAlgorithm();
          if (algorithm != null && algorithm.length() > 0) {
              print0(ucase ? "ALGORITHM = " : "algorithm = ");
              print0(algorithm);
              println();
          }
  
          SQLName definer = x.getDefiner();
          if (definer != null) {
              print0(ucase ? "DEFINER = " : "definer = ");
              definer.accept(this);
              println();
          }
  
          String sqlSecurity = x.getSqlSecurity();
          if (sqlSecurity != null && sqlSecurity.length() > 0) {
              print0(ucase ? "SQL SECURITY " : "sql security ");
              print0(sqlSecurity);
              println();
          }
  
          this.indentCount--;
  
          print0(ucase ? "VIEW " : "view ");
  
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
  
          x.getTableSource().accept(this);
  
          List<SQLTableElement> columns = x.getColumns();
          if (columns.size() > 0) {
              print0(" (");
              this.indentCount++;
              println();
              for (int i = 0; i < columns.size(); ++i) {
                  if (i != 0) {
                      print0(", ");
                      println();
                  }
                  columns.get(i).accept(this);
              }
              this.indentCount--;
              println();
              print(')');
          }
  
          printComment(x.getComment());
  
          println();
          print0(ucase ? "AS" : "as");
          println();
  
          SQLSelect subQuery = x.getSubQuery();
          if (subQuery != null) {
              subQuery.accept(this);
          }
  
          if (x.isWithCheckOption()) {
              println();
              print0(ucase ? "WITH CHECK OPTION" : "with check option");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateViewStatement x) {
          print0(ucase ? "CREATE " : "create ");
          if (x.isOrReplace()) {
              print0(ucase ? "OR REPLACE " : "or replace ");
          }
  
          this.indentCount++;
          String algorithm = x.getAlgorithm();
          if (algorithm != null && algorithm.length() > 0) {
              print0(ucase ? "ALGORITHM = " : "algorithm = ");
              print0(algorithm);
              println();
          }
  
          SQLName definer = x.getDefiner();
          if (definer != null) {
              print0(ucase ? "DEFINER = " : "definer = ");
              definer.accept(this);
              println();
          }
  
          String sqlSecurity = x.getSqlSecurity();
          if (sqlSecurity != null && sqlSecurity.length() > 0) {
              print0(ucase ? "SQL SECURITY " : "sql security ");
              print0(sqlSecurity);
              println();
          }
  
          this.indentCount--;
          if (x.isTemporary()) {
              print0(ucase ? "TEMP " : "temp ");
          }
          print0(ucase ? "VIEW " : "view ");
  
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
  
          x.getTableSource().accept(this);
  
          if (x.isOnCluster()) {
              print0(ucase ? " ON CLUSTER" : " on cluster");
          }
  
          SQLName to = x.getTo();
          if (to != null) {
              print0(ucase ? " TO " : " to ");
              to.accept(this);
          }
  
          List<SQLTableElement> columns = x.getColumns();
          if (columns.size() > 0) {
              print0(" (");
              this.indentCount++;
              println();
              for (int i = 0; i < columns.size(); ++i) {
                  if (i != 0) {
                      print0(", ");
                      println();
                  }
                  columns.get(i).accept(this);
              }
              this.indentCount--;
              println();
              print(')');
          }
  
          printComment(x.getComment());
  
          printCreateViewAs(x);
  
          if (x.isWithCheckOption()) {
              println();
              print0(ucase ? "WITH CHECK OPTION" : "with check option");
          }
  
          return false;
      }
  
      protected void printCreateViewAs(SQLCreateViewStatement x) {
          println();
          print0(ucase ? "AS" : "as");
          println();
  
          SQLSelect subQuery = x.getSubQuery();
          if (subQuery != null) {
              subQuery.accept(this);
          }
  
          SQLBlockStatement script = x.getScript();
          if (script != null) {
              script.accept(this);
          }
      }
  
      public boolean visit(SQLCreateViewStatement.Column x) {
          x.getExpr().accept(this);
  
          SQLCharExpr comment = x.getComment();
          if (comment != null) {
              print0(ucase ? " COMMENT " : " comment ");
              comment.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropIndex x) {
          print0(ucase ? "DROP INDEX " : "drop index ");
          x.getIndexName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLOver x) {
          print0(ucase ? "(" : "(");
          if (x.getPartitionBy().size() > 0) {
              print0(ucase ? "PARTITION BY " : "partition by ");
              printAndAccept(x.getPartitionBy(), ", ");
              print(' ');
          }
  
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              orderBy.accept(this);
          }
  
          SQLOrderBy distributeBy = x.getDistributeBy();
          if (distributeBy != null) {
              List<SQLSelectOrderByItem> items = distributeBy.getItems();
  
              if (items.size() > 0) {
                  print0(ucase ? "DISTRIBUTE BY " : "distribute by ");
  
                  for (int i = 0, size = items.size(); i < size; ++i) {
                      if (i != 0) {
                          print0(", ");
                      }
                      SQLSelectOrderByItem item = items.get(i);
                      visit(item);
                  }
              }
          }
  
          SQLOrderBy sortBy = x.getSortBy();
          if (sortBy != null) {
              List<SQLSelectOrderByItem> items = sortBy.getItems();
  
              if (items.size() > 0) {
                  if (distributeBy != null) {
                      print0(" ");
                  }
                  print0(ucase ? "SORT BY " : "sort by ");
  
                  for (int i = 0, size = items.size(); i < size; ++i) {
                      if (i != 0) {
                          print0(", ");
                      }
                      SQLSelectOrderByItem item = items.get(i);
                      visit(item);
                  }
              }
          }
  
          if (x.getOf() != null) {
              print0(ucase ? " OF " : " of ");
              x.getOf().accept(this);
          }
  
  //        if (x.getWindowing() != null) {
  //            if (SQLOver.WindowingType.ROWS.equals(x.getWindowingType())) {
  //                print0(ucase ? " ROWS" : " rows");
  //            } else if (SQLOver.WindowingType.RANGE.equals(x.getWindowingType())) {
  //                print0(ucase ? " RANGE" : " range");
  //            }
  //
  //            printWindowingExpr(x.getWindowing());
  //
  //            if (x.isWindowingPreceding()) {
  //                print0(ucase ? " PRECEDING" : " preceding");
  //            } else if (x.isWindowingFollowing()) {
  //                print0(ucase ? " FOLLOWING" : " following");
  //            }
  //        }
  
          final SQLExpr windowingBetweenBegin = x.getWindowingBetweenBegin();
          final SQLExpr windowingBetweenEnd = x.getWindowingBetweenEnd();
          final SQLOver.WindowingBound beginBound = x.getWindowingBetweenBeginBound();
          final SQLOver.WindowingBound endBound = x.getWindowingBetweenEndBound();
          final SQLOver.WindowingType windowingType = x.getWindowingType();
  
          if (windowingBetweenEnd != null || endBound != null) {
              if (windowingType != null) {
                  print(' ');
                  print0(ucase ? windowingType.name : windowingType.nameLCase);
              }
              print0(ucase ? " BETWEEN" : " between");
  
              printWindowingExpr(windowingBetweenBegin);
  
              if (beginBound != null) {
                  print(' ');
                  print0(ucase ? beginBound.name : beginBound.nameLCase);
              }
  
              print0(ucase ? " AND" : " and");
  
              printWindowingExpr(windowingBetweenEnd);
  
              if (endBound != null) {
                  print(' ');
                  print0(ucase ? endBound.name : endBound.nameLCase);
              }
          } else {
              if (windowingType != null) {
                  print(' ');
                  print0(ucase ? windowingType.name : windowingType.nameLCase);
              }
  
              printWindowingExpr(windowingBetweenBegin);
  
              if (beginBound != null) {
                  print(' ');
                  print0(ucase ? beginBound.name : beginBound.nameLCase);
              }
          }
  
          if (x.isExcludeCurrentRow()) {
              print0(ucase ? " EXCLUDE CURRENT ROW" : " exclude current row");
          }
  
          print(')');
          return false;
      }
  
      void printWindowingExpr(SQLExpr expr) {
          if (expr == null) {
              return;
          }
          print(' ');
          if (expr instanceof SQLIdentifierExpr) {
              String ident = ((SQLIdentifierExpr) expr).getName();
              print0(ucase ? ident : ident.toLowerCase());
          } else {
              expr.accept(this);
          }
      }
  
      @Override
      public boolean visit(SQLKeep x) {
          if (x.getDenseRank() == SQLKeep.DenseRank.FIRST) {
              print0(ucase ? "KEEP (DENSE_RANK FIRST " : "keep (dense_rank first ");
          } else {
              print0(ucase ? "KEEP (DENSE_RANK LAST " : "keep (dense_rank last ");
          }
  
          x.getOrderBy().accept(this);
          print(')');
  
          return false;
      }
  
      @Override
      public boolean visit(SQLColumnPrimaryKey x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
          print0(ucase ? "PRIMARY KEY" : "primary key");
          if (x.isNotEnforced()) {
              print0(ucase ? " NOT ENFORCED" : " not enforced");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLColumnUniqueKey x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
          print0(ucase ? "UNIQUE" : "unique");
          return false;
      }
  
      @Override
      public boolean visit(SQLColumnCheck x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
          print0(ucase ? "CHECK " : "check ");
          x.getExpr().accept(this);
          print("");
  
          Boolean enable = x.getEnable();
          if (enable != null) {
              if (enable.booleanValue()) {
                  print0(ucase ? " ENABLE" : " enable");
              } else {
                  print0(ucase ? " DISABLE" : " disable");
              }
          }
  
          Boolean enforced = x.getEnforced();
          if (enforced != null) {
              if (enforced.booleanValue()) {
                  print0(ucase ? " ENFORCED" : " enforced");
              } else {
                  print0(ucase ? " NOT ENFORCED" : " not enforced");
              }
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLColumnDefault x) {
          print0(ucase ? "DEFAULT " : "default ");
          x.getDefaultExpr().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLWithSubqueryClause x) {
          if ((!isParameterized()) && isPrettyFormat() && x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          print0(ucase ? "WITH " : "with ");
          if (Boolean.TRUE.equals(x.getRecursive())) {
              print0(ucase ? "RECURSIVE " : "recursive ");
          }
          this.indentCount++;
          printlnAndAccept(x.getEntries(), ",");
          this.indentCount--;
          return false;
      }
  
      @Override
      public boolean visit(SQLWithSubqueryClause.Entry x) {
          if (isPrettyFormat() && x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          print0(x.getAlias());
  
          if (x.getColumns().size() > 0) {
              print0(" (");
              printAndAccept(x.getColumns(), ", ");
              print(')');
          }
          print(' ');
          print0(ucase ? "AS " : "as ");
          print('(');
          this.indentCount++;
          println();
          SQLSelect query = x.getSubQuery();
          if (query != null) {
              query.accept(this);
          } else {
              x.getReturningStatement().accept(this);
          }
          this.indentCount--;
          println();
          print(')');
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableModifyClusteredBy x) {
          print0(ucase ? "CLUSTERED BY " : "clustered by ");
          if (!x.getClusterColumns().isEmpty()) {
              printAndAccept(x.getClusterColumns(), ", ");
          } else {
              print0("()");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAlterColumn x) {
          if (DbType.odps == dbType) {
              print0(ucase ? "CHANGE COLUMN" : "change column");
          } else if (DbType.hive == dbType || DbType.spark == dbType) {
              print0(ucase ? "CHANGE" : "change");
          } else {
              print0(ucase ? "ALTER COLUMN" : "alter column");
          }
  
          SQLName originColumn = x.getOriginColumn();
          if (originColumn != null) {
              print(' ');
              originColumn.accept(this);
          }
  
          SQLColumnDefinition column = x.getColumn();
          if (column != null) {
              print(' ');
              column.accept(this);
          }
  
          if (x.isSetNotNull()) { // postgresql
              print0(ucase ? " SET NOT NULL" : " set not null");
          }
  
          if (x.isDropNotNull()) { // postgresql
              print0(ucase ? " DROP NOT NULL" : " drop not null");
          }
  
          if (x.getSetDefault() != null) { // postgresql
              print0(ucase ? " SET DEFAULT " : " set default ");
              x.getSetDefault().accept(this);
          }
  
          if (x.isDropDefault()) { // postgresql
              print0(ucase ? " DROP DEFAULT" : " drop default");
          }
  
          final SQLDataType dataType = x.getDataType();
          if (dataType != null) {
              print0(ucase ? " SET DATA TYPE " : " set data type ");
              dataType.accept(this);
          }
  
          final SQLName after = x.getAfter();
          if (after != null) {
              print0(ucase ? " AFTER " : " after ");
              after.accept(this);
          }
  
          if (x.isFirst()) {
              print0(ucase ? " FIRST" : " first");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLCheck x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
  
          print0(ucase ? "CHECK " : "check ");
          this.indentCount++;
          x.getExpr().accept(this);
          this.indentCount--;
          print("");
  
          Boolean enforced = x.getEnforced();
          if (enforced != null) {
              if (enforced.booleanValue()) {
                  print0(ucase ? " ENFORCED" : " enforced");
              } else {
                  print0(ucase ? " NOT ENFORCED" : " not enforced");
              }
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDefault x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
  
          print0(ucase ? "DEFAULT " : "default ");
          this.indentCount++;
          x.getExpr().accept(this);
          this.indentCount--;
  
          print0(ucase ? " FOR " : " for ");
  
          this.indentCount++;
          x.getColumn().accept(this);
          this.indentCount--;
  
          if (x.isWithValues()) {
              print0(ucase ? " WITH VALUES" : " with values");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropForeignKey x) {
          print0(ucase ? "DROP FOREIGN KEY " : "drop foreign key ");
          x.getIndexName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropPrimaryKey x) {
          print0(ucase ? "DROP PRIMARY KEY" : "drop primary key");
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropKey x) {
          print0(ucase ? "DROP KEY " : "drop key ");
          x.getKeyName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTablePartitionCount x) {
          print0(ucase ? "PARTITIONS " : "partitons ");
          x.getCount().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableBlockSize x) {
          print0(ucase ? "BLOCK_SIZE " : "block_size ");
          x.getSize().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableEnableKeys x) {
          print0(ucase ? "ENABLE KEYS" : "enable keys");
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDisableKeys x) {
          print0(ucase ? "DISABLE KEYS" : "disable keys");
          return false;
      }
  
      public boolean visit(SQLAlterTableDisableConstraint x) {
          print0(ucase ? "DISABLE CONSTRAINT " : "disable constraint ");
          x.getConstraintName().accept(this);
          return false;
      }
  
      public boolean visit(SQLAlterTableEnableConstraint x) {
          print0(ucase ? "ENABLE CONSTRAINT " : "enable constraint ");
          x.getConstraintName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropConstraint x) {
          print0(ucase ? "DROP CONSTRAINT " : "drop constraint ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getConstraintName().accept(this);
  
          if (x.isCascade()) {
              print0(ucase ? " CASCADE" : " cascade");
          } else if (x.isRestrict()) {
              print0(ucase ? " RESTRICT" : " restrict");
          }
          return false;
      }
      @Override
      public boolean visit(SQLAlterTableDropCheck x) {
          print0(ucase ? "DROP CHECK " : "drop check ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getCheckName().accept(this);
          return false;
      }
      @Override
      public boolean visit(SQLAlterTableValidateConstraint x) {
          print0(ucase ? "VALIDATE CONSTRAINT " : "validate constraint ");
          x.getConstraintName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableStatement x) {
          print0(ucase ? "ALTER TABLE " : "alter table ");
  
          if (x.isOnly()) {
              print0(ucase ? "ONLY " : "only ");
          }
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          printTableSourceExpr(x.getName());
  
          SQLName on = x.getOn();
          if (on != null) {
              print(ucase ? " ON CLUSTER " : " on cluster ");
              printExpr(on);
          }
  
          this.indentCount++;
          for (int i = 0; i < x.getItems().size(); ++i) {
              SQLAlterTableItem item = x.getItems().get(i);
              if (i != 0) {
                  SQLAlterTableItem former = x.getItems().get(i - 1);
                  if ((this.dbType == DbType.hive || this.dbType == DbType.odps || this.dbType == DbType.spark)
                          && former instanceof SQLAlterTableAddPartition
                          && item instanceof SQLAlterTableAddPartition) {
                      // ignore comma
                  } else {
                      print(',');
                  }
              }
              println();
              item.accept(this);
          }
          this.indentCount--;
  
          if (x.isMergeSmallFiles()) {
              print0(ucase ? " MERGE SMALLFILES" : " merge smallfiles");
          }
  
          List<SQLSelectOrderByItem> clusteredBy = x.getClusteredBy();
          if (clusteredBy.size() > 0) {
              println();
              if (x.isRange()) {
                  print0(ucase ? "RANGE " : "range ");
              }
              print0(ucase ? "CLUSTERED BY (" : "clustered by (");
              printAndAccept(clusteredBy, ",");
              print0(")");
          }
  
          List<SQLSelectOrderByItem> sortedBy = x.getSortedBy();
          printSortedBy(sortedBy);
          printIntoBuckets(x.getBuckets());
          printIntoShards(x.getShards());
  
          if (x.isNotClustered()) {
              print0(ucase ? " NOT CLUSTERED" : " not clustered");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLExprHint x) {
          x.getExpr().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateIndexStatement x) {
          print0(ucase ? "CREATE " : "create ");
  
          String type = x.getType();
  
          if (type != null) {
              print0(ucase ? type.toUpperCase() : type.toLowerCase());
              print(' ');
          }
  
          if (x.isGlobal()) {
              print0(ucase ? "GLOBAL " : "global ");
          }
  
          if (x.isLocal()) {
              print0(ucase ? "LOCAL " : "local ");
          }
  
          print0(ucase ? "INDEX " : "index ");
  
          x.getName().accept(this);
          print0(ucase ? " ON " : " on ");
          x.getTable().accept(this);
          print0(" (");
          printAndAccept(x.getItems(), ", ");
          print(')');
  
          List<SQLName> covering = x.getCovering();
          if (covering.size() > 0) {
              print0(ucase ? " COVERING (" : " covering (");
              printAndAccept(covering, ", ");
              print(')');
          }
  
          final SQLExpr dbPartitionBy = x.getDbPartitionBy();
          if (dbPartitionBy != null) {
              print0(ucase ? " DBPARTITION BY " : " dbpartition by ");
              dbPartitionBy.accept(this);
          }
  
          final SQLExpr tablePartitionBy = x.getTablePartitionBy();
          if (tablePartitionBy != null) {
              print0(ucase ? " TBPARTITION BY " : " tbpartition by ");
              tablePartitionBy.accept(this);
          }
  
          SQLExpr tablePartitions = x.getTablePartitions();
          if (tablePartitions != null) {
              print0(ucase ? " TBPARTITIONS " : " tbpartitions ");
              tablePartitions.accept(this);
          }
  
          if (x.getIndexDefinition().hasOptions()) {
              String using = x.getIndexDefinition().getOptions().getIndexType();
              if (using != null) {
                  print0(ucase ? " USING " : " using ");
                  print0(ucase ? using.toUpperCase() : using.toLowerCase());
              }
  
              x.getIndexDefinition().getOptions().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLUnique x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
          print0(ucase ? "UNIQUE (" : "unique (");
          printAndAccept(x.getColumns(), ", ");
          print(')');
          if (x.isDisableNovalidate()) {
              print0(ucase ? " DISABLE NOVALIDATE" : " disable novalidate");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLPrimaryKeyImpl x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
  
          print0(ucase ? "PRIMARY KEY " : "primary key ");
  
          if (x.isClustered()) {
              print0(ucase ? "CLUSTERED " : "clustered ");
          }
  
          print('(');
          printAndAccept(x.getColumns(), ", ");
          print(')');
  
          if (x.isDisableNovalidate()) {
              print0(ucase ? " DISABLE NOVALIDATE" : " disable novalidate");
          }
  
          if (x.isNotEnforced()) {
              print0(ucase ? " NOT ENFORCED" : " not enforced");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableRenameColumn x) {
          print0(ucase ? "RENAME COLUMN " : "rename column ");
          x.getColumn().accept(this);
          print0(ucase ? " TO " : " to ");
          x.getTo().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableRenameConstraint x) {
          print0(ucase ? "RENAME CONSTRAINT " : "rename constraint ");
          x.getConstraint().accept(this);
          print0(ucase ? " TO " : " to ");
          x.getTo().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLColumnReference x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
  
          print0(ucase ? "REFERENCES " : "references ");
          x.getTable().accept(this);
          if (!x.getColumns().isEmpty()) {
              print0(" (");
              printAndAccept(x.getColumns(), ", ");
              print(')');
          }
  
          if (x.isNotEnforced()) {
              print0(ucase ? " NOT ENFORCED" : " not enforced");
          }
          SQLForeignKeyImpl.Match match = x.getReferenceMatch();
          if (match != null) {
              print0(ucase ? " MATCH " : " match ");
              print0(ucase ? match.name : match.nameLCase);
          }
  
          if (x.getOnDelete() != null) {
              print0(ucase ? " ON DELETE " : " on delete ");
              print0(ucase ? x.getOnDelete().name : x.getOnDelete().nameLCase);
          }
  
          if (x.getOnUpdate() != null) {
              print0(ucase ? " ON UPDATE " : " on update ");
              print0(ucase ? x.getOnUpdate().name : x.getOnUpdate().nameLCase);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLForeignKeyImpl x) {
          SQLName name = x.getName();
          if (name != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              name.accept(this);
              print(' ');
          }
  
          print0(ucase ? "FOREIGN KEY (" : "foreign key (");
          printAndAccept(x.getReferencingColumns(), ", ");
          print(')');
  
          this.indentCount++;
          println();
          print0(ucase ? "REFERENCES " : "references ");
          x.getReferencedTableName().accept(this);
  
          if (x.getReferencedColumns().size() > 0) {
              print0(" (");
              printAndAccept(x.getReferencedColumns(), ", ");
              print(')');
          }
  
          if (x.isOnDeleteCascade()) {
              println();
              print0(ucase ? "ON DELETE CASCADE" : "on delete cascade");
          } else if (x.isOnDeleteSetNull()) {
              print0(ucase ? "ON DELETE SET NULL" : "on delete set null");
          }
  
          if (x.isDisableNovalidate()) {
              print0(ucase ? " DISABLE NOVALIDATE" : " disable novalidate");
          }
  
          if (x.isNotEnforced()) {
              print0(ucase ? " NOT ENFORCED" : " not enforced");
          }
  
          this.indentCount--;
          return false;
      }
  
      @Override
      public boolean visit(SQLDropSequenceStatement x) {
          print0(ucase ? "DROP SEQUENCE " : "drop sequence ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public void endVisit(SQLDropSequenceStatement x) {
      }
  
      @Override
      public boolean visit(SQLDropTriggerStatement x) {
          print0(ucase ? "DROP TRIGGER " : "drop trigger ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public void endVisit(SQLDropUserStatement x) {
      }
  
      @Override
      public boolean visit(SQLDropUserStatement x) {
          print0(ucase ? "DROP USER " : "drop user ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          printAndAccept(x.getUsers(), ", ");
          return false;
      }
  
      @Override
      public boolean visit(SQLCancelJobStatement x) {
          print0(ucase ? "CANCEL JOB " : "cancel job ");
          x.getJobName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLExplainAnalyzeStatement x) {
          print0(ucase ? "EXPLAIN ANALYZE " : "explain analyze ");
          println();
          x.getSelect().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLExplainStatement x) {
          print0(ucase ? "EXPLAIN" : "explain");
          if (x.getHints() != null && x.getHints().size() > 0) {
              print(' ');
              printAndAccept(x.getHints(), " ");
          }
  
          if (x.isExtended()) {
              print0(ucase ? " EXTENDED" : " extended");
          }
  
          if (x.isDependency()) {
              print0(ucase ? " DEPENDENCY" : " dependency");
          }
  
          if (x.isAuthorization()) {
              print0(ucase ? " AUTHORIZATION" : " authorization");
          }
  
          String type = x.getType();
          if (type != null) {
              print(' ');
              if (type.indexOf(' ') >= 0) {
                  print('(');
                  print0(type);
                  print(')');
              } else {
                  print0(type);
              }
          }
          println();
          x.getStatement().accept(this);
          return false;
      }
  
      protected void printGrantPrivileges(SQLGrantStatement x) {
      }
  
      @Override
      public boolean visit(SQLGrantStatement x) {
          print0(ucase ? "GRANT " : "grant ");
          printAndAccept(x.getPrivileges(), ", ");
  
          printGrantOn(x);
  
          if (x.getUsers() != null) {
              print0(ucase ? " TO " : " to ");
              printAndAccept(x.getUsers(), ",");
          }
  
          if (x.getWithGrantOption()) {
              print0(ucase ? " WITH GRANT OPTION" : " with grant option");
          }
  
          boolean with = false;
          SQLExpr maxQueriesPerHour = x.getMaxQueriesPerHour();
          if (maxQueriesPerHour != null) {
              if (!with) {
                  print0(ucase ? " WITH" : " with");
                  with = true;
              }
              print0(ucase ? " MAX_QUERIES_PER_HOUR " : " max_queries_per_hour ");
              maxQueriesPerHour.accept(this);
          }
  
          SQLExpr maxUpdatesPerHour = x.getMaxUpdatesPerHour();
          if (maxUpdatesPerHour != null) {
              if (!with) {
                  print0(ucase ? " WITH" : " with");
                  with = true;
              }
              print0(ucase ? " MAX_UPDATES_PER_HOUR " : " max_updates_per_hour ");
              maxUpdatesPerHour.accept(this);
          }
  
          SQLExpr maxConnectionsPerHour = x.getMaxConnectionsPerHour();
          if (maxConnectionsPerHour != null) {
              if (!with) {
                  print0(ucase ? " WITH" : " with");
                  with = true;
              }
              print0(ucase ? " MAX_CONNECTIONS_PER_HOUR " : " max_connections_per_hour ");
              maxConnectionsPerHour.accept(this);
          }
  
          SQLExpr maxUserConnections = x.getMaxUserConnections();
          if (maxUserConnections != null) {
              if (!with) {
                  print0(ucase ? " WITH" : " with");
                  with = true;
              }
              print0(ucase ? " MAX_USER_CONNECTIONS " : " max_user_connections ");
              maxUserConnections.accept(this);
          }
  
          if (x.isAdminOption()) {
              if (!with) {
                  print0(ucase ? " WITH" : " with");
                  with = true;
              }
              print0(ucase ? " ADMIN OPTION" : " admin option");
          }
  
          if (x.getIdentifiedBy() != null) {
              print0(ucase ? " IDENTIFIED BY " : " identified by ");
              x.getIdentifiedBy().accept(this);
          }
  
          return false;
      }
  
      protected void printGrantOn(SQLGrantStatement x) {
          if (x.getResource() != null) {
              print0(ucase ? " ON " : " on ");
  
              SQLObjectType resourceType = x.getResourceType();
              if (resourceType != null) {
                  print0(ucase ? resourceType.name : resourceType.nameLCase);
                  print(' ');
              }
  
              x.getResource().accept(this);
          }
      }
  
      @Override
      public boolean visit(SQLRevokeStatement x) {
          print0(ucase ? "REVOKE " : "revoke ");
  
          if (x.isGrantOption()) {
              print0(ucase ? "GRANT OPTION" : "grant option");
  
              if (x.getPrivileges().size() > 0) {
                  print0(ucase ? " FOR " : " for ");
              }
          }
  
          printAndAccept(x.getPrivileges(), ", ");
  
          if (x.getResource() != null) {
              print0(ucase ? " ON " : " on ");
  
              if (x.getResourceType() != null) {
                  print0(x.getResourceType().name());
                  print(' ');
              }
  
              x.getResource().accept(this);
          }
  
          if (x.getUsers() != null) {
              print0(ucase ? " FROM " : " from ");
  
              if (dbType == DbType.odps) {
                  print0(ucase ? "USER " : "user ");
              }
  
              printAndAccept(x.getUsers(), ", ");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropDatabaseStatement x) {
          print0(ucase ? "DROP " : "drop ");
  
          if (x.isPhysical()) {
              print0(ucase ? "PHYSICAL " : "physical ");
          }
  
          print0(ucase ? "DATABASE " : "database ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getDatabase().accept(this);
  
          final Boolean restrict = x.getRestrict();
          if (restrict != null && restrict.booleanValue()) {
              print0(ucase ? " RESTRICT" : " restrict");
          }
  
          if (x.isCascade()) {
              print0(ucase ? " CASCADE" : " cascade");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropCatalogStatement x) {
          if (x.isExternal()) {
              print0(ucase ? "DROP EXTERNAL CATALOG " : "drop external catalog ");
          } else {
              print0(ucase ? "DROP CATALOG " : "drop catalog ");
          }
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropFunctionStatement x) {
          List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  visit((SQLCommentHint) hint);
                  println();
              }
          }
  
          if (x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          if (x.isTemporary()) {
              print0(ucase ? "DROP TEMPORARY FUNCTION " : "drop temporary function ");
          } else {
              print0(ucase ? "DROP FUNCTION " : "drop function ");
          }
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getName().accept(this);
          if (x.getParameters() != null) {
              print0("(");
              int paramSize = x.getParameters().size();
              if (paramSize > 0) {
                  this.indentCount++;
                  println();
                  for (int i = 0; i < paramSize; ++i) {
                      if (i != 0) {
                          print0(", ");
                          println();
                      }
                      SQLParameter param = x.getParameters().get(i);
                      param.accept(this);
                  }
                  this.indentCount--;
                  println();
              }
              print(')');
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLDropTableSpaceStatement x) {
          print0(ucase ? "DROP TABLESPACE " : "drop tablespace ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getName().accept(this);
  
          SQLExpr engine = x.getEngine();
          if (engine != null) {
              print0(ucase ? " ENGINE " : " engine ");
              engine.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLDropProcedureStatement x) {
          print0(ucase ? "DROP PROCEDURE " : "drop procedure ");
  
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
  
          x.getName().accept(this);
  
          return false;
      }
  
      @Override
      public boolean visit(SQLIndexOptions x) {
          /*
          String using = x.getIndexType();
          if (using != null) {
              print0(ucase ? " USING " : " using ");
              print0(using);
          }
          */
          if (x.isGlobal()) {
              print0(ucase ? " GLOBAL" : " global");
          } else if (x.isLocal()) {
              print0(ucase ? " LOCAL" : " local");
          }
  
          if (x.isInvisible()) {
              print0(ucase ? " INVISIBLE" : " invisible");
          } else if (x.isVisible()) {
              print0(ucase ? " VISIBLE" : " visible");
          }
  
          SQLExpr keyBlockSize = x.getKeyBlockSize();
          if (keyBlockSize != null) {
              print0(ucase ? " KEY_BLOCK_SIZE = " : " key_block_size = ");
              printExpr(keyBlockSize, parameterized);
          }
  
          String parserName = x.getParserName();
          if (parserName != null) {
              print0(ucase ? " WITH PARSER " : " with parser ");
              print0(parserName);
          }
  
          SQLExpr comment = x.getComment();
          if (comment != null) {
              print0(ucase ? " COMMENT " : " comment ");
              printExpr(comment, parameterized);
          }
  
          String algorithm = x.getAlgorithm();
          if (algorithm != null) {
              print0(ucase ? " ALGORITHM = " : " algorithm = ");
              print0(algorithm);
          }
  
          String lock = x.getLock();
          if (lock != null) {
              print0(ucase ? " LOCK = " : " lock = ");
              print0(lock);
          }
  
          final List<SQLAssignItem> options = x.getOtherOptions();
          if (options.size() > 0) {
              for (SQLAssignItem option : options) {
                  print(' ');
                  option.accept(this);
              }
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLIndexDefinition x) {
          /*
          if (x.hasConstraint()) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              if (x.getSymbol() != null) {
                  x.getSymbol().accept(this);
                  print(' ');
              }
          }
          */
  
          boolean mysql = DbType.mysql == dbType;
  
          String type = x.getType();
  
          if (type != null) {
              print0(ucase ? type.toUpperCase() : type.toLowerCase());
              print(' ');
          }
  
          if (x.isGlobal()) {
              print0(ucase ? "GLOBAL " : "global ");
          } else if (x.isLocal()) {
              print0(ucase ? "LOCAL " : "local ");
          }
  
          if (x.isIndex()) {
              print0(ucase ? "INDEX " : "index ");
          }
  
          if (x.isKey()) {
              print0(ucase ? "KEY " : "key ");
          }
  
          if (x.getName() != null && (type == null || !type.equalsIgnoreCase("primary"))) {
              x.getName().accept(this);
              print(' ');
          }
  
          String using = x.getOptions().getIndexType();
          if (using != null) {
              print0(ucase ? "USING " : "using ");
              print0(using);
              print(' ');
          }
  
          if (x.isHashMapType()) {
              print0(ucase ? "HASHMAP " : "hashmap ");
          }
  
          if (x.getColumns().size() > 0) {
              print('(');
              printAndAccept(x.getColumns(), ", ");
              print(')');
          }
  
          if (x.getAnalyzerName() != null) {
              print0(ucase ? " WITH ANALYZER " : " with analyzer ");
              x.getAnalyzerName().accept(this);
          } else {
              if (x.getIndexAnalyzerName() != null) {
                  print0(ucase ? " WITH INDEX ANALYZER " : " with index analyzer ");
                  x.getIndexAnalyzerName().accept(this);
              }
  
              if (x.getQueryAnalyzerName() != null) {
                  print0(ucase ? " WITH QUERY ANALYZER " : " with query analyzer ");
                  x.getQueryAnalyzerName().accept(this);
              }
  
              if (x.getWithDicName() != null) {
                  printUcase(" WITH DICT ");
                  x.getWithDicName().accept(this);
              }
          }
  
          List<SQLName> covering = x.getCovering();
          if (covering.size() > 0) {
              print0(ucase ? " COVERING (" : " covering (");
              printAndAccept(covering, ", ");
              print(')');
          }
  
          final SQLExpr dbPartitionBy = x.getDbPartitionBy();
          if (dbPartitionBy != null) {
              print0(ucase ? " DBPARTITION BY " : " dbpartition by ");
              dbPartitionBy.accept(this);
          }
  
          final SQLExpr tablePartitionBy = x.getTbPartitionBy();
          if (tablePartitionBy != null) {
              print0(ucase ? " TBPARTITION BY " : " tbpartition by ");
              tablePartitionBy.accept(this);
          }
  
          SQLExpr tablePartitions = x.getTbPartitions();
          if (tablePartitions != null) {
              print0(ucase ? " TBPARTITIONS " : " tbpartitions ");
              tablePartitions.accept(this);
          }
  
          if (x.hasOptions()) {
              x.getOptions().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAddIndex x) {
          print0(ucase ? "ADD " : "add ");
  
          visit(x.getIndexDefinition());
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAlterIndex x) {
          print0(ucase ? "ALTER INDEX " : "alter index ");
  
          visit(x.getIndexDefinition());
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAddConstraint x) {
          if (x.isWithNoCheck()) {
              print0(ucase ? "WITH NOCHECK " : "with nocheck ");
          }
  
          print0(ucase ? "ADD " : "add ");
  
          x.getConstraint().accept(this);
          if (x.isNoInherit()) {
              print0(ucase ? " NO  INHERIT" : " no inherit");
          }
          if (x.isNotValid()) {
              print0(ucase ? " NOT VALID" : " not valid");
          }
          return false;
      }
  
      public boolean visit(SQLCreateTriggerStatement x) {
          print0(ucase ? "CREATE " : "create ");
  
          if (x.isOrReplace()) {
              print0(ucase ? "OR REPLACE " : "or replace ");
          }
  
          print0(ucase ? "TRIGGER " : "trigger ");
  
          x.getName().accept(this);
  
          this.indentCount++;
          println();
          if (TriggerType.INSTEAD_OF.equals(x.getTriggerType())) {
              print0(ucase ? "INSTEAD OF" : "instead of");
          } else {
              String triggerTypeName = x.getTriggerType().name();
              print0(ucase ? triggerTypeName : triggerTypeName.toLowerCase());
          }
  
          if (x.isInsert()) {
              print0(ucase ? " INSERT" : " insert");
          }
  
          if (x.isDelete()) {
              if (x.isInsert()) {
                  print0(ucase ? " OR" : " or");
              }
              print0(ucase ? " DELETE" : " delete");
          }
  
          if (x.isUpdate()) {
              if (x.isInsert() || x.isDelete()) {
                  print0(ucase ? " OR" : " or");
              }
              print0(ucase ? " UPDATE" : " update");
  
              List<SQLName> colums = x.getUpdateOfColumns();
              for (SQLName colum : colums) {
                  print(' ');
                  colum.accept(this);
              }
          }
  
          println();
          print0(ucase ? "ON " : "on ");
          x.getOn().accept(this);
  
          if (x.isForEachRow()) {
              println();
              print0(ucase ? "FOR EACH ROW" : "for each row");
          }
  
          SQLExpr when = x.getWhen();
          if (when != null) {
              println();
              print0(ucase ? "WHEN " : "when ");
              when.accept(this);
          }
          this.indentCount--;
          println();
          if (x.getExecuteType() != null && x.getExecuteFunc() != null) {
              print0(ucase ? x.getExecuteType().toUpperCase() : x.getExecuteType().toLowerCase());
              print0(" ");
              x.getExecuteFunc().accept(this);
          }
          if (x.getBody() != null) {
              x.getBody().accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLBooleanExpr x) {
          if (isParameterized()) {
              print('?');
              return false;
          }
          print0(x.getBooleanValue() ? "true" : "false");
          return false;
      }
  
      @Override
      public boolean visit(SQLUnionQueryTableSource x) {
          print('(');
          this.indentCount++;
          println();
          x.getUnion().accept(this);
          this.indentCount--;
          println();
          print(')');
  
          final List<SQLName> columns = x.getColumns();
          final String alias = x.getAlias();
          if (alias != null) {
              if (columns.size() > 0) {
                  print0(" AS ");
              } else {
                  print(' ');
              }
              print0(alias);
          }
          printPivot(x.getPivot());
          printUnpivot(x.getUnpivot());
  
          return false;
      }
  
      @Override
      public boolean visit(SQLTimestampExpr x) {
          if (this.parameterized) {
              print('?');
              incrementReplaceCunt();
  
              if (this.parameters != null) {
                  ExportParameterVisitorUtils.exportParameter(this.parameters, x);
              }
          } else {
              print0(ucase ? "TIMESTAMP " : "timestamp ");
  
              if (x.isWithTimeZone()) {
                  print0(ucase ? " WITH TIME ZONE " : " with time zone ");
              }
  
              print('\'');
              print0(x.getValue());
              print('\'');
  
              if (x.getTimeZone() != null) {
                  print0(ucase ? " AT TIME ZONE '" : " at time zone '");
                  print0(x.getTimeZone());
                  print('\'');
              }
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLBinaryExpr x) {
          print0("b'");
          print0(x.getText());
          print('\'');
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableRename x) {
          print0(ucase ? "RENAME TO " : "rename to ");
          x.getTo().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLShowTablesStatement x) {
          final List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          if (x.isFull()) {
              print0(ucase ? "SHOW FULL TABLES" : "show full tables");
          } else {
              print0(ucase ? "SHOW TABLES" : "show tables");
          }
  
          if (x.isExtended()) {
              print0(ucase ? " EXTENDED" : " extended");
          }
  
          if (x.getDatabase() != null) {
              print0(ucase ? " FROM " : " from ");
              x.getDatabase().accept(this);
          }
  
          if (x.getLike() != null) {
              print0(ucase ? " LIKE " : " like ");
              x.getLike().accept(this);
          } else if (x.getWhere() != null) {
              print0(ucase ? " WHERE " : " where ");
              x.getWhere().accept(this);
          }
          return false;
      }
  
      protected void printlnComment(List<String> comments) {
          if (comments != null) {
              for (int i = 0; i < comments.size(); ++i) {
                  String comment = comments.get(i);
                  boolean lineComment = comment.startsWith("--");
                  if (lineComment) {
                      comment = comment.trim();
                  }
                  if (i != 0 && lineComment) {
                      println();
                  }
  
                  printComment(comment);
              }
          }
      }
  
      public void printComment(String comment) {
          if (comment == null) {
              return;
          }
  
          if (isEnabled(VisitorFeature.OutputSkipMultilineComment) && comment.startsWith("/*")) {
              return;
          }
  
          if (isEnabled(VisitorFeature.OutputSkipSingleLineComment)
                  && (comment.startsWith("-") || comment.startsWith("#"))) {
              return;
          }
  
          if (comment.startsWith("--")
                  && comment.length() > 2
                  && comment.charAt(2) != ' '
                  && comment.charAt(2) != '-') {
              print0("-- ");
              print0(comment.substring(2));
          } else if (comment.startsWith("#")
                  && comment.length() > 1
                  && comment.charAt(1) != ' '
                  && comment.charAt(1) != '#') {
              print0("# ");
              print0(comment.substring(1));
          } else {
              print0(comment);
          }
  
          char first = '\0';
          for (int i = 0; i < comment.length(); i++) {
              char c = comment.charAt(i);
              if (CharTypes.isWhitespace(c)) {
                  continue;
              }
              first = c;
              break;
          }
  
          if (first == '-' || first == '#') {
              endLineComment = true;
          }
      }
  
      protected void printlnComments(List<String> comments) {
          if (comments != null) {
              for (int i = 0; i < comments.size(); ++i) {
                  String comment = comments.get(i);
                  printComment(comment);
                  println();
              }
          }
      }
  
      protected void printAfterComments(List<String> comments) {
          if (comments != null) {
              print0(" ");
              int i = 0;
              String comment = comments.get(i);
              printComment(comment);
              i++;
              for (; i < comments.size(); ++i) {
                  println();
                  comment = comments.get(i);
                  printComment(comment);
              }
          }
      }
  
      @Override
      public boolean visit(SQLAlterViewRenameStatement x) {
          print0(ucase ? "ALTER VIEW " : "alter view ");
          x.getName().accept(this);
  
          SQLName to = x.getTo();
          if (to != null) {
              print0(ucase ? " RENAME TO " : " rename to ");
              printExpr(to);
          }
  
          SQLName changeOwnerTo = x.getChangeOwnerTo();
          if (changeOwnerTo != null) {
              print0(ucase ? " CHANGEOWNER TO " : " changeowner to ");
              printExpr(changeOwnerTo);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAddPartition x) {
          boolean printAdd = true;
          if (x.getParent() instanceof SQLAlterTableStatement) {
              SQLAlterTableStatement stmt = (SQLAlterTableStatement) x.getParent();
              int p = stmt.getChildren().indexOf(x);
              if (p > 0 && stmt.getChildren().get(p - 1) instanceof SQLAlterTableAddPartition) {
                  printAdd = false;
              }
          }
  
          if (printAdd) {
              print0(ucase ? "ADD " : "add ");
  
              if (x.isIfNotExists()) {
                  print0(ucase ? "IF NOT EXISTS " : "if not exists ");
              }
          } else {
              print('\t');
              indentCount++;
          }
  
          if (x.getPartitionCount() != null) {
              print0(ucase ? "PARTITION PARTITIONS " : "partition partitions ");
              x.getPartitionCount().accept(this);
          }
  
          if (x.getPartitions().size() > 0) {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartitions(), ", ");
              print(')');
          }
  
          SQLExpr location = x.getLocation();
          if (location != null) {
              print0(ucase ? " LOCATION " : " location ");
              location.accept(this);
          }
  
          if (!printAdd) {
              indentCount--;
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAddExtPartition x) {
          print0(ucase ? "ADD " : "add ");
  
          MySqlExtPartition extPartition = x.getExtPartition();
          if (extPartition != null) {
              println();
              extPartition.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropExtPartition x) {
          print0(ucase ? "DROP " : "drop ");
  
          MySqlExtPartition extPartition = x.getExtPartition();
          if (extPartition != null) {
              println();
              extPartition.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableReOrganizePartition x) {
          print0(ucase ? "REORGANIZE " : "reorganize ");
  
          printAndAccept(x.getNames(), ", ");
  
          print0(ucase ? " INTO (" : " into (");
          printAndAccept(x.getPartitions(), ", ");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDropPartition x) {
          boolean printDrop = true;
          if (x.getParent() instanceof SQLAlterTableStatement) {
              SQLAlterTableStatement stmt = (SQLAlterTableStatement) x.getParent();
              int p = stmt.getChildren().indexOf(x);
              if (p > 0 && stmt.getChildren().get(p - 1) instanceof SQLAlterTableDropPartition) {
                  printDrop = false;
              }
          }
  
          if (printDrop) {
              print0(ucase ? "DROP " : "drop ");
  
              if (x.isIfExists()) {
                  print0(ucase ? "IF EXISTS " : "if exists ");
              }
          } else {
              print('\t');
              indentCount++;
          }
  
          if (x.getAttribute("SIMPLE") != null) {
              print0(ucase ? "PARTITION " : "partition ");
              printAndAccept(x.getPartitions(), ",");
          } else {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartitions(), ", ");
              print(')');
          }
  
          if (x.isPurge()) {
              print0(ucase ? " PURGE" : " purge");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableRenamePartition x) {
          print0(ucase ? "PARTITION (" : "partition (");
          printAndAccept(x.getPartition(), ", ");
          print0(ucase ? ") RENAME TO PARTITION(" : ") rename to partition(");
          printAndAccept(x.getTo(), ", ");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetComment x) {
          print0(ucase ? "SET COMMENT " : "set comment ");
          x.getComment().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetTableSpace x) {
          print0(ucase ? "SET TABLESPACE " : "set tablespace ");
          x.getTableSpaceName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetSchema x) {
          print0(ucase ? "SET SCHEMA " : "set schema ");
          x.getSchemaName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetFileFormat x) {
          print0(ucase ? "SET FILEFORMAT " : "set fileformat ");
          x.getValue().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLPrivilegeItem x) {
          SQLExpr action = x.getAction();
          if (action instanceof SQLIdentifierExpr) {
              String actionName = ((SQLIdentifierExpr) action).getName();
              print0(ucase ? actionName.toUpperCase() : actionName.toLowerCase());
          } else {
              printExpr(action);
          }
  
          List<SQLName> columns = x.getColumns();
          int size = columns.size();
          if (size > 0) {
              print0("(");
              for (int i = 0; i < size; ++i) {
                  if (i != 0) {
                      print0(", ");
                  }
                  SQLName column = columns.get(i);
                  if (column instanceof SQLIdentifierExpr) {
                      print0(((SQLIdentifierExpr) column).getName());
                  } else {
                      printExpr(column);
                  }
              }
              print0(")");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetLifecycle x) {
          print0(ucase ? "SET LIFECYCLE " : "set lifecycle ");
          x.getLifecycle().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetLocation x) {
          print0(ucase ? "SET LOCATION " : "set location ");
          x.getLocation().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableEnableLifecycle x) {
          if (!x.getPartition().isEmpty()) {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartition(), ", ");
              print0(") ");
          }
  
          print0(ucase ? "ENABLE LIFECYCLE" : "enable lifecycle");
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableArchive x) {
          if (!x.getPartition().isEmpty()) {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartition(), ", ");
              print0(") ");
          }
  
          print0(ucase ? "ARCHIVE" : "archive");
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDisableLifecycle x) {
          if (!x.getPartition().isEmpty()) {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartition(), ", ");
              print0(") ");
          }
  
          print0(ucase ? "DISABLE LIFECYCLE" : "disable lifecycle");
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTablePartition x) {
          if (!x.getPartition().isEmpty()) {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartition(), ", ");
              print0(") ");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTablePartitionSetProperties x) {
          if (!x.getPartition().isEmpty()) {
              print0(ucase ? "PARTITION (" : "partition (");
              printAndAccept(x.getPartition(), ", ");
              print0(") ");
          }
  
          if (!x.getPartitionProperties().isEmpty()) {
              print0(ucase ? "SET PARTITIONPROPERTIES (" : "set partitionproperties (");
              printAndAccept(x.getPartitionProperties(), ", ");
              print0(") ");
          }
  
          SQLExpr location = x.getLocation();
          if (location != null) {
              println();
              print0(ucase ? "SET LOCATION " : "set location ");
              location.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableTouch x) {
          print0(ucase ? "TOUCH" : "touch");
          if (!x.getPartition().isEmpty()) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartition(), ", ");
              print(')');
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetSerde x) {
          print0(ucase ? "SET SERDE " : "set serde ");
          x.getSerde().accept(this);
  
          if (x.getSerdeProperties().size() > 0) {
              println();
              print0(ucase ? "WITH SERDEPROPERTIES (" : "with serdeproperties (");
              printAndAccept(x.getSerdeProperties(), ", ");
              print(')');
          }
  
          return false;
      }
  
      public void printArrayExprPrefix() {
      }
      @Override
      public boolean visit(SQLArrayExpr x) {
          SQLExpr expr = x.getExpr();
  
          boolean arrayValue = expr instanceof SQLIdentifierExpr
                  && ((SQLIdentifierExpr) expr).nameHashCode64() == FnvHash.Constants.ARRAY;
          if (arrayValue && printNameQuote) {
              print0(((SQLIdentifierExpr) expr).getName());
          } else if (expr != null) {
              expr.accept(this);
          } else {
              printArrayExprPrefix();
          }
  
          if (x.getDataType() != null) {
              print('<');
              x.getDataType().accept(this);
              print('>');
          }
  
          boolean brace = arrayValue && (dbType == DbType.hive || dbType == DbType.spark || dbType == DbType.odps);
  
          print(brace ? '(' : '[');
          List<SQLExpr> values = x.getValues();
          int size = values.size();
          for (int i = 0, columnIndex = 0; i < size; i++, columnIndex++) {
              SQLExpr value = values.get(i);
              if (i != 0) {
                  print(',');
                  if (i % 5 == 0 || value instanceof SQLStructExpr) {
                      println();
                      columnIndex = 0;
                  } else {
                      print0(' ');
                  }
              }
              printExpr(value);
          }
          print(brace ? ')' : ']');
          return false;
      }
  
      @Override
      public boolean visit(SQLOpenStatement x) {
          print0(ucase ? "OPEN " : "open ");
          printExpr(x.getCursorName(), parameterized);
  
          List<SQLExpr> columns = x.getColumns();
          if (columns.size() > 0) {
              print('(');
              printAndAccept(columns, ", ");
              print(')');
          }
  
          SQLExpr forExpr = x.getFor();
          if (forExpr != null) {
              print0(ucase ? " FOR " : " for ");
              forExpr.accept(this);
          }
  
          List<SQLExpr> using = x.getUsing();
          if (using.size() > 0) {
              print0(ucase ? " USING " : " using ");
              printAndAccept(using, ", ");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLFetchStatement x) {
          print0(ucase ? "FETCH " : "fetch ");
          x.getCursorName().accept(this);
          if (x.isBulkCollect()) {
              print0(ucase ? " BULK COLLECT INTO " : " bulk collect into ");
          } else {
              print0(ucase ? " INTO " : " into ");
          }
          printAndAccept(x.getInto(), ", ");
  
          final SQLLimit limit = x.getLimit();
          if (limit != null) {
              print(' ');
              limit.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLCloseStatement x) {
          print0(ucase ? "CLOSE " : "close ");
          printExpr(x.getCursorName(), parameterized);
          return false;
      }
  
      @Override
      public boolean visit(SQLGroupingSetExpr x) {
          print0(ucase ? "GROUPING SETS" : "grouping sets");
          print0(" (");
          printAndAccept(x.getParameters(), ", ");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLIfStatement x) {
          print0(ucase ? "IF " : "if ");
          x.getCondition().accept(this);
          this.indentCount++;
          println();
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              SQLStatement item = x.getStatements().get(i);
              item.accept(this);
              if (i != size - 1) {
                  println();
              }
          }
          this.indentCount--;
  
          for (SQLIfStatement.ElseIf elseIf : x.getElseIfList()) {
              println();
              elseIf.accept(this);
          }
  
          if (x.getElseItem() != null) {
              println();
              x.getElseItem().accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLIfStatement.Else x) {
          print0(ucase ? "ELSE" : "else");
          this.indentCount++;
          println();
  
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              if (i != 0) {
                  println();
              }
              SQLStatement item = x.getStatements().get(i);
              item.accept(this);
          }
  
          this.indentCount--;
          return false;
      }
  
      @Override
      public boolean visit(SQLIfStatement.ElseIf x) {
          if (x.isConcatenated()) {
              print0(ucase ? "ELSEIF " : "elseif ");
          } else {
              print0(ucase ? "ELSE IF " : "else if ");
          }
          x.getCondition().accept(this);
          println();
          print0(ucase ? " THEN" : " then");
          this.indentCount++;
          println();
  
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              if (i != 0) {
                  println();
              }
              SQLStatement item = x.getStatements().get(i);
              item.accept(this);
          }
  
          this.indentCount--;
          return false;
      }
  
      @Override
      public boolean visit(SQLLoopStatement x) {
          print0(ucase ? "LOOP" : "loop");
          this.indentCount++;
          println();
  
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              SQLStatement item = x.getStatements().get(i);
              item.accept(this);
  
              if (i != size - 1) {
                  println();
              }
          }
  
          this.indentCount--;
          println();
          print0(ucase ? "END LOOP" : "end loop");
          if (x.getLabelName() != null) {
              print(' ');
              print0(x.getLabelName());
          }
          return false;
      }
  
      public boolean visit(OracleFunctionDataType x) {
          if (x.isStatic()) {
              print0(ucase ? "STATIC " : "static ");
          }
  
          print0(ucase ? "FUNCTION " : "function ");
  
          print0(x.getName());
  
          print(" (");
          printAndAccept(x.getParameters(), ", ");
          print(")");
          print0(ucase ? " RETURN " : " return ");
          x.getReturnDataType().accept(this);
  
          SQLStatement block = x.getBlock();
          if (block != null) {
              println();
              print0(ucase ? "IS" : "is");
              println();
              block.accept(this);
          }
  
          return false;
      }
  
      public boolean visit(OracleProcedureDataType x) {
          if (x.isStatic()) {
              print0(ucase ? "STATIC " : "static ");
          }
  
          print0(ucase ? "PROCEDURE " : "procedure ");
  
          print0(x.getName());
  
          if (x.getParameters().size() > 0) {
              print(" (");
              printAndAccept(x.getParameters(), ", ");
              print(")");
          }
  
          SQLStatement block = x.getBlock();
          if (block != null) {
              println();
              print0(ucase ? "IS" : "is");
              println();
              block.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLParameter x) {
          SQLName name = x.getName();
          if (x.getDataType().getName().equalsIgnoreCase("CURSOR")) {
              print0(ucase ? "CURSOR " : "cursor ");
              x.getName().accept(this);
              print0(ucase ? " IS" : " is");
              this.indentCount++;
              println();
              SQLSelect select = ((SQLQueryExpr) x.getDefaultValue()).getSubQuery();
              select.accept(this);
              this.indentCount--;
  
          } else {
              if (x.isMap()) {
                  print0(ucase ? "MAP MEMBER " : "map member ");
              } else if (x.isOrder()) {
                  print0(ucase ? "ORDER MEMBER " : "order member ");
              } else if (x.isMember()) {
                  print0(ucase ? "MEMBER " : "member ");
              }
              SQLDataType dataType = x.getDataType();
  
              if (DbType.oracle == dbType
                      || dataType instanceof OracleFunctionDataType
                      || dataType instanceof OracleProcedureDataType) {
                  if (dataType instanceof OracleFunctionDataType) {
                      OracleFunctionDataType functionDataType = (OracleFunctionDataType) dataType;
                      visit(functionDataType);
                      return false;
                  }
  
                  if (dataType instanceof OracleProcedureDataType) {
                      OracleProcedureDataType procedureDataType = (OracleProcedureDataType) dataType;
                      visit(procedureDataType);
                      return false;
                  }
  
                  String dataTypeName = dataType.getName();
                  boolean printType = (dataTypeName.startsWith("TABLE OF") && x.getDefaultValue() == null)
                          || dataTypeName.equalsIgnoreCase("REF CURSOR")
                          || dataTypeName.startsWith("VARRAY(");
                  if (printType) {
                      print0(ucase ? "TYPE " : "type ");
                  }
  
                  //枚举类型特殊处理
                  if ("ENUM".equals(dataTypeName)) {
                      dataType.accept(this);
                      return false;
                  } else {
                      name.accept(this);
                  }
                  if (x.getParamType() == SQLParameter.ParameterType.IN) {
                      print0(ucase ? " IN " : " in ");
                  } else if (x.getParamType() == SQLParameter.ParameterType.OUT) {
                      print0(ucase ? " OUT " : " out ");
                  } else if (x.getParamType() == SQLParameter.ParameterType.INOUT) {
                      print0(ucase ? " IN OUT " : " in out ");
                  } else {
                      print(' ');
                  }
  
                  if (x.isNoCopy()) {
                      print0(ucase ? "NOCOPY " : "nocopy ");
                  }
  
                  if (x.isConstant()) {
                      print0(ucase ? "CONSTANT " : "constant ");
                  }
  
                  if (printType) {
                      print0(ucase ? "IS " : "is ");
                  }
              } else {
                  if (x.getParamType() == SQLParameter.ParameterType.IN) {
                      boolean skip = DbType.mysql == dbType
                              && x.getParent() instanceof SQLCreateFunctionStatement;
  
                      if (!skip) {
                          print0(ucase ? "IN " : "in ");
                      }
                  } else if (x.getParamType() == SQLParameter.ParameterType.OUT) {
                      print0(ucase ? "OUT " : "out ");
                  } else if (x.getParamType() == SQLParameter.ParameterType.INOUT) {
                      print0(ucase ? "INOUT " : "inout ");
                  }
                  x.getName().accept(this);
                  print(' ');
              }
  
              dataType.accept(this);
              if (x.isNotNull()) {
                  print0(ucase ? " NOT NULL" : " not null");
              }
              printParamDefaultValue(x);
          }
  
          return false;
      }
  
      protected void printParamDefaultValue(SQLParameter x) {
          if (x.getDefaultValue() != null) {
              print0(" := ");
              x.getDefaultValue().accept(this);
          }
      }
  
      @Override
      public boolean visit(SQLDeclareItem x) {
          SQLDataType dataType = x.getDataType();
  
          if (dataType instanceof SQLRecordDataType) {
              print0(ucase ? "TYPE " : "type ");
          }
  
          x.getName().accept(this);
  
          if (x.getType() == SQLDeclareItem.Type.TABLE) {
              print0(ucase ? " TABLE" : " table");
              int size = x.getTableElementList().size();
  
              if (size > 0) {
                  print0(" (");
                  this.indentCount++;
                  println();
                  for (int i = 0; i < size; ++i) {
                      if (i != 0) {
                          print(',');
                          println();
                      }
                      x.getTableElementList().get(i).accept(this);
                  }
                  this.indentCount--;
                  println();
                  print(')');
              }
          } else if (x.getType() == SQLDeclareItem.Type.CURSOR) {
              print0(ucase ? " CURSOR" : " cursor");
          } else {
              if (dataType != null) {
                  if (dataType instanceof SQLRecordDataType) {
                      print0(ucase ? " IS " : " is ");
                  } else {
                      print(' ');
                  }
                  dataType.accept(this);
              }
              if (x.getValue() != null) {
                  if (DbType.mysql == getDbType() || DbType.bigquery == getDbType()) {
                      print0(ucase ? " DEFAULT " : " default ");
                  } else {
                      print0(" = ");
                  }
                  x.getValue().accept(this);
              }
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLPartitionValue x) {
          if (x.getOperator() == SQLPartitionValue.Operator.LessThan //
                  && DbType.oracle != getDbType() && x.getItems().size() == 1 //
                  && x.getItems().get(0) instanceof SQLIdentifierExpr) {
              SQLIdentifierExpr ident = (SQLIdentifierExpr) x.getItems().get(0);
              if ("MAXVALUE".equalsIgnoreCase(ident.getName())) {
                  print0(ucase ? "VALUES LESS THAN MAXVALUE" : "values less than maxvalue");
                  return false;
              }
          }
  
          if (x.getOperator() == SQLPartitionValue.Operator.LessThan) {
              print0(ucase ? "VALUES LESS THAN (" : "values less than (");
          } else if (x.getOperator() == SQLPartitionValue.Operator.In) {
              print0(ucase ? "VALUES IN (" : "values in (");
          } else if (x.getOperator() == SQLPartitionValue.Operator.FixedRange) {
              print(ucase ? "VALUES [" : "values [");
          } else {
              print(ucase ? "VALUES (" : "values (");
          }
          printAndAccept(x.getItems(), ", ", false);
          print(')');
          return false;
      }
  
      public DbType getDbType() {
          return dbType;
      }
  
      public boolean isUppCase() {
          return ucase;
      }
  
      public void setUppCase(boolean val) {
          this.config(VisitorFeature.OutputUCase, val);
      }
  
      @Override
      public boolean visit(SQLPartitionBatch x) {
          print0(ucase ? "START (" : "start (");
          x.getStart().accept(this);
          print0(ucase ? ") END (" : ") end (");
          x.getEnd().accept(this);
          print0(ucase ? ") EVERY (" : ") every (");
          x.getEvery().accept(this);
          print0(")");
          return false;
      }
      @Override
      public boolean visit(SQLPartitionSingle x) {
          boolean isDbPartiton = false, isTbPartition = false;
          final SQLObject parent = x.getParent();
          if (parent != null) {
              final SQLObject parent2 = parent.getParent();
              if (parent2 instanceof MySqlCreateTableStatement) {
                  MySqlCreateTableStatement stmt = (MySqlCreateTableStatement) parent2;
                  isDbPartiton = parent == stmt.getDbPartitionBy();
                  isTbPartition = parent == stmt.getTablePartitionBy();
              }
          }
  
          if (isDbPartiton) {
              print0(ucase ? "DBPARTITION " : "dbpartition ");
          } else if (isTbPartition) {
              print0(ucase ? "TBPARTITION " : "tbpartition ");
          } else {
              print0(ucase ? "PARTITION " : "partition ");
          }
          x.getName().accept(this);
          if (x.getValues() != null) {
              print(' ');
              x.getValues().accept(this);
          }
  
          if (x.getSubPartitionsCount() != null) {
              this.indentCount++;
              println();
              print0(ucase ? "SUBPARTITIONS " : "subpartitions ");
              x.getSubPartitionsCount().accept(this);
              this.indentCount--;
          }
  
          if (x.getSubPartitions().size() > 0) {
              print(" (");
              this.indentCount++;
              for (int i = 0; i < x.getSubPartitions().size(); ++i) {
                  if (i != 0) {
                      print(',');
                  }
                  println();
                  x.getSubPartitions().get(i).accept(this);
              }
              this.indentCount--;
              println();
              print(')');
          }
  
          SQLExpr locality = x.getLocality();
          if (locality != null) {
              print(ucase ? " LOCALITY = " : " locality = ");
              locality.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLPartitionByRange x) {
          SQLExpr interval = x.getInterval();
          if (x.getColumns().isEmpty()
                  && (interval instanceof SQLBetweenExpr || interval instanceof SQLMethodInvokeExpr)) {
              interval.accept(this);
          } else {
              print0(ucase ? "RANGE" : "range");
  
              boolean columns = true;
              for (SQLExpr column : x.getColumns()) {
                  if (!(column instanceof SQLName)) {
                      columns = false;
                      break;
                  }
              }
  
              if (x.getColumns().size() == 1) {
                  if (DbType.mysql == getDbType() && columns) {
                      print0(ucase ? " COLUMNS (" : " columns (");
                  } else {
                      print0(" (");
                  }
                  x.getColumns().get(0).accept(this);
                  print(')');
              } else {
                  if (DbType.mysql == getDbType() && columns) {
                      print0(ucase ? " COLUMNS (" : " columns (");
                  } else {
                      print0(" (");
                  }
                  printAndAccept(x.getColumns(), ", ");
                  print(')');
              }
  
              if (interval != null) {
                  print0(ucase ? " INTERVAL (" : " interval (");
                  interval.accept(this);
                  print(')');
              }
          }
  
          printPartitionsCountAndSubPartitions(x);
          printSQLPartitions(x.getPartitions());
          return false;
      }
  
      @Override
      public boolean visit(SQLPartitionByList x) {
          print0(ucase ? "LIST " : "list ");
          if (SQLPartitionByList.PartitionByListType.LIST_COLUMNS.equals(x.getType())) {
              print0(ucase ? "COLUMNS (" : "columns (");
              printAndAccept(x.getColumns(), ", ");
              print0(")");
          } else {
              print('(');
              printAndAccept(x.getColumns(), ", ");
              print0(")");
          }
  
          printPartitionsCountAndSubPartitions(x);
  
          printSQLPartitions(x.getPartitions());
          return false;
      }
  
      @Override
      public boolean visit(SQLPartitionByHash x) {
          if (x.isLinear()) {
              print0(ucase ? "LINEAR HASH " : "linear hash ");
          } else {
              if (x.isUnique()) {
                  print0(ucase ? "UNI_HASH " : "uni_hash ");
              } else {
                  print0(ucase ? "HASH " : "hash ");
              }
          }
  
          if (x.isKey()) {
              print0(ucase ? "KEY" : "key");
          }
          if (!x.getColumns().isEmpty()) {
              print('(');
              printAndAccept(x.getColumns(), ", ");
              print(')');
          }
  
          printPartitionsCountAndSubPartitions(x);
  
          printSQLPartitions(x.getPartitions());
  
          return false;
      }
  
      @Override
      public boolean visit(SQLPartitionByValue x) {
          print0(ucase ? "VALUE " : "value ");
  
          print('(');
          printAndAccept(x.getColumns(), ", ");
          print(')');
  
          printPartitionsCountAndSubPartitions(x);
  
          printSQLPartitions(x.getPartitions());
  
          return false;
      }
  
      protected void printSQLPartitions(List<SQLPartition> partitions) {
          int partitionsSize = partitions.size();
          if (partitionsSize > 0) {
              print0(" (");
              this.indentCount++;
              for (int i = 0; i < partitionsSize; ++i) {
                  println();
                  partitions.get(i).accept(this);
                  if (i != partitionsSize - 1) {
                      print0(",");
                  }
              }
              this.indentCount--;
              println();
              print(')');
          }
      }
  
      protected void printPartitionsCountAndSubPartitions(SQLPartitionBy x) {
          final SQLExpr partitionsCount = x.getPartitionsCount();
          if (partitionsCount != null) {
              boolean isDbPartiton = false, isTbPartition = false;
              if (x.getParent() instanceof MySqlCreateTableStatement) {
                  MySqlCreateTableStatement stmt = (MySqlCreateTableStatement) x.getParent();
                  isDbPartiton = x == stmt.getDbPartitionBy();
                  isTbPartition = x == stmt.getTablePartitionBy();
              }
  
              if (Boolean.TRUE.equals(x.getAttribute("ads.partition"))) {
                  print0(ucase ? " PARTITION NUM " : " partition num ");
              } else {
                  if (isDbPartiton) {
                      print0(ucase ? " DBPARTITIONS " : " dbpartitions ");
                  } else if (isTbPartition) {
                      print0(ucase ? " TBPARTITIONS " : " tbpartitions ");
                  } else {
                      print0(ucase ? " PARTITIONS " : " partitions ");
                  }
              }
  
              partitionsCount.accept(this);
          }
  
          printLifeCycle(x.getLifeCycle());
  
          if (x.getSubPartitionBy() != null) {
              println();
              x.getSubPartitionBy().accept(this);
          }
  
          if (x.getStoreIn().size() > 0) {
              println();
              print0(ucase ? "STORE IN (" : "store in (");
              printAndAccept(x.getStoreIn(), ", ");
              print(')');
          }
      }
  
      @Override
      public boolean visit(SQLSubPartitionByHash x) {
          if (x.isLinear()) {
              print0(ucase ? "SUBPARTITION BY LINEAR HASH " : "subpartition by linear hash ");
          } else {
              print0(ucase ? "SUBPARTITION BY HASH " : "subpartition by hash ");
          }
  
          if (x.isKey()) {
              print0(ucase ? "KEY" : "key");
          }
  
          print('(');
          x.getExpr().accept(this);
          print(')');
  
          if (x.getSubPartitionsCount() != null) {
              print0(ucase ? " SUBPARTITIONS " : " subpartitions ");
              x.getSubPartitionsCount().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSubPartitionByRange x) {
          print0(ucase ? "SUBPARTITION BY RANGE " : "subpartition by range ");
  
          SQLExpr subPartitionsCount = x.getSubPartitionsCount();
          if (subPartitionsCount != null) {
              print0(ucase ? " SUBPARTITIONS " : " subpartitions ");
              subPartitionsCount.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSubPartitionByList x) {
          if (x.isLinear()) {
              print0(ucase ? "SUBPARTITION BY LINEAR HASH " : "subpartition by linear hash ");
          } else {
              print0(ucase ? "SUBPARTITION BY HASH " : "subpartition by hash ");
          }
  
          print('(');
          x.getColumn().accept(this);
          print(')');
  
          if (x.getSubPartitionsCount() != null) {
              print0(ucase ? " SUBPARTITIONS " : " subpartitions ");
              x.getSubPartitionsCount().accept(this);
          }
  
          if (x.getSubPartitionTemplate().size() > 0) {
              this.indentCount++;
              println();
              print0(ucase ? "SUBPARTITION TEMPLATE (" : "subpartition template (");
              this.indentCount++;
              println();
              printlnAndAccept(x.getSubPartitionTemplate(), ",");
              this.indentCount--;
              println();
              print(')');
              this.indentCount--;
          }
  
          printLifeCycle(x.getLifecycle());
  
          return false;
      }
  
      @Override
      public boolean visit(SQLSubPartition x) {
          print0(ucase ? "SUBPARTITION " : "subpartition ");
          x.getName().accept(this);
  
          if (x.getValues() != null) {
              print(' ');
              x.getValues().accept(this);
          }
  
          SQLName tableSpace = x.getTableSpace();
          if (tableSpace != null) {
              print0(ucase ? " TABLESPACE " : " tablespace ");
              tableSpace.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterDatabaseStatement x) {
          print0(ucase ? "ALTER DATABASE " : "alter database ");
          x.getName().accept(this);
          if (x.isUpgradeDataDirectoryName()) {
              print0(ucase ? " UPGRADE DATA DIRECTORY NAME" : " upgrade data directory name");
          }
  
          if (x.getProperties().size() > 0) {
              print0(ucase ? " SET DBPROPERTIES (" : "set dbproperties (");
              printAndAccept(x.getProperties(), ", ");
              print(')');
          }
  
          SQLAlterDatabaseItem item = x.getItem();
          if (item instanceof MySqlAlterDatabaseSetOption) {
              MySqlAlterDatabaseSetOption setOption = (MySqlAlterDatabaseSetOption) item;
              print0(ucase ? " SET " : " set ");
              printAndAccept(setOption.getOptions(), ", ");
  
              SQLName on = setOption.getOn();
              if (on != null) {
                  print0(ucase ? " ON " : " on ");
                  on.accept(this);
              }
          }
  
          if (item instanceof MySqlAlterDatabaseKillJob) {
              print0(ucase ? " KILL " : " kill ");
              MySqlAlterDatabaseKillJob kill = (MySqlAlterDatabaseKillJob) item;
              kill.getJobType().accept(this);
              print0(" ");
              kill.getJobId().accept(this);
          }
  
          SQLAlterCharacter character = x.getCharacter();
          if (character != null) {
              print(' ');
              character.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableConvertCharSet x) {
          print0(ucase ? "CONVERT TO CHARACTER SET " : "convert to character set ");
          x.getCharset().accept(this);
  
          if (x.getCollate() != null) {
              print0(ucase ? " COLLATE " : " collate ");
              x.getCollate().accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableCoalescePartition x) {
          print0(ucase ? "COALESCE PARTITION " : "coalesce partition ");
          x.getCount().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableTruncatePartition x) {
          print0(ucase ? "TRUNCATE PARTITION " : "truncate partition ");
          if (x.getPartitionValues().isEmpty()) {
              printPartitions(x.getPartitions());
          } else {
              print0(ucase ? "FOR " : "for ");
              print0("(");
              printAndAccept(x.getPartitionValues(), ", ");
              print0(")");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableDiscardPartition x) {
          print0(ucase ? "DISCARD PARTITION " : "discard partition ");
          printPartitions(x.getPartitions());
  
          if (x.isTablespace()) {
              print0(ucase ? " TABLESPACE" : " tablespace");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableExchangePartition x) {
          print0(ucase ? "EXCHANGE PARTITION " : "exchange partition ");
          printAndAccept(x.getPartitions(), ", ");
          print0(ucase ? " WITH TABLE " : " with table ");
          x.getTable().accept(this);
  
          Boolean validation = x.getValidation();
          if (validation != null) {
              if (validation) {
                  print0(ucase ? " WITH VALIDATION" : " with validation");
              } else {
                  print0(ucase ? " WITHOUT VALIDATION" : " without validation");
              }
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableImportPartition x) {
          print0(ucase ? "IMPORT PARTITION " : "import partition ");
          printPartitions(x.getPartitions());
  
          if (x.isTablespace()) {
              print0(ucase ? " TABLESPACE" : " tablespace");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAnalyzePartition x) {
          print0(ucase ? "ANALYZE PARTITION " : "analyze partition ");
  
          printPartitions(x.getPartitions());
          return false;
      }
  
      protected void printPartitions(List<SQLName> partitions) {
          if (partitions.size() == 1 && "ALL".equalsIgnoreCase(partitions.get(0).getSimpleName())) {
              print0(ucase ? "ALL" : "all");
          } else {
              printAndAccept(partitions, ", ");
          }
      }
  
      @Override
      public boolean visit(SQLAlterTableCheckPartition x) {
          print0(ucase ? "CHECK PARTITION " : "check partition ");
          printPartitions(x.getPartitions());
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableOptimizePartition x) {
          print0(ucase ? "OPTIMIZE PARTITION " : "optimize partition ");
          printPartitions(x.getPartitions());
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableRebuildPartition x) {
          print0(ucase ? "REBUILD PARTITION " : "rebuild partition ");
          printPartitions(x.getPartitions());
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableRepairPartition x) {
          print0(ucase ? "REPAIR PARTITION " : "repair partition ");
          printPartitions(x.getPartitions());
          return false;
      }
  
      @Override
      public boolean visit(SQLSequenceExpr x) {
          x.getSequence().accept(this);
          print('.');
          print0(ucase ? x.getFunction().name : x.getFunction().nameLCase);
          return false;
      }
  
      @Override
      public boolean visit(SQLMergeStatement x) {
          print0(ucase ? "MERGE " : "merge ");
          if (x.getHints().size() > 0) {
              printAndAccept(x.getHints(), ", ");
              print(' ');
          }
  
          print0(ucase ? "INTO " : "into ");
          x.getInto().accept(this);
  
          println();
          print0(ucase ? "USING " : "using ");
          x.getUsing().accept(this);
  
          print0(ucase ? " ON " : " on ");
          x.getOn().accept(this);
  
          for (SQLMergeStatement.When when : x.getWhens()) {
              println();
              when.accept(this);
          }
  
          SQLErrorLoggingClause errorLogging = x.getErrorLoggingClause();
          if (errorLogging != null) {
              println();
              errorLogging.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(WhenUpdate x) {
          print0(ucase ? "WHEN MATCHED THEN UPDATE" : "when matched then update");
          println();
          incrementIndent();
          print(ucase ? "SET " : "set ");
          printlnAndAccept(x.getItems(), ",");
          decrementIndent();
  
          SQLExpr where = x.getWhere();
          if (where != null) {
              println();
              print0(ucase ? "WHERE " : "where ");
              printExpr(where, parameterized);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLMergeStatement.WhenDelete x) {
          print0(ucase ? "WHEN" : "when");
          if (x.isNot()) {
              print0(ucase ? " NOT" : " not");
          }
          print0(ucase ? " MATCHED" : " matched");
          SQLName by = x.getBy();
          if (by != null) {
              print0(ucase ? " BY " : " by ");
              by.accept(this);
          }
  
          SQLExpr where = x.getWhere();
          if (where != null) {
              print0(ucase ? " AND " : " and ");
              printExpr(where, parameterized);
          }
          print0(ucase ? " THEN DELETE" : " then delete");
          return false;
      }
  
      @Override
      public boolean visit(WhenInsert x) {
          print0(ucase ? "WHEN NOT MATCHED" : "when not matched");
          SQLName by = x.getBy();
          if (by != null) {
              print0(ucase ? " BY " : " by ");
              by.accept(this);
          }
          print0(ucase ? " THEN INSERT" : " then insert");
          if (x.isInsertRow()) {
              printMergeInsertRow();
          } else {
              if (x.getColumns().size() > 0) {
                  printAndAccept(" (", ")", x.getColumns(), ", ", 5);
              }
              println();
              printAndAccept(
                      ucase ? "VALUES (" : "values (",
                      ")",
                      x.getValues(),
                      ", ",
                      5
              );
          }
          if (x.getWhere() != null) {
              this.indentCount++;
              println();
              print0(ucase ? "WHERE " : "where ");
              x.getWhere().accept(this);
              this.indentCount--;
          }
  
          return false;
      }
  
      public void printMergeInsertRow() {
          print(ucase ? " ROW" : "row");
      }
      @Override
      public boolean visit(SQLErrorLoggingClause x) {
          print0(ucase ? "LOG ERRORS " : "log errors ");
          if (x.getInto() != null) {
              print0(ucase ? "INTO " : "into ");
              x.getInto().accept(this);
              print(' ');
          }
  
          if (x.getSimpleExpression() != null) {
              print('(');
              x.getSimpleExpression().accept(this);
              print(')');
          }
  
          if (x.getLimit() != null) {
              print0(ucase ? " REJECT LIMIT " : " reject limit ");
              x.getLimit().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateSequenceStatement x) {
          print0(ucase ? "CREATE " : "create ");
  
          if (x.isGroup()) {
              print0(ucase ? "GROUP " : "group ");
          } else if (x.isSimple()) {
              print0(ucase ? "SIMPLE " : "simple ");
              final Boolean cache = x.getWithCache();
              if (cache != null && cache.booleanValue()) {
                  print0(ucase ? "WITH CACHE " : "with cache ");
              }
          } else if (x.isTime()) {
              print0(ucase ? "TIME " : "time ");
          }
  
          print0(ucase ? "SEQUENCE " : "sequence ");
          x.getName().accept(this);
  
          if (x.getStartWith() != null) {
              print0(ucase ? " START WITH " : " start with ");
              x.getStartWith().accept(this);
          }
  
          if (x.getIncrementBy() != null) {
              print0(ucase ? " INCREMENT BY " : " increment by ");
              x.getIncrementBy().accept(this);
          }
  
          if (x.getMaxValue() != null) {
              print0(ucase ? " MAXVALUE " : " maxvalue ");
              x.getMaxValue().accept(this);
          }
  
          if (x.isNoMaxValue()) {
              if (DbType.postgresql == dbType) {
                  print0(ucase ? " NO MAXVALUE" : " no maxvalue");
              } else {
                  print0(ucase ? " NOMAXVALUE" : " nomaxvalue");
              }
          }
  
          if (x.getMinValue() != null) {
              print0(ucase ? " MINVALUE " : " minvalue ");
              x.getMinValue().accept(this);
          }
  
          if (x.isNoMinValue()) {
              if (DbType.postgresql == dbType) {
                  print0(ucase ? " NO MINVALUE" : " no minvalue");
              } else {
                  print0(ucase ? " NOMINVALUE" : " nominvalue");
              }
          }
  
          if (x.getCycle() != null) {
              if (x.getCycle().booleanValue()) {
                  print0(ucase ? " CYCLE" : " cycle");
              } else {
                  if (DbType.postgresql == dbType) {
                      print0(ucase ? " NO CYCLE" : " no cycle");
                  } else {
                      print0(ucase ? " NOCYCLE" : " nocycle");
                  }
              }
          }
  
          Boolean cache = x.getCache();
          if (cache != null) {
              if (cache.booleanValue()) {
                  print0(ucase ? " CACHE" : " cache");
  
                  SQLExpr cacheValue = x.getCacheValue();
                  if (cacheValue != null) {
                      print(' ');
                      cacheValue.accept(this);
                  }
              } else {
                  print0(ucase ? " NOCACHE" : " nocache");
              }
          }
  
          Boolean order = x.getOrder();
          if (order != null) {
              if (order.booleanValue()) {
                  print0(ucase ? " ORDER" : " order");
              } else {
                  print0(ucase ? " NOORDER" : " noorder");
              }
          }
  
          final SQLExpr unitCount = x.getUnitCount();
          if (unitCount != null) {
              print0(ucase ? " UNIT COUNT " : " unit count ");
              printExpr(unitCount);
          }
  
          final SQLExpr unitIndex = x.getUnitIndex();
          if (unitIndex != null) {
              print0(ucase ? " INDEX " : " index ");
              printExpr(unitIndex);
          }
  
          if (x.getStep() != null) {
              print0(ucase ? " STEP " : " step ");
              printExpr(x.getStep());
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterSequenceStatement x) {
          print0(ucase ? "ALTER SEQUENCE " : "alter sequence ");
          x.getName().accept(this);
  
          if (x.isChangeToSimple()) {
              print0(ucase ? " CHANGE TO SIMPLE" : " change to simple");
              final Boolean cache = x.getWithCache();
              if (cache != null && cache.booleanValue()) {
                  print0(ucase ? " WITH CACHE" : "  with cache");
              }
          } else if (x.isChangeToGroup()) {
              print0(ucase ? " CHANGE TO GROUP" : " change to group");
          } else if (x.isChangeToTime()) {
              print0(ucase ? " CHANGE TO TIME" : " change to time");
          }
  
          if (x.getStartWith() != null) {
              print0(ucase ? " START WITH " : " start with ");
              x.getStartWith().accept(this);
          }
  
          if (x.getIncrementBy() != null) {
              print0(ucase ? " INCREMENT BY " : " increment by ");
              x.getIncrementBy().accept(this);
          }
  
          if (x.getMaxValue() != null) {
              print0(ucase ? " MAXVALUE " : " maxvalue ");
              x.getMaxValue().accept(this);
          }
  
          if (x.isNoMaxValue()) {
              if (DbType.postgresql == dbType) {
                  print0(ucase ? " NO MAXVALUE" : " no maxvalue");
              } else {
                  print0(ucase ? " NOMAXVALUE" : " nomaxvalue");
              }
          }
  
          if (x.getMinValue() != null) {
              print0(ucase ? " MINVALUE " : " minvalue ");
              x.getMinValue().accept(this);
          }
  
          if (x.isNoMinValue()) {
              if (DbType.postgresql == dbType) {
                  print0(ucase ? " NO MINVALUE" : " no minvalue");
              } else {
                  print0(ucase ? " NOMINVALUE" : " nominvalue");
              }
          }
  
          if (x.getCycle() != null) {
              if (x.getCycle().booleanValue()) {
                  print0(ucase ? " CYCLE" : " cycle");
              } else {
                  if (DbType.postgresql == dbType) {
                      print0(ucase ? " NO CYCLE" : " no cycle");
                  } else {
                      print0(ucase ? " NOCYCLE" : " nocycle");
                  }
              }
          }
  
          Boolean cache = x.getCache();
          if (cache != null) {
              if (cache.booleanValue()) {
                  print0(ucase ? " CACHE" : " cache");
  
                  SQLExpr cacheValue = x.getCacheValue();
                  if (cacheValue != null) {
                      print(' ');
                      cacheValue.accept(this);
                  }
              } else {
                  print0(ucase ? " NOCACHE" : " nocache");
              }
          }
  
          Boolean order = x.getOrder();
          if (order != null) {
              if (order.booleanValue()) {
                  print0(ucase ? " ORDER" : " order");
              } else {
                  print0(ucase ? " NOORDER" : " noorder");
              }
          }
  
          if (x.isRestart()) {
              print0(ucase ? " RESTART" : " restart");
  
              SQLExpr restartWith = x.getRestartWith();
              if (restartWith != null) {
                  print0(ucase ? " WITH " : " with ");
                  restartWith.accept(this);
              }
          }
  
          return false;
      }
  
      public boolean visit(SQLDateExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLTimeExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLDateTimeExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLTimestampNTZExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLRealExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLDecimalExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLDoubleExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLFloatExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLSmallIntExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLTinyIntExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLBigIntExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      public boolean visit(SQLLimit x) {
          print0(ucase ? "LIMIT " : "limit ");
          SQLExpr offset = x.getOffset();
          boolean offsetClause = x.isOffsetClause();
          if (offset != null && !offsetClause) {
              printExpr(offset, parameterized);
              print0(", ");
          }
  
          SQLExpr rowCount = x.getRowCount();
          if (rowCount != null) {
              printExpr(rowCount, parameterized);
          }
  
          if (offset != null && offsetClause) {
              print0(ucase ? " OFFSET " : " offset ");
              printExpr(offset, parameterized);
          }
  
          final List<SQLExpr> by = x.getBy();
          if (by != null) {
              print0(ucase ? " BY " : " by ");
              for (int i = 0; i < by.size(); i++) {
                  if (i != 0) {
                      print0(", ");
                  }
                  by.get(i).accept(this);
              }
          }
  
          return false;
      }
  
      public boolean visit(SQLDescribeStatement x) {
          print0(ucase ? "DESC " : "desc ");
          if (x.getObjectType() != null) {
              print0(x.getObjectType().name());
              print(' ');
          }
  
          if (x.isExtended()) {
              print0(ucase ? "EXTENDED " : "extended ");
          }
  
          if (x.isFormatted()) {
              print0(ucase ? "FORMATTED " : "formatted ");
          }
  
          if (x.getObject() != null) {
              x.getObject().accept(this);
          }
  
          SQLName column = x.getColumn();
          if (column != null) {
              print(' ');
              column.accept(this);
          }
  
          if (x.getPartition().size() > 0) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartition(), ", ");
              print(')');
          }
          return false;
      }
  
      protected void printHierarchical(SQLSelectQueryBlock x) {
          SQLExpr startWith = x.getStartWith(), connectBy = x.getConnectBy();
          if (startWith != null || connectBy != null) {
              println();
              if (x.getStartWith() != null) {
                  print0(ucase ? "START WITH " : "start with ");
                  x.getStartWith().accept(this);
                  println();
              }
  
              print0(ucase ? "CONNECT BY " : "connect by ");
  
              if (x.isNoCycle()) {
                  print0(ucase ? "NOCYCLE " : "nocycle ");
              }
  
              if (x.isPrior()) {
                  print0(ucase ? "PRIOR " : "prior ");
              }
  
              x.getConnectBy().accept(this);
          }
      }
  
      public void printOracleSegmentAttributes(OracleSegmentAttributes x) {
          if (x.getPctfree() != null) {
              println();
              print0(ucase ? "PCTFREE " : "pctfree ");
              print(x.getPctfree());
          }
  
          if (x.getPctused() != null) {
              println();
              print0(ucase ? "PCTUSED " : "pctused ");
              print(x.getPctused());
          }
  
          if (x.getInitrans() != null) {
              println();
              print0(ucase ? "INITRANS " : "initrans ");
              print(x.getInitrans());
          }
  
          if (x.getMaxtrans() != null) {
              println();
              print0(ucase ? "MAXTRANS " : "maxtrans ");
              print(x.getMaxtrans());
          }
  
          if (Boolean.FALSE.equals(x.getCompress())) {
              println();
              print0(ucase ? "NOCOMPRESS" : "nocompress");
          } else if (Boolean.TRUE.equals(x.getCompress())) {
              println();
              print0(ucase ? "COMPRESS" : "compress");
  
              if (x.getCompressLevel() != null) {
                  print(' ');
                  print(x.getCompressLevel());
              }
          }
  
          if (Boolean.TRUE.equals(x.getLogging())) {
              println();
              print0(ucase ? "LOGGING" : "logging");
          } else if (Boolean.FALSE.equals(x.getLogging())) {
              println();
              print0(ucase ? "NOLOGGING" : "nologging");
          }
  
          printTablespace(x.getTablespace());
  
          if (x.getStorage() != null) {
              println();
              x.getStorage().accept(this);
          }
      }
  
      @Override
      public boolean visit(SQLWhileStatement x) {
          String label = x.getLabelName();
  
          if (label != null && !label.isEmpty()) {
              print0(x.getLabelName());
              print0(": ");
          }
          print0(ucase ? "WHILE " : "while ");
          x.getCondition().accept(this);
          print0(ucase ? " LOOP" : " loop");
          println();
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              SQLStatement item = x.getStatements().get(i);
              item.accept(this);
              if (i != size - 1) {
                  println();
              }
          }
          println();
          print0(ucase ? "END LOOP" : "end loop");
          if (label != null && !label.isEmpty()) {
              print(' ');
              print0(label);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLDeclareStatement x) {
          boolean printDeclare = !(x.getParent() instanceof OracleCreatePackageStatement);
          if (printDeclare) {
              print0(ucase ? "DECLARE " : "declare ");
          }
          this.printAndAccept(x.getItems(), ", ");
          return false;
      }
  
      @Override
      public boolean visit(SQLReturnStatement x) {
          print0(ucase ? "RETURN" : "return");
  
          if (x.getExpr() != null) {
              print(' ');
              x.getExpr().accept(this);
          }
          return false;
      }
  
      public void postVisit(SQLObject x) {
          if (x instanceof SQLStatement) {
              SQLStatement stmt = (SQLStatement) x;
  
              boolean printSemi = printStatementAfterSemi == null
                      ? stmt.isAfterSemi()
                      : printStatementAfterSemi.booleanValue();
  
              if (printSemi) {
                  print(';');
              }
          }
      }
  
      @Override
      public boolean visit(SQLArgument x) {
          SQLParameter.ParameterType type = x.getType();
          if (type != null) {
              print0(type.name());
              print(' ');
          }
  
          x.getExpr().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLCommitStatement x) {
          print0(ucase ? "COMMIT" : "commit");
  
          if (x.isWrite()) {
              print0(ucase ? " WRITE" : " write");
              if (x.getWait() != null) {
                  if (x.getWait().booleanValue()) {
                      print0(ucase ? " WAIT" : " wait");
                  } else {
                      print0(ucase ? " NOWAIT" : " nowait");
                  }
              }
  
              if (x.getImmediate() != null) {
                  if (x.getImmediate().booleanValue()) {
                      print0(ucase ? " IMMEDIATE" : " immediate");
                  } else {
                      print0(ucase ? " BATCH" : " batch");
                  }
              }
          }
  
          if (x.isWork()) {
              print0(ucase ? " WORK" : " work");
          }
  
          if (x.getChain() != null) {
              if (x.getChain().booleanValue()) {
                  print0(ucase ? " AND CHAIN" : " and chain");
              } else {
                  print0(ucase ? " AND NO CHAIN" : " and no chain");
              }
          }
  
          if (x.getRelease() != null) {
              if (x.getRelease().booleanValue()) {
                  print0(ucase ? " AND RELEASE" : " and release");
              } else {
                  print0(ucase ? " AND NO RELEASE" : " and no release");
              }
          }
  
          return false;
      }
  
      public boolean visit(SQLFlashbackExpr x) {
          print0(x.getType().name());
          print(' ');
          SQLExpr expr = x.getExpr();
          if (expr instanceof SQLBinaryOpExpr && !((SQLBinaryOpExpr) expr).isParenthesized()) {
              print('(');
              expr.accept(this);
              print(')');
          } else {
              expr.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLDropMaterializedViewStatement x) {
          print0(ucase ? "DROP MATERIALIZED VIEW " : "drop materialized view ");
          if (x.isIfExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLShowMaterializedViewStatement x) {
          print0(ucase ? "SHOW MATERIALIZED VIEWS" : "show materialized views");
  
          if (x.getLike() != null) {
              printUcase(" LIKE ");
              x.getLike().accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowCreateMaterializedViewStatement x) {
          print0(ucase ? "SHOW CREATE MATERIALIZED VIEW " : "show create materialized view ");
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLRefreshMaterializedViewStatement x) {
          print0(ucase ? "REFRESH MATERIALIZED" : "refresh materialized");
  
          if (x.isConcurrently()) {
              print0(ucase ? " CONCURRENTLY" : " concurrently");
          }
  
          print0(ucase ? " VIEW " : " view ");
  
          x.getName().accept(this);
  
          if (x.isWithNoData()) {
              print0(ucase ? " WITH NO DATA" : " with no data");
          } else if (x.isWithData()) {
              print0(ucase ? " WITH DATA" : " with data");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterMaterializedViewStatement x) {
          print0(ucase ? "ALTER MATERIALIZED VIEW " : "alter materialized view ");
          x.getName().accept(this);
  
          if (x.isRefresh()) {
              println();
              print(ucase ? "REFRESH" : "refresh");
  
              if (x.isRefreshFast()) {
                  print(ucase ? " FAST" : " fast");
              } else if (x.isRefreshComplete()) {
                  print(ucase ? " COMPLETE" : " complete");
              } else if (x.isRefreshForce()) {
                  print(ucase ? " FORCE" : " force");
              }
  
              if (x.isRefreshOnCommit()) {
                  print(ucase ? " ON COMMIT" : " on commit");
              } else if (x.isRefreshOnDemand()) {
                  print(ucase ? " ON DEMAND" : " on demand");
              } else if (x.isRefreshOnOverWrite()) {
                  print(ucase ? " ON OVERWRITE" : " on overwrite");
              }
  
              if (x.getStartWith() != null) {
                  println();
                  print(ucase ? "START WITH " : "start with ");
                  x.getStartWith().accept(this);
              }
  
              if (x.getNext() != null) {
                  print(ucase ? " NEXT " : " next ");
                  x.getNext().accept(this);
              }
  
          }
  
          Boolean enableQueryRewrite = x.getEnableQueryRewrite();
          if (enableQueryRewrite != null) {
              println();
              if (enableQueryRewrite) {
                  print(ucase ? "ENABLE QUERY REWRITE" : "enable query rewrite");
              } else {
                  print(ucase ? "DISABLE QUERY REWRITE" : "disable query rewrite");
              }
          }
  
          if (x.isRebuild()) {
              print0(ucase ? " REBUILD" : " rebuild");
          }
  
          List<SQLExpr> partitions = x.getPartitions();
          if (!partitions.isEmpty()) {
              print0(ucase ? "PARTITIONS (" : "partitions (");
              printAndAccept(partitions, ", ");
              print(')');
          }
  
          return false;
      }
  
      @Override
      public void endVisit(SQLAlterMaterializedViewStatement x) {
      }
  
      public boolean visit(SQLCreateMaterializedViewStatement x) {
          print0(ucase ? "CREATE MATERIALIZED VIEW " : "create materialized view ");
          x.getName().accept(this);
  
          if (dbType == DbType.mysql) {
              printTableElements(x.getTableElementList());
  
              if (x.getDistributedByType() != null) {
                  println();
                  if (isEnabled(VisitorFeature.OutputDistributedLiteralInCreateTableStmt)) {
                      print0(ucase ? "DISTRIBUTED BY " : "distributed by ");
                  } else {
                      print0(ucase ? "DISTRIBUTE BY " : "distribute by ");
                  }
  
                  SQLName distributeByType = x.getDistributedByType();
  
                  if ("HASH".equalsIgnoreCase(distributeByType.getSimpleName())) {
                      print0(ucase ? "HASH(" : "hash(");
                      printAndAccept(x.getDistributedBy(), ",");
                      print0(")");
  
                  } else if ("DUPLICATE".equalsIgnoreCase(distributeByType.getSimpleName())) {
                      print0(ucase ? "DUPLICATE(" : "duplicate(");
                      printAndAccept(x.getDistributedBy(), ",");
                      print0(")");
  
                  } else if ("BROADCAST".equalsIgnoreCase(distributeByType.getSimpleName())) {
                      print0(ucase ? "BROADCAST " : "broadcast ");
                  }
              }
  
              for (SQLAssignItem option : x.getTableOptions()) {
                  String key = ((SQLIdentifierExpr) option.getTarget()).getName();
  
                  print(' ');
                  print0(ucase ? key : key.toLowerCase());
  
                  if ("TABLESPACE".equals(key)) {
                      print(' ');
                      option.getValue().accept(this);
                      continue;
                  }
  
                  print0(" = ");
  
                  option.getValue().accept(this);
              }
  
              printComment(x.getComment());
          }
  
          SQLPartitionBy partitionBy = x.getPartitionBy();
          if (partitionBy != null) {
              println();
              print0(ucase ? "PARTITION BY " : "partition by ");
              partitionBy.accept(this);
          }
  
          this.printOracleSegmentAttributes(x);
          println();
  
          Boolean cache = x.getCache();
          if (cache != null) {
              print(cache ? "CACHE" : "NOCACHE");
              println();
          }
  
          Boolean parallel = x.getParallel();
          if (parallel != null) {
              if (parallel) {
                  print(ucase ? "PARALLEL" : "parallel");
                  Integer parallelValue = x.getParallelValue();
                  if (parallelValue != null) {
                      print(' ');
                      print(parallelValue);
                  }
              } else {
                  print(ucase ? "NOPARALLEL" : "noparallel");
              }
              println();
          }
  
          if (x.isBuildImmediate()) {
              println(ucase ? "BUILD IMMEDIATE" : "build immediate");
          }
  
          if (x.isRefresh()) {
              print(ucase ? "REFRESH" : "refresh");
  
              if (x.isRefreshFast()) {
                  print(ucase ? " FAST" : " fast");
              } else if (x.isRefreshComplete()) {
                  print(ucase ? " COMPLETE" : " complete");
              } else if (x.isRefreshForce()) {
                  print(ucase ? " FORCE" : " force");
              }
  
              if (x.isRefreshOnCommit()) {
                  print(ucase ? " ON COMMIT" : " on commit");
              } else if (x.isRefreshOnDemand()) {
                  print(ucase ? " ON DEMAND" : " on demand");
              } else if (x.isRefreshOnOverWrite()) {
                  print(ucase ? " ON OVERWRITE" : " on overwrite");
              }
  
              if (x.getStartWith() != null) {
                  println();
                  print(ucase ? "START WITH " : "start with ");
                  x.getStartWith().accept(this);
              }
  
              if (x.getNext() != null) {
                  print(ucase ? " NEXT " : " next ");
                  x.getNext().accept(this);
              }
  
              println();
          }
  
          Boolean enableQueryRewrite = x.getEnableQueryRewrite();
          if (enableQueryRewrite != null) {
              if (enableQueryRewrite) {
                  print(ucase ? "ENABLE QUERY REWRITE" : "enable query rewrite");
              } else {
                  print(ucase ? "DISABLE QUERY REWRITE" : "disable query rewrite");
              }
              println();
          }
  
          println(ucase ? "AS" : "as");
          x.getQuery().accept(this);
          return false;
      }
  
      public boolean visit(SQLCreateUserStatement x) {
          print0(ucase ? "CREATE USER " : "create user ");
          x.getUser().accept(this);
          print0(ucase ? " IDENTIFIED BY " : " identified by ");
          x.getPassword().accept(this);
          return false;
      }
  
      public boolean visit(SQLAlterFunctionStatement x) {
          print0(ucase ? "ALTER FUNCTION " : "alter function ");
          x.getName().accept(this);
  
          if (x.isDebug()) {
              print0(ucase ? " DEBUG" : " debug");
          }
  
          if (x.isReuseSettings()) {
              print0(ucase ? " REUSE SETTINGS" : " reuse settings");
          }
  
          return false;
      }
  
      public boolean visit(SQLAlterTypeStatement x) {
          print0(ucase ? "ALTER TYPE " : "alter type ");
          x.getName().accept(this);
  
          if (x.isCompile()) {
              print0(ucase ? " COMPILE" : " compile");
          }
  
          if (x.isBody()) {
              print0(ucase ? " BODY" : " body");
          }
  
          if (x.isDebug()) {
              print0(ucase ? " DEBUG" : " debug");
          }
  
          if (x.isReuseSettings()) {
              print0(ucase ? " REUSE SETTINGS" : " reuse settings");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLIntervalExpr x) {
          print0(ucase ? "INTERVAL " : "interval ");
          SQLExpr value = x.getValue();
  
          boolean quote = (dbType == DbType.presto || dbType == DbType.trino || dbType == DbType.supersql)
                  && !(value instanceof SQLTextLiteralExpr);
          if (quote) {
              print('\'');
          }
          value.accept(this);
          if (quote) {
              print('\'');
          }
  
          SQLIntervalUnit unit = x.getUnit();
          if (unit != null) {
              print(' ');
              print0(ucase ? unit.name : unit.nameLCase);
          }
          return false;
      }
  
      public Boolean getPrintStatementAfterSemi() {
          return printStatementAfterSemi;
      }
  
      public void setPrintStatementAfterSemi(Boolean printStatementAfterSemi) {
          this.printStatementAfterSemi = printStatementAfterSemi;
      }
  
      public void config(VisitorFeature... features) {
          for (VisitorFeature feature : features) {
              config(feature, true);
          }
      }
  
      public void config(VisitorFeature feature, boolean state) {
          super.config(feature, state);
          if (feature == VisitorFeature.OutputUCase) {
              this.ucase = state;
          } else if (feature == VisitorFeature.OutputParameterized) {
              this.parameterized = state;
          } else if (feature == VisitorFeature.OutputParameterizedQuesUnMergeInList) {
              this.parameterizedQuesUnMergeInList = state;
          } else if (feature == VisitorFeature.OutputParameterizedQuesUnMergeValuesList) {
              this.parameterizedQuesUnMergeValuesList = state;
          } else if (feature == VisitorFeature.OutputParameterizedUnMergeShardingTable) {
              this.shardingSupport = !state;
              this.parameterizedQuesUnMergeValuesList = state;
          } else if (feature == VisitorFeature.OutputNameQuote) {
              this.printNameQuote = state;
          }
      }
  
      public void setFeatures(int features) {
          super.setFeatures(features);
          this.ucase = isEnabled(VisitorFeature.OutputUCase);
          this.parameterized = isEnabled(VisitorFeature.OutputParameterized);
          this.parameterizedQuesUnMergeInList = isEnabled(VisitorFeature.OutputParameterizedQuesUnMergeInList);
          this.parameterizedQuesUnMergeValuesList = isEnabled(VisitorFeature.OutputParameterizedQuesUnMergeValuesList);
          this.shardingSupport = !isEnabled(VisitorFeature.OutputParameterizedUnMergeShardingTable);
          this.printNameQuote = isEnabled(VisitorFeature.OutputNameQuote);
      }
  
      /////////////// for oracle
      public boolean visit(OracleCursorExpr x) {
          print0(ucase ? "CURSOR(" : "cursor(");
          this.indentCount++;
          println();
          x.getQuery().accept(this);
          this.indentCount--;
          println();
          print(')');
          return false;
      }
  
      public boolean visit(OracleDatetimeExpr x) {
          x.getExpr().accept(this);
          SQLExpr timeZone = x.getTimeZone();
  
          if (timeZone instanceof SQLIdentifierExpr) {
              if (((SQLIdentifierExpr) timeZone).getName().equalsIgnoreCase("LOCAL")) {
                  print0(ucase ? " AT LOCAL" : " at local");
                  return false;
              }
          }
  
          print0(ucase ? " AT TIME ZONE " : " at time zone ");
          timeZone.accept(this);
  
          return false;
      }
  
      public boolean visit(SQLAtTimeZoneExpr x) {
          x.getExpr().accept(this);
          SQLExpr timeZone = x.getTimeZone();
  
          if (timeZone instanceof SQLIdentifierExpr) {
              if (((SQLIdentifierExpr) timeZone).getName().equalsIgnoreCase("LOCAL")) {
                  print0(ucase ? " AT LOCAL" : " at local");
                  return false;
              }
          }
  
          print0(ucase ? " AT TIME ZONE " : " at time zone ");
          timeZone.accept(this);
  
          return false;
      }
  
      ///////////// for odps & hive
      @Override
      public boolean visit(SQLLateralViewTableSource x) {
          SQLTableSource tableSource = x.getTableSource();
          if (tableSource != null) {
              tableSource.accept(this);
          }
  
          this.indentCount++;
          println();
          print0(ucase ? "LATERAL VIEW " : "lateral view ");
  
          if (x.isOuter()) {
              print0(ucase ? "OUTER " : "outer ");
          }
  
          x.getMethod().accept(this);
          if (x.getAlias() != null) {
              print(' ');
              print0(x.getAlias());
          }
  
          if (x.getColumns() != null && x.getColumns().size() > 0) {
              print0(ucase ? " AS " : " as ");
              printAndAccept(x.getColumns(), ", ");
          }
  
          SQLExpr on = x.getOn();
          if (on != null) {
              println();
              print0(ucase ? "ON " : "on ");
              printExpr(on);
          }
  
          this.indentCount--;
          return false;
      }
  
      @Override
      public boolean visit(SQLShowErrorsStatement x) {
          print0(ucase ? "SHOW ERRORS" : "show errors");
          return false;
      }
  
      @Override
      public boolean visit(SQLShowRecylebinStatement x) {
          print0(ucase ? "SHOW RECYCLEBIN" : "show recyclebin");
          return false;
      }
  
      @Override
      public boolean visit(SQLShowCatalogsStatement x) {
          print0(ucase ? "SHOW CATALOGS" : "show catalogs");
          final SQLExpr like = x.getLike();
          if (like != null) {
              print0(ucase ? " LIKE " : " like ");
              printExpr(like);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowFunctionsStatement x) {
          if (x.getKind() != null) {
              print0(ucase ? "SHOW " : "show ");
              printExpr(x.getKind());
              print0(ucase ? " FUNCTIONS" : " functions");
          } else {
              print0(ucase ? "SHOW FUNCTIONS" : "show functions");
          }
  
          final SQLExpr like = x.getLike();
          if (like != null) {
              print0(ucase ? " LIKE " : " like ");
              printExpr(like);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowSessionStatement x) {
          print0(ucase ? "SHOW SESSION" : "show session");
          final SQLExpr like = x.getLike();
          if (like != null) {
              print0(ucase ? " LIKE " : " like ");
              printExpr(like);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterCharacter x) {
          if (x.getCharacterSet() != null) {
              print0(ucase ? "CHARACTER SET = " : "character set = ");
              x.getCharacterSet().accept(this);
          }
  
          if (x.getCollate() != null) {
              if (x.getCharacterSet() != null) {
                  print0(", ");
              }
              print0(ucase ? "COLLATE = " : "collate = ");
              x.getCollate().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLRecordDataType x) {
          print0(ucase ? "RECORD (" : "record (");
          indentCount++;
          println();
          List<SQLColumnDefinition> columns = x.getColumns();
          for (int i = 0; i < columns.size(); i++) {
              if (i != 0) {
                  println();
              }
              columns.get(i).accept(this);
              if (i != columns.size() - 1) {
                  print0(", ");
              }
          }
          indentCount--;
          println();
          print(')');
  
          return false;
      }
  
      @Override
      public boolean visit(SQLExprStatement x) {
          x.getExpr().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLBlockStatement x) {
          if (!x.getParameters().isEmpty()) {
              this.indentCount++;
              if (x.getParent() instanceof SQLCreateProcedureStatement) {
                  SQLCreateProcedureStatement procedureStatement = (SQLCreateProcedureStatement) x.getParent();
                  if (procedureStatement.isCreate()) {
                      printIndent();
                  }
              }
              if (!(x.getParent() instanceof SQLCreateProcedureStatement
                      || x.getParent() instanceof SQLCreateFunctionStatement
                      || x.getParent() instanceof OracleFunctionDataType
                      || x.getParent() instanceof OracleProcedureDataType)
              ) {
                  print0(ucase ? "DECLARE" : "declare");
                  println();
              }
  
              for (int i = 0, size = x.getParameters().size(); i < size; ++i) {
                  if (i != 0) {
                      println();
                  }
                  SQLParameter param = x.getParameters().get(i);
                  param.accept(this);
                  print(';');
              }
  
              this.indentCount--;
              println();
          }
          if (x.isHaveBeginEnd()) {
              print0(ucase ? "BEGIN" : "begin");
          }
          this.indentCount++;
  
          for (int i = 0, size = x.getStatementList().size(); i < size; ++i) {
              println();
              SQLStatement stmt = x.getStatementList().get(i);
              stmt.accept(this);
          }
          this.indentCount--;
  
          SQLStatement exception = x.getException();
          if (exception != null) {
              println();
              exception.accept(this);
          }
  
          println();
          if (x.isHaveBeginEnd()) {
              print0(ucase ? "END;" : "end;");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateProcedureStatement x) {
          boolean create = x.isCreate();
          if (!create) {
              print0(ucase ? "PROCEDURE " : "procedure ");
          } else if (x.isOrReplace()) {
              print0(ucase ? "CREATE OR REPLACE PROCEDURE " : "create or replace procedure ");
          } else {
              print0(ucase ? "CREATE PROCEDURE " : "create procedure ");
          }
          x.getName().accept(this);
  
          int paramSize = x.getParameters().size();
  
          if (paramSize > 0) {
              print0(" (");
              this.indentCount++;
              println();
  
              for (int i = 0; i < paramSize; ++i) {
                  if (i != 0) {
                      print0(", ");
                      println();
                  }
                  SQLParameter param = x.getParameters().get(i);
                  param.accept(this);
              }
  
              this.indentCount--;
              println();
              print(')');
          }
  
          SQLName authid = x.getAuthid();
          if (authid != null) {
              print(ucase ? " AUTHID " : " authid ");
              authid.accept(this);
          }
  
          SQLStatement block = x.getBlock();
          String wrappedSource = x.getWrappedSource();
          if (wrappedSource != null) {
              print0(ucase ? " WRAPPED " : " wrapped ");
              print0(wrappedSource);
          } else {
              if (block != null && !create) {
                  println();
                  print("IS");
                  println();
              } else {
                  println();
                  if (block instanceof SQLBlockStatement) {
                      SQLBlockStatement blockStatement = (SQLBlockStatement) block;
                      if (blockStatement.getParameters().size() > 0 || authid != null) {
                          println(ucase ? "AS" : "as");
                      } else {
                          println(ucase ? "IS" : "is");
                      }
                  }
              }
  
              String javaCallSpec = x.getJavaCallSpec();
              if (javaCallSpec != null) {
                  print0(ucase ? "LANGUAGE JAVA NAME '" : "language java name '");
                  print0(javaCallSpec);
                  print('\'');
                  return false;
              }
          }
  
          boolean afterSemi = false;
          if (block != null) {
              block.accept(this);
  
              if (block instanceof SQLBlockStatement
                      && ((SQLBlockStatement) block).getStatementList().size() > 0) {
                  afterSemi = ((SQLBlockStatement) block).getStatementList().get(0).isAfterSemi();
              }
          }
  
          if ((!afterSemi) && x.getParent() instanceof OracleCreatePackageStatement) {
              print(';');
          }
          return false;
      }
  
      protected boolean hiveVisit(SQLExternalRecordFormat x) {
          indentCount++;
          if (x.getDelimitedBy() != null) {
              println();
              print0(ucase ? "LINES TERMINATED BY " : "lines terminated by ");
              x.getDelimitedBy().accept(this);
          }
  
          SQLExpr terminatedBy = x.getTerminatedBy();
          if (terminatedBy != null) {
              println();
              print0(ucase ? "FIELDS TERMINATED BY " : "fields terminated by ");
              terminatedBy.accept(this);
          }
  
          SQLExpr escapedBy = x.getEscapedBy();
          if (escapedBy != null) {
              println();
              print0(ucase ? "ESCAPED BY " : "escaped by ");
              escapedBy.accept(this);
          }
  
          SQLExpr collectionItemsTerminatedBy = x.getCollectionItemsTerminatedBy();
          if (collectionItemsTerminatedBy != null) {
              println();
              print0(ucase ? "COLLECTION ITEMS TERMINATED BY " : "collection items terminated by ");
              collectionItemsTerminatedBy.accept(this);
          }
  
          SQLExpr mapKeysTerminatedBy = x.getMapKeysTerminatedBy();
          if (mapKeysTerminatedBy != null) {
              println();
              print0(ucase ? "MAP KEYS TERMINATED BY " : "map keys terminated by ");
              mapKeysTerminatedBy.accept(this);
          }
  
          SQLExpr linesTerminatedBy = x.getLinesTerminatedBy();
          if (linesTerminatedBy != null) {
              println();
              print0(ucase ? "LINES TERMINATED BY " : "lines terminated by ");
              linesTerminatedBy.accept(this);
          }
  
          SQLExpr nullDefinedAs = x.getNullDefinedAs();
          if (nullDefinedAs != null) {
              println();
              print0(ucase ? "NULL DEFINED AS " : "null defined as ");
              nullDefinedAs.accept(this);
          }
  
          SQLExpr serde = x.getSerde();
          if (serde != null) {
              println();
              print0(ucase ? "SERDE " : "serde ");
              serde.accept(this);
          }
  
          indentCount--;
  
          return false;
      }
  
      public boolean visit(SQLExternalRecordFormat x) {
          SQLExpr delimitedBy = x.getDelimitedBy();
          if (delimitedBy != null) {
              println();
              print0(ucase ? "RECORDS DELIMITED BY " : "records delimited by ");
              delimitedBy.accept(this);
          }
  
          Boolean logfile = x.getLogfile();
          if (logfile != null) {
              if (logfile) {
                  print0(ucase ? " LOGFILE" : " logfile");
              } else {
                  print0(ucase ? " NOLOGFILE" : " nologfile");
              }
          }
  
          Boolean badfile = x.getBadfile();
          if (badfile != null) {
              if (badfile) {
                  print0(ucase ? " BADFILE" : " badfile");
              } else {
                  print0(ucase ? " NOBADFILE" : " nobadfile");
              }
          }
  
          SQLExpr terminatedBy = x.getTerminatedBy();
          if (terminatedBy != null) {
              println();
              print0(ucase ? "FIELDS TERMINATED BY " : "fields terminated by ");
              terminatedBy.accept(this);
          }
  
          if (x.isLtrim()) {
              print0(ucase ? " LTRIM" : " ltrim");
          }
  
          if (x.isMissingFieldValuesAreNull()) {
              print0(ucase ? " MISSING FIELD VALUES ARE NULL" : " missing field values are null");
          }
  
          if (x.isRejectRowsWithAllNullFields()) {
              print0(ucase ? " REJECT ROWS WITH ALL NULL FIELDS" : " reject rows with all null fields");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLArrayDataType x) {
          final List<SQLExpr> arguments = x.getArguments();
          if (Boolean.TRUE.equals(x.getAttribute("ads.arrayDataType"))) {
              x.getComponentType().accept(this);
              print('[');
              printAndAccept(arguments, ", ");
              print(']');
          } else {
              SQLDataType componentType = x.getComponentType();
              if (x.isUsedForCast() && componentType != null && componentType.getArguments() != null && componentType.getArguments().size() > 0) {
                  componentType.accept(this);
                  print0(ucase ? " ARRAY" : " array");
              } else if (componentType != null) {
                  print0(ucase ? "ARRAY<" : "array<");
                  componentType.accept(this);
                  print('>');
              } else {
                  print0(ucase ? "ARRAY" : "array");
              }
  
              if (arguments.size() > 0) {
                  print('(');
                  printAndAccept(arguments, ", ");
                  print(')');
              }
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLMapDataType x) {
          print0(ucase ? "MAP<" : "map<");
  
          SQLDataType keyType = x.getKeyType();
          SQLDataType valueType = x.getValueType();
  
          keyType.accept(this);
          print0(", ");
  
          valueType.accept(this);
          print('>');
          return false;
      }
  
      @Override
      public boolean visit(SQLStructDataType x) {
          print0(ucase ? "STRUCT<" : "struct<");
          List<SQLStructDataType.Field> fields = x.getFields();
          boolean needPrintLine = false;
          if (fields.size() > 5) {
              needPrintLine = true;
          } else {
              for (SQLStructDataType.Field field : fields) {
                  SQLDataType fieldDataType = field.getDataType();
                  if (fieldDataType instanceof SQLArrayDataType || fieldDataType instanceof SQLStructDataType) {
                      needPrintLine = true;
                      break;
                  }
              }
          }
          printlnAndAccept(x.getFields(), needPrintLine);
          print('>');
          return false;
      }
  
      @Override
      public boolean visit(SQLRowDataType x) {
          print0(ucase ? "ROW(" : "row(");
          printAndAccept(x.getFields(), ",");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLUnionDataType x) {
          print0(ucase ? "UNIONTYPE<" : "uniontype<");
          printAndAccept(x.getItems(), ", ");
          print('>');
          return false;
      }
  
      @Override
      public boolean visit(SQLStructDataType.Field x) {
          SQLName name = x.getName();
          if (name != null) {
              name.accept(this);
          }
          SQLDataType dataType = x.getDataType();
  
          if (dataType != null) {
              if (x.getParent() instanceof SQLRowDataType) {
                  if (name != null) {
                      print(' ');
                  }
              } else {
                  print(':');
              }
  
              dataType.accept(this);
          }
  
          return false;
      }
  
      public boolean visit(SQLAlterTableMergePartition x) {
          print0(ucase ? "MERGE " : "merge ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          println();
          printlnAndAccept(x.getPartitions(), ", ");
          println();
          print0(ucase ? "OVERWRITE" : "overwrite");
          println();
          x.getOverwritePartition().accept(this);
          return false;
      }
  
      public boolean visit(SQLPartitionSpec x) {
          print0(ucase ? "PARTITION (" : "partition (");
          printAndAccept(x.getItems(), ", ");
          print(')');
          return false;
      }
  
      public boolean visit(SQLPartitionSpec.Item x) {
          x.getColumn().accept(this);
          print0(" = ");
          x.getValue().accept(this);
          return false;
      }
  
      public boolean visit(SQLAlterTableSubpartitionAvailablePartitionNum x) {
          print0(ucase ? "SUBPARTITION_AVAILABLE_PARTITION_NUM = " : "subpartition_available_partition_num = ");
          x.getNumber().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLShowDatabasesStatement x) {
          print0(ucase ? "SHOW " : "show ");
          if (x.isFull()) {
              print0(ucase ? "FULL " : "full ");
          }
          if (x.isPhysical()) {
              print0(ucase ? "PHYSICAL " : "physical ");
          }
          print0(ucase ? "DATABASES" : "databases");
  
          if (x.getLike() != null) {
              print0(ucase ? " LIKE " : " like ");
              x.getLike().accept(this);
          }
  
          if (x.getWhere() != null) {
              print0(ucase ? " WHERE " : " where ");
              x.getWhere().accept(this);
          }
  
          if (x.isExtra()) {
              print0(ucase ? " EXTRA" : " extra");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLShowTableGroupsStatement x) {
          print0(ucase ? "SHOW TABLEGROUPS" : "show tablegroups");
  
          if (x.getDatabase() != null) {
              print0(ucase ? " IN " : " in ");
              x.getDatabase().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLShowColumnsStatement x) {
          final List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          if (x.isFull()) {
              print0(ucase ? "SHOW FULL COLUMNS" : "show full columns");
          } else {
              print0(ucase ? "SHOW COLUMNS" : "show columns");
          }
  
          if (x.getTable() != null) {
              print0(ucase ? " FROM " : " from ");
              if (x.getDatabase() != null) {
                  x.getDatabase().accept(this);
                  print('.');
              }
              x.getTable().accept(this);
          }
  
          if (x.getLike() != null) {
              print0(ucase ? " LIKE " : " like ");
              x.getLike().accept(this);
          }
  
          if (x.getWhere() != null) {
              print0(ucase ? " WHERE " : " where ");
              x.getWhere().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLShowCreateTableStatement x) {
          final List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          if (x.isAll()) {
              print0(ucase ? "SHOW ALL CREATE TABLE " : "show all create table ");
          } else {
              print0(ucase ? "SHOW CREATE TABLE " : "show create table ");
          }
          x.getName().accept(this);
  
          if (x.getLikeMapping() != null) {
              print0(ucase ? " LIKE MAPPING (" : " like mapping ( ");
              x.getLikeMapping().accept(this);
              print0(")");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowProcessListStatement x) {
          print0(ucase ? "SHOW " : "show ");
  
          if (x.isFull()) {
              print0(ucase ? "FULL " : "full ");
          }
  
          print0(ucase ? "PROCESSLIST" : "processlist");
  
          if (x.isMpp()) {
              print0(ucase ? " MPP" : " mpp");
          }
  
          SQLExpr where = x.getWhere();
          if (where != null) {
              print0(ucase ? " WHERE " : " where ");
              where.accept(this);
          }
  
          SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              print(' ');
              orderBy.accept(this);
          }
  
          SQLLimit limit = x.getLimit();
          if (limit != null) {
              print(' ');
              limit.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetOption x) {
          print0(ucase ? "SET TBLPROPERTIES (" : "set tblproperties (");
          printAndAccept(x.getOptions(), ", ");
          print(')');
  
          final SQLExpr on = x.getOn();
          if (on != null) {
              print0(ucase ? " ON " : " on ");
              on.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableSetSerdeProperties x) {
          print0(ucase ? "SET SERDEPROPERTIES (" : "set serdeproperties (");
          printAndAccept(x.getSerdeProperties(), ", ");
          print(')');
  
          final SQLExpr on = x.getOn();
          if (on != null) {
              print0(ucase ? " ON " : " on ");
              on.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLShowCreateViewStatement x) {
          print0(ucase ? "SHOW CREATE VIEW " : "show create view ");
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLShowViewsStatement x) {
          print0(ucase ? "SHOW VIEWS" : "show views");
          if (x.getDatabase() != null) {
              print0(ucase ? " FROM " : " from ");
              x.getDatabase().accept(this);
          }
  
          if (x.getLike() != null) {
              print0(ucase ? " LIKE " : " like ");
              x.getLike().accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLAlterTableRenameIndex x) {
          print0(ucase ? "RENAME INDEX " : "rename index ");
          x.getName().accept(this);
          print0(ucase ? " TO " : " to ");
          x.getTo().accept(this);
          return false;
      }
  
      public boolean visit(SQLCreateRoleStatement x) {
          print0(ucase ? "CREATE ROLE " : "create role ");
          x.getName().accept(this);
          return false;
      }
  
      public boolean visit(SQLDropRoleStatement x) {
          print0(ucase ? "DROP ROLE " : "drop role ");
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public void endVisit(SQLMatchAgainstExpr x) {
      }
  
      @Override
      public boolean visit(SQLMatchAgainstExpr x) {
          print0(ucase ? "MATCH (" : "match (");
          printAndAccept(x.getColumns(), ", ");
          print(')');
  
          print0(ucase ? " AGAINST (" : " against (");
          x.getAgainst().accept(this);
          if (x.getSearchModifier() != null) {
              print(' ');
              print0(ucase ? x.getSearchModifier().name : x.getSearchModifier().nameLCase);
          }
          print(')');
  
          return false;
      }
  
      public boolean visit(MySqlPrimaryKey x) {
          visit(x.getIndexDefinition());
          /*
          if (x.getName() != null) {
              print0(ucase ? "CONSTRAINT " : "constraint ");
              x.getName().accept(this);
              print(' ');
          }
  
          print0(ucase ? "PRIMARY KEY" : "primary key");
  
          if (x.getIndexType() != null) {
              print0(ucase ? " USING " : " using ");
              print0(x.getIndexType());
          }
  
          print0(" (");
  
          for (int i = 0, size = x.getColumns().size(); i < size; ++i) {
              if (i != 0) {
                  print0(", ");
              }
              x.getColumns().get(i).accept(this);
          }
          print(')');
  
          SQLExpr keyBlockSize = x.getKeyBlockSize();
          if (keyBlockSize != null) {
              print0(ucase ? " KEY_BLOCK_SIZE = " : " key_block_size = ");
              keyBlockSize.accept(this);
          }
  
          SQLExpr comment = x.getComment();
          if (comment != null) {
              print0(ucase ? " COMMENT " : " comment ");
              comment.accept(this);
          }
          */
  
          return false;
      }
  
      public boolean visit(MySqlCreateTableStatement x) {
          final List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          if (isPrettyFormat() && x.hasBeforeComment()) {
              printlnComments(x.getBeforeCommentsDirect());
          }
  
          print0(ucase ? "CREATE " : "create ");
  
          for (SQLCommentHint hint : x.getHints()) {
              hint.accept(this);
              print(' ');
          }
  
          printCreateTableFeatures(x);
  
          print0(ucase ? "TABLE " : "table ");
  
          if (x.isIfNotExists()) {
              print0(ucase ? "IF NOT EXISTS " : "if not exists ");
          }
  
          printTableSourceExpr(x.getName());
  
          if (x.getLike() != null) {
              print0(ucase ? " LIKE " : " like ");
              x.getLike().accept(this);
          }
  
          printTableElements(x.getTableElementList());
  
          if (x.isBroadCast()) {
              print0(ucase ? " BROADCAST" : " broadcast");
          }
  
          List<SQLAssignItem> tableOptions = x.getTableOptions();
          if (Boolean.TRUE.equals(x.getAttribute("ads.options"))) {
              if (tableOptions.size() > 0) {
                  println();
                  print0(ucase ? "OPTIONS (" : "options (");
                  printAndAccept(tableOptions, ", ");
                  print(')');
              }
          } else {
              for (SQLAssignItem option : tableOptions) {
                  String key = ((SQLIdentifierExpr) option.getTarget()).getName();
  
                  print(' ');
                  print0(ucase ? key : key.toLowerCase());
  
                  if ("TABLESPACE".equals(key)) {
                      print(' ');
                      option.getValue().accept(this);
                      continue;
                  }
  
                  print0(" = ");
  
                  option.getValue().accept(this);
              }
          }
  
          SQLExpr comment = x.getComment();
          if (comment != null) {
              print0(ucase ? " COMMENT " : " comment ");
              comment.accept(this);
          }
  
          if (x.getDistributeByType() != null) {
              println();
              if (isEnabled(VisitorFeature.OutputDistributedLiteralInCreateTableStmt)) {
                  print0(ucase ? "DISTRIBUTED BY " : "distributed by ");
              } else {
                  print0(ucase ? "DISTRIBUTE BY " : "distribute by ");
              }
  
              SQLName distributeByType = x.getDistributeByType();
  
              if ("HASH".equalsIgnoreCase(distributeByType.getSimpleName())) {
                  print0(ucase ? "HASH(" : "hash(");
                  printAndAccept(x.getDistributeBy(), ",");
                  print0(")");
  
              } else if ("DUPLICATE".equalsIgnoreCase(distributeByType.getSimpleName())) {
                  print0(ucase ? "DUPLICATE(" : "duplicate(");
                  printAndAccept(x.getDistributeBy(), ",");
                  print0(")");
  
              } else if ("BROADCAST".equalsIgnoreCase(distributeByType.getSimpleName())) {
                  print0(ucase ? "BROADCAST " : "broadcast ");
              }
          }
  
          printPartitionBy(x);
  
          List<SQLSelectOrderByItem> clusteredBy = x.getClusteredBy();
          if (clusteredBy.size() > 0) {
              println();
              print0(ucase ? "CLUSTERED BY (" : "clustered by (");
              printAndAccept(clusteredBy, ",");
              print0(")");
          }
  
          SQLExpr dbPartitionBy = x.getDbPartitionBy();
          if (dbPartitionBy != null) {
              println();
              print0(ucase ? "DBPARTITION BY " : "dbpartition by ");
              dbPartitionBy.accept(this);
          }
  
          SQLExpr dbpartitions = x.getDbpartitions();
          if (dbpartitions != null) {
              print0(ucase ? " DBPARTITIONS " : " dbpartitions ");
              dbpartitions.accept(this);
          }
  
          SQLExpr tbPartitionsBy = x.getTablePartitionBy();
          if (tbPartitionsBy != null) {
              println();
              print0(ucase ? "TBPARTITION BY " : "tbpartition by ");
              tbPartitionsBy.accept(this);
          }
  
          SQLExpr tablePartitions = x.getTablePartitions();
          if (tablePartitions != null) {
              print0(ucase ? " TBPARTITIONS " : " tbpartitions ");
              tablePartitions.accept(this);
          }
  
          MySqlExtPartition extPartition = x.getExtPartition();
          if (extPartition != null) {
              println();
              extPartition.accept(this);
          }
  
          if (x.getArchiveBy() != null) {
              println();
              print0(ucase ? "ARCHIVE BY = " : "archive by = ");
              x.getArchiveBy().accept(this);
          }
  
          if (x.getTableGroup() != null) {
              println();
              print0(ucase ? "TABLEGROUP " : "tablegroup ");
              x.getTableGroup().accept(this);
          }
  
          if (x.isReplace()) {
              println();
              print0(ucase ? "REPLACE " : "replace ");
          } else if (x.isIgnore()) {
              println();
              print0(ucase ? "IGNORE " : "ignore ");
          }
  
          if (x.getSelect() != null) {
              println();
              print0(ucase ? "AS" : "as");
              println();
              x.getSelect().accept(this);
          }
          if (x.getWithSelect() != null) {
              println();
              print0(ucase ? "AS" : "as");
              println();
              x.getWithSelect().accept(this);
          }
  
          if (x.getStoredBy() != null) {
              println();
              print0(ucase ? " STORED BY " : " stored by ");
              x.getStoredBy().accept(this);
          }
  
          if (x.getWith().size() > 0) {
              println();
              print0(ucase ? " WITH (" : " with (");
              int i = 0;
              for (Map.Entry<String, SQLName> option : x.getWith().entrySet()) {
                  if (i != 0) {
                      print0(", ");
                  }
                  print0(option.getKey());
                  print0(" = ");
                  option.getValue().accept(this);
                  i++;
              }
              print(')');
          }
  
          if (x.getWithData() != null) {
              println();
              if (x.getWithData()) {
                  print0(ucase ? "WITH DATA" : "with data");
              } else {
                  print0(ucase ? "WITH NO DATA" : "with no data");
              }
          }
  
          for (SQLCommentHint hint : x.getOptionHints()) {
              print(' ');
              hint.accept(this);
          }
          return false;
      }
  
      protected void printCreateTableFeatures(SQLCreateTableStatement x) {
          if (x.isEnabled(SQLCreateTableStatement.Feature.OrReplace)) {
              print0(ucase ? "OR REPLACE " : "or replace ");
          }
          SQLCreateTableStatement.Feature[] features = {
                  SQLCreateTableStatement.Feature.Global,
                  SQLCreateTableStatement.Feature.Local,
                  SQLCreateTableStatement.Feature.Temporary,
                  SQLCreateTableStatement.Feature.Shadow,
                  SQLCreateTableStatement.Feature.External,
                  SQLCreateTableStatement.Feature.Transactional,
                  SQLCreateTableStatement.Feature.Dimension,
                  SQLCreateTableStatement.Feature.Unlogged
          };
  
          for (SQLCreateTableStatement.Feature feature : features) {
              if (x.isEnabled(feature)) {
                  String name = feature.name();
                  print0(ucase ? name.toUpperCase() : name.toLowerCase());
                  print(' ');
              }
          }
      }
  
      @Override
      public boolean visit(SQLShowPartitionsStmt x) {
          print0(ucase ? "SHOW PARTITIONS " : "show partitions ");
          x.getTableSource().accept(this);
  
          if (x.getPartition().size() > 0) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartition(), ", ");
              print0(")");
          }
  
          SQLExpr where = x.getWhere();
          if (where != null) {
              print0(ucase ? " WHERE " : " where ");
              where.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLValuesExpr x) {
          print0(ucase ? "VALUES" : "values");
          printAndAccept(x.getValues(), ", ");
          return false;
      }
  
      @Override
      public boolean visit(SQLDumpStatement x) {
          List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          print0(ucase ? "DUMP DATA " : "dump data ");
  
          if (x.isOverwrite()) {
              print0(ucase ? "OVERWRITE " : "overwrite ");
          }
  
          SQLExprTableSource into = x.getInto();
          if (into != null) {
              print0(ucase ? "INTO " : "into ");
              into.accept(this);
              print0(" ");
          }
  
          x.getSelect().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLValuesTableSource x) {
          List<SQLName> columns = x.getColumns();
  
          boolean quote = columns.size() > 0;
          if (quote) {
              if (x.getParent() instanceof SQLSelect && x.getParent().getParent() instanceof SQLCreateViewStatement) {
                  quote = false;
              }
          }
          if (quote) {
              print('(');
          }
          print0(ucase ? "VALUES " : "values ");
          printAndAccept(x.getValues(), ", ");
  
          if (quote) {
              print(')');
          }
  
          String alias = x.getAlias();
          if (alias != null) {
              print0(ucase ? " AS " : " as ");
              print0(alias);
          }
  
          if (columns.size() > 0) {
              if (alias == null) {
                  print0(ucase ? " AS" : " as");
              }
              print0(" (");
              printAndAccept(columns, ", ");
              print(')');
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLRefreshTableStatement x) {
          print0(ucase ? "REFRESH TABLE " : "refresh table ");
          x.getName().accept(this);
          return false;
      }
      @Override
      public boolean visit(SQLExtractExpr x) {
          print0(ucase ? "EXTRACT(" : "extract(");
          print0(x.getUnit().name());
          print0(ucase ? " FROM " : " from ");
          x.getValue().accept(this);
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLWindow x) {
          x.getName().accept(this);
          print0(ucase ? " AS " : " as ");
          x.getOver().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLJSONExpr x) {
          return visit((SQLTypeExpr) x);
      }
  
      @Override
      public boolean visit(SQLAnnIndex x) {
          print0(ucase ? "ANNINDX (type = '" : "annindx (type = '");
  
          final int indexType = x.getIndexType();
          if ((indexType & SQLAnnIndex.IndexType.Flat.mask) != 0) {
              print0(ucase ? "FLAT" : "flat");
          }
  
          if ((indexType & SQLAnnIndex.IndexType.FastIndex.mask) != 0) {
              if (indexType != SQLAnnIndex.IndexType.FastIndex.mask) {
                  print(',');
              }
              print0(ucase ? "FLAT_INDEX" : "flat_index");
          }
  
          print0(ucase ? "', DISTANCE = '" : "', distance = '");
          print0(x.getDistance().name());
          print0("'");
  
          final int rtIndexType = x.getRtIndexType();
          if (rtIndexType != 0) {
              print0(ucase ? ", RTTYPE = '" : ", rttype = '");
  
              if ((rtIndexType & SQLAnnIndex.IndexType.Flat.mask) != 0) {
                  print0(ucase ? "FLAT" : "flat");
              }
  
              if ((rtIndexType & SQLAnnIndex.IndexType.FastIndex.mask) != 0) {
                  if (rtIndexType != SQLAnnIndex.IndexType.FastIndex.mask) {
                      print(',');
                  }
                  print0(ucase ? "FLAT_INDEX " : "flat_index ");
              }
  
              print('\'');
          }
  
          print(')');
  
          return false;
      }
  
      public boolean visit(SQLAlterTableRecoverPartitions x) {
          print0(ucase ? "RECOVER PARTITIONS" : "recover partitions");
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterIndexStatement x) {
          print0(ucase ? "ALTER INDEX " : "alter index ");
          x.getName().accept(this);
  
          if (x.getRenameTo() != null) {
              print0(ucase ? " RENAME TO " : " rename to ");
              x.getRenameTo().accept(this);
          }
  
          final SQLExprTableSource table = x.getTable();
          if (table != null) {
              print0(ucase ? " ON " : " on ");
              table.accept(this);
          }
  
          if (x.getPartitions().size() > 0) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartitions(), ", ");
              print(')');
          }
  
          if (x.isCompile()) {
              print0(ucase ? " COMPILE" : " compile");
          }
  
          if (Boolean.TRUE.equals(x.getEnable())) {
              print0(ucase ? " ENABLE" : " enable");
          }
          if (Boolean.FALSE.equals(x.getEnable())) {
              print0(ucase ? " DISABLE" : " disable");
          }
  
          if (x.isUnusable()) {
              print0(ucase ? " UNUSABLE" : " unusable");
          }
  
          if (x.getMonitoringUsage() != null) {
              print0(ucase ? " MONITORING USAGE" : " monitoring usage");
          }
  
          if (x.getRebuild() != null) {
              print(' ');
              x.getRebuild().accept(this);
          }
  
          if (x.getParallel() != null) {
              print0(ucase ? " PARALLEL" : " parallel");
              x.getParallel().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterIndexStatement.Rebuild x) {
          print0(ucase ? "REBUILD" : "rebuild");
  
          if (x.getOption() != null) {
              print(' ');
              x.getOption().accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowIndexesStatement x) {
          final List<SQLCommentHint> headHints = x.getHeadHintsDirect();
          if (headHints != null) {
              for (SQLCommentHint hint : headHints) {
                  hint.accept(this);
                  println();
              }
          }
  
          print0(ucase ? "SHOW " : "show ");
          print0(ucase ? x.getType().toUpperCase() : x.getType().toLowerCase());
  
          final SQLExprTableSource table = x.getTable();
          if (table != null) {
              print0(ucase ? " FROM " : " from ");
              x.getTable().accept(this);
          }
  
          if (x.getHints() != null && x.getHints().size() > 0) {
              print(' ');
              printAndAccept(x.getHints(), " ");
          }
  
          if (x.getWhere() != null) {
              print0(ucase ? " WHERE " : " where ");
              x.getWhere().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAnalyzeTableStatement x) {
          print0(ucase ? "ANALYZE TABLE " : "analyze table ");
  
          printAndAccept(x.getTables(), ", ");
  
          SQLPartitionRef partition = x.getPartition();
          if (partition != null) {
              print(' ');
              partition.accept(this);
          }
  
          if (x.isComputeStatistics()) {
              print0(ucase ? " COMPUTE STATISTICS" : " compute statistics");
          }
  
          if (x.isForColums()) {
              print0(ucase ? " FOR COLUMNS" : " for columns");
          }
  
          if (x.isCacheMetadata()) {
              print0(ucase ? " CACHE METADATA" : " cache metadata");
          }
  
          if (x.isNoscan()) {
              print0(ucase ? " NOSCAN" : " noscan");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLPartitionRef x) {
          print0(ucase ? "PARTITION (" : "partition (");
          for (int i = 0; i < x.getItems().size(); i++) {
              if (i != 0) {
                  print(", ");
              }
              SQLPartitionRef.Item item = x.getItems().get(i);
              visit(item);
          }
          print(')');
  
          return false;
      }
  
      public boolean visit(SQLPartitionRef.Item x) {
          visit(x.getColumnName());
  
          SQLExpr value = x.getValue();
          if (value != null) {
              SQLBinaryOperator operator = x.getOperator();
              if (operator == null) {
                  print(" = ");
              } else {
                  printOperator(operator);
              }
  
              printExpr(value);
          }
  
          return false;
      }
  
      public boolean visit(SQLExportTableStatement x) {
          print0(ucase ? "EXPORT TABLE " : "export table ");
          x.getTable().accept(this);
  
          if (x.getPartition().size() > 0) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartition(), ", ");
              print(')');
          }
  
          final SQLExpr to = x.getTo();
          if (to != null) {
              print0(ucase ? " TO " : " to ");
              to.accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLImportTableStatement x) {
          if (x.isExtenal()) {
              print0(ucase ? "IMPORT EXTERNAL" : "import external");
          } else {
              print0(ucase ? "IMPORT" : "import");
          }
  
          final SQLExprTableSource table = x.getTable();
          if (table != null) {
              print0(ucase ? " TABLE " : " table ");
              table.accept(this);
          }
  
          if (x.getPartition().size() > 0) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(x.getPartition(), ", ");
              print(')');
          }
  
          final SQLExpr from = x.getFrom();
          if (from != null) {
              print0(ucase ? " FROM " : " from ");
              from.accept(this);
          }
  
          final SQLExpr location = x.getLocation();
          if (location != null) {
              print0(ucase ? " LOCATION " : " location ");
              location.accept(this);
          }
  
          if (x.getVersion() != null) {
              print0(ucase ? " VERSIOIN = " : " version = ");
              x.getVersion().accept(this);
          }
  
          if (x.isUsingBuild()) {
              print0(ucase ? " BUILD = 'Y'" : " build = 'y'");
          }
  
          return false;
      }
  
      public boolean visit(SQLTableSampling x) {
          print0(ucase ? "TABLESAMPLE " : "tablesample ");
  
          if (x.isBernoulli()) {
              print0(ucase ? "BERNOULLI " : "bernoulli ");
          } else if (x.isSystem()) {
              print0(ucase ? "SYSTEM " : "system ");
          }
  
          print('(');
  
          final SQLExpr bucket = x.getBucket();
          if (bucket != null) {
              print0(ucase ? "BUCKET " : "bucket ");
              bucket.accept(this);
          }
  
          final SQLExpr outOf = x.getOutOf();
          if (outOf != null) {
              print0(ucase ? " OUT OF " : " out of ");
              outOf.accept(this);
          }
  
          final SQLExpr on = x.getOn();
          if (on != null) {
              print0(ucase ? " ON " : " on ");
              on.accept(this);
          }
  
          final SQLExpr percent = x.getPercent();
          if (percent != null) {
              percent.accept(this);
              print0(ucase ? " PERCENT" : " percent");
          }
  
          final SQLExpr rows = x.getRows();
          if (rows != null) {
              rows.accept(this);
  
              if (!JdbcUtils.isMysqlDbType(dbType) && !JdbcUtils.isPgsqlDbType(dbType)) {
                  print0(ucase ? " ROWS" : " rows");
              }
          }
  
          final SQLExpr size = x.getByteLength();
          if (size != null) {
              size.accept(this);
          }
  
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLSizeExpr x) {
          x.getValue().accept(this);
          print0(x.getUnit().name());
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableArchivePartition x) {
          print0(ucase ? "ARCHIVE PARTITION (" : "archive partition (");
          printAndAccept(x.getPartitions(), ", ");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableUnarchivePartition x) {
          print0(ucase ? "UNARCHIVE PARTITION (" : "unarchive partition (");
          printAndAccept(x.getPartitions(), ", ");
          print(')');
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateOutlineStatement x) {
          print0(ucase ? "CREATE OUTLINE " : "create outline ");
          x.getName().accept(this);
          print0(ucase ? " ON " : " on ");
          x.getOn().accept(this);
          print0(ucase ? " TO " : " to ");
          x.getTo().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLDropOutlineStatement x) {
          print0(ucase ? "DROP OUTLINE " : "drop outline ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLDropOfflineModelStatement x) {
          print0(ucase ? "DROP OFFLINEMODEL " : "drop offlinemodel ");
          if (x.isIfExists()) {
              print0(ucase ? "IF EXISTS " : "if exists ");
          }
          x.getName().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLShowQueryTaskStatement x) {
          if (x.isFull()) {
              print0(ucase ? "SHOW FULL QUERY_TASK" : "show full query_task");
          } else {
              print0(ucase ? "SHOW QUERY_TASK" : "show query_task");
          }
  
          if (x.getUser() != null) {
              println();
              print0(ucase ? "FOR " : "for ");
              printExpr(x.getUser());
          }
  
          final SQLExpr where = x.getWhere();
          if (where != null) {
              println();
              print0(ucase ? "WHERE " : "where ");
              printExpr(where);
          }
  
          final SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              println();
              visit(orderBy);
          }
  
          final SQLLimit limit = x.getLimit();
          if (limit != null) {
              println();
              visit(limit);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLShowOutlinesStatement x) {
          print0(ucase ? "SHOW OUTLINES" : "show outlines");
  
          final SQLExpr where = x.getWhere();
          if (where != null) {
              println();
              print0(ucase ? "WHERE " : "where ");
              printExpr(where);
          }
  
          final SQLOrderBy orderBy = x.getOrderBy();
          if (orderBy != null) {
              println();
              visit(orderBy);
          }
  
          final SQLLimit limit = x.getLimit();
          if (limit != null) {
              println();
              visit(limit);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLPurgeTableStatement x) {
          print0(ucase ? "PURGE TABLE " : "purge table ");
          x.getTable().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLPurgeRecyclebinStatement x) {
          print0(ucase ? "PURGE RECYCLEBIN" : "purge recyclebin");
          return false;
      }
  
      @Override
      public boolean visit(SQLPurgeLogsStatement x) {
          print0(ucase ? "PURGE" : "purge");
  
          if (x.isBinary()) {
              print0(ucase ? " BINARY" : " binary");
          }
  
          if (x.isMaster()) {
              print0(ucase ? " MASTER" : " master");
          }
  
          if (x.isAll()) {
              print0(ucase ? " ALL" : " all");
              return false;
          }
  
          print0(ucase ? " LOGS" : " logs");
  
          final SQLExpr to = x.getTo();
          if (to != null) {
              print0(ucase ? " TO " : " to ");
              to.accept(this);
          }
  
          if (x.getBefore() != null) {
              print0(ucase ? " BEFORE " : " before ");
              x.getBefore().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterOutlineStatement x) {
          print0(ucase ? "ALTER OUTLINE " : "alter outline ");
          x.getName().accept(this);
  
          if (x.isResync()) {
              print0(ucase ? " RESYNC" : " resync");
          }
  
          if (x.isDisable()) {
              print0(ucase ? " DISABLE" : " disable");
          }
  
          if (x.isEnable()) {
              print0(ucase ? " ENABLE" : " enable");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterTableAddSupplemental x) {
          print0(ucase ? "ADD " : "add ");
          x.getElement().accept(this);
          return false;
      }
  
      public boolean visit(SQLDbLinkExpr x) {
          SQLExpr expr = x.getExpr();
          if (expr instanceof SQLMethodInvokeExpr) {
              SQLMethodInvokeExpr methodInvokeExpr = (SQLMethodInvokeExpr) x.getExpr();
              SQLExpr owner = methodInvokeExpr.getOwner();
              if (owner != null) {
                  printMethodOwner(owner);
              }
  
              String function = methodInvokeExpr.getMethodName();
  
              printFunctionName(function);
              print('@');
              print0(x.getDbLink());
  
              printMethodParameters(methodInvokeExpr);
          } else {
              if (expr != null) {
                  expr.accept(this);
                  print('@');
              }
              print0(x.getDbLink());
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowStatisticStmt x) {
          if (x.isFull()) {
              print0(ucase ? "SHOW FULL STATS" : "show full stats");
          } else {
              print0(ucase ? "SHOW STATS" : "show stats");
          }
  
          List<SQLAssignItem> partitions = x.getPartitions();
          if (!partitions.isEmpty()) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(partitions, ", ");
              print(')');
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowStatisticListStmt x) {
          print0(ucase ? "SHOW STATISTIC_LIST" : "show statistic_list");
  
          SQLExprTableSource table = x.getTableSource();
          if (table != null) {
              print(' ');
              table.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowPackagesStatement x) {
          print0(ucase ? "SHOW PACKAGES" : "show packages");
          return false;
      }
  
      @Override
      public boolean visit(SQLShowGrantsStatement x) {
          print0(ucase ? "SHOW GRANTS" : "show grants");
          SQLExpr user = x.getUser();
          if (user != null) {
              print0(ucase ? " FOR " : " for ");
              user.accept(this);
          }
  
          SQLExpr on = x.getOn();
          if (on != null) {
              print0(ucase ? " ON " : " on ");
              on.accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLCurrentTimeExpr x) {
          final SQLCurrentTimeExpr.Type type = x.getType();
          print(ucase ? type.name : type.nameLCase);
  
          String timeZone = x.getTimeZone();
          if (timeZone != null) {
              print(ucase ? " AT TIME ZONE" : " at time zone");
              print0(timeZone);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLCurrentUserExpr x) {
          print(ucase ? "CURRENT_USER" : "current_user");
          return false;
      }
  
      @Override
      public boolean visit(SQLAdhocTableSource x) {
          final SQLCreateTableStatement definition = x.getDefinition();
          definition.accept(this);
          return false;
      }
  
      public boolean visit(SQLExecuteImmediateStatement x) {
          print(ucase ? "EXECUTE IMMEDIATE " : "execute immediate ");
          x.getDynamicSql().accept(this);
          List<SQLExpr> into = x.getInto();
          if (!into.isEmpty()) {
              print(ucase ? " INTO " : " into ");
              printAndAccept(into, ", ");
          }
          return false;
      }
  
      public boolean visit(SQLContinueStatement x) {
          print(ucase ? "CONTINUE" : "continue");
          return false;
      }
  
      public boolean visit(SQLLeaveStatement x) {
          print(ucase ? "LEAVE" : "leave");
          return false;
      }
  
      protected void printCreateTableLike(SQLCreateTableStatement x) {
          SQLExprTableSource like = x.getLike();
          if (like == null) {
              return;
          }
          println();
          print0(ucase ? "LIKE " : "like ");
          like.accept(this);
      }
  
      protected void printPartitionedBy(SQLCreateTableStatement x) {
          List<SQLColumnDefinition> partitionColumns = x.getPartitionColumns();
          int partitionSize = partitionColumns.size();
          if (partitionSize == 0) {
              return;
          }
  
          println();
          print0(ucase ? "PARTITIONED BY (" : "partitioned by (");
          this.indentCount++;
          println();
          for (int i = 0; i < partitionSize; ++i) {
              SQLColumnDefinition column = partitionColumns.get(i);
              printPartitionedByColumn(column);
  
              if (i != partitionSize - 1) {
                  print(',');
              }
              if (this.isPrettyFormat() && column.hasAfterComment()) {
                  print(' ');
                  printlnComment(column.getAfterCommentsDirect());
              }
  
              if (i != partitionSize - 1) {
                  println();
              }
          }
          this.indentCount--;
          println();
          print(')');
      }
  
      protected void printLifeCycle(SQLExpr lifeCycle) {
          if (lifeCycle == null) {
              return;
          }
          println();
          print0(ucase ? "LIFECYCLE " : "lifecycle ");
          lifeCycle.accept(this);
      }
  
      protected void printPartitionedByColumn(SQLColumnDefinition column) {
          column.accept(this);
      }
  
      protected void printSerdeProperties(Map<String, SQLObject> serdeProperties) {
          if (serdeProperties.isEmpty()) {
              return;
          }
  
          println();
          print0(ucase ? "WITH SERDEPROPERTIES (" : "with serdeproperties (");
          incrementIndent();
          println();
          int i = 0;
          for (Map.Entry<String, SQLObject> option : serdeProperties.entrySet()) {
              if (i != 0) {
                  print(",");
                  println();
              }
              String key = option.getKey();
  
              boolean unquote = false;
              char c0;
              if (key.length() > 0 && (c0 = key.charAt(0)) != '"' && c0 != '`' && c0 != '\'') {
                  unquote = true;
              }
  
              if (unquote) {
                  print('\'');
              }
              print0(key);
              if (unquote) {
                  print('\'');
              }
  
              print0(" = ");
              option.getValue().accept(this);
              ++i;
          }
          decrementIndent();
          println();
          print(')');
      }
  
      protected void printTableOptionsPrefix(SQLCreateTableStatement x) {
          println();
          print0(ucase ? "OPTIONS (" : "options (");
          incrementIndent();
          println();
      }
  
      protected void printTableOptionsPostfix(SQLCreateTableStatement x) {
          decrementIndent();
          println();
          print(')');
      }
  
      protected void printTableOption(SQLExpr name, SQLExpr value, int index) {
          if (index != 0) {
              print(",");
              println();
          }
  
          String key = name.toString();
  
          boolean unquote = false;
          char c0;
          if (!key.isEmpty() && (c0 = key.charAt(0)) != '"' && c0 != '`' && c0 != '\'') {
              unquote = true;
          }
  
          if (unquote) {
              print('\'');
          }
          print0(key);
          if (unquote) {
              print('\'');
          }
  
          print0(" = ");
          value.accept(this);
      }
  
      protected void printTableOptions(SQLCreateTableStatement x) {
          List<SQLAssignItem> tblProperties = x.getTableOptions();
          if (tblProperties.isEmpty()) {
              return;
          }
  
          printTableOptionsPrefix(x);
          int i = 0;
          for (SQLAssignItem property : tblProperties) {
              printTableOption(property.getTarget(), property.getValue(), i);
              ++i;
          }
  
          printTableOptionsPostfix(x);
      }
  
      protected void printIntoBuckets(int buckets) {
          if (buckets > 0) {
              println();
              print0(ucase ? "INTO " : "into ");
              print(buckets);
              print0(ucase ? " BUCKETS" : " buckets");
          }
      }
  
      protected void printIntoShards(int shards) {
          if (shards > 0) {
              println();
              print0(ucase ? "INTO " : "into ");
              print(shards);
              print0(ucase ? " SHARDS" : " shards");
          }
      }
  
      protected void printLocation(SQLCreateTableStatement x) {
          SQLExpr location = x.getLocation();
          if (location != null) {
              println();
              print0(ucase ? "LOCATION " : "location ");
              printExpr(location, parameterized);
          }
      }
  
      protected void printRowFormat(SQLCreateTableStatement x) {
          SQLExternalRecordFormat format = x.getRowFormat();
          if (format != null) {
              println();
              print0(ucase ? "ROW FORMAT" : "row format");
              if (format.getSerde() == null) {
                  print0(ucase ? " DELIMITED" : " delimited ");
              }
              visit(format);
              println();
          }
      }
      @Override
      public boolean visit(HiveInputOutputFormat x) {
          print0(ucase ? "INPUTFORMAT " : "inputformat ");
          x.getInput().accept(this);
          println();
          print0(ucase ? "OUTPUTFORMAT " : "outputformat ");
          x.getOutput().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLWhoamiStatement x) {
          print0(ucase ? "WHO AM I" : "who am i");
          return false;
      }
  
      @Override
      public boolean visit(SQLForStatement x) {
          print0(ucase ? "FOR " : "for ");
          x.getIndex().accept(this);
          print0(ucase ? " IN " : " in ");
  
          SQLExpr range = x.getRange();
          range.accept(this);
  
          println();
          print0(ucase ? "LOOP" : "loop");
  
          this.indentCount++;
          println();
  
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              SQLStatement stmt = x.getStatements().get(i);
              stmt.accept(this);
              if (i != size - 1) {
                  println();
              }
          }
  
          this.indentCount--;
          println();
          print0(ucase ? "END LOOP" : "end loop");
          return false;
      }
  
      @Override
      public boolean visit(SQLCopyFromStatement x) {
          print0(ucase ? "COPY " : "copy ");
          SQLExprTableSource table = x.getTable();
          table.accept(this);
  
          List<SQLName> columns = x.getColumns();
          if (columns.size() > 0) {
              print('(');
              printAndAccept(columns, ", ");
              print(")");
          }
  
          List<SQLAssignItem> partitions = x.getPartitions();
          if (partitions.size() > 0) {
              print0(ucase ? " PARTITIONS (" : " partitions (");
              printAndAccept(partitions, ", ");
              print(")");
          }
  
          print0(ucase ? " FROM " : " from ");
          x.getFrom().accept(this);
  
          SQLExpr accessKeyId = x.getAccessKeyId();
          SQLExpr accessKeySecret = x.getAccessKeySecret();
          if (accessKeyId != null || accessKeySecret != null) {
              print0(ucase ? " CREDENTIALS" : " credentials");
              if (accessKeyId != null) {
                  print0(ucase ? " access_key_id " : " access_key_id ");
                  accessKeyId.accept(this);
              }
  
              if (accessKeySecret != null) {
                  print0(ucase ? " ACCESS_KEY_SECRET " : " access_key_secret ");
                  accessKeySecret.accept(this);
              }
          }
  
          List<SQLAssignItem> options = x.getOptions();
          if (options.size() > 0) {
              print0(ucase ? " WITH (" : " with (");
              printAndAccept(options, ", ");
              print(')');
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLShowUsersStatement x) {
          print0(ucase ? "SHOW USERS" : "show users");
          return false;
      }
  
      @Override
      public boolean visit(SQLSyncMetaStatement x) {
          print0(ucase ? "SYNC META TABLE" : "sync meta table");
  
          Boolean restrict = x.getRestrict();
          if (restrict != null && restrict.booleanValue()) {
              print0(ucase ? " RESTRICT " : " restrict ");
          }
  
          Boolean ignore = x.getIgnore();
          if (ignore != null && ignore.booleanValue()) {
              print0(ucase ? " IGNORE " : " ignore ");
          }
  
          SQLName from = x.getFrom();
          if (from != null) {
              print0(ucase ? " FROM " : " from ");
              from.accept(this);
          }
  
          SQLExpr like = x.getLike();
          if (like != null) {
              print0(ucase ? " LIKE " : " like ");
              like.accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLTableLike x) {
          print0(ucase ? "LIKE " : "like ");
          x.getTable().accept(this);
  
          if (x.isIncludeProperties()) {
              print0(ucase ? " INCLUDING PROPERTIES" : " including properties");
          } else if (x.isExcludeProperties()) {
              print0(ucase ? " EXCLUDING PROPERTIES" : " excluding properties");
          } else if (x.isIncludeDistribution()) {
              print0(ucase ? " INCLUDING DISTRIBUTION" : " including distribution");
          } else if (x.isExcludeDistribution()) {
              print0(ucase ? " EXCLUDING DISTRIBUTION" : " excluding distribution");
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLValuesQuery x) {
          print0(ucase ? "VALUES " : "values ");
          printAndAccept(x.getValues(), ", ");
          return false;
      }
  
      @Override
      public boolean visit(SQLBuildTableStatement x) {
          print0(ucase ? "BUILD TABLE " : "build table ");
  
          x.getTable().accept(this);
  
          if (x.getVersion() != null) {
              print0(ucase ? " VERSION = " : " version = ");
              x.getVersion().accept(this);
          }
  
          if (x.isWithSplit()) {
              print0(ucase ? " WITH SPLIT" : " with split");
          }
  
          if (x.isForce()) {
              print0(ucase ? " FORCE = true" : " force = true");
          } else {
              print0(ucase ? " FORCE = false" : " force = false");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLExportDatabaseStatement x) {
          print0(ucase ? "EXPORT DATABASE " : "export database ");
  
          x.getDb().accept(this);
  
          print0(ucase ? " REALTIME = " : " realtime = ");
          if (x.isRealtime()) {
              print0(ucase ? "'Y'" : "'y'");
          } else {
              print0(ucase ? "'N'" : "'n'");
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLImportDatabaseStatement x) {
          print0(ucase ? "IMPORT DATABASE " : "import database ");
  
          x.getDb().accept(this);
  
          if (x.getStatus() != null) {
              print0(ucase ? " STATUS = " : " status = ");
              x.getStatus().accept(this);
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLRenameUserStatement x) {
          print0(ucase ? "RENAME USER " : "rename user ");
  
          x.getName().accept(this);
          print0(ucase ? " TO " : " to ");
          x.getTo().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLSubmitJobStatement x) {
          print0(ucase ? "SUBMIT JOB " : "submit job ");
  
          if (x.isAwait()) {
              print0(ucase ? "AWAIT " : "await ");
          }
  
          x.getStatment().accept(this);
          return false;
      }
  
      @Override
      public boolean visit(SQLRestoreStatement x) {
          print0(ucase ? "RESTORE " : "restore ");
  
          x.getType().accept(this);
          print0(ucase ? " FROM " : " from ");
  
          printAndAccept(x.getProperties(), ",");
          return false;
      }
  
      @Override
      public boolean visit(SQLArchiveTableStatement x) {
          print0(ucase ? "ARCHIVE TABLE " : "archive table ");
  
          x.getTable().accept(this);
  
          for (int i = 0; i < x.getSpIdList().size(); i++) {
              if (i != 0) {
                  print0(",");
              }
              print0(" ");
              x.getSpIdList().get(i).accept(this);
              print0(":");
              x.getpIdList().get(i).accept(this);
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLBackupStatement x) {
          print0(ucase ? "BACKUP " : "backup ");
  
          String type = x.getType().getSimpleName().toUpperCase();
          String action = x.getAction().getSimpleName().toUpperCase();
  
          if ("BACKUP_DATA".equalsIgnoreCase(type)) {
              if ("BACKUP".equalsIgnoreCase(action)) {
                  print0(ucase ? "DATA INTO " : "data into ");
              } else if ("BACKUP_CANCEL".equalsIgnoreCase(action)) {
                  print0(ucase ? "CANCEL " : "cancel ");
              }
          } else if ("BACKUP_LOG".equalsIgnoreCase(type)) {
              print0(ucase ? "LOG " : "log ");
              if ("BACKUP".equalsIgnoreCase(action)) {
                  print0(ucase ? "INTO " : "into ");
              } else if ("LIST_LOG".equalsIgnoreCase(action)) {
                  print0(ucase ? "LIST_LOGS" : "list_logs");
              } else if ("STATUS".equalsIgnoreCase(action)) {
                  print0(ucase ? "STATUS" : "status");
              }
          }
  
          printAndAccept(x.getProperties(), ",");
  
          return false;
      }
  
      public void visitStatementList(List<SQLStatement> statementList) {
          boolean printStmtSeperator;
          if (DbType.sqlserver == dbType) {
              printStmtSeperator = false;
          } else {
              printStmtSeperator = DbType.oracle != dbType;
          }
  
          for (int i = 0, size = statementList.size(); i < size; i++) {
              SQLStatement stmt = statementList.get(i);
  
              if (i > 0) {
                  SQLStatement preStmt = statementList.get(i - 1);
                  if (printStmtSeperator && !preStmt.isAfterSemi()) {
                      print(";");
                  }
  
                  List<String> comments = preStmt.getAfterCommentsDirect();
                  if (comments != null) {
                      for (int j = 0; j < comments.size(); ++j) {
                          String comment = comments.get(j);
                          if (j != 0) {
                              println();
                          }
                          printComment(comment);
                      }
                  }
  
                  if (printStmtSeperator) {
                      println();
                  }
  
                  if (!(stmt instanceof SQLSetStatement)) {
                      println();
                  }
              }
              {
                  List<String> comments = stmt.getBeforeCommentsDirect();
                  if (comments != null) {
                      for (String comment : comments) {
                          printComment(comment);
                          println();
                      }
                  }
              }
              stmt.accept(this);
  
              if (i == size - 1) {
                  List<String> comments = stmt.getAfterCommentsDirect();
                  if (comments != null) {
                      for (int j = 0; j < comments.size(); ++j) {
                          String comment = comments.get(j);
                          if (j != 0) {
                              println();
                          }
                          printComment(comment);
                      }
                  }
              }
          }
      }
  
      @Override
      public boolean visit(SQLCreateResourceGroupStatement x) {
          print0(ucase ? "CREATE RESOURCE GROUP " : "create resource group ");
          x.getName().accept(this);
  
          for (Map.Entry<String, SQLExpr> entry : x.getProperties().entrySet()) {
              print(' ');
              print(entry.getKey());
              print(" = ");
  
              SQLExpr value = entry.getValue();
              if (value instanceof SQLListExpr) {
                  printAndAccept(((SQLListExpr) value).getItems(), ",");
              } else {
                  value.accept(this);
              }
          }
  
          if (x.getEnable() != null) {
              if (x.getEnable()) {
                  print0(ucase ? " ENABLE" : " enable");
              } else {
                  print0(ucase ? " DISABLE" : " disable");
              }
          }
          return false;
      }
  
      @Override
      public boolean visit(SQLAlterResourceGroupStatement x) {
          print0(ucase ? "ALTER RESOURCE GROUP " : "create resource group ");
          x.getName().accept(this);
  
          for (Map.Entry<String, SQLExpr> entry : x.getProperties().entrySet()) {
              print(' ');
              print(entry.getKey());
              print(" = ");
  
              SQLExpr value = entry.getValue();
              if (value instanceof SQLListExpr) {
                  printAndAccept(((SQLListExpr) value).getItems(), ",");
              } else {
                  value.accept(this);
              }
          }
  
          if (x.getEnable() != null) {
              if (x.getEnable()) {
                  print0(ucase ? " ENABLE" : " enable");
              } else {
                  print0(ucase ? " DISABLE" : " disable");
              }
          }
  
          return false;
      }
  
      @Override
      public boolean visit(MySqlKillStatement x) {
          if (MySqlKillStatement.Type.CONNECTION.equals(x.getType())) {
              print0(ucase ? "KILL CONNECTION " : "kill connection ");
          } else if (MySqlKillStatement.Type.QUERY.equals(x.getType())) {
              print0(ucase ? "KILL QUERY " : "kill query ");
          } else {
              print0(ucase ? "KILL " : "kill ");
          }
  
          printAndAccept(x.getThreadIds(), ", ");
          return false;
      }
  
      @Override
      public boolean visit(SQLTop x) {
          boolean parameterized = this.parameterized;
          this.parameterized = false;
  
          print0(ucase ? "TOP " : "top ");
  
          boolean paren = false;
  
          if (x.isParentheses()) {
              paren = true;
              print('(');
          }
  
          x.getExpr().accept(this);
  
          if (paren) {
              print(')');
          }
  
          if (x.isPercent()) {
              print0(ucase ? " PERCENT" : " percent");
          }
  
          this.parameterized = parameterized;
          return false;
      }
  
      public boolean visit(OdpsNewExpr x) {
          print0(ucase ? "NEW " : "new ");
          return super.visit((SQLMethodInvokeExpr) x);
      }
  
      public boolean visit(SQLPurgeTemporaryOutputStatement x) {
          print0(ucase ? "PURGE TEMPORARY OUTPUT " : "purge temporary output ");
          printExpr(x.getName());
          return false;
      }
  
      public boolean visit(SQLCloneTableStatement x) {
          print0(ucase ? "CLONE TABLE " : "clone table ");
          x.getFrom().accept(this);
          print0(ucase ? " TO " : " to ");
          x.getTo().accept(this);
          return false;
      }
  
      public boolean visit(SQLAlterTableAttachPartition x) {
          print0(ucase ? "ATTACH PARTITION " : "attach partition ");
          x.getPartitionName().accept(this);
          if (x.isDefaultFlag()) {
              print0(ucase ? " DEFAULT" : " default");
          }
          return false;
      }
  
      public boolean visit(SQLAlterTableDetachPartition x) {
          print0(ucase ? "DETACH  PARTITION " : "detach partition ");
          x.getPartitionName().accept(this);
          if (x.isConcurrently()) {
              print0(ucase ? " CONCURRENTLY" : " concurrently");
          }
          if (x.isFinalize()) {
              print0(ucase ? " FINALIZE" : " finalize");
          }
          return false;
      }
      public boolean visit(SQLOptimizeStatement x) {
          print0(ucase ? "OPTIMIZE TABLE " : "optimize table ");
  
          printAndAccept(x.getTableSources(), ", ");
  
          SQLName cluster = x.getCluster();
          if (cluster != null) {
              print0(ucase ? " ON CLUSTER " : " on cluster ");
              cluster.accept(this);
          }
  
          if (x.isFinal()) {
              print0(ucase ? " FINAL" : " final");
          }
  
          if (x.isDeduplicate()) {
              print0(ucase ? " DEDUPLICATE" : " deduplicate");
  
              SQLExpr deduplicateBy = x.getDeduplicateBy();
              if (deduplicateBy != null) {
                  print0(ucase ? " BY " : " by ");
                  deduplicateBy.accept(this);
              }
          }
  
          return false;
      }
  
      @Override
      public boolean visit(SQLPivot x) {
          print0(ucase ? "PIVOT" : "pivot");
          if (x.isXml()) {
              print0(ucase ? " XML" : " xml");
          }
          print0(" (");
          printAndAccept(x.getItems(), ", ");
  
          if (x.getPivotFor().size() > 0) {
              print0(ucase ? " FOR " : " for ");
              if (x.getPivotFor().size() == 1) {
                  ((SQLExpr) x.getPivotFor().get(0)).accept(this);
              } else {
                  print('(');
                  printAndAccept(x.getPivotFor(), ", ");
                  print(')');
              }
          }
  
          if (x.getPivotIn().size() > 0) {
              print0(ucase ? " IN (" : " in (");
              printAndAccept(x.getPivotIn(), ", ");
              print(')');
          }
  
          print(')');
  
          return false;
      }
  
      @Override
      public boolean visit(SQLUnpivot x) {
          print0(ucase ? "UNPIVOT" : "unpivot");
          if (x.getNullsIncludeType() != null) {
              print(' ');
              print0(SQLUnpivot.NullsIncludeType.toString(x.getNullsIncludeType(), ucase));
          }
  
          print0(" (");
          if (x.getItems().size() == 1) {
              ((SQLExpr) x.getItems().get(0)).accept(this);
          } else {
              print0(" (");
              printAndAccept(x.getItems(), ", ");
              print(')');
          }
  
          if (x.getPivotFor().size() > 0) {
              print0(ucase ? " FOR " : " for ");
              if (x.getPivotFor().size() == 1) {
                  ((SQLExpr) x.getPivotFor().get(0)).accept(this);
              } else {
                  print('(');
                  printAndAccept(x.getPivotFor(), ", ");
                  print(')');
              }
          }
  
          if (x.getPivotIn().size() > 0) {
              print0(ucase ? " IN (" : " in (");
              printAndAccept(x.getPivotIn(), ", ");
              print(')');
          }
  
          print(')');
  
          return false;
      }
  
      public boolean visit(SQLShowHistoryStatement x) {
          if (x.isTables()) {
              print0(ucase ? "SHOW HISTORY FOR TABLES " : "show history for tables ");
          } else {
              print0(ucase ? "SHOW HISTORY FOR TABLE " : "show history for table ");
          }
          x.getTable().accept(this);
  
          List<SQLAssignItem> properties = x.getProperties();
          if (!properties.isEmpty()) {
              print0(" (");
              printAndAccept(properties, ", ");
              print(')');
          }
  
          List<SQLAssignItem> partitions = x.getPartitions();
          if (!partitions.isEmpty()) {
              print0(ucase ? " PARTITION (" : " partition (");
              printAndAccept(partitions, ", ");
              print(')');
          }
          // HISTORY
          return false;
      }
  
      public boolean visit(SQLCostStatement x) {
          print0(ucase ? "COST SQL" : "cost sql");
          println();
          x.getStatement().accept(this);
          return false;
      }
  
      public boolean visit(SQLPartitionOf x) {
          x.getParentTable().accept(this);
          if (x.isUseDefault()) {
              print0(ucase ? " DEFAULT" : " pdefault");
          }
          if (x.getColumnName() != null || x.getConstraintName() != null || x.getCheckExpr() != null) {
              print0(" (");
          }
          if (x.getColumnName() != null) {
              x.getColumnName().accept(this);
          }
          if (x.getConstraintName() != null) {
              print0(ucase ? " CONSTRAINT " : " constraint ");
              x.getConstraintName().accept(this);
          }
          if (x.getCheckExpr() != null) {
              print0(ucase ? " CHECK " : " check ");
              x.getCheckExpr().accept(this);
              print0("");
          }
          if (x.getDefaultExpr() != null) {
              print0(ucase ? " DEFAULT " : " default ");
              x.getDefaultExpr().accept(this);
          }
          if (x.getColumnName() != null || x.getConstraintName() != null || x.getCheckExpr() != null) {
              print0(" )");
          }
          if (x.getForValuesFrom() != null && x.getForValuesTo() != null) {
              print0(ucase ? " FOR VALUES FROM (" : " for values from (");
              printAndAccept(x.getForValuesFrom(), ", ");
              print0(ucase ? ") TO (" : ") to (");
              printAndAccept(x.getForValuesTo(), ", ");
              print0(")");
          }
          if (x.getForValuesIn() != null) {
              print0(ucase ? " FOR VALUES IN (" : " for values in (");
              printAndAccept(x.getForValuesIn(), ", ");
              print0(")");
          }
          if (x.getForValuesModulus() != null && x.getForValuesRemainder() != null) {
              print0(ucase ? " FOR VALUES WITH (" : " for values with (");
              print0(ucase ? "MODULUS " : "modulus ");
              x.getForValuesModulus().accept(this);
              print0(" , ");
              print0(ucase ? "REMAINDER " : "remainder ");
              x.getForValuesRemainder().accept(this);
              print0(")");
          }
          return false;
      }
  
      public boolean visit(SQLAlterProcedureStatement x) {
          print0(ucase ? "ALTER PROCEDURE " : "alter procedure ");
          x.getName().accept(this);
  
          SQLExpr comment = x.getComment();
          if (comment != null) {
              print0(ucase ? " COMMENT " : " comment ");
              comment.accept(this);
          }
  
          if (x.isLanguageSql()) {
              print0(ucase ? " LANGUAGE SQL" : " language sql");
          }
  
          SQLExpr sqlSecurity = x.getSqlSecurity();
          if (sqlSecurity != null) {
              print0(ucase ? " SQL SECURITY " : " sql security ");
              sqlSecurity.accept(this);
          }
  
          if (x.isContainsSql()) {
              print0(ucase ? " CONTAINS SQL" : " contains sql");
          }
          return false;
      }
  
      public boolean visit(SQLCommitTransactionStatement x) {
          print0(ucase ? "COMMIT TRANSACTION" : "commit transaction");
          return false;
      }
  
      public boolean visit(SQLRollbackTransactionStatement x) {
          print0(ucase ? "ROLLBACK TRANSACTION" : "rollback transaction");
          return false;
      }
  
      public boolean visit(SQLStartTransactionStatement x) {
          print0(ucase ? "START TRANSACTION" : "start transaction");
          return false;
      }
  
      public boolean visit(SQLRaiseStatement x) {
          print0(ucase ? "RAISE" : "raise");
          if (x.getMessage() != null) {
              print0(ucase ? " USING MESSAGE = " : " using message = ");
              x.getMessage().accept(this);
          }
          return false;
      }
  
      public boolean visit(SQLComputeIncrementalStatsStatement x) {
          print0(ucase ? "COMPUTE INCREMENTAL STATS " : "compute incremental stats ");
          x.getName().accept(this);
          if (x.getPartition() != null) {
              print0(ucase ? " PARTITION " : " partition ");
              x.getPartition().accept(this);
          }
          return false;
      }
      @Override
      public boolean visit(SQLExceptionStatement.Item x) {
          print0(ucase ? "WHEN " : "when ");
          x.getWhen().accept(this);
          print0(ucase ? " THEN" : " then");
  
          this.indentCount++;
          if (x.getStatements().size() > 1) {
              println();
          } else {
              if (x.getStatements().size() == 1
                      && x.getStatements().get(0) instanceof SQLIfStatement) {
                  println();
              } else {
                  print(' ');
              }
          }
  
          for (int i = 0, size = x.getStatements().size(); i < size; ++i) {
              if (i != 0 && size > 1) {
                  println();
              }
              SQLStatement stmt = x.getStatements().get(i);
              stmt.accept(this);
          }
  
          this.indentCount--;
          return false;
      }
  
      @Override
      public boolean visit(SQLExceptionStatement x) {
          print0(ucase ? "EXCEPTION" : "exception");
          this.indentCount++;
          List<SQLExceptionStatement.Item> items = x.getItems();
          for (int i = 0, size = items.size(); i < size; ++i) {
              println();
              SQLExceptionStatement.Item item = items.get(i);
              item.accept(this);
          }
          this.indentCount--;
          return false;
      }
      protected void tryPrintLparen(SQLExprImpl x) {
          if (x.isParenthesized()) {
              print('(');
          }
          for (int i = 1; i < x.getParenthesizedCount(); ++i) {
              print('(');
          }
      }
  
      protected void tryPrintRparen(SQLExprImpl x) {
          if (x.isParenthesized()) {
              print(')');
          }
          for (int i = 1; i < x.getParenthesizedCount(); ++i) {
              print(')');
          }
      }
  
      public boolean visit(SQLSelectQueryTemplate x) {
          print0(x.getText());
          return false;
      }
  
      @Override
      public boolean visit(SQLCreateFunctionStatement x) {
          boolean create = x.isCreate();
          if (create) {
              print0(ucase ? "CREATE " : "create ");
  
              if (x.isOrReplace()) {
                  print0(ucase ? "OR REPLACE " : "or replace ");
              }
          }
  
          if (x.isTemporary()) {
              print0(ucase ? "TEMPORARY " : "temporary ");
          }
          print0(ucase ? "FUNCTION " : "function ");
  
          x.getName().accept(this);
  
          int paramSize = x.getParameters().size();
  
          if (paramSize > 0) {
              print0(" (");
              this.indentCount++;
              println();
  
              for (int i = 0; i < paramSize; ++i) {
                  if (i != 0) {
                      print(',');
                      println();
                  }
                  SQLParameter param = x.getParameters().get(i);
                  param.accept(this);
              }
  
              this.indentCount--;
              println();
              print(')');
          }
  
          printCreateFunctionBody(x);
          return false;
      }
  
      @Override
      public boolean visit(SQLResetStatement x) {
          print0(ucase ? "RESET " : "reset ");
          for (int i = 0; i < x.getOptions().size(); ++i) {
              if (i != 0) {
                  print0(", ");
              }
              print0(x.getOptions().get(i));
          }
          return false;
      }
      protected void printCreateFunctionBody(SQLCreateFunctionStatement x) {
          printCreateFunctionReturns(x);
  
          SQLStatement block = x.getBlock();
          if (block == null) {
              return;
          }
          println();
          println(ucase ? "AS" : "as");
          block.accept(this);
      }
  
      protected void printCreateFunctionReturns(SQLCreateFunctionStatement x) {
          SQLDataType returnDataType = x.getReturnDataType();
          if (returnDataType == null) {
              return;
          }
          println();
          print(ucase ? "RETURN " : "return ");
          returnDataType.accept(this);
      }
  
      @Override
      public String toString() {
          return appender.toString();
      }
  }
  