package util.sqlparse.visitor.postgresql.visitor;

import bean.Column;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLPartition;
import com.alibaba.druid.sql.ast.expr.SQLDbLinkExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.expr.SQLQueryExpr;
import com.alibaba.druid.sql.ast.expr.SQLSequenceExpr;
import com.alibaba.druid.sql.ast.expr.SQLValuableExpr;
import com.alibaba.druid.sql.ast.statement.SQLAlterTableItem;
import com.alibaba.druid.sql.ast.statement.SQLColumnConstraint;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLColumnReference;
import com.alibaba.druid.sql.ast.statement.SQLDeleteStatement;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLInsertStatement;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLLateralViewTableSource;
import com.alibaba.druid.sql.ast.statement.SQLPrivilegeItem;
import com.alibaba.druid.sql.ast.statement.SQLRevokeStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem;
import com.alibaba.druid.sql.ast.statement.SQLSelectQueryBlock;
import com.alibaba.druid.sql.ast.statement.SQLSelectStatement;
import com.alibaba.druid.sql.ast.statement.SQLSubqueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLTableElement;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUnionQuery;
import com.alibaba.druid.sql.ast.statement.SQLUnionQueryTableSource;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.ast.statement.SQLUpdateStatement;
import com.alibaba.druid.sql.ast.statement.SQLWithSubqueryClause;
import com.alibaba.druid.sql.dialect.mysql.ast.MysqlForeignKey;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.PGSQLObjectCollection;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.tablesource.PGExprTableSource;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.tablesource.PGSubqueryTableSource;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGAlterTableStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGCreateTableStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGCreateViewStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGDeleteStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGGrantStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGInsertStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGSelectQueryBlock;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGUpdateStatement;
import com.alibaba.druid.stat.TableStat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import util.sqlparse.visitor.common.Owner;
import util.sqlparse.visitor.common.memo.AliasField;
import util.sqlparse.visitor.common.memo.FieldMemo;
import util.sqlparse.visitor.common.memo.TableMemo;
import util.sqlparse.visitor.common.scope.ClauseScope;
import util.sqlparse.visitor.common.scope.Scope;
import util.sqlparse.visitor.common.scope.TableScope;
import util.sqlparse.visitor.common.utils.Alias;

public class ScopeVisitor extends ParseVisitor {
   private final Alias alias = new Alias();
   private final String ALIAS_COLUMN_TAG = "alias-column";
   private boolean debug = false;

   public ScopeVisitor(Scope scope) {
      super(scope);
   }

   public boolean visit(SQLSelectStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Select;
      }

      if (this.debug) {
         System.out.println("enter SQLSelectStatement " + x.toString());
      }

      this.enterClauseScope(x);
      return true;
   }

   public boolean visit(SQLInsertStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Insert;
      }

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

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

      if (x.getColumns().size() == 0) {
         String schema = null;
         String table = null;
         PGInsertStatement stmt = (PGInsertStatement)x;
         if (stmt.getQuery() != null) {
            stmt.getQuery().accept(this);
         } else {
            SQLName tableName = x.getTableName();
            if (tableName instanceof SQLIdentifierExpr) {
               SQLIdentifierExpr expr = (SQLIdentifierExpr)tableName;
               table = expr.getName();
               schema = this.scope.context.getSchema();
            } else if (tableName instanceof SQLPropertyExpr) {
               SQLPropertyExpr expr = (SQLPropertyExpr)tableName;
               table = expr.getName();
               schema = expr.getOwnerName();
            }

            if (table != null && schema != null) {
               List<Column> columns = this.scope.context.getColumns(schema, table);
               if (columns != null && columns.size() > 0) {
                  for(Column column : columns) {
                     String name = this.scope.dialector.wrap(column.getColumnName());
                     SQLExpr expr = new SQLIdentifierExpr(name);
                     x.getColumns().add(expr);
                     expr.setParent(x);
                  }
               }
            }
         }
      }

      for(SQLExpr column : x.getColumns()) {
         column.accept(this);
         FieldMemo field = (FieldMemo)column.getAttribute("field");
         if (field != null) {
            field.isSelectItem = true;
         }
      }

      if (x.getValuesList() != null) {
         for(SQLInsertStatement.ValuesClause clause : x.getValuesList()) {
            clause.accept(this);
         }
      }

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

      return false;
   }

   public boolean visit(PGInsertStatement x) {
      return this.visit((SQLInsertStatement)x);
   }

   public boolean visit(SQLUpdateStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Update;
      }

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

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

      for(int i = 0; i < x.getItems().size(); ++i) {
         SQLUpdateSetItem item = (SQLUpdateSetItem)x.getItems().get(i);
         if (item != null) {
            item.accept(this);
         }
      }

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

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

      return false;
   }

   public boolean visit(PGUpdateStatement x) {
      return this.visit((SQLUpdateStatement)x);
   }

   public boolean visit(SQLDeleteStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Delete;
      }

      this.enterClauseScope(x);
      this.visitChild(x.getTableSource());
      String schema = null;
      String table = null;
      SQLName tableName = x.getTableName();
      if (tableName instanceof SQLIdentifierExpr) {
         SQLIdentifierExpr expr = (SQLIdentifierExpr)tableName;
         table = expr.getName();
         schema = this.scope.context.getSchema();
      } else if (tableName instanceof SQLPropertyExpr) {
         SQLPropertyExpr expr = (SQLPropertyExpr)tableName;
         table = expr.getName();
         schema = expr.getOwnerName();
      }

      ArrayList<SQLExpr> columList = new ArrayList();
      if (table != null && schema != null) {
         List<Column> columns = this.scope.context.getColumns(schema, table);
         if (columns != null && columns.size() > 0) {
            for(Column column : columns) {
               String name = this.scope.dialector.wrap(column.getColumnName());
               SQLExpr expr = new SQLIdentifierExpr(name);
               columList.add(expr);
               expr.setParent(x);
            }
         }
      }

      for(SQLExpr column : columList) {
         column.accept(this);
         FieldMemo field = (FieldMemo)column.getAttribute("field");
         if (field != null) {
            field.isSelectItem = true;
         }
      }

      return true;
   }

   public boolean visit(PGDeleteStatement x) {
      return this.visit((SQLDeleteStatement)x);
   }

   public boolean visit(SQLWithSubqueryClause x) {
      if (this.debug) {
         System.out.println("enter SQLWithSubqueryClause  " + x.toString());
      }

      Scope scope = this.enterClauseScope(x);
      scope.top = true;
      scope.fromField = scope.isFromField(x);
      return true;
   }

   public boolean visit(SQLWithSubqueryClause.Entry x) {
      TableScope scope = this.enterTableScope(x);
      scope.fromField = scope.isFromField(x);
      scope.top = true;
      if (x.getSubQuery() != null) {
         x.getSubQuery().accept(this);
      }

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

      TableMemo table = this.registerTable(x);
      table.topTable = true;
      x.putAttribute("table", table);
      x.putAttribute("scope", scope);
      scope.table = table;
      return false;
   }

   public void endVisit(SQLWithSubqueryClause.Entry x) {
      if (this.debug) {
         System.out.println("enter SQLWithSubqueryClause.Entry  " + x.toString());
      }

      Scope current = this.scope.getCurrent();
      TableMemo table = (TableMemo)x.getAttribute("table");
      boolean addColumns = x.getColumns().size() == 0;
      List<FieldMemo> fields = new ArrayList();
      current.getSubFieldsForWith(fields);

      for(Scope child : current.getChildren()) {
         table.getChildren().addAll(child.getBranchTables());
      }

      int k = 0;

      for(FieldMemo field : fields) {
         FieldMemo newNameField = field.copy();
         newNameField.name = field.alias;
         newNameField.alias = field.alias;
         if (!addColumns) {
            SQLExpr expr = (SQLExpr)x.getColumns().get(k);
            SQLName exprName = (SQLName)expr;
            String fieldName = exprName.getSimpleName();
            if (!this.isEqual(fieldName, field.alias)) {
               newNameField.name = fieldName;
               newNameField.alias = fieldName;
            }

            newNameField.ref = expr;
            newNameField.exprs.add(expr);
         } else {
            SQLIdentifierExpr name = new SQLIdentifierExpr();
            String fieldName = newNameField.name;

            for(AliasField aliasField : field.aliasFields) {
               if (aliasField.isOutput) {
                  fieldName = aliasField.alias;
                  break;
               }
            }

            newNameField.name = fieldName;
            newNameField.alias = fieldName;
            name.setName(fieldName);
            x.getColumns().add(name);
            name.setParent(x);
            newNameField.ref = name;
            newNameField.exprs.add(name);
         }

         newNameField.table = table;
         newNameField.scope = current;
         newNameField.tables.add(table);
         newNameField.children.add(field);
         current.addField(newNameField);
         ++k;
      }

      this.scope.exitScope();
   }

   public boolean visit(SQLSelectQueryBlock x) {
      this.enterClauseScope(x);
      this.scope.fromField = this.scope.isFromField(x);
      if (x.getFrom() != null) {
         this.parseView(x.getFrom(), x);
      } else {
         SQLIdentifierExpr tableName = new SQLIdentifierExpr();
         tableName.setName("dual");
         SQLExprTableSource tableSource = new SQLExprTableSource();
         tableSource.putAttribute("virtualTable", true);
         tableSource.setExpr((SQLExpr)tableName);
         tableSource.setParent(x);
         x.setFrom((SQLTableSource)tableSource);
      }

      this.visitChild(x.getFrom());
      this.parseSelectList(x);
      this.visitChild(x.getWindows());
      this.visitChild(x.getWhere());
      this.visitChild(x.getStartWith());
      this.visitChild(x.getConnectBy());
      this.visitChild(x.getGroupBy());
      this.visitChild(x.getOrderBy());
      this.visitChild(x.getDistributeBy());
      this.visitChild(x.getSortBy());
      this.visitChild(x.getWaitTime());
      return false;
   }

   public boolean visit(PGSelectQueryBlock x) {
      return this.visit((SQLSelectQueryBlock)x);
   }

   private void parseSelectList(SQLSelectQueryBlock x) {
      Scope current = this.scope.getCurrent();
      List<SQLSelectItem> selectList = x.getSelectList();

      for(int i = 0; i < selectList.size(); ++i) {
         SQLSelectItem item = (SQLSelectItem)selectList.get(i);
         boolean isAll = this.scope.isAllColumn(item.getExpr());
         if (item.getAlias() == null && !isAll) {
            this.setAlias(item);
         }

         if (isAll && item.getAttribute("expanded") == null) {
            item.putAttribute("expanded", true);
            SQLObject parent = item.getParent();
            Owner owner = this.scope.getAllOwner(item.getExpr());
            TableMemo table = owner.alias == null ? null : current.getTable(item.getExpr());
            SQLTableSource tableSource = x.getFrom();
            boolean isSub = table != null && table.isSub || tableSource instanceof SQLSubqueryTableSource || tableSource instanceof SQLUnionQueryTableSource;
            List<FieldMemo> fields = current.expandAllColumns(table, isSub);
            if (fields.size() != 0) {
               int k = 0;
               selectList.remove(i);
               String alias = x.getFrom().getAlias();
               if (alias == null) {
                  alias = owner.alias;
               }

               for(FieldMemo field : fields) {
                  if (field.name != null && !field.name.equalsIgnoreCase("*")) {
                     TableMemo fieldTable = field.table;
                     SQLPropertyExpr propExpr = new SQLPropertyExpr();
                     propExpr.setOwner(alias == null ? fieldTable.alias : alias);
                     propExpr.setName(field.alias);
                     SQLSelectItem newItem = new SQLSelectItem();
                     newItem.setParent(parent);
                     newItem.setExpr(propExpr);
                     newItem.setAlias(field.alias);
                     field.scope = current;
                     field.isSelectItem = true;
                     field.ref = newItem;
                     field.exprs.add(propExpr);
                     if (field.containsAttribute("alias-column")) {
                        newItem.putAttribute("alias-column");
                        newItem.putAttribute("field", field);
                        propExpr.putAttribute("field", field);
                     }

                     selectList.add(i + k++, newItem);
                  }
               }
            }
         }
      }

      int k = Integer.MIN_VALUE;

      for(int i = 0; i < selectList.size(); ++i) {
         SQLSelectItem item = (SQLSelectItem)selectList.get(i);
         SQLExpr sqlExpr = item.getExpr();
         boolean isPrimitive = this.scope.isPrimitive(sqlExpr);
         item.putAttribute("isPrimitive", isPrimitive);
         if (!item.containsAttribute("alias-column")) {
            item.accept(this);
         }

         FieldMemo field = (FieldMemo)sqlExpr.getAttribute("field");
         if (sqlExpr instanceof SQLValuableExpr) {
            field.isConstant = true;
         }

         field.id = k++;
         field.getFieldIds().add(i);
      }

   }

   private SQLTableSource parseView(SQLTableSource ts, SQLObject parent) {
      if (!(ts instanceof SQLExprTableSource) && !(ts instanceof PGExprTableSource)) {
         if (ts instanceof SQLJoinTableSource) {
            SQLJoinTableSource jts = (SQLJoinTableSource)ts;
            SQLTableSource left = this.parseView(jts.getLeft(), jts);
            if (left != null) {
               jts.setLeft(left);
               left.setParent(jts);
            }

            SQLTableSource right = this.parseView(jts.getRight(), jts);
            if (right != null) {
               jts.setRight(right);
               right.setParent(jts);
            }
         }
      } else {
         SQLTableSource viewTableSource = this.getViewTableSource(ts);
         if (viewTableSource != null) {
            if (parent instanceof PGExprTableSource) {
               PGSelectQueryBlock block = (PGSelectQueryBlock)parent;
               block.setFrom(viewTableSource);
               viewTableSource.setParent(parent);
            }

            return viewTableSource;
         }
      }

      return null;
   }

   private SQLTableSource getViewTableSource(SQLTableSource ts) {
      SQLExprTableSource e = (SQLExprTableSource)ts;
      String name = e.getTableName();
      if (name == null) {
         return ts;
      } else {
         String schema = e.getSchema() == null ? this.scope.getDefaultSchema() : e.getSchema();
         SQLSelect view = this.scope.context.getView(schema.toLowerCase(), name.toLowerCase());
         if (view != null && this.scope.context.sqlType == TableStat.Mode.Select) {
            SQLSubqueryTableSource subQuery = new SQLSubqueryTableSource();
            String alias = e.getAlias() == null ? e.getTableName() : e.getAlias();
            subQuery.setAlias(alias);
            subQuery.setSelect(view.clone());
            return subQuery;
         } else {
            return null;
         }
      }
   }

   public boolean visit(SQLUnionQuery x) {
      Scope scope = this.enterClauseScope(x);
      scope.union = true;
      scope.isolated = false;
      this.visitChild(x.getRelations());

      for(Scope s1 : scope.getChildren()) {
         s1.isolated = false;
      }

      this.visitChild(x.getOrderBy());
      this.visitChild(x.getLimit());
      return false;
   }

   public boolean visit(SQLLateralViewTableSource x) {
      TableMemo table = this.registerTable((SQLTableSource)x);
      TableScope scope = this.enterTableScope(x);
      scope.table = table;
      scope.isolated = false;
      return true;
   }

   public boolean visit(SQLExprTableSource x) {
      this.registerTable((SQLTableSource)x);
      return true;
   }

   public boolean visit(SQLSubqueryTableSource x) {
      if (this.debug) {
         System.out.println("enter SQLSubqueryTableSource  " + x.toString());
      }

      if (x.getAlias() == null) {
         x.setAlias(this.alias.aliasTable());
      }

      TableMemo table = this.registerTable((SQLTableSource)x);
      TableScope scope = this.enterTableScope(x);
      scope.isolated = false;
      scope.table = table;
      if (x.getSelect() != null) {
         x.getSelect().accept(this);
      }

      for(Scope s : scope.getChildren()) {
         s.isolated = false;
      }

      return false;
   }

   public boolean visit(PGSubqueryTableSource x) {
      if (this.debug) {
         System.out.println("enter SQLSubqueryTableSource  " + x.toString());
      }

      if (x.getAlias() == null) {
         x.setAlias(this.alias.aliasTable());
      }

      TableMemo table = this.registerTable((SQLTableSource)x);
      TableScope scope = this.enterTableScope(x);
      scope.table = table;
      scope.isolated = false;
      if (x.getSelect() != null) {
         x.getSelect().accept(this);
      }

      for(Scope s : scope.getChildren()) {
         s.isolated = false;
      }

      return false;
   }

   public boolean visit(PGExprTableSource x) {
      this.registerTable((SQLTableSource)x);
      return true;
   }

   private void parseVirtualTable(SQLExprTableSource ts) {
      if (ts != null) {
         List<SQLName> columns = ts.getColumns();
         if (columns != null && columns.size() != 0) {
            TableMemo table = (TableMemo)ts.getAttribute("table");
            if (table != null) {
               ;
            }
         }
      }
   }

   public void endVisit(PGExprTableSource x) {
      if (!(x.getExpr() instanceof SQLIdentifierExpr) && !(x.getExpr() instanceof SQLDbLinkExpr)) {
         if (this.debug) {
            System.out.println("exit GPExprTableSource  " + x);
         }
      } else if (this.debug) {
         System.out.println("end GPExprTableSource" + x.toString());
      }

   }

   public boolean visit(SQLJoinTableSource x) {
      this.scope.getCurrent().join = true;
      return true;
   }

   public boolean visit(SQLUnionQueryTableSource x) {
      TableMemo table = this.registerTable((SQLTableSource)x);
      TableScope scope = this.enterTableScope(x);
      scope.union = true;
      scope.fromField = scope.isFromField(x);
      x.putAttribute("table", table);
      x.putAttribute("scope", scope);
      scope.table = table;
      scope.isolated = false;
      this.visitChild(x.getUnion());

      for(Scope s1 : scope.getChildren()) {
         s1.isolated = false;
      }

      return false;
   }

   public boolean visit(SQLSelectItem x) {
      FieldMemo field = this.createField(x.getExpr(), x.getAlias(), true);
      field.ref = x;
      return false;
   }

   public boolean visit(SQLIdentifierExpr x) {
      if (this.scope.getCurrent().isFieldState()) {
         FieldMemo field = this.createField(x, x.getName(), false);
         x.putAttribute("field", field);
      } else if (!(x.getParent() instanceof SQLTableSource) && !(x.getParent() instanceof SQLPartition) && !(x.getParent() instanceof SQLDbLinkExpr)) {
         x.putAttribute("orderBy", x.getParent() instanceof SQLSelectOrderByItem);
         FieldMemo field = this.createField(x, x.getName(), false);
         x.putAttribute("field", field);
         if (x.getParent() instanceof SQLUpdateSetItem) {
            field.isSelectItem = true;
         }

         SQLObject parent = x.getParent();
         if (parent instanceof SQLListExpr) {
            SQLListExpr parentList = (SQLListExpr)parent;
            if (parentList.getParent() instanceof SQLUpdateSetItem) {
               field.isSelectItem = true;
            }
         }
      }

      return false;
   }

   public boolean visit(SQLPropertyExpr x) {
      if (this.scope.getCurrent().isFieldState()) {
         this.createField(x, x.getName(), false);
      } else if (!(x.getParent() instanceof SQLTableSource) && !(x.getParent() instanceof SQLPartition)) {
         FieldMemo field = this.createField(x, x.getName(), false);
         if (x.getParent() instanceof SQLUpdateSetItem) {
            field.isSelectItem = true;
         }
      }

      return false;
   }

   private void createSequenceTable(String name) {
      Scope current = this.scope.getCurrent();
      TableMemo table = new TableMemo();
      table.name = name;
      table.alias = name;
      table.nowName = table.alias;
      table.schema = this.scope.getDefaultSchema();
      table.sequence = true;
      table.scope = current;
      current.addTable(table);
      this.scope.context.append(table);
   }

   private FieldMemo createField(SQLExpr expr, String alias, boolean isSelectItem) {
      Scope current = this.scope.getCurrent();
      FieldMemo field = new FieldMemo();
      if (expr instanceof SQLIdentifierExpr) {
         SQLIdentifierExpr idExpr = (SQLIdentifierExpr)expr;
         boolean isOrderBy = expr.getAttribute("orderBy") != null && (Boolean)expr.getAttribute("orderBy");
         if (isOrderBy) {
            String name = current.dialector.wrap(idExpr.getSimpleName());
            FieldMemo exist = current.getFieldByAlias(name);
            if (exist != null) {
               return exist;
            }
         }

         field.name = idExpr.getName();
         field.atom = true;
         this.setField(field, alias, isSelectItem, expr);
         field.children = this.scope.getCurrent().getSubRelationFields(field);
         this.setAtom(field);
         field = current.addField(field);
      } else if (expr instanceof SQLPropertyExpr) {
         SQLPropertyExpr prop = (SQLPropertyExpr)expr;
         field.name = prop.getName();
         field.atom = true;
         this.setField(field, alias, isSelectItem, expr);
         field.children = this.scope.getCurrent().getSubRelationFields(field);
         this.setAtom(field);
         field = current.addField(field);
      } else if (expr instanceof SQLSequenceExpr) {
         SQLSequenceExpr prop = (SQLSequenceExpr)expr;
         field.name = prop.getFunction().toString();
         this.createSequenceTable(prop.getSequence().toString());
         this.setField(field, alias, isSelectItem, expr);
         field.children = this.scope.getCurrent().getSubRelationFields(field);
         field = current.addField(field);
      } else if (expr instanceof SQLValuableExpr) {
         field.name = alias;
         field.isConstant = true;
         this.setField(field, alias, isSelectItem, expr);
         field = current.addField(field);
      } else {
         alias = current.dialector.wrap(alias);
         field.name = alias;
         field.alias = alias;
         field.complex = true;
         field.isSelectItem = isSelectItem;
         field.ref = expr;
         if (expr instanceof SQLSelectItem) {
            field.exprs.add(((SQLSelectItem)expr).getExpr());
            AliasField aliasField = new AliasField();
            aliasField.alias = alias;
            aliasField.field = field;
            aliasField.expr = expr;
            aliasField.isOutput = true;
            aliasField.scope = this.scope.getCurrent();
         } else {
            field.exprs.add(expr);
            AliasField aliasField = new AliasField();
            aliasField.alias = alias;
            aliasField.field = field;
            aliasField.expr = expr;
            aliasField.isOutput = true;
            aliasField.scope = this.scope.getCurrent();
         }

         current.openFieldState();
         expr.accept(this);
         current.closeFieldState();
         List<SQLExpr> exprs = current.getSubFieldExprs(expr);
         List<FieldMemo> subFields = new ArrayList();
         current.getFieldsByExprs(exprs, subFields);
         List<TableMemo> tables = new ArrayList();
         List<FieldMemo> children = new ArrayList();
         Set<String> names = new HashSet();
         boolean isPrimitive = this.scope.isPrimitive(expr);
         if (!isPrimitive) {
            current.getSubBranchFields(subFields);
         }

         if (subFields.size() <= 0) {
            tables.addAll(current.getBranchTables());
         } else {
            for(FieldMemo subField : subFields) {
               if (!names.contains(subField.getUniqueName())) {
                  children.add(subField);
                  names.add(subField.getUniqueName());
               }

               if (subField.table != null && !names.contains(subField.table.getQualifiedName())) {
                  tables.add(subField.table);
                  names.add(subField.table.getQualifiedName());
               } else if (subField.tables.size() > 0) {
                  for(TableMemo table : subField.tables) {
                     if (!names.contains(table.getQualifiedName())) {
                        tables.add(table);
                        names.add(table.getQualifiedName());
                     }
                  }
               }
            }
         }

         field.tables = tables;
         if (tables.size() == 1) {
            field.table = (TableMemo)tables.get(0);
         }

         field.children = children;
         this.setAtom(field);
         current.addField(field);
      }

      expr.putAttribute("field", field);
      return field;
   }

   private void setAtom(FieldMemo field) {
      if (field.children.size() != 0) {
         Set<String> fdNames = field.tableNames();
         boolean found = false;

         for(FieldMemo child : field.children) {
            if (this.isEqual(field.name, child.alias)) {
               Set<String> cdNames = child.tableNames();
               cdNames.retainAll(fdNames);
               if (cdNames.size() > 0) {
                  found = true;
               }
            }
         }

         if (!found) {
            field.atom = false;
         }

      }
   }

   private void setField(FieldMemo field, String alias, boolean isSelectItem, SQLExpr expr) {
      Scope current = this.scope.getCurrent();
      field.alias = alias == null ? field.name : alias;
      field.alias = current.dialector.wrap(field.alias);
      field.name = current.dialector.wrap(field.name);
      field.isSelectItem = isSelectItem;
      field.ref = expr;
      if (expr instanceof SQLSequenceExpr) {
         field.table = this.scope.getCurrent().getTable(((SQLSequenceExpr)expr).getSequence());
      } else {
         field.table = this.scope.getCurrent().getTable(expr);
      }

      field.tables.add(field.table);
      field.exprs.add(expr);
      AliasField aliasField = new AliasField();
      aliasField.alias = alias;
      aliasField.field = field;
      aliasField.expr = expr;
      aliasField.isOutput = isSelectItem;
      aliasField.scope = this.scope.getCurrent();
   }

   private TableMemo registerTable(SQLTableSource x) {
      Scope current = this.scope.getCurrent();
      if (x instanceof SQLExprTableSource) {
         SQLExprTableSource exprTableSource = (SQLExprTableSource)x;
         TableMemo table = new TableMemo(x);
         SQLExprTableSource tableSource = (SQLExprTableSource)x;
         String tableName = current.dialector.wrap(tableSource.getTableName());
         String schemaName = current.dialector.wrap(tableSource.getSchema() == null ? this.scope.getDefaultSchema() : tableSource.getSchema());
         table.name = tableName;
         table.alias = current.dialector.wrap(tableSource.getAlias() == null ? table.name : tableSource.getAlias());
         table.nowName = table.alias;
         table.schema = schemaName;
         if (table.name == null) {
            table.name = table.alias;
         }

         if (table.name.equals("*") && exprTableSource.getExpr() != null && exprTableSource.getExpr() instanceof SQLPropertyExpr) {
            return null;
         } else {
            if (table.name == null && table.alias != null) {
               table.name = table.alias;
            }

            if (exprTableSource.getExpr() != null && exprTableSource.getExpr() instanceof SQLMethodInvokeExpr) {
               table.isSub = true;
            }

            if (exprTableSource.getExpr() != null && exprTableSource.getExpr() instanceof SQLDbLinkExpr) {
               SQLDbLinkExpr link = (SQLDbLinkExpr)exprTableSource.getExpr();
               Owner owner = this.scope.getOwner(link.getExpr());
               table.name = current.dialector.wrap(owner.table);
               if (owner.schema != null) {
                  table.schema = current.dialector.wrap(owner.schema);
               }

               if (link.getExpr().getParent() == null) {
                  link.getExpr().setParent(link);
               }

               table.dbLink = exprTableSource.getExpr().toString();
            }

            if (x instanceof PGExprTableSource) {
               PGExprTableSource ref = (PGExprTableSource)x;
               if (ref.getExpr() != null && ref.getExpr() instanceof SQLMethodInvokeExpr) {
                  SQLMethodInvokeExpr method = (SQLMethodInvokeExpr)ref.getExpr();
                  if (method.getArguments().size() == 1 && method.getArguments().get(0) instanceof SQLQueryExpr) {
                     if (table.name == null) {
                        String aliasTableName = current.dialector.wrap(this.alias.aliasTable());
                        ref.setAlias(aliasTableName);
                        table.name = aliasTableName;
                        if (table.alias == null) {
                           table.alias = table.name;
                        }

                        table.isSub = true;
                     }

                     this.setTableRelation(table);
                     x.putAttribute("table", table);
                     return table;
                  }
               }
            }

            TableMemo refTable = current.getParentTable(schemaName, tableName);
            if (refTable != null) {
               table.schema = refTable.schema;
               table.topTable = true;
               table.scope = current;
               table.refTable = refTable;
               table.getChildren().add(refTable);
               current.addTable(table);
               table.topTable = refTable.topTable;
               return refTable;
            } else {
               boolean isFunction = false;
               SQLExpr expr = ((SQLExprTableSource)x).getExpr();
               isFunction = expr instanceof SQLMethodInvokeExpr;
               this.setTableRelation(table);
               table.system = this.scope.dialector.isSystemTable(table.name);
               if (!table.isSub && !isFunction && !table.system) {
                  this.scope.context.append(table);
               }

               x.putAttribute("table", table);
               return table;
            }
         }
      } else if (x instanceof SQLSubqueryTableSource) {
         SQLSubqueryTableSource tableSource = (SQLSubqueryTableSource)x;
         TableMemo table = new TableMemo(x);
         table.name = current.dialector.wrap(tableSource.getAlias());
         table.alias = current.dialector.wrap(tableSource.getAlias());
         table.nowName = table.name;
         table.schema = current.dialector.wrap(this.scope.getDefaultSchema());
         table.isSub = true;
         this.setTableRelation(table);
         x.putAttribute("table", table);
         return table;
      } else if (x instanceof SQLLateralViewTableSource) {
         SQLLateralViewTableSource tableSource = (SQLLateralViewTableSource)x;
         TableMemo table = new TableMemo(x);
         table.name = current.dialector.wrap(tableSource.getAlias());
         table.alias = current.dialector.wrap(tableSource.getAlias());
         table.schema = current.dialector.wrap(this.scope.getDefaultSchema());
         table.isSub = true;
         this.setTableRelation(table);
         x.putAttribute("table", table);
         return table;
      } else if (x instanceof SQLUnionQueryTableSource) {
         SQLUnionQueryTableSource tableSource = (SQLUnionQueryTableSource)x;
         TableMemo table = new TableMemo(x);
         table.name = current.dialector.wrap(tableSource.getAlias());
         table.alias = table.name;
         table.schema = current.dialector.wrap(this.scope.getDefaultSchema());
         table.nowName = table.name;
         table.isSub = true;
         this.setTableRelation(table);
         x.putAttribute("table", table);
         return table;
      } else {
         return null;
      }
   }

   private TableMemo registerTable(SQLWithSubqueryClause.Entry x) {
      Scope current = this.scope.getCurrent();
      TableMemo table = new TableMemo(x);
      table.name = current.dialector.wrap(x.getAlias());
      table.nowName = current.dialector.wrap(table.name);
      table.alias = table.name;
      table.schema = current.dialector.wrap(this.scope.getDefaultSchema());
      table.isSub = true;
      table.topTable = true;
      this.setTableRelation(table);
      return table;
   }

   private void setTableRelation(TableMemo table) {
      Scope current = this.scope.getCurrent();
      table.name = current.dialector.wrap(table.name);
      table.alias = current.dialector.wrap(table.alias);
      table.schema = current.dialector.wrap(table.schema);
      table.scope = current;
      current.addTable(table);
      TableScope tableScope = current.getNearestParentTableScope();
      if (tableScope != null) {
         TableMemo parentTable = tableScope.table;
         if (parentTable != null) {
            parentTable.getChildren().add(table);
            if (table.isSub) {
               parentTable.isSub = true;
            }
         }
      }

   }

   private TableScope enterTableScope(SQLTableSource x) {
      TableScope ts = new TableScope();
      ts.ref = x;
      ts.alias = ts.name = x.getAlias();
      this.scope.enterScope((Scope)ts);
      return ts;
   }

   private ClauseScope enterClauseScope(SQLObject x) {
      ClauseScope clauseScope = new ClauseScope();
      clauseScope.ref = x;
      this.scope.enterScope((Scope)clauseScope);
      return clauseScope;
   }

   private void setAlias(SQLSelectItem x) {
      if (x.getAlias() == null) {
         SQLExpr e = x.getExpr();
         if (e instanceof SQLIdentifierExpr) {
            String name = ((SQLIdentifierExpr)e).getName();
            if (!name.toUpperCase().equals("ROWID")) {
               x.setAlias(name);
            }
         } else if (e instanceof SQLPropertyExpr) {
            String name = ((SQLPropertyExpr)e).getName();
            if (!name.toUpperCase().equals("ROWID")) {
               x.setAlias(name);
            }
         } else if (e instanceof SQLNumericLiteralExpr) {
            x.setAlias("\"" + e + "\"");
         } else {
            x.setAlias(this.scope.dialector.wrap(e.toString()));
         }

      }
   }

   public void endVisit(SQLMethodInvokeExpr x) {
      if ("LATERAL".equalsIgnoreCase(x.getMethodName())) {
         this.scope.getCurrent().isolated = false;

         for(Scope s : this.scope.getCurrent().getChildren()) {
            s.isolated = false;
         }
      }

   }

   public boolean visit(PGCreateTableStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Create;
      }

      this.enterClauseScope(x);
      this.visitChild(x.getTableSource());

      for(SQLTableElement tableElement : x.getTableElementList()) {
         if (tableElement instanceof SQLColumnDefinition) {
            this.createField(((SQLColumnDefinition)tableElement).getName(), null, true);

            for(SQLColumnConstraint constraint : ((SQLColumnDefinition)tableElement).getConstraints()) {
               if (constraint instanceof SQLColumnReference) {
                  SQLExprTableSource tableSource = new SQLExprTableSource(((SQLColumnReference)constraint).getTable());
                  this.registerTable((SQLTableSource)tableSource);

                  for(SQLName sqlName : ((SQLColumnReference)constraint).getColumns()) {
                     if (sqlName instanceof SQLIdentifierExpr) {
                        SQLPropertyExpr propertyExpr = new SQLPropertyExpr(((SQLColumnReference)constraint).getTable(), ((SQLIdentifierExpr)sqlName).getName());
                        this.createField(propertyExpr, null, true);
                     }
                  }
               }
            }
         } else if (tableElement instanceof MysqlForeignKey) {
            this.visitChild(tableElement);
         }
      }

      this.visitChild(x.getLike());
      this.visitChild(x.getSelect());
      return false;
   }

   public boolean visit(PGCreateViewStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.CreateView;
      }

      this.visitChild(x.getSubQuery());
      return false;
   }

   public boolean visit(PGAlterTableStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Alter;
      }

      this.enterClauseScope(x);
      this.visitChild(x.getTableSource());

      for(SQLAlterTableItem alterTableItem : x.getItems()) {
         this.visitChild(alterTableItem);
      }

      return false;
   }

   public boolean visit(PGGrantStatement x) {
      this.enterClauseScope(x);
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Other;
      }

      SQLObject resource = x.getResource();
      if (resource instanceof PGSQLObjectCollection) {
         PGSQLObjectCollection groups = (PGSQLObjectCollection)resource;

         for(int i = 0; i < groups.size(); ++i) {
            SQLObject group = groups.get(i);
            if (group instanceof SQLPropertyExpr) {
               SQLPropertyExpr p = (SQLPropertyExpr)group;
               PGExprTableSource ts = new PGExprTableSource();
               ts.setExpr(p);
               ts.setParent(resource);
               p.setParent(resource);
               this.visitChild(ts);
            } else if (group instanceof SQLIdentifierExpr) {
               SQLIdentifierExpr id = (SQLIdentifierExpr)group;
               String owner = this.scope.context.getSchema();
               SQLPropertyExpr prop = new SQLPropertyExpr(owner, id.getName());
               groups.remove(i);
               groups.add(i, (SQLObject)prop);
               PGExprTableSource ts = new PGExprTableSource();
               ts.setExpr(prop);
               ts.setParent(resource);
               prop.setParent(resource);
               this.visitChild(ts);
            }
         }
      } else if (resource instanceof SQLPropertyExpr) {
         SQLPropertyExpr p = (SQLPropertyExpr)resource;
         if (!p.getName().equals("*")) {
            PGExprTableSource ts = new PGExprTableSource();
            ts.setExpr(p);
            ts.setParent(x);
            p.setParent(x);
            this.visitChild(ts);
         }
      } else {
         this.visitChild(x.getResource());
      }

      for(SQLPrivilegeItem privilegeItem : x.getPrivileges()) {
         this.visitChild(privilegeItem.getColumns());
      }

      return false;
   }

   public boolean visit(SQLRevokeStatement x) {
      if (this.scope.context.sqlType == null) {
         this.scope.context.sqlType = TableStat.Mode.Other;
      }

      this.enterClauseScope(x);

      for(SQLPrivilegeItem privilegeItem : x.getPrivileges()) {
         SQLExpr action = privilegeItem.getAction();
         if (action instanceof SQLIdentifierExpr) {
            String name = ((SQLIdentifierExpr)action).getName();
            if ("PROXY".equalsIgnoreCase(name)) {
               return false;
            }
         }
      }

      this.visitChild(x.getResource());

      for(SQLPrivilegeItem privilegeItem : x.getPrivileges()) {
         this.visitChild(privilegeItem.getColumns());
      }

      return false;
   }
}
