package com.chenyang.druid.sql.dialect.spark.visitor;

import com.chenyang.druid.DbType;
import com.chenyang.druid.sql.ast.SQLCommentHint;
import com.chenyang.druid.sql.ast.SQLExpr;
import com.chenyang.druid.sql.ast.SQLHint;
import com.chenyang.druid.sql.ast.SQLLimit;
import com.chenyang.druid.sql.ast.SQLName;
import com.chenyang.druid.sql.ast.SQLObject;
import com.chenyang.druid.sql.ast.expr.SQLBetweenExpr;
import com.chenyang.druid.sql.ast.expr.SQLLiteralExpr;
import com.chenyang.druid.sql.ast.statement.SQLAssignItem;
import com.chenyang.druid.sql.ast.statement.SQLColumnDefinition;
import com.chenyang.druid.sql.ast.statement.SQLInsertStatement;
import com.chenyang.druid.sql.ast.statement.SQLJoinTableSource;
import com.chenyang.druid.sql.ast.statement.SQLSelect;
import com.chenyang.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.chenyang.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.chenyang.druid.sql.ast.statement.SQLTableElement;
import com.chenyang.druid.sql.ast.statement.SQLTableSource;
import com.chenyang.druid.sql.dialect.hive.visitor.HiveOutputVisitor;
import com.chenyang.druid.sql.dialect.spark.ast.expr.HiveRowFormat;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkColumnDef;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkColumnSpecExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkDateExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkDefProperty;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkIntervalExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkLimit;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkNullsExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkNumberExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkSQLSubqueryTableSource;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkSampleExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkSelectJoinTableSource;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkSelectPivot;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkSelectPivotBase;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkSelectTableReference;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkTablePartition;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkTransformExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkValuesExpr;
import com.chenyang.druid.sql.dialect.spark.ast.expr.SparkWithExpr;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkAlterDatabaseStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkAlterTableStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkAlterViewStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkCacheTableStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkCreateDatabaseStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkCreateTableStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkCreateViewStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkExplainStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkInsertStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkLoadStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkRefreshStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkRepairTableStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkShowCreateTableStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkShowDatabasesStatement;
import com.chenyang.druid.sql.dialect.spark.ast.statement.SparkShowTableStatement;
import java.util.List;
import java.util.Map;

public class SparkOutputVisitor extends HiveOutputVisitor implements SparkVisitor {
   public SparkOutputVisitor(Appendable appender, DbType dbType) {
      super(appender, dbType);
   }

   public SparkOutputVisitor(Appendable appender) {
      super(appender);
   }

   public boolean visit(SparkCreateTableStatement x) {
      this.print0(this.ucase ? "CREATE " : "create ");
      if (x.isExternal()) {
         this.print0(this.ucase ? "EXTERNAL " : "external ");
      }

      if (x.isIfNotExists()) {
         this.print0(this.ucase ? "TABLE IF NOT EXISTS " : "table if not exists ");
      } else {
         this.print0(this.ucase ? "TABLE " : "table ");
      }

      x.getName().accept(this);
      if (x.getLike() != null) {
         this.print0(this.ucase ? " LIKE " : " like ");
         x.getLike().accept(this);
      }

      List<SQLTableElement> tableElementList = x.getTableElementList();
      int size = tableElementList.size();
      if (size > 0) {
         this.print0(" (");
         if (this.isPrettyFormat() && x.hasBodyBeforeComment()) {
            this.print(' ');
            this.printlnComment(x.getBodyBeforeCommentsDirect());
         }

         ++this.indentCount;
         this.println();

         for(int i = 0; i < size; ++i) {
            SQLTableElement element = (SQLTableElement)tableElementList.get(i);
            element.accept(this);
            if (i != size - 1) {
               this.print(',');
            }

            if (this.isPrettyFormat() && element.hasAfterComment()) {
               this.print(' ');
               this.printlnComment(element.getAfterCommentsDirect());
            }

            if (i != size - 1) {
               this.println();
            }
         }

         --this.indentCount;
         this.println();
         this.print(')');
      }

      if (x.getDatasource() != null) {
         this.println();
         this.print0(this.ucase ? "USING " : "using ");
         this.print0(x.getDatasource().toString());
      }

      List<SQLAssignItem> tableOptions = x.getTableOptions();
      if (tableOptions.size() > 0) {
         this.println();
         this.print0(this.ucase ? "OPTIONS (" : "options (");
         this.printAndAccept(tableOptions, ", ");
         this.print(')');
      }

      if (x.getComment() != null) {
         this.println();
         this.print0(this.ucase ? "COMMENT " : "comment ");
         x.getComment().accept(this);
      }

      int partitionSize = x.getPartitionColumns().size();
      if (partitionSize > 0) {
         this.println();
         this.print0(this.ucase ? "PARTITIONED BY (" : "partitioned by (");
         ++this.indentCount;
         this.println();

         for(int i = 0; i < partitionSize; ++i) {
            SQLColumnDefinition column = (SQLColumnDefinition)x.getPartitionColumns().get(i);
            column.accept(this);
            if (i != partitionSize - 1) {
               this.print(',');
            }

            if (this.isPrettyFormat() && column.hasAfterComment()) {
               this.print(' ');
               this.printlnComment(column.getAfterCommentsDirect());
            }

            if (i != partitionSize - 1) {
               this.println();
            }
         }

         --this.indentCount;
         this.println();
         this.print(')');
      }

      List<SQLSelectOrderByItem> clusteredBy = x.getClusteredBy();
      if (clusteredBy.size() > 0) {
         this.println();
         this.print0(this.ucase ? "CLUSTERED BY (" : "clustered by (");
         this.printAndAccept(clusteredBy, ",");
         this.print(')');
      }

      List<SQLSelectOrderByItem> sortedBy = x.getSortedBy();
      if (sortedBy.size() > 0) {
         this.println();
         this.print0(this.ucase ? "SORTED BY (" : "sorted by (");
         this.printAndAccept(sortedBy, ", ");
         this.print(')');
      }

      int buckets = x.getBuckets();
      if (buckets > 0) {
         this.println();
         this.print0(this.ucase ? "INTO " : "into ");
         this.print(buckets);
         this.print0(this.ucase ? " BUCKETS" : " buckets");
         if (x.getBucketSelect() != null) {
            this.print0(" (");
            x.getBucketSelect().accept(this);
            this.print0(")");
         }
      }

      if (x.getRowFormat() != null) {
         this.println();
         this.print0(this.ucase ? "ROW FORMAT" : "row rowFormat");
         if (x.getRowFormat().getSerde() == null) {
            this.print0(this.ucase ? " DELIMITED" : " delimited ");
         }

         x.getRowFormat().accept(this);
      }

      if (x.getHiveFormat() != null) {
         this.println();
         x.getHiveFormat().accept(this);
      }

      SQLExpr storedAs = x.getStoredAs();
      if (storedAs != null) {
         this.println();
         this.print0(this.ucase ? "STORED AS " : "stored as ");
         storedAs.accept(this);
      }

      this.printTblProperties(x.getTblProperties());
      SQLSelect select = x.getSelect();
      if (select != null) {
         this.println();
         this.print0(this.ucase ? "AS" : "as");
         this.println();
         select.accept(this);
      }

      Map<String, SQLObject> serdeProperties = x.getSerdeProperties();
      if (serdeProperties.size() > 0) {
         this.println();
         this.print0(this.ucase ? "TBLPROPERTIES (" : "tblproperties (");
         String seperator = "";

         for(Map.Entry<String, SQLObject> entry : serdeProperties.entrySet()) {
            this.print0("'" + (String)entry.getKey() + "'='");
            ((SQLObject)entry.getValue()).accept(this);
            this.print0("'" + seperator);
            seperator = ",";
         }

         this.print(')');
      }

      SQLExpr location = x.getLocation();
      if (location != null) {
         this.println();
         this.print0(this.ucase ? "LOCATION " : "location ");
         location.accept(this);
      }

      return false;
   }

   public void endVisit(SparkCreateTableStatement x) {
   }

   public boolean visit(SparkWithExpr expr) {
      this.print0("(");
      expr.getStatement().accept(this);
      this.print0(")");
      return false;
   }

   public void endVisit(SparkWithExpr expr) {
   }

   public boolean visit(SparkValuesExpr expr) {
      expr.getTypeExpr().accept(this);
      this.print0(" ");
      this.print0("(");

      for(int i = 0; i < expr.getValues().size(); ++i) {
         SQLExpr sqlExpr = (SQLExpr)expr.getValues().get(i);
         sqlExpr.accept(this);
         if (i < expr.getValues().size() - 1) {
            this.print0(",");
         }
      }

      this.print0(")");
      return false;
   }

   public void endVisit(SparkValuesExpr expr) {
   }

   public boolean visit(SQLLimit expr) {
      SparkLimit limit = (SparkLimit)expr;
      if (limit.isAll()) {
         this.print0(this.ucase ? "LIMIT ALL " : "limit all ");
         return false;
      } else {
         super.visit((SQLLimit)expr);
         return false;
      }
   }

   public void endVisit(SparkLimit expr) {
   }

   public boolean visit(SparkSelectPivot x) {
      this.print0(this.ucase ? "PIVOT" : "pivot");
      if (x.isXml()) {
         this.print0(this.ucase ? " XML" : " xml");
      }

      this.print0(" (");
      this.printAndAccept(x.getItems(), ", ");
      if (x.getPivotFor().size() > 0) {
         this.print0(this.ucase ? " FOR " : " for ");
         if (x.getPivotFor().size() == 1) {
            ((SQLExpr)x.getPivotFor().get(0)).accept(this);
         } else {
            this.print('(');
            this.printAndAccept(x.getPivotFor(), ", ");
            this.print(')');
         }
      }

      if (x.getPivotIn().size() > 0) {
         this.print0(this.ucase ? " IN (" : " in (");
         this.printAndAccept(x.getPivotIn(), ", ");
         this.print(')');
      }

      this.print(')');
      return false;
   }

   public void endVisit(SparkSelectPivot x) {
   }

   public boolean visit(SparkSelectPivot.Item x) {
      x.getExpr().accept(this);
      if (x.getAlias() != null && x.getAlias().length() > 0) {
         this.print0(this.ucase ? " AS " : " as ");
         this.print0(x.getAlias());
      }

      return false;
   }

   public void endVisit(SparkSelectPivot.Item x) {
   }

   public boolean visit(SparkSQLSubqueryTableSource x) {
      this.print('(');
      ++this.indentCount;
      this.println();
      x.getSelect().accept(this);
      --this.indentCount;
      this.println();
      this.print(')');
      SparkSelectPivotBase pivot = x.getPivot();
      if (pivot != null) {
         this.println();
         pivot.accept(this);
      }

      this.printFlashback(x.getFlashback());
      if (x.getAlias() != null && x.getAlias().length() != 0) {
         this.print(' ');
         this.print0(x.getAlias());
      }

      return false;
   }

   public void endVisit(SparkSQLSubqueryTableSource x) {
   }

   private void printFlashback(SQLExpr flashback) {
      if (flashback != null) {
         this.println();
         if (flashback instanceof SQLBetweenExpr) {
            flashback.accept(this);
         } else {
            this.print0(this.ucase ? "AS OF " : "as of ");
            flashback.accept(this);
         }

      }
   }

   public boolean visit(SparkSelectTableReference x) {
      if (x.isOnly()) {
         this.print0(this.ucase ? "ONLY (" : "only (");
         this.printTableSourceExpr(x.getExpr());
         if (x.getPartition() != null) {
            this.print(' ');
            x.getPartition().accept(this);
         }

         this.print(')');
      } else {
         this.printTableSourceExpr(x.getExpr());
         if (x.getPartition() != null) {
            this.print(' ');
            x.getPartition().accept(this);
         }
      }

      if (x.getHints().size() > 0) {
         this.printHints(x.getHints());
      }

      if (x.getSample() != null) {
         x.getSample().accept(this);
      }

      if (x.getPivot() != null) {
         this.println();
         x.getPivot().accept(this);
      }

      this.printFlashback(x.getFlashback());
      this.printAlias(x.getAlias());
      return false;
   }

   public void endVisit(SparkSelectTableReference x) {
   }

   public boolean visit(SparkNullsExpr x) {
      if (x != null) {
         x.getExpr().accept(this);
         if (x.isIgnore()) {
            this.print0(this.ucase ? " IGNORE NULLS " : " ignore nulls ");
         } else {
            this.print0(this.ucase ? " NULLS " : " nulls ");
         }
      }

      return false;
   }

   public void endVisit(SparkNullsExpr x) {
   }

   public boolean visit(SparkDefProperty x) {
      x.getKey().accept(this);
      if (x.getValue() != null) {
         this.print0(" = ");
         x.getValue().accept(this);
      }

      return false;
   }

   public void endVisit(SparkDefProperty x) {
   }

   public boolean visit(SparkColumnDef x) {
      x.getColumn().accept(this);
      this.print0(" ");
      if (x.getType() != null) {
         x.getType().accept(this);
      }

      return false;
   }

   public void endVisit(SparkColumnDef x) {
   }

   public boolean visit(SparkTransformExpr x) {
      this.print0(this.ucase ? "TRANSFORM" : "transform");
      this.print0("(");

      for(int i = 0; i < x.getArguments().size(); ++i) {
         ((SQLExpr)x.getArguments().get(i)).accept(this);
         if (i != x.getArguments().size() - 1) {
            this.print0(",");
         }
      }

      this.print0(")");
      if (x.getRowFormat() != null) {
         this.println();
         x.getRowFormat().accept(this);
      }

      if (x.getRecordWriter() != null) {
         this.println();
         this.print0(this.ucase ? "RECORDWRITER " : "recordwriter");
         x.getRecordWriter().accept(this);
      }

      if (x.getCommandScript() != null) {
         this.println();
         x.getCommandScript().accept(this);
      }

      if (x.getUsingRowFormat() != null) {
         this.println();
         x.getUsingRowFormat().accept(this);
      }

      if (x.getRecordReader() != null) {
         this.println();
         this.print0(this.ucase ? "RECORDREADER " : "recordreader");
         x.getRecordReader().accept(this);
      }

      return false;
   }

   public void endVisit(SparkTransformExpr x) {
   }

   public boolean visit(HiveRowFormat x) {
      this.print0(this.ucase ? "ROW FORMAT " : "row format ");
      if (x.getSede() != null) {
         this.println();
         x.getSede().accept(this);
      }

      if (x.getDelimitor() != null) {
         this.println();
         x.getDelimitor().accept(this);
      }

      return false;
   }

   public void endVisit(HiveRowFormat x) {
   }

   public boolean visit(SparkTablePartition x) {
      this.print0(this.ucase ? "PARTITION " : "partition ");
      this.print0("(");

      for(int i = 0; i < x.getCols().size(); ++i) {
         ((SparkDefProperty)x.getCols().get(i)).accept(this);
         if (i != x.getCols().size() - 1) {
            this.print0(",");
         }
      }

      this.print0(")");
      return false;
   }

   public void endVisit(SparkTablePartition x) {
   }

   public boolean visit(SparkDateExpr x) {
      this.print0(this.ucase ? "DATE " : "date ");
      x.getValue().accept(this);
      return false;
   }

   public void endVisit(SparkDateExpr x) {
   }

   public boolean visit(SparkInsertStatement x) {
      if (x.hasBeforeComment()) {
         this.printlnComments(x.getBeforeCommentsDirect());
      }

      if (x.isDirectory()) {
         this.print0(this.ucase ? "INSERT OVERWRITE " : "insert overwrite ");
         if (x.isLocal()) {
            this.print0(this.ucase ? "LOCAL " : "local ");
         }

         this.print0(this.ucase ? "DIRECTORY " : "directory ");
         if (x.getDirectoryPath() != null) {
            this.print0(" ");
            x.getDirectoryPath().accept(this);
         }

         if (x.getSparkFormat() != null) {
            this.println();
            x.getSparkFormat().accept(this);
         }

         if (x.getHiveFormat() != null) {
            this.println();
            x.getHiveFormat().accept(this);
         }

         if (x.getStoredAs() != null) {
            this.println();
            this.print0(this.ucase ? "STORED AS " : "stored as ");
            x.getStoredAs().accept(this);
         }
      } else {
         if (x.isOverwrite()) {
            this.print0(this.ucase ? "INSERT OVERWRITE TABLE " : "insert overwrite table ");
         } else {
            this.print0(this.ucase ? "INSERT INTO TABLE " : "insert into table ");
         }

         x.getTableSource().accept(this);
         this.print0(" ");
         if (x.getColumns().size() > 0) {
            this.println();
            this.print0("(");

            for(int i = 0; i < x.getColumns().size(); ++i) {
               ((SQLExpr)x.getColumns().get(i)).accept(this);
               if (i != x.getColumns().size() - 1) {
                  this.print0(" , ");
               }
            }

            this.print0(") ");
         }
      }

      this.println();
      if (x.getFromTable() != null) {
         this.print0(this.ucase ? "FROM " : "from ");
         x.getFromTable().accept(this);
         this.print0(" ");
      }

      SQLSelect select = x.getQuery();
      SQLExpr table = x.getQueryTable();
      List<SQLInsertStatement.ValuesClause> valuesList = x.getValuesList();
      if (select != null) {
         select.accept(this);
      } else if (!valuesList.isEmpty()) {
         this.print0(this.ucase ? "VALUES " : "values ");
         this.printAndAccept(valuesList, " ,\n\t ");
      } else if (table != null) {
         this.print0(this.ucase ? "TABLE " : "table ");
         x.getQueryTable().accept(this);
      }

      return false;
   }

   public void endVisit(SparkInsertStatement x) {
   }

   public boolean visit(SparkInsertStatement.SparkFormat x) {
      this.print0(this.ucase ? "USING " : "using ");
      x.getFileFormat().accept(this);
      this.println();
      if (x.getOptions().size() > 0) {
         this.print0(this.ucase ? "OPTIONS " : "options ");
         this.print0(" (");

         for(int i = 0; i < x.getOptions().size(); ++i) {
            ((SQLAssignItem)x.getOptions().get(i)).accept(this);
            if (i != x.getOptions().size() - 1) {
               this.print0(" , ");
            }
         }

         this.print0(") ");
      }

      return false;
   }

   public void endVisit(SparkInsertStatement.SparkFormat x) {
   }

   public boolean visit(SparkLoadStatement x) {
      this.print0(this.ucase ? "LOAD DATA " : "load data ");
      if (x.isLocal()) {
         this.print0(this.ucase ? "LOCAL " : "local ");
      }

      this.print0(this.ucase ? "INPATH " : "inpath ");
      x.getInPath().accept(this);
      this.print0(" ");
      if (x.isOverwrite()) {
         this.print0(this.ucase ? "OVERWRITE " : "overwrite ");
      }

      this.print0(this.ucase ? "INTO TABLE " : "into table ");
      x.getTable().accept(this);
      return false;
   }

   public void endVisit(SparkLoadStatement x) {
   }

   public boolean visit(SparkSampleExpr x) {
      this.print0(this.ucase ? " TABLESAMPLE " : " tablesample ");
      this.print0("(");
      if (x.getPercent() != null) {
         x.getPercent().accept(this);
         this.print0(this.ucase ? " PERCENT" : " percent");
      }

      if (x.getRows() != null) {
         x.getRows().accept(this);
         this.print0(this.ucase ? " ROWS" : " rows");
      }

      if (x.getBucket() != null) {
         this.print0(this.ucase ? "BUCKET " : "bucket");
         x.getBucket().accept(this);
         this.print0(this.ucase ? " OUT OF " : " out of ");
         x.getBucketOutOf().accept(this);
      }

      this.print0(")");
      return false;
   }

   public void endVisit(SparkSampleExpr x) {
   }

   public boolean visit(SparkIntervalExpr x) {
      this.print0(this.ucase ? "INTERVAL " : "interval ");

      for(SparkIntervalExpr.SparkIntervalItem interval : x.getIntervals()) {
         interval.accept(this);
      }

      if (x.getTo() != null) {
         x.getTo().accept(this);
      }

      return false;
   }

   public void endVisit(SparkIntervalExpr x) {
   }

   public boolean visit(SparkIntervalExpr.SparkIntervalItem x) {
      this.print0(" ");
      x.getValue().accept(this);
      if (x.getType() != null) {
         this.print(" ");
         this.print0(this.ucase ? x.getType().toString() : x.getType().toString().toLowerCase());
      }

      return false;
   }

   public void endVisit(SparkIntervalExpr.SparkIntervalItem x) {
   }

   public boolean visit(SparkNumberExpr x) {
      this.print0(x.getLiteral());
      return false;
   }

   public void endVisit(SparkNumberExpr x) {
   }

   public void endVisit(SparkExplainStatement x) {
   }

   public boolean visit(HiveRowFormat.RowFormatSede x) {
      this.print0(this.ucase ? "SERDE " : "sede ");
      x.getSedeClass().accept(this);
      this.print0(" ");
      if (x.getProps() != null && x.getProps().size() > 0) {
         this.print0(this.ucase ? " WITH SERDEPROPERTIES (" : " with serderproperties (");

         for(int i = 0; i < x.getProps().size(); ++i) {
            ((SparkDefProperty)x.getProps().get(i)).accept(this);
            if (i != x.getProps().size() - 1) {
               this.print(",");
            }
         }

         this.print0(") ");
      }

      return false;
   }

   public void endVisit(HiveRowFormat.RowFormatSede x) {
   }

   public boolean visit(SparkTransformExpr.CommandScriptDef x) {
      this.print0(this.ucase ? "USING " : "using ");
      x.getCommandOrScript().accept(this);
      if (x.getColDefs() != null && x.getColDefs().size() > 0) {
         this.print0(this.ucase ? " AS  (" : " as (");

         for(int i = 0; i < x.getColDefs().size(); ++i) {
            ((SparkColumnDef)x.getColDefs().get(i)).accept(this);
            if (i != x.getColDefs().size() - 1) {
               this.print(",");
            }
         }

         this.print0(") ");
      }

      return false;
   }

   public void endVisit(SparkTransformExpr.CommandScriptDef x) {
   }

   public boolean visit(HiveRowFormat.RowFormatDelimitor x) {
      this.print0(this.ucase ? "DELIMITED  " : "delimited ");
      if (x.getFieldTerminatedChar() != null) {
         this.println();
         this.print0(this.ucase ? "FIELDS TERMINATED BY   " : "fields terminated by ");
         x.getFieldTerminatedChar().accept(this);
         if (x.getEscapeChar() != null) {
            this.print0(this.ucase ? " ESCAPED BY    " : " escaped by ");
            x.getEscapeChar().accept(this);
         }
      }

      if (x.getCollectionItemsTerminatedChar() != null) {
         this.println();
         this.print0(this.ucase ? "COLLECTION ITEMS TERMINATED BY " : "collection items terminated by ");
         x.getCollectionItemsTerminatedChar().accept(this);
      }

      if (x.getMapKeyTerminatedChar() != null) {
         this.println();
         this.print0(this.ucase ? "MAP KEYS TERMINATED BY " : "map keys terminated by ");
         x.getMapKeyTerminatedChar().accept(this);
      }

      if (x.getRowTerminatedChar() != null) {
         this.println();
         this.print0(this.ucase ? "LINES TERMINATED BY " : "lines terminated by ");
         x.getRowTerminatedChar().accept(this);
      }

      if (x.getNullChar() != null) {
         this.println();
         this.print0(this.ucase ? "NULL DEFINED AS " : "null defined as ");
         x.getNullChar().accept(this);
      }

      return false;
   }

   public void endVisit(HiveRowFormat.RowFormatDelimitor x) {
   }

   private void printHints(List<SQLHint> hints) {
      if (hints.size() > 0) {
         this.print0("/*+ ");
         this.printAndAccept(hints, ", ");
         this.print0(" */");
      }

   }

   public boolean visit(SparkSelectJoinTableSource x) {
      x.getLeft().accept(this);
      SQLTableSource right = x.getRight();
      if (x.getJoinType() == SQLJoinTableSource.JoinType.COMMA) {
         this.print0(", ");
         x.getRight().accept(this);
      } else {
         boolean isRoot = x.getParent() instanceof SQLSelectQueryBlock;
         if (isRoot) {
            ++this.indentCount;
         }

         this.println();
         this.print0(this.ucase ? x.getJoinType().name : x.getJoinType().name_lcase);
         this.print(' ');
         if (right instanceof SQLJoinTableSource) {
            this.print('(');
            this.incrementIndent();
            this.println();
            right.accept(this);
            this.decrementIndent();
            this.println();
            this.print(')');
         } else {
            right.accept(this);
         }

         if (isRoot) {
            --this.indentCount;
         }

         if (x.getCondition() != null) {
            this.print0(this.ucase ? " ON " : " on ");
            this.incrementIndent();
            x.getCondition().accept(this);
            this.decrementIndent();
            this.print(' ');
         }

         if (x.getUsing().size() > 0) {
            this.print0(this.ucase ? " USING (" : " using (");
            this.printAndAccept(x.getUsing(), ", ");
            this.print(')');
         }

         this.printFlashback(x.getFlashback());
      }

      SparkSelectPivotBase pivot = x.getPivot();
      if (pivot != null) {
         this.println();
         pivot.accept(this);
      }

      return false;
   }

   public void endVisit(SparkSelectJoinTableSource x) {
   }

   public boolean visit(SparkExplainStatement x) {
      this.print0(this.ucase ? "EXPLAIN" : "explain");
      if (x.getHints() != null && x.getHints().size() > 0) {
         this.print(' ');
         this.printAndAccept(x.getHints(), " ");
      }

      if (x.isExtended()) {
         this.print0(this.ucase ? " EXTENDED" : " extended");
      }

      if (x.isCodegen()) {
         this.print0(this.ucase ? " CODEGEN" : " codegen");
      }

      if (x.isCost()) {
         this.print0(this.ucase ? " COST" : " cost");
      }

      if (x.isFormatted()) {
         this.print0(this.ucase ? " FORMATTED" : " formatted");
      }

      this.println();
      x.getStatement().accept(this);
      return false;
   }

   public void endVisit(SparkShowDatabasesStatement x) {
   }

   public boolean visit(SparkShowDatabasesStatement x) {
      this.print0(this.ucase ? "SHOW " : "show ");
      if (x.isDatabases()) {
         this.print0(this.ucase ? "DATABASES" : "databases");
      } else {
         this.print0(this.ucase ? "SCHEMAS " : "schemas");
      }

      if (x.getLike() != null) {
         this.print0(this.ucase ? " LIKE " : " like ");
         x.getLike().accept(this);
      }

      return false;
   }

   public void endVisit(SparkRepairTableStatement x) {
   }

   public boolean visit(SparkAlterDatabaseStatement x) {
      this.print0(this.ucase ? "ALTER " : "alter ");
      x.getAlterType().accept(this);
      this.print0(" ");
      x.getName().accept(this);
      this.print0(" ");
      this.print0(this.ucase ? "SET " : "set ");
      if (x.getLocation() != null) {
         this.print0(this.ucase ? "LOCATION " : "location ");
         x.getLocation().accept(this);
      } else {
         x.getPropType().accept(this);
         this.print0(" ");
         this.acceptChild(x.getProps());
      }

      return false;
   }

   public void endVisit(SparkAlterDatabaseStatement x) {
   }

   public boolean visit(SparkAlterTableStatement x) {
      this.print0(this.ucase ? "ALTER TABLE " : "alter table ");
      x.getTable().accept(this);
      this.println();
      this.acceptChild((SQLObject)x.getRename());
      this.acceptChild((SQLObject)x.getAddColumns());
      this.acceptChild((SQLObject)x.getDropColumns());
      this.acceptChild((SQLObject)x.getRenameColumn());
      this.acceptChild((SQLObject)x.getChangeColumn());
      this.acceptChild((SQLObject)x.getReplaceColumns());
      this.acceptChild((SQLObject)x.getAddOrDropPartition());
      this.acceptChild((SQLObject)x.getSetUnsetProps());
      if (x.getAlterTableType() == SparkAlterTableStatement.AlterTableType.RECOVER_PARTITIONS) {
         this.print0(this.ucase ? "RECOVER PARTITIONS" : "recover partitions");
      }

      return false;
   }

   public void endVisit(SparkAlterTableStatement x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableReNameTo x) {
      this.print0(this.ucase ? "RENAME TO " : "rename to ");
      this.acceptChild((SQLObject)x.getName());
      this.acceptChild((SQLObject)x.getPartition());
      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableReNameTo x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableAddColumns x) {
      this.print0(this.ucase ? "ADD COLUMNS " : "add columns ");
      this.acceptChild(x.getColumns());
      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableAddColumns x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableDropColumns x) {
      this.print0(this.ucase ? "DROP " : "drop ");
      if (x.getColumns().size() == 0) {
         this.print0(this.ucase ? "COLUMNS " : "columns ");
      } else {
         this.print0(this.ucase ? "COLUMN " : "column ");
         this.acceptChild(x.getColumns());
      }

      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableDropColumns x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableRenameColumn x) {
      this.print0(this.ucase ? "RENAME COLUMN " : "rename column ");
      this.acceptChild((SQLObject)x.getColumn());
      this.print0(this.ucase ? " TO " : " to ");
      this.acceptChild((SQLObject)x.getTo());
      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableRenameColumn x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableChangeColumn x) {
      this.print0(this.ucase ? "ALTER  COLUMN " : "alter column ");
      this.acceptChild((SQLObject)x.getColumn());
      this.print0(" ");
      this.acceptChild((SQLObject)x.getSpec());
      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableChangeColumn x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableReplaceColumns x) {
      this.print0(this.ucase ? "REPLACE COLUMNS " : "replace columns ");
      this.acceptChild(x.getColumns());
      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableReplaceColumns x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableAddOrDropPartition x) {
      if (x.isAdd()) {
         this.print0(this.ucase ? "ADD " : "add ");
         if (x.isExists()) {
            this.print0(this.ucase ? "IF NOT EXISTS " : "if not exists ");
         }
      } else {
         this.print0(this.ucase ? "DROP " : "drop ");
         this.print0(this.ucase ? "IF EXISTS " : "if exists ");
      }

      this.acceptChild(x.getPartitions(), " ", "", " ");
      if (x.isPurge()) {
         this.print0(this.ucase ? " PRUGE " : " purge ");
      }

      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableAddOrDropPartition x) {
   }

   public boolean visit(SparkAlterTableStatement.SparkAlterTableSetUnsetProps x) {
      if (x.isUnset()) {
         this.print0(this.ucase ? "UNSET " : "unset ");
      } else {
         this.print0(this.ucase ? "SET " : "set ");
      }

      if (x.getFileFormat() != null) {
         this.print0(this.ucase ? "FILEFORMAT " : "fileformat ");
         this.acceptChild((SQLObject)x.getFileFormat());
      }

      if (x.getLocation() != null) {
         this.print0(this.ucase ? "LOCATION " : "location ");
         this.acceptChild((SQLObject)x.getLocation());
      }

      if (x.getSerde() != null) {
         this.print0(this.ucase ? "SERDE " : "serde ");
         this.acceptChild((SQLObject)x.getSerde());
         this.print0(" ");
         if (x.getProps().size() > 0) {
            this.print0(this.ucase ? "WITH " : "with ");
         }
      }

      if (x.getProps().size() > 0) {
         if (x.isTblProps()) {
            this.print0(this.ucase ? "TBLPROPERTIES " : "tblproperties ");
         } else {
            this.print0(this.ucase ? "SERDEPROPERTIES " : "serdeproperties ");
         }
      }

      if (x.isExists()) {
         this.print0(this.ucase ? "IF EXISTS " : "if exists ");
      }

      this.acceptChild(x.getProps());
      return false;
   }

   public void endVisit(SparkAlterTableStatement.SparkAlterTableSetUnsetProps x) {
   }

   public boolean visit(SparkAlterViewStatement x) {
      this.print0(this.ucase ? "ALTER VIEW " : "alter view ");
      this.acceptChild((SQLObject)x.getName());
      this.print0(" ");
      this.acceptChild((SQLObject)x.getRename());
      this.acceptChild((SQLObject)x.getSetProps());
      if (x.getSelect() != null) {
         this.print0(this.ucase ? "AS " : "as ");
         this.println();
         this.acceptChild((SQLObject)x.getSelect());
      }

      return false;
   }

   public void endVisit(SparkAlterViewStatement x) {
   }

   public boolean visit(SparkAlterViewStatement.SparkAlterViewReNameTo x) {
      this.print0(this.ucase ? "RENAME TO " : "rename to ");
      this.acceptChild((SQLObject)x.getName());
      return false;
   }

   public void endVisit(SparkAlterViewStatement.SparkAlterViewReNameTo x) {
   }

   public boolean visit(SparkAlterViewStatement.SparkAlterViewSetUnsetProps x) {
      if (x.isUnset()) {
         this.print0(this.ucase ? "UNSET TBLPROPERTIES " : "unset tblproperties ");
      } else {
         this.print0(this.ucase ? "SET TBLPROPERTIES " : "set tblproperties ");
         if (x.isExists()) {
            this.print0(this.ucase ? "IF EXISTS  " : "if exists ");
         }
      }

      this.acceptChild(x.getProps());
      return false;
   }

   public void endVisit(SparkAlterViewStatement.SparkAlterViewSetUnsetProps x) {
   }

   public boolean visit(SparkColumnSpecExpr x) {
      if (x.getName() != null) {
         x.getName().accept(this);
      }

      if (x.getType() != null) {
         this.print0(" ");
         x.getType().accept(this);
      }

      if (x.getComment() != null) {
         this.print0(" ");
         this.print0(this.ucase ? "COMMENT " : "comment ");
         x.getComment().accept(this);
      }

      if (x.getPosition() != null) {
         this.print0(" ");
         x.getPosition().accept(this);
      }

      return false;
   }

   public void endVisit(SparkColumnSpecExpr x) {
   }

   public boolean visit(SparkCreateDatabaseStatement x) {
      this.print0(this.ucase ? "CREATE " : "create ");
      x.getType().accept(this);
      this.print0(" ");
      if (x.isExists()) {
         this.print0(this.ucase ? "IF NOT EXISTS " : "if not exists ");
      }

      x.getName().accept(this);
      if (x.getComment() != null) {
         this.print0(" ");
         this.print0(this.ucase ? "COMMENT " : "comment ");
         x.getComment().accept(this);
      }

      if (x.getLocation() != null) {
         this.print0(" ");
         this.print0(this.ucase ? "LOCATION " : "location ");
         x.getLocation().accept(this);
      }

      if (x.getProps() != null && x.getProps().size() > 0) {
         this.print0(this.ucase ? " WITH DBPROPERTIES  " : " with dbproperties ");
         this.acceptChild(x.getProps());
      }

      return false;
   }

   public void endVisit(SparkCreateDatabaseStatement x) {
   }

   public boolean visit(SparkCacheTableStatement x) {
      this.print0(this.ucase ? "CACHE " : "cache ");
      if (x.isLazy()) {
         this.print0(this.ucase ? "LAZY " : "lazy ");
      }

      this.print0(this.ucase ? "TABLE " : "table ");
      x.getTable().accept(this);
      this.print0(" ");
      if (x.getOptions().size() > 0) {
         this.println();
         this.print0(this.ucase ? "OPTIONS " : "options ");
         this.acceptChild(x.getOptions());
      }

      if (x.getQuery() != null) {
         x.getQuery().accept(this);
      }

      return false;
   }

   public void endVisit(SparkCacheTableStatement x) {
   }

   public boolean visit(SparkCacheTableStatement.SparkCacheTableStatementQuery x) {
      this.println();
      this.print0(this.ucase ? "AS " : "as ");
      if (x.getTableQuery() != null) {
         this.println();
         this.print0(this.ucase ? "TABLE " : "table ");
         x.getTableQuery().accept(this);
      }

      if (x.getFromQuery() != null) {
         this.println();
         this.print0(this.ucase ? "FROM " : "from ");
         x.getTableQuery().accept(this);
      }

      if (x.getQuery() != null) {
         this.println();
         x.getQuery().accept(this);
      }

      return false;
   }

   public void endVisit(SparkCacheTableStatement.SparkCacheTableStatementQuery x) {
   }

   private void acceptChild(SQLObject e) {
      if (e != null) {
         e.accept(this);
      }
   }

   private void acceptChild(List e) {
      this.acceptChild(e, ", ", "(", ")");
   }

   private void acceptChild(List e, String splitor, String begin, String end) {
      if (e != null && e.size() != 0) {
         if (begin != null) {
            this.print0(begin);
         }

         for(int i = 0; i < e.size(); ++i) {
            SQLObject o = (SQLObject)e.get(i);
            if (o != null) {
               o.accept(this);
               if (i != e.size() - 1 && splitor != null) {
                  this.print0(splitor);
               }
            }
         }

         if (end != null) {
            this.print0(end);
         }

      }
   }

   public boolean visit(SparkRepairTableStatement x) {
      this.print0(this.ucase ? "MSCK REPAIR TABLE " : "msck repair table ");
      x.getName().accept(this);
      if (x.getAction() != null) {
         this.print0(" ");
         this.print0(x.getAction().name);
         this.print0(this.ucase ? " PARTITIONS " : " partitions");
      }

      return false;
   }

   public void endVisit(SparkCreateViewStatement x) {
   }

   public boolean visit(SparkCreateViewStatement x) {
      this.print0(this.ucase ? "CREATE " : "create ");
      if (x.isOrReplace()) {
         this.print0(this.ucase ? "OR REPLACE " : "or replace ");
      }

      if (x.isGlobal()) {
         this.print0(this.ucase ? "GLOBAL" : "global");
      }

      if (x.isTemporary()) {
         this.print0(this.ucase ? " TEMPORARY " : " temporary ");
      }

      ++this.indentCount;
      String algorithm = x.getAlgorithm();
      if (algorithm != null && algorithm.length() > 0) {
         this.print0(this.ucase ? "ALGORITHM = " : "algorithm = ");
         this.print0(algorithm);
         this.println();
      }

      SQLName definer = x.getDefiner();
      if (definer != null) {
         this.print0(this.ucase ? "DEFINER = " : "definer = ");
         definer.accept(this);
         this.println();
      }

      String sqlSecurity = x.getSqlSecurity();
      if (sqlSecurity != null && sqlSecurity.length() > 0) {
         this.print0(this.ucase ? "SQL SECURITY = " : "sql security = ");
         this.print0(sqlSecurity);
         this.println();
      }

      --this.indentCount;
      this.print0(this.ucase ? "VIEW " : "view ");
      if (x.isIfNotExists()) {
         this.print0(this.ucase ? "IF NOT EXISTS " : "if not exists ");
      }

      x.getTableSource().accept(this);
      List<SQLTableElement> columns = x.getColumns();
      if (columns.size() > 0) {
         this.print0(" (");
         ++this.indentCount;
         this.println();

         for(int i = 0; i < columns.size(); ++i) {
            if (i != 0) {
               this.print0(", ");
               this.println();
            }

            ((SQLTableElement)columns.get(i)).accept(this);
         }

         --this.indentCount;
         this.println();
         this.print(')');
      }

      SQLLiteralExpr comment = x.getComment();
      if (comment != null) {
         this.println();
         this.print0(this.ucase ? "COMMENT " : "comment ");
         comment.accept(this);
      }

      this.printTblProperties(x.getTblProperties());
      this.println();
      this.print0(this.ucase ? "AS" : "as");
      this.println();
      x.getSubQuery().accept(this);
      if (x.isWithLocal()) {
         this.println();
         this.print0(this.ucase ? "WITH LOCAL" : "with local");
      }

      if (x.isWithCascaded()) {
         this.println();
         this.print0(this.ucase ? "WITH CASCADED" : "with cascaded");
      }

      if (x.isWithCheckOption()) {
         if (!x.isWithLocal() && !x.isWithCascaded()) {
            this.println();
            this.print0(this.ucase ? "WITH" : "with");
         }

         this.print0(this.ucase ? " CHECK OPTION" : " check option");
      }

      if (x.isWithReadOnly()) {
         this.println();
         this.print0(this.ucase ? "WITH READ ONLY" : "with read only");
      }

      return false;
   }

   public void endVisit(SparkShowCreateTableStatement x) {
   }

   protected void printTblProperties(List<SQLAssignItem> tblProperties) {
      if (tblProperties.size() > 0) {
         this.println();
         this.print0(this.ucase ? "TBLPROPERTIES (" : "tblproperties (");
         this.incrementIndent();
         this.println();
         int i = 0;

         for(SQLAssignItem property : tblProperties) {
            if (i != 0) {
               this.print(",");
               this.println();
            }

            String key = property.getTarget().toString();
            boolean unquote = false;
            char c0;
            if (key.length() > 0 && (c0 = key.charAt(0)) != '"' && c0 != '`' && c0 != '\'') {
               unquote = true;
            }

            if (unquote) {
               this.print('\'');
            }

            this.print0(key);
            if (unquote) {
               this.print('\'');
            }

            this.print0(" = ");
            property.getValue().accept(this);
            ++i;
         }

         this.decrementIndent();
         this.println();
         this.print(')');
      }

   }

   public boolean visit(SparkShowCreateTableStatement x) {
      List<SQLCommentHint> headHints = x.getHeadHintsDirect();
      if (headHints != null) {
         for(SQLCommentHint hint : headHints) {
            hint.accept(this);
            this.println();
         }
      }

      if (x.isAll()) {
         this.print0(this.ucase ? "SHOW ALL CREATE TABLE " : "show all create table ");
      } else {
         this.print0(this.ucase ? "SHOW CREATE TABLE " : "show create table ");
      }

      x.getTable().accept(this);
      if (x.isSerde()) {
         this.print0(this.ucase ? " AS SERDE" : " as serde");
      }

      return false;
   }

   public boolean visit(SparkRefreshStatement x) {
      this.print0(this.ucase ? "REFRESH " : "refresh ");
      if (x.getType() == SparkRefreshStatement.Type.TABLE) {
         this.print0(this.ucase ? "TABLE " : "table ");
      } else if (x.getType() == SparkRefreshStatement.Type.FUNCTION) {
         this.print0(this.ucase ? "FUNCTION " : "function ");
      }

      x.getName().accept(this);
      return false;
   }

   public void endVisit(SparkRefreshStatement x) {
   }

   public boolean visit(SparkShowTableStatement x) {
      List<SQLCommentHint> headHints = x.getHeadHintsDirect();
      if (headHints != null) {
         for(SQLCommentHint hint : headHints) {
            hint.accept(this);
            this.println();
         }
      }

      this.print0(this.ucase ? "SHOW TABLE" : "show table");
      if (x.isExtended()) {
         this.print0(this.ucase ? " EXTENDED" : " extended");
      }

      if (x.getDatabase() != null) {
         this.print0(this.ucase ? " FROM " : " from ");
         x.getDatabase().accept(this);
      }

      if (x.getLike() != null) {
         this.print0(this.ucase ? " LIKE " : " like ");
         x.getLike().accept(this);
      }

      if (x.getPartition() != null) {
         this.print0(" ");
         x.getPartition().accept0(this);
      }

      return false;
   }

   public void endVisit(SparkShowTableStatement x) {
   }
}
