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

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLLimit;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLBetweenExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLFlashbackExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLListExpr;
import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLSelectQuery;
import com.alibaba.druid.sql.ast.statement.SQLTableSource;
import com.alibaba.druid.sql.dialect.dm.ast.clause.DmPartitionExtensionClause;
import com.alibaba.druid.sql.dialect.dm.ast.clause.DmSampleClause;
import com.alibaba.druid.sql.dialect.dm.ast.stmt.DmSelectPivot;
import com.alibaba.druid.sql.dialect.dm.ast.stmt.DmSelectQueryBlock;
import com.alibaba.druid.sql.dialect.dm.ast.stmt.DmSelectSubqueryTableSource;
import com.alibaba.druid.sql.dialect.dm.ast.stmt.DmSelectTableReference;
import com.alibaba.druid.sql.dialect.dm.ast.stmt.DmSelectTableSource;
import com.alibaba.druid.sql.dialect.dm.ast.stmt.DmSelectUnPivot;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLSelectListCache;
import com.alibaba.druid.sql.parser.SQLSelectParser;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;

public class DmSelectParser extends SQLSelectParser {
   protected boolean returningFlag;

   public DmSelectParser(SQLExprParser exprParser) {
      super(exprParser);
      this.returningFlag = false;
   }

   public DmSelectParser(SQLExprParser exprParser, SQLSelectListCache selectListCache) {
      super(exprParser, selectListCache);
      this.returningFlag = false;
   }

   public DmSelectParser(String sql) {
      this((SQLExprParser)(new DmExprParser(sql)));
   }

   public SQLLimit parseTop() {
      if (this.lexer.token() != Token.TOP) {
         return null;
      } else {
         SQLLimit limit = new SQLLimit();
         this.lexer.nextTokenValue();
         SQLExpr temp;
         if (this.lexer.token() == Token.LITERAL_INT) {
            temp = new SQLIntegerExpr(this.lexer.integerValue());
            this.lexer.nextTokenComma();
            if (this.lexer.token() != Token.COMMA && this.lexer.token() != Token.EOF && this.lexer.token() != Token.IDENTIFIER && this.lexer.token() != Token.PERCENT && this.lexer.token() != Token.STAR) {
               temp = this.exprParser.primaryRest(temp);
               temp = this.exprParser.exprRest(temp);
            }
         } else {
            temp = this.expr();
         }

         if (this.lexer.token() == Token.COMMA) {
            limit.setOffset(temp);
            this.lexer.nextTokenValue();
            SQLExpr rowCount;
            if (this.lexer.token() == Token.LITERAL_INT) {
               rowCount = new SQLIntegerExpr(this.lexer.integerValue());
               this.lexer.nextToken();
            } else {
               rowCount = this.expr();
            }

            limit.setRowCount(rowCount);
         } else if (this.lexer.identifierEquals(FnvHash.Constants.OFFSET)) {
            limit.setRowCount(temp);
            this.lexer.nextToken();
            limit.setOffset(this.expr());
         } else {
            limit.setRowCount(temp);
         }

         if (this.lexer.token() == Token.BY && this.dbType == DbType.clickhouse) {
            this.lexer.nextToken();

            while(true) {
               SQLExpr item = this.expr();
               limit.addBy(item);
               if (this.lexer.token() != Token.COMMA) {
                  break;
               }

               this.lexer.nextToken();
            }
         }

         return limit;
      }
   }

   public SQLSelectQuery query(SQLObject parent, boolean acceptUnion) {
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         SQLSelectQuery select = this.query();
         this.accept(Token.RPAREN);
         return this.queryRest(select, acceptUnion);
      } else if (this.lexer.token() == Token.VALUES) {
         return this.valuesQuery(acceptUnion);
      } else {
         DmSelectQueryBlock queryBlock = new DmSelectQueryBlock();
         if (this.lexer.hasComment() && this.lexer.isKeepComments()) {
            queryBlock.addBeforeComment(this.lexer.readAndResetComments());
         }

         this.accept(Token.SELECT);
         if (this.lexer.token() == Token.HINT) {
            this.exprParser.parseHints(queryBlock.getHints());
         }

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

         this.parseHints(queryBlock);
         if (this.lexer.token() == Token.DISTINCT) {
            queryBlock.setDistionOption(2);
            this.lexer.nextToken();
         } else if (this.lexer.token() == Token.UNIQUE) {
            queryBlock.setDistionOption(3);
            this.lexer.nextToken();
         } else if (this.lexer.token() == Token.ALL) {
            queryBlock.setDistionOption(1);
            this.lexer.nextToken();
         }

         if (this.lexer.token() == Token.TOP) {
            SQLLimit sqlLimit = this.parseTop();
            queryBlock.setTop(sqlLimit);
         }

         if (this.lexer.token() == Token.PERCENT) {
            queryBlock.setPercent(true);
            this.lexer.nextToken();
         }

         if (this.lexer.token() == Token.WITH) {
            queryBlock.setWithties(true);
            this.lexer.nextToken();
            this.lexer.nextToken();
         }

         this.exprParser.parseHints(queryBlock.getHints());
         this.parseSelectList(queryBlock);
         this.parseInto(queryBlock);
         this.parseFrom(queryBlock);
         if (this.lexer.token() == Token.WHEN) {
            this.lexer.nextToken();
            this.lexer.nextToken();
            this.lexer.nextToken();
         }

         this.parseWhere(queryBlock);
         this.parseHierachical(queryBlock);
         this.parseGroupBy(queryBlock);
         this.parseFetchClause(queryBlock);
         if (this.lexer.token() == Token.FOR) {
            this.lexer.nextToken();
            this.accept(Token.UPDATE);
            queryBlock.setForUpdate(true);
            if (!this.lexer.identifierEquals(FnvHash.Constants.NO_WAIT) && !this.lexer.identifierEquals(FnvHash.Constants.NOWAIT)) {
               if (this.lexer.identifierEquals(FnvHash.Constants.WAIT)) {
                  this.lexer.nextToken();
                  SQLExpr waitTime = this.exprParser.primary();
                  queryBlock.setWaitTime(waitTime);
               }
            } else {
               this.lexer.nextToken();
               queryBlock.setNoWait(true);
            }
         }

         return this.queryRest(queryBlock, acceptUnion);
      }
   }

   protected void parseInto(DmSelectQueryBlock x) {
      if (this.lexer.token() == Token.INTO) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.FROM) {
            return;
         }

         SQLExpr expr = this.expr();
         if (this.lexer.token() != Token.COMMA) {
            x.setIntoVariable(expr);
            return;
         }

         SQLListExpr list = new SQLListExpr();
         list.addItem(expr);

         while(this.lexer.token() == Token.COMMA) {
            this.lexer.nextToken();
            list.addItem(this.expr());
         }

         x.setIntoVariable(list);
      }

   }

   public void parseFetchClause(DmSelectQueryBlock queryBlock) {
      if (this.lexer.token() == Token.LIMIT) {
         SQLLimit limit = this.exprParser.parseLimit();
         queryBlock.setLimit(limit);
      } else {
         if (this.lexer.identifierEquals(FnvHash.Constants.OFFSET) || this.lexer.token() == Token.OFFSET) {
            this.lexer.nextToken();
            SQLExpr offset = this.exprParser.expr();
            queryBlock.setOffset(offset);
            if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS) || this.lexer.token() == Token.ROW || this.lexer.token() == Token.ROWS) {
               this.lexer.nextToken();
            }
         }

         if (this.lexer.token() == Token.FETCH) {
            this.lexer.nextToken();
            if (this.lexer.token() != Token.FIRST && this.lexer.token() != Token.NEXT && !this.lexer.identifierEquals(FnvHash.Constants.NEXT)) {
               this.acceptIdentifier("FIRST");
            } else {
               this.lexer.nextToken();
            }

            SQLExpr first = this.exprParser.primary();
            boolean percent = false;
            if (this.lexer.token() == Token.PERCENT) {
               percent = true;
               this.lexer.nextToken();
            }

            queryBlock.setFirst(first, percent);
            if (this.lexer.identifierEquals(FnvHash.Constants.ROW) || this.lexer.identifierEquals(FnvHash.Constants.ROWS) || this.lexer.token() == Token.ROW || this.lexer.token() == Token.ROWS) {
               this.lexer.nextToken();
            }

            if (this.lexer.token() == Token.ONLY) {
               this.lexer.nextToken();
            } else {
               this.acceptIdentifier("ONLY");
            }
         }

      }
   }

   public SQLTableSource parseTableSource() {
      SQLTableSource tableSource = this.parseTableSourcePrimary();
      return tableSource instanceof DmSelectTableSource ? this.parseTableSourceRest((DmSelectTableSource)tableSource) : this.parseTableSourceRest(tableSource);
   }

   private SQLExpr flashback() {
      this.accept(Token.OF);
      if (this.lexer.identifierEquals("SCN")) {
         this.lexer.nextToken();
         return new SQLFlashbackExpr(SQLFlashbackExpr.Type.SCN, this.expr());
      } else if (this.lexer.identifierEquals("SNAPSHOT")) {
         return this.expr();
      } else {
         this.lexer.nextToken();
         return new SQLFlashbackExpr(SQLFlashbackExpr.Type.TIMESTAMP, this.expr());
      }
   }

   protected SQLTableSource parseTableSourceRest(DmSelectTableSource tableSource) {
      if (this.lexer.token() == Token.AS) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OF) {
            tableSource.setFlashback(this.flashback());
            return this.parseTableSourceRest(tableSource);
         }

         tableSource.setAlias(this.tableAlias(true));
      } else if (tableSource.getAlias() != null && tableSource.getAlias().length() != 0) {
         if (this.lexer.token() == Token.OUTER) {
         }
      } else if (this.lexer.token() != Token.LEFT && this.lexer.token() != Token.RIGHT && this.lexer.token() != Token.FULL) {
         String tableAlias = this.tableAlias();
         tableSource.setAlias(tableAlias);
      }

      if (this.lexer.token() == Token.HINT) {
         this.exprParser.parseHints(tableSource.getHints());
      }

      SQLJoinTableSource.JoinType joinType = null;
      if (this.lexer.token() == Token.LEFT) {
         this.lexer.nextToken();
         boolean inner = this.lexer.token() == Token.INNER;
         if (this.lexer.token() == Token.OUTER || this.lexer.token() == Token.INNER) {
            this.lexer.nextToken();
         }

         this.accept(Token.JOIN);
         if (inner) {
            joinType = SQLJoinTableSource.JoinType.INNER_JOIN;
         } else {
            joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
         }
      }

      if (this.lexer.token() == Token.RIGHT) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
         }

         this.accept(Token.JOIN);
         joinType = SQLJoinTableSource.JoinType.RIGHT_OUTER_JOIN;
      }

      if (this.lexer.token() == Token.FULL) {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            joinType = SQLJoinTableSource.JoinType.FULL_OUTER_JOIN;
         } else {
            joinType = SQLJoinTableSource.JoinType.FULL_JOIN;
         }

         this.accept(Token.JOIN);
      }

      boolean natural = this.lexer.token() == Token.NATURAL;
      boolean full = false;
      boolean left = false;
      boolean isright = false;
      if (natural) {
         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.FULL) {
         this.lexer.nextToken();
         if (natural) {
            full = true;
         }

         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_FULL_OUTER_JOIN;
               this.accept(Token.JOIN);
            }
         }
      }

      if (this.lexer.token() == Token.LEFT) {
         this.lexer.nextToken();
         if (natural) {
            left = true;
         }

         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_OUTER_JOIN;
               this.accept(Token.JOIN);
            } else {
               joinType = SQLJoinTableSource.JoinType.LEFT_OUTER_JOIN;
               this.accept(Token.JOIN);
            }
         }
      }

      if (this.lexer.token() == Token.RIGHT) {
         this.lexer.nextToken();
         if (natural) {
            isright = true;
         }

         if (this.lexer.token() == Token.OUTER) {
            this.lexer.nextToken();
            if (natural) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_RIGHT_OUTER_JOIN;
               this.accept(Token.JOIN);
            } else {
               joinType = SQLJoinTableSource.JoinType.RIGHT_OUTER_JOIN;
               this.accept(Token.JOIN);
            }
         }
      }

      if (this.lexer.token() == Token.OUTER) {
         this.lexer.nextToken();
         if (natural) {
            joinType = SQLJoinTableSource.JoinType.NATURAL_OUTER_JOIN;
            this.accept(Token.JOIN);
         }
      }

      if (this.lexer.token() == Token.INNER) {
         this.lexer.nextToken();
         this.accept(Token.JOIN);
         if (natural) {
            joinType = SQLJoinTableSource.JoinType.NATURAL_INNER_JOIN;
         } else {
            joinType = SQLJoinTableSource.JoinType.INNER_JOIN;
         }
      }

      if (this.lexer.token() == Token.CROSS) {
         this.lexer.nextToken();
         this.accept(Token.JOIN);
         joinType = SQLJoinTableSource.JoinType.CROSS_JOIN;
      }

      if (this.lexer.token() == Token.JOIN) {
         this.lexer.nextToken();
         if (natural) {
            if (full) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_FULL_JOIN;
            } else if (left) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_LEFT_JOIN;
            } else if (isright) {
               joinType = SQLJoinTableSource.JoinType.NATURAL_RIGHT_JOIN;
            } else {
               joinType = SQLJoinTableSource.JoinType.NATURAL_JOIN;
            }
         } else {
            joinType = SQLJoinTableSource.JoinType.JOIN;
         }
      }

      if (this.lexer.token() == Token.COMMA) {
         this.lexer.nextToken();
         joinType = SQLJoinTableSource.JoinType.COMMA;
      }

      if (joinType != null) {
         SQLJoinTableSource join = new SQLJoinTableSource();
         join.setLeft(tableSource);
         join.setJoinType(joinType);
         SQLTableSource right = this.parseTableSourcePrimary();
         String tableAlias = this.tableAlias();
         right.setAlias(tableAlias);
         join.setRight(right);
         if (this.lexer.token() == Token.ON) {
            this.lexer.nextToken();
            join.setCondition(this.exprParser.expr());
            if (this.lexer.token() == Token.ON && tableSource instanceof SQLJoinTableSource && ((SQLJoinTableSource)tableSource).getCondition() == null) {
               this.lexer.nextToken();
               SQLExpr leftCondidition = this.exprParser.expr();
               ((SQLJoinTableSource)tableSource).setCondition(leftCondidition);
            }
         } else if (this.lexer.token() == Token.USING) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            this.exprParser.exprList(join.getUsing(), join);
            this.accept(Token.RPAREN);
         }

         return this.parseTableSourceRest(join);
      } else {
         return tableSource;
      }
   }

   public SQLTableSource parseTableSourcePrimary() {
      if (this.lexer.token() != Token.LPAREN) {
         if (this.lexer.token() == Token.SELECT) {
            throw new ParserException("TODO. " + this.lexer.info());
         } else {
            DmSelectTableReference tableReference = new DmSelectTableReference();
            if (this.lexer.identifierEquals("ONLY")) {
               this.lexer.nextToken();
               tableReference.setOnly(true);
               this.accept(Token.LPAREN);
               this.parseTableSourceQueryTableExpr(tableReference);
               this.accept(Token.RPAREN);
            } else {
               this.parseTableSourceQueryTableExpr(tableReference);
               this.parsePivot(tableReference);
            }

            return tableReference;
         }
      } else {
         this.lexer.nextToken();
         DmSelectSubqueryTableSource tableSource;
         if (this.lexer.token() != Token.SELECT && this.lexer.token() != Token.WITH) {
            if (this.lexer.token() != Token.LPAREN) {
               if (this.lexer.token() != Token.IDENTIFIER && this.lexer.token() != Token.LITERAL_ALIAS) {
                  throw new ParserException("TODO :" + this.lexer.info());
               }

               SQLTableSource identTable = this.parseTableSource();
               this.accept(Token.RPAREN);
               this.parsePivot((DmSelectTableSource)identTable);
               return identTable;
            }

            tableSource = (DmSelectSubqueryTableSource)this.parseTableSource();
         } else {
            tableSource = new DmSelectSubqueryTableSource(this.select());
         }

         this.accept(Token.RPAREN);
         if ((this.lexer.token() == Token.UNION || this.lexer.token() == Token.MINUS || this.lexer.token() == Token.EXCEPT) && tableSource instanceof DmSelectSubqueryTableSource) {
            SQLSelect select = tableSource.getSelect();
            SQLSelectQuery selectQuery = this.queryRest(select.getQuery(), true);
            select.setQuery(selectQuery);
         }

         this.parsePivot(tableSource);
         return tableSource;
      }
   }

   private void parsePivot(DmSelectTableSource tableSource) {
      if (this.lexer.identifierEquals(FnvHash.Constants.PIVOT)) {
         this.lexer.nextToken();
         DmSelectPivot pivot = new DmSelectPivot();
         if (this.lexer.identifierEquals("XML")) {
            this.lexer.nextToken();
            pivot.setXml(true);
         }

         this.accept(Token.LPAREN);

         while(true) {
            DmSelectPivot.Item item = new DmSelectPivot.Item();
            item.setExpr((SQLAggregateExpr)this.exprParser.expr());
            item.setAlias(this.as());
            pivot.addItem(item);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.FOR);
               if (this.lexer.token() != Token.LPAREN) {
                  pivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                  this.lexer.nextToken();
               } else {
                  this.lexer.nextToken();

                  while(true) {
                     pivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
                     this.lexer.nextToken();
                     if (this.lexer.token() != Token.COMMA) {
                        this.accept(Token.RPAREN);
                        break;
                     }

                     this.lexer.nextToken();
                  }
               }

               this.accept(Token.IN);
               this.accept(Token.LPAREN);
               if (this.lexer.token() == Token.SELECT) {
                  SQLExpr expr = this.exprParser.expr();
                  item = new DmSelectPivot.Item();
                  item.setExpr(expr);
                  pivot.getPivotIn().add(item);
               } else {
                  while(true) {
                     item = new DmSelectPivot.Item();
                     item.setExpr(this.exprParser.expr());
                     item.setAlias(this.as());
                     pivot.getPivotIn().add(item);
                     if (this.lexer.token() != Token.COMMA) {
                        break;
                     }

                     this.lexer.nextToken();
                  }
               }

               this.accept(Token.RPAREN);
               this.accept(Token.RPAREN);
               tableSource.setPivot(pivot);
               break;
            }

            this.lexer.nextToken();
         }
      } else if (this.lexer.identifierEquals("UNPIVOT")) {
         this.lexer.nextToken();
         DmSelectUnPivot unPivot = new DmSelectUnPivot();
         if (this.lexer.identifierEquals("INCLUDE")) {
            this.lexer.nextToken();
            this.acceptIdentifier("NULLS");
            unPivot.setNullsIncludeType(DmSelectUnPivot.NullsIncludeType.INCLUDE_NULLS);
         } else if (this.lexer.identifierEquals("EXCLUDE")) {
            this.lexer.nextToken();
            this.acceptIdentifier("NULLS");
            unPivot.setNullsIncludeType(DmSelectUnPivot.NullsIncludeType.EXCLUDE_NULLS);
         }

         this.accept(Token.LPAREN);
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            this.exprParser.exprList(unPivot.getItems(), unPivot);
            this.accept(Token.RPAREN);
         } else {
            unPivot.addItem(this.exprParser.expr());
         }

         this.accept(Token.FOR);
         if (this.lexer.token() != Token.LPAREN) {
            unPivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
            this.lexer.nextToken();
         } else {
            this.lexer.nextToken();

            while(true) {
               unPivot.getPivotFor().add(new SQLIdentifierExpr(this.lexer.stringVal()));
               this.lexer.nextToken();
               if (this.lexer.token() != Token.COMMA) {
                  this.accept(Token.RPAREN);
                  break;
               }

               this.lexer.nextToken();
            }
         }

         this.accept(Token.IN);
         this.accept(Token.LPAREN);
         if (this.lexer.token() == Token.LPAREN) {
            throw new ParserException("TODO. " + this.lexer.info());
         }

         if (this.lexer.token() == Token.SELECT) {
            throw new ParserException("TODO. " + this.lexer.info());
         }

         while(true) {
            DmSelectPivot.Item item = new DmSelectPivot.Item();
            item.setExpr(this.exprParser.expr());
            item.setAlias(this.as());
            unPivot.getPivotIn().add(item);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               this.accept(Token.RPAREN);
               tableSource.setPivot(unPivot);
               break;
            }

            this.lexer.nextToken();
         }
      }

   }

   private void parseTableSourceQueryTableExpr(DmSelectTableReference tableReference) {
      tableReference.setExpr(this.exprParser.expr());
      if (this.lexer.identifierEquals("SAMPLE")) {
         this.lexer.nextToken();
         DmSampleClause sample = new DmSampleClause();
         if (this.lexer.identifierEquals("BLOCK")) {
            sample.setBlock(true);
            this.lexer.nextToken();
         }

         this.accept(Token.LPAREN);
         this.exprParser.exprList(sample.getPercent(), sample);
         this.accept(Token.RPAREN);
         if (this.lexer.identifierEquals("SEED")) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            sample.setSeedValue(this.expr());
            this.accept(Token.RPAREN);
         }

         tableReference.setSampleClause(sample);
      }

      if (this.lexer.token() == Token.PARTITION) {
         this.lexer.nextToken();
         DmPartitionExtensionClause partition = new DmPartitionExtensionClause();
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            partition.setPartition(this.exprParser.name());
            this.accept(Token.RPAREN);
         } else if (this.lexer.token() == Token.BY) {
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            partition.setPartition(this.exprParser.name());
            this.accept(Token.RPAREN);
         } else {
            this.accept(Token.FOR);
            this.accept(Token.LPAREN);
            this.exprParser.names(partition.getFor());
            this.accept(Token.RPAREN);
         }

         tableReference.setPartition(partition);
      }

      if (this.lexer.identifierEquals("SUBPARTITION")) {
         this.lexer.nextToken();
         DmPartitionExtensionClause partition = new DmPartitionExtensionClause();
         partition.setSubPartition(true);
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();
            partition.setPartition(this.exprParser.name());
            this.accept(Token.RPAREN);
         } else {
            this.accept(Token.FOR);
            this.accept(Token.LPAREN);
            this.exprParser.names(partition.getFor());
            this.accept(Token.RPAREN);
         }

         tableReference.setPartition(partition);
      }

      if (this.lexer.identifierEquals("VERSIONS")) {
         SQLBetweenExpr betweenExpr = new SQLBetweenExpr();
         betweenExpr.setTestExpr(new SQLIdentifierExpr("VERSIONS"));
         this.lexer.nextToken();
         this.accept(Token.BETWEEN);
         SQLFlashbackExpr start = new SQLFlashbackExpr();
         if (this.lexer.identifierEquals("SCN")) {
            this.lexer.nextToken();
            start.setType(SQLFlashbackExpr.Type.SCN);
         } else {
            this.acceptIdentifier("TIMESTAMP");
            start.setType(SQLFlashbackExpr.Type.TIMESTAMP);
         }

         SQLBinaryOpExpr binaryExpr = (SQLBinaryOpExpr)this.exprParser.expr();
         if (binaryExpr.getOperator() != SQLBinaryOperator.BooleanAnd) {
            throw new ParserException("syntax error : " + binaryExpr.getOperator() + ", " + this.lexer.info());
         }

         start.setExpr(binaryExpr.getLeft());
         betweenExpr.setBeginExpr(start);
         betweenExpr.setEndExpr(binaryExpr.getRight());
         tableReference.setFlashback(betweenExpr);
      }

   }

   private void parseHints(DmSelectQueryBlock queryBlock) {
      this.exprParser.parseHints(queryBlock.getHints());
   }
}
