package com.alibaba.druid.sql.dialect.postgresql.parser;

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLDataType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLPartitionBy;
import com.alibaba.druid.sql.ast.SQLPartitionByHash;
import com.alibaba.druid.sql.ast.SQLPartitionByList;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLUpdateSetItem;
import com.alibaba.druid.sql.dialect.postgresql.ast.PGSQLPartition;
import com.alibaba.druid.sql.dialect.postgresql.ast.PGSQLPartitionBy;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.PGOptionValue;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.constraint.PGConstraint;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.constraint.PGStorageParameter;
import com.alibaba.druid.sql.dialect.postgresql.ast.expr.tablesource.PGExprTableSource;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.PGCreateTableStatement;
import com.alibaba.druid.sql.dialect.postgresql.ast.stmt.alterTable.PGColumnDefinition;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLCreateTableParser;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;

public class PGCreateTableParser extends SQLCreateTableParser {
   public PGCreateTableParser(Lexer lexer) {
      super((SQLExprParser)(new PGExprParser(lexer)));
   }

   public PGCreateTableParser(String sql) {
      super((SQLExprParser)(new PGExprParser(sql)));
   }

   public PGCreateTableParser(SQLExprParser exprParser) {
      super(exprParser);
   }

   public SQLPartitionBy parsePartitionBy() {
      this.lexer.nextToken();
      this.accept(Token.BY);
      if (!this.lexer.identifierEquals("LIST")) {
         if (!this.lexer.identifierEquals("HASH") && !this.lexer.identifierEquals("UNI_HASH")) {
            throw new ParserException("TODO " + this.lexer.info());
         } else {
            SQLPartitionByHash hash = new SQLPartitionByHash();
            if (this.lexer.identifierEquals("UNI_HASH")) {
               hash.setUnique(true);
            }

            this.lexer.nextToken();
            if (this.lexer.token() == Token.KEY) {
               this.lexer.nextToken();
               hash.setKey(true);
            }

            this.accept(Token.LPAREN);
            this.exprParser.exprList(hash.getColumns(), hash);
            this.accept(Token.RPAREN);
            return hash;
         }
      } else {
         this.lexer.nextToken();
         SQLPartitionByList list = new SQLPartitionByList();
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            list.addColumn(this.exprParser.expr());
            this.accept(Token.RPAREN);
         } else {
            this.acceptIdentifier("COLUMNS");
            this.accept(Token.LPAREN);

            while(true) {
               list.addColumn(this.exprParser.name());
               if (this.lexer.token() != Token.COMMA) {
                  this.accept(Token.RPAREN);
                  break;
               }

               this.lexer.nextToken();
            }
         }

         return list;
      }
   }

   public PGSQLPartitionBy parsePartitionBy2() {
      this.lexer.nextToken();
      this.accept(Token.BY);
      PGSQLPartitionBy partitionBy1 = new PGSQLPartitionBy();
      partitionBy1.setPartitionType(this.exprParser.name());
      this.accept(Token.LPAREN);
      partitionBy1.setColumn(this.exprParser.name());
      this.accept(Token.RPAREN);
      boolean useTemplate = false;
      boolean hasSubpartition = false;

      while(this.lexer.identifierEquals("SUBPARTITION")) {
         hasSubpartition = true;
         this.lexer.nextToken();
         this.accept(Token.BY);
         PGSQLPartitionBy subPartitionBy = new PGSQLPartitionBy();
         subPartitionBy.setIfSubPartitionBy(true);
         subPartitionBy.setPartitionType(this.exprParser.name());
         this.accept(Token.LPAREN);
         subPartitionBy.setColumn(this.exprParser.name());
         this.accept(Token.RPAREN);
         partitionBy1.getSubPartitionByList().add(subPartitionBy);
         if (this.lexer.identifierEquals("SUBPARTITION")) {
            Lexer.SavePoint mark = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.token() == Token.BY) {
               this.lexer.reset(mark);
               continue;
            }

            if (this.lexer.identifierEquals("TEMPLATE")) {
               useTemplate = true;
               subPartitionBy.setIfUseTemplate(true);
               this.lexer.nextToken();
               this.accept(Token.LPAREN);
               subPartitionBy.setPartitionList(this.parsePartitionSpec());
               this.accept(Token.RPAREN);
               continue;
            }
         }

         if (!this.lexer.identifierEquals("SUBPARTITION")) {
            break;
         }
      }

      if (!useTemplate && hasSubpartition) {
         int level = 0;

         do {
            if (this.lexer.token() == Token.LPAREN) {
               ++level;
               this.lexer.nextToken();
            }

            ArrayList<PGSQLPartition> partition = this.parsePartitionSpec();
            if (level == 1) {
               partitionBy1.getPartitionList().addAll(partition);
            } else {
               ((PGSQLPartitionBy)partitionBy1.getSubPartitionByList().get(level - 2)).getPartitionList().addAll(partition);
            }

            while(this.lexer.token() == Token.RPAREN) {
               --level;
               this.lexer.nextToken();
            }

            if (this.lexer.token() == Token.COMMA) {
               this.lexer.nextToken();
            }
         } while(level != 0);

         for(PGSQLPartitionBy partitionBy : partitionBy1.getSubPartitionByList()) {
            HashSet<String> strings = new HashSet();
            ArrayList<PGSQLPartition> partitionList = partitionBy.getPartitionList();
            Iterator<PGSQLPartition> iterator = partitionList.iterator();

            while(iterator.hasNext()) {
               PGSQLPartition next = (PGSQLPartition)iterator.next();
               String s = next.getName().toString();
               if (strings.contains(s)) {
                  iterator.remove();
               } else {
                  strings.add(s);
               }
            }
         }
      } else if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         ArrayList<PGSQLPartition> specList = this.parsePartitionSpec();

         for(PGSQLPartition spec : specList) {
            spec.setSubPartition(false);
         }

         this.lexer.nextToken();
         partitionBy1.setPartitionList(specList);
      }

      return partitionBy1;
   }

   private ArrayList<PGSQLPartition> parsePartitionSpec() {
      ArrayList<PGSQLPartition> specList = new ArrayList();
      SQLName defaultName = null;

      while(true) {
         if (this.lexer.token() == Token.DEFAULT) {
            this.accept(Token.DEFAULT);
            this.lexer.nextToken();
            defaultName = this.exprParser.name();
         } else {
            PGSQLPartition partition = new PGSQLPartition();
            specList.add(partition);
            if (this.lexer.token() == Token.PARTITION) {
               partition.setSubPartition(false);
               this.lexer.nextToken();
               partition.setName(this.exprParser.name());
            } else if (this.lexer.identifierEquals("SUBPARTITION")) {
               partition.setSubPartition(true);
               this.lexer.nextToken();
               partition.setName(this.exprParser.name());
            }

            if (this.lexer.token() == Token.VALUES) {
               this.lexer.nextToken();
               this.accept(Token.LPAREN);

               while(true) {
                  partition.getValueList().add(this.exprParser.expr());
                  if (this.lexer.token() != Token.COMMA) {
                     this.accept(Token.RPAREN);
                     break;
                  }

                  this.lexer.nextToken();
               }
            } else {
               if (this.lexer.token() == Token.START) {
                  partition.setStart(true);
                  this.lexer.nextToken();
                  this.accept(Token.LPAREN);
                  if (this.lexer.token() == Token.IDENTIFIER) {
                     partition.setStartDataType(this.exprParser.name());
                  }

                  partition.setStartValue(this.exprParser.expr());
                  this.accept(Token.RPAREN);
                  if (this.lexer.identifierEquals("INCLUSIVE")) {
                     partition.setStartInclusive(true);
                     this.lexer.nextToken();
                  } else if (this.lexer.identifierEquals("EXCLUSIVE")) {
                     partition.setStartExclusive(true);
                     this.lexer.nextToken();
                  }
               }

               if (this.lexer.token() == Token.END) {
                  partition.setEnd(true);
                  this.lexer.nextToken();
                  this.accept(Token.LPAREN);
                  if (this.lexer.token() == Token.IDENTIFIER) {
                     partition.setStartDataType(this.exprParser.name());
                  }

                  partition.setEndValue(this.exprParser.expr());
                  this.accept(Token.RPAREN);
                  if (this.lexer.identifierEquals("INCLUSIVE")) {
                     partition.setEndInclusive(true);
                     this.lexer.nextToken();
                  } else if (this.lexer.identifierEquals("EXCLUSIVE")) {
                     partition.setEndExclusive(true);
                     this.lexer.nextToken();
                  }
               }

               if (this.lexer.identifierEquals("EVERY")) {
                  partition.setEvery(true);
                  this.lexer.nextToken();
                  this.accept(Token.LPAREN);
                  if (this.lexer.token() == Token.IDENTIFIER) {
                     partition.setEveryDataType(this.exprParser.name());
                  }

                  partition.setEveryValue(this.exprParser.expr());
                  this.accept(Token.RPAREN);
               }
            }

            if (this.lexer.token() == Token.WITH) {
               partition.setWith(true);
               this.lexer.nextToken();
               this.accept(Token.LPAREN);

               while(true) {
                  SQLUpdateSetItem item = this.exprParser.parseUpdateSetItem();
                  partition.getWithList().add(item);
                  if (this.lexer.token() != Token.COMMA) {
                     this.accept(Token.RPAREN);
                     break;
                  }

                  this.lexer.nextToken();
               }
            }

            if (this.lexer.token() == Token.TABLESPACE) {
               this.lexer.nextToken();
               partition.setTableSpace(this.exprParser.name());
            }
         }

         if (this.lexer.token() != Token.COMMA) {
            boolean hasDefault = false;

            for(PGSQLPartition spec : specList) {
               if (spec.getName() != null && spec.getName().equals(defaultName)) {
                  spec.setIfDefault(true);
                  hasDefault = true;
               } else {
                  spec.setIfDefault(false);
               }
            }

            if (defaultName != null && !hasDefault) {
               PGSQLPartition spec = new PGSQLPartition();
               spec.setName(defaultName);
               spec.setIfDefault(true);
               specList.add(spec);
            }

            return specList;
         }

         this.lexer.nextToken();
      }
   }

   public PGCreateTableStatement parseCreateTable() {
      List<String> comments = null;
      if (this.lexer.isKeepComments() && this.lexer.hasComment()) {
         comments = this.lexer.readAndResetComments();
      }

      PGCreateTableStatement stmt = this.parseCreateTable(true);
      if (comments != null) {
         stmt.addBeforeComment(comments);
      }

      return stmt;
   }

   public PGCreateTableStatement parseCreateTable(boolean acceptCreate) {
      PGCreateTableStatement createTable = new PGCreateTableStatement();
      createTable.setDbType(DbType.postgresql);
      if (acceptCreate) {
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            createTable.addBeforeComment(this.lexer.readAndResetComments());
         }

         this.accept(Token.CREATE);
      }

      boolean isGlobal = false;
      boolean isLocal = false;
      if (this.lexer.token() != Token.GLOBAL && !this.lexer.identifierEquals("GLOBAL")) {
         if (this.lexer.token() == Token.LOCAL || this.lexer.identifierEquals("LOCAL")) {
            this.lexer.nextToken();
            isLocal = true;
         }
      } else {
         this.lexer.nextToken();
         isGlobal = true;
      }

      if (this.lexer.token() != Token.TEMPORARY && !this.lexer.identifierEquals("TEMPORARY")) {
         if (this.lexer.token() == Token.TEMP || this.lexer.identifierEquals("TEMP")) {
            this.lexer.nextToken();
            if (isGlobal) {
               createTable.setTableType(PGCreateTableStatement.TableType.GLOBAL_TEMP);
            } else if (isLocal) {
               createTable.setTableType(PGCreateTableStatement.TableType.LOCAL_TEMP);
            } else {
               createTable.setTableType(PGCreateTableStatement.TableType.TEMP);
            }
         }
      } else {
         this.lexer.nextToken();
         if (isGlobal) {
            createTable.setTableType(PGCreateTableStatement.TableType.GLOBAL_TEMPORARY);
         } else if (isLocal) {
            createTable.setTableType(PGCreateTableStatement.TableType.LOCAL_TEMPORARY);
         } else {
            createTable.setTableType(PGCreateTableStatement.TableType.TEMPORARY);
         }
      }

      if (this.lexer.token() == Token.UNLOGGED || this.lexer.identifierEquals("UNLOGGED")) {
         this.lexer.nextToken();
         if (isGlobal) {
            createTable.setTableType(PGCreateTableStatement.TableType.GLOBAL_UNLOGGED);
         } else if (isLocal) {
            createTable.setTableType(PGCreateTableStatement.TableType.LOCAL_UNLOGGED);
         } else {
            createTable.setTableType(PGCreateTableStatement.TableType.UNLOGGED);
         }
      }

      this.accept(Token.TABLE);
      if (this.lexer.token() == Token.IF || this.lexer.identifierEquals("IF")) {
         this.lexer.nextToken();
         this.accept(Token.NOT);
         this.accept(Token.EXISTS);
         createTable.setIfNotExiists(true);
      }

      PGExprTableSource tableSource = new PGExprTableSource();
      SQLName name = this.exprParser.name();
      tableSource.setExpr(name);
      createTable.setTableSource(tableSource);
      createTable.setName(name);
      if (this.lexer.token() == Token.OF) {
         this.lexer.nextToken();
         createTable.setOfType(this.exprParser.name());
      }

      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();

         do {
            PGColumnDefinition gpColumnDefinition = this.parseColumnDefinition();
            gpColumnDefinition.setParent(createTable);
            createTable.getTableElementList().add(gpColumnDefinition);
            if (this.lexer.token() != Token.COMMA) {
               break;
            }

            this.lexer.nextToken();
         } while(this.lexer.token() != Token.RPAREN);

         this.accept(Token.RPAREN);
         if (this.lexer.identifierEquals(FnvHash.Constants.INHERITS)) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);

            while(this.lexer.token() != Token.RPAREN) {
               SQLName inherits = this.exprParser.name();
               createTable.addParentTable(inherits);
               if (this.lexer.token() == Token.COMMA) {
                  this.lexer.nextToken();
               }
            }

            this.accept(Token.RPAREN);
            this.lexer.nextToken();
         }
      }

      if (this.lexer.token() == Token.WITH) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         this.parseAssignItems(createTable.getTableOptions(), createTable, false);
         this.accept(Token.RPAREN);
      }

      if (this.lexer.token() == Token.ON) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.COMMIT || this.lexer.identifierEquals("COMMIT")) {
            this.lexer.nextToken();
            if (this.lexer.token() == Token.DELETE) {
               this.lexer.nextToken();
               this.lexer.nextToken();
               createTable.setOnCommit(PGCreateTableStatement.OnCommit.deleteRows);
            } else if (this.lexer.token() == Token.DROP) {
               this.lexer.nextToken();
               createTable.setOnCommit(PGCreateTableStatement.OnCommit.drop);
            } else if (this.lexer.identifierEquals("PRESERVE")) {
               this.lexer.nextToken();
               this.lexer.nextToken();
               createTable.setOnCommit(PGCreateTableStatement.OnCommit.preserveRows);
            }
         }
      }

      if (this.lexer.token() == Token.TABLESPACE) {
         this.lexer.nextToken();
         createTable.setTablespace(this.exprParser.name());
      }

      if (this.lexer.token() == Token.AS) {
         this.lexer.nextToken();
         SQLSelectParser sqlSelectParser = this.createSQLSelectParser();
         SQLSelect select = sqlSelectParser.select();
         createTable.setSelect(select);
      }

      if (this.lexer.identifierEquals("DISTRIBUTED")) {
         this.lexer.nextToken();
         createTable.setDistributedBy(this.parseDistributedBy());
      }

      if (this.lexer.token() == Token.PARTITION) {
         PGSQLPartitionBy partitionBy = this.parsePartitionBy2();
         createTable.setPartitioning(partitionBy);
      }

      return createTable;
   }

   private PGCreateTableStatement.PGTableDistributedBy parseDistributedBy() {
      PGCreateTableStatement.PGTableDistributedBy by = new PGCreateTableStatement.PGTableDistributedBy();
      if (this.lexer.token() == Token.BY) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         PGOptionValue value = null;

         do {
            value = this.parseOpionValue(false);
            if (value != null) {
               by.addColum(value);
            }

            if (this.lexer.token() == Token.COMMA) {
               this.lexer.nextToken();
            }
         } while(value != null);

         this.lexer.nextToken();
         by.setOption(PGCreateTableStatement.PGTableDistributedBy.Option.by);
      } else if (this.lexer.identifierEquals("RANDOMLY")) {
         this.lexer.nextToken();
         by.setOption(PGCreateTableStatement.PGTableDistributedBy.Option.randomly);
      } else if (this.lexer.identifierEquals("REPLICATED")) {
         this.lexer.nextToken();
         by.setOption(PGCreateTableStatement.PGTableDistributedBy.Option.replicated);
      }

      return by;
   }

   private PGOptionValue parseOpionValue(boolean eq) {
      if (this.lexer.token() == Token.RPAREN) {
         return null;
      } else {
         Lexer.SavePoint mark = this.lexer.mark();
         PGOptionValue e = new PGOptionValue();
         SQLExpr expr = this.exprParser.expr();
         if (eq) {
            if (expr instanceof SQLBinaryOpExpr) {
               SQLBinaryOpExpr bin = (SQLBinaryOpExpr)expr;
               e.setOption(bin.getLeft());
               e.setValue(bin.getRight());
               return e;
            } else if (this.lexer.token() == Token.EQ) {
               e.setOption(expr);
               this.lexer.nextToken();
               e.setValue(this.exprParser.expr());
               return e;
            } else {
               this.lexer.reset(mark);
               return null;
            }
         } else {
            e.setOption(expr);
            if (this.lexer.token() != Token.COMMA && this.lexer.token() != Token.RPAREN) {
               e.setValue(this.exprParser.expr());
            }

            return e;
         }
      }
   }

   private PGColumnDefinition parseColumnDefinition() {
      String TABLE_CONSTRAINT_FLAG = "table-constraint";
      PGColumnDefinition definition = new PGColumnDefinition();
      definition.setName(this.exprParser.name());
      if (this.lexer.token() != Token.WITH && !this.lexer.identifierEquals("WITH")) {
         SQLDataType sqlDataType = this.exprParser.parseDataType();
         definition.setDataType(sqlDataType);
         if (this.lexer.identifierEquals("COLLATE")) {
            this.lexer.nextToken();
            definition.setCollateExpr(this.exprParser.expr());
         }
      } else {
         this.lexer.nextToken();
         this.lexer.nextToken();
         definition.setWith(true);
      }

      PGConstraint constraint = null;

      do {
         constraint = this.parseColConstraint();
         if (constraint != null) {
            Object attribute = constraint.getAttribute("table-constraint");
            if (attribute != null && (Boolean)attribute) {
               definition.setTableConstraint(constraint);
               break;
            }

            definition.addConstraint(constraint);
         }
      } while(constraint != null);

      if (this.lexer.identifierEquals("ENCODING")) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         SQLExpr encoding = null;

         do {
            SQLExpr tbConstraint = this.parseOpionValue(true);
            if (tbConstraint != null) {
               definition.addEncoding(tbConstraint);
            }

            if (this.lexer.token() == Token.COMMA) {
               this.lexer.nextToken();
            }
         } while(this.lexer.token() != Token.RPAREN);

         this.lexer.nextToken();
      }

      PGConstraint tbConstraint = this.parseColConstraint();
      if (tbConstraint != null) {
         definition.setTableConstraint(tbConstraint);
      }

      if (this.lexer.token() == Token.LIKE || this.lexer.identifierEquals("LIKE")) {
         this.lexer.nextToken();
         PGExprTableSource ts = new PGExprTableSource();
         ts.setExpr(this.exprParser.expr());
         definition.setLike(ts);
         PGColumnDefinition.ColumnLikeOption option = null;

         do {
            option = this.parseLikeOption();
            if (option != null) {
               definition.addOption(option);
            }
         } while(option != null);
      }

      PGConstraint.PGReference reference = null;

      do {
         reference = this.parseReference();
         if (reference != null) {
            definition.addColumnReferenceStorageDirective(reference);
         }
      } while(reference != null);

      return definition;
   }

   private PGConstraint parseColConstraint() {
      String TABLE_CONSTRAINT_FLAG = "table-constraint";
      boolean flag = false;
      PGConstraint c = new PGConstraint();
      if (this.lexer.token() == Token.CONSTRAINT) {
         this.lexer.nextToken();
         c.setName(this.exprParser.name());
         flag = true;
      }

      if (this.lexer.token() != Token.NOT && !this.lexer.identifierEquals("NOT")) {
         if (this.lexer.token() != Token.NULL && !this.lexer.identifierEquals("NULL")) {
            if (this.lexer.token() == Token.CHECK) {
               this.lexer.nextToken();
               this.accept(Token.LPAREN);
               PGConstraint.PGConstraintCheck check = new PGConstraint.PGConstraintCheck();
               check.setExpr(this.exprParser.expr());
               c.setCheck(check);
               this.accept(Token.RPAREN);
               if (this.lexer.identifierEquals("NO")) {
                  check.setNoInherit(true);
                  this.lexer.nextToken();
                  this.lexer.nextToken();
               }

               flag = true;
            } else if (this.lexer.token() != Token.DEFAULT && !this.lexer.identifierEquals("DEFAULT")) {
               if (this.lexer.token() != Token.UNIQUE && !this.lexer.identifierEquals("UNIQUE")) {
                  if (this.lexer.token() != Token.PRIMARY && !this.lexer.identifierEquals("PRIMARY")) {
                     if (this.lexer.token() != Token.FOREIGN && !this.lexer.identifierEquals("FOREIGN")) {
                        if (this.lexer.token() == Token.REFERENCES || this.lexer.identifierEquals("REFERENCES")) {
                           PGConstraint.PGReference ref = this.parseReference();
                           c.setReference(ref);
                           flag = true;
                        }
                     } else {
                        this.lexer.nextToken();
                        c.putAttribute("table-constraint", true);
                        this.accept(Token.KEY);
                        this.accept(Token.LPAREN);
                        PGConstraint.PGForeignKey foreign = new PGConstraint.PGForeignKey();

                        do {
                           foreign.addColumn(this.exprParser.name());
                           if (this.lexer.token() == Token.COMMA) {
                              this.lexer.nextToken();
                           }
                        } while(this.lexer.token() != Token.RPAREN);

                        this.lexer.nextToken();
                        foreign.setReference(this.parseReference());
                        flag = true;
                     }
                  } else {
                     this.lexer.nextToken();
                     this.accept(Token.KEY);
                     PGConstraint.PGPrimaryKey primaryKey = new PGConstraint.PGPrimaryKey();
                     if (this.lexer.token() == Token.LPAREN) {
                        c.putAttribute("table-constraint", true);
                        this.lexer.nextToken();

                        do {
                           primaryKey.addColumn(this.exprParser.name());
                           if (this.lexer.token() == Token.COMMA) {
                              this.lexer.nextToken();
                           }
                        } while(this.lexer.token() != Token.RPAREN);

                        this.lexer.nextToken();
                     }

                     primaryKey.setIndexParamaters(this.parseIndexParamaters());
                     c.setPrimaryKey(primaryKey);
                     flag = true;
                  }
               } else {
                  this.lexer.nextToken();
                  PGConstraint.PGUnique unique = new PGConstraint.PGUnique();
                  if (this.lexer.token() == Token.LPAREN) {
                     c.putAttribute("table-constraint", true);
                     this.lexer.nextToken();

                     do {
                        unique.addColumn(this.exprParser.name());
                        if (this.lexer.token() == Token.COMMA) {
                           this.lexer.nextToken();
                        }
                     } while(this.lexer.token() != Token.RPAREN);

                     this.lexer.nextToken();
                  }

                  unique.setIndexParamaters(this.parseIndexParamaters());
                  c.setUnique(unique);
                  flag = true;
               }
            } else {
               this.lexer.nextToken();
               c.setDefValue(this.exprParser.expr());
               c.putAttribute("table-constraint", true);
               flag = true;
            }
         } else {
            this.lexer.nextToken();
            c.setiNull(true);
            c.putAttribute("table-constraint", true);
            flag = true;
         }
      } else {
         Lexer.SavePoint mark = this.lexer.mark();
         this.lexer.nextToken();
         if (this.lexer.token() != Token.NULL && !this.lexer.identifierEquals("NULL")) {
            this.lexer.reset(mark);
         } else {
            c.setNotNull(true);
            this.lexer.nextToken();
            flag = true;
         }
      }

      PGConstraint.ConstraintType constraintType = this.parseConstraintType();
      if (constraintType != null) {
         c.setType(constraintType);
         flag = true;
      }

      return !flag ? null : c;
   }

   private PGConstraint.PGReference parseReference() {
      if (this.lexer.token() != Token.REFERENCES && !this.lexer.identifierEquals("REFERENCES")) {
         return null;
      } else {
         this.lexer.nextToken();
         PGConstraint.PGReference ref = new PGConstraint.PGReference();
         ref.setRefTable(this.exprParser.expr());
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();

            do {
               ref.addColumn(this.exprParser.name());
               if (this.lexer.token() == Token.COMMA) {
                  this.lexer.nextToken();
               }
            } while(this.lexer.token() != Token.RPAREN);

            this.lexer.nextToken();
         }

         if (this.lexer.identifierEquals("MATCH")) {
            this.lexer.nextToken();
            if (this.lexer.token() != Token.FULL && !this.lexer.identifierEquals("FULL")) {
               if (this.lexer.identifierEquals("PARTIAL")) {
                  this.lexer.nextToken();
                  ref.setMatchType(PGConstraint.PGReference.MatchType.partial);
               } else if (this.lexer.identifierEquals("SIMPLE")) {
                  this.lexer.nextToken();
                  ref.setMatchType(PGConstraint.PGReference.MatchType.simple);
               }
            } else {
               this.lexer.nextToken();
               ref.setMatchType(PGConstraint.PGReference.MatchType.full);
            }
         }

         if (this.lexer.token() == Token.ON || this.lexer.identifierEquals("ON")) {
            this.lexer.nextToken();
            if (this.lexer.token() == Token.DELETE) {
               this.lexer.nextToken();
               ref.setDeleteAction(this.parseKeyAction());
            } else if (this.lexer.token() == Token.UPDATE) {
               this.lexer.nextToken();
               ref.setUpdateAction(this.parseKeyAction());
            }
         }

         return ref;
      }
   }

   private PGConstraint.PGReference.KeyAction parseKeyAction() {
      if (this.lexer.token() == Token.ON) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.DELETE) {
            this.lexer.nextToken();
            return PGConstraint.PGReference.KeyAction.onDelete;
         }

         if (this.lexer.token() == Token.UPDATE) {
            this.lexer.nextToken();
            return PGConstraint.PGReference.KeyAction.onUpdate;
         }
      } else {
         if (this.lexer.identifierEquals("NO")) {
            this.lexer.nextToken();
            this.lexer.nextToken();
            return PGConstraint.PGReference.KeyAction.noAction;
         }

         if (this.lexer.token() == Token.RESTRICT || this.lexer.identifierEquals("RESTRICT")) {
            this.lexer.nextToken();
            return PGConstraint.PGReference.KeyAction.restrict;
         }

         if (this.lexer.token() == Token.CASCADE || this.lexer.identifierEquals("CASCADE")) {
            this.lexer.nextToken();
            return PGConstraint.PGReference.KeyAction.cascade;
         }

         if (this.lexer.token() == Token.SET || this.lexer.identifierEquals("SET")) {
            this.lexer.nextToken();
            if (this.lexer.token() == Token.DEFAULT || this.lexer.identifierEquals("DEFAULT")) {
               this.lexer.nextToken();
               return PGConstraint.PGReference.KeyAction.setDefault;
            }

            if (this.lexer.token() == Token.NULL || this.lexer.identifierEquals("NULL")) {
               this.lexer.nextToken();
               return PGConstraint.PGReference.KeyAction.setNull;
            }
         }
      }

      return null;
   }

   private PGConstraint.ConstraintType parseConstraintType() {
      if (this.lexer.identifierEquals("DEFERRABLE")) {
         this.lexer.nextToken();
         return PGConstraint.ConstraintType.deferrable;
      } else if (this.lexer.token() != Token.NOT && !this.lexer.identifierEquals("NOT")) {
         if (this.lexer.token() == Token.INITIALLY || this.lexer.identifierEquals("INITIALLY")) {
            this.lexer.nextToken();
            if (this.lexer.token() == Token.DEFERRED || this.lexer.identifierEquals("DEFERRED")) {
               this.lexer.nextToken();
               return PGConstraint.ConstraintType.initiallyDeferred;
            }

            if (this.lexer.token() == Token.IMMEDIATE || this.lexer.identifierEquals("IMMEDIATE")) {
               this.lexer.nextToken();
               return PGConstraint.ConstraintType.initiallyImmediate;
            }
         }

         return null;
      } else {
         this.lexer.nextToken();
         this.lexer.nextToken();
         return PGConstraint.ConstraintType.notDeferrable;
      }
   }

   private PGColumnDefinition.ColumnLikeOption parseLikeOption() {
      PGColumnDefinition.ColumnLikeOption o = new PGColumnDefinition.ColumnLikeOption();
      boolean flag = false;
      if (this.lexer.identifierEquals("INCLUDING")) {
         this.lexer.nextToken();
         flag = true;
         o.setIncludeType(PGColumnDefinition.ColumnLikeOption.IncludeType.including);
      } else if (this.lexer.identifierEquals("EXCLUDING")) {
         this.lexer.nextToken();
         flag = true;
         o.setIncludeType(PGColumnDefinition.ColumnLikeOption.IncludeType.excluding);
      }

      if (!flag) {
         return null;
      } else {
         if (this.lexer.identifierEquals("DEFAULTS")) {
            o.setOption(PGColumnDefinition.ColumnLikeOption.OptionType.defaults);
            this.lexer.nextToken();
         } else if (this.lexer.identifierEquals("CONSTRAINTS")) {
            o.setOption(PGColumnDefinition.ColumnLikeOption.OptionType.constraints);
            this.lexer.nextToken();
         } else if (this.lexer.identifierEquals("INDEXES")) {
            o.setOption(PGColumnDefinition.ColumnLikeOption.OptionType.indexes);
            this.lexer.nextToken();
         } else if (this.lexer.token() != Token.STORAGE && !this.lexer.identifierEquals("STORAGE")) {
            if (this.lexer.identifierEquals("COMMENTS")) {
               o.setOption(PGColumnDefinition.ColumnLikeOption.OptionType.comments);
               this.lexer.nextToken();
            } else if (this.lexer.token() == Token.ALL || this.lexer.identifierEquals("ALL")) {
               o.setOption(PGColumnDefinition.ColumnLikeOption.OptionType.all);
               this.lexer.nextToken();
            }
         } else {
            o.setOption(PGColumnDefinition.ColumnLikeOption.OptionType.storage);
            this.lexer.nextToken();
         }

         return o;
      }
   }

   private PGConstraint.PGIndexParamaters parseIndexParamaters() {
      PGConstraint.PGIndexParamaters ps = new PGConstraint.PGIndexParamaters();
      if (this.lexer.token() == Token.WITH || this.lexer.identifierEquals("WITH")) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);

         do {
            PGStorageParameter parameter = new PGStorageParameter();
            parameter.setOption(this.exprParser.expr());
            if (this.lexer.token() != Token.COMMA && this.lexer.token() != Token.RPAREN) {
               parameter.setValue(this.exprParser.expr());
            }

            ps.addParameter(parameter);
            if (this.lexer.token() == Token.COMMA) {
               this.lexer.nextToken();
            }
         } while(this.lexer.token() != Token.RPAREN);

         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.USING || this.lexer.identifierEquals("USING")) {
         this.lexer.nextToken();
         this.accept(Token.INDEX);
         this.accept(Token.TABLESPACE);
         ps.setTablespace(this.exprParser.expr());
      }

      return ps;
   }
}
