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

import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.ast.SQLCommentHint;
import com.alibaba.druid.sql.ast.SQLDataType;
import com.alibaba.druid.sql.ast.SQLDataTypeImpl;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLKeep;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLObject;
import com.alibaba.druid.sql.ast.SQLOrderBy;
import com.alibaba.druid.sql.ast.SQLOver;
import com.alibaba.druid.sql.ast.SQLPartition;
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.SQLPartitionByRange;
import com.alibaba.druid.sql.ast.SQLPartitionValue;
import com.alibaba.druid.sql.ast.SQLSubPartition;
import com.alibaba.druid.sql.ast.SQLSubPartitionBy;
import com.alibaba.druid.sql.ast.SQLSubPartitionByHash;
import com.alibaba.druid.sql.ast.SQLSubPartitionByList;
import com.alibaba.druid.sql.ast.expr.SQLAggregateExpr;
import com.alibaba.druid.sql.ast.expr.SQLAggregateOption;
import com.alibaba.druid.sql.ast.expr.SQLArrayExpr;
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.SQLCharExpr;
import com.alibaba.druid.sql.ast.expr.SQLDbLinkExpr;
import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLIntegerExpr;
import com.alibaba.druid.sql.ast.expr.SQLMethodInvokeExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumberExpr;
import com.alibaba.druid.sql.ast.expr.SQLNumericLiteralExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.expr.SQLSequenceExpr;
import com.alibaba.druid.sql.ast.expr.SQLSizeExpr;
import com.alibaba.druid.sql.ast.expr.SQLTimestampExpr;
import com.alibaba.druid.sql.ast.expr.SQLUnaryExpr;
import com.alibaba.druid.sql.ast.expr.SQLUnaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.ast.statement.SQLCharacterDataType;
import com.alibaba.druid.sql.ast.statement.SQLCheck;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement;
import com.alibaba.druid.sql.ast.statement.SQLSelect;
import com.alibaba.druid.sql.ast.statement.SQLUnique;
import com.alibaba.druid.sql.dialect.oracle.ast.OracleDataTypeIntervalDay;
import com.alibaba.druid.sql.dialect.oracle.ast.OracleDataTypeIntervalYear;
import com.alibaba.druid.sql.dialect.oracle.ast.OracleSegmentAttributes;
import com.alibaba.druid.sql.dialect.oracle.ast.clause.OracleLobStorageClause;
import com.alibaba.druid.sql.dialect.oracle.ast.clause.OracleStorageClause;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleAnalytic;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleAnalyticWindowing;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleBinaryDoubleExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleBinaryFloatExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleCursorExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleDatetimeExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleIntervalExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleIntervalType;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleIsOfTypeExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleIsSetExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleJsonArrayAggExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleJsonObjectExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleMethodInvokeAccessExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleMultisetExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleOuterExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleRangeExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleSysdateExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleTreatExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.expr.OracleXmlContentExpr;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleCheck;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleConstraint;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleCreateIndexStatement;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleForeignKey;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OraclePrimaryKey;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUnique;
import com.alibaba.druid.sql.dialect.oracle.ast.stmt.OracleUsingIndexClause;
import com.alibaba.druid.sql.parser.Lexer;
import com.alibaba.druid.sql.parser.ParserException;
import com.alibaba.druid.sql.parser.SQLExprParser;
import com.alibaba.druid.sql.parser.SQLParserFeature;
import com.alibaba.druid.sql.parser.Token;
import com.alibaba.druid.util.FnvHash;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.List;

public class OracleExprParser extends SQLExprParser {
   public boolean allowStringAdditive;
   public static final String[] AGGREGATE_FUNCTIONS;
   public static final long[] AGGREGATE_FUNCTIONS_CODES;

   public OracleExprParser(Lexer lexer) {
      super(lexer);
      this.allowStringAdditive = false;
      this.aggregateFunctions = AGGREGATE_FUNCTIONS;
      this.aggregateFunctionHashCodes = AGGREGATE_FUNCTIONS_CODES;
      this.dbType = DbType.oracle;
   }

   public OracleExprParser(String text) {
      this((Lexer)(new OracleLexer(text)));
      this.lexer.nextToken();
      this.dbType = DbType.oracle;
   }

   public OracleExprParser(String text, SQLParserFeature... features) {
      this((Lexer)(new OracleLexer(text, features)));
      this.lexer.nextToken();
      this.dbType = DbType.oracle;
   }

   protected boolean isCharType(long hash) {
      return hash == FnvHash.Constants.CHAR || hash == FnvHash.Constants.CHARACTER || hash == FnvHash.Constants.NCHAR || hash == FnvHash.Constants.VARCHAR || hash == FnvHash.Constants.VARCHAR2 || hash == FnvHash.Constants.NVARCHAR || hash == FnvHash.Constants.NVARCHAR2 || hash == FnvHash.Constants.CHARACTER;
   }

   public SQLDataType parseDataType(boolean restrict) {
      if (this.lexer.token() != Token.CONSTRAINT && this.lexer.token() != Token.COMMA) {
         if (this.lexer.token() != Token.DEFAULT && this.lexer.token() != Token.NOT && this.lexer.token() != Token.NULL) {
            if (this.lexer.token() == Token.INTERVAL) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals("YEAR")) {
                  this.lexer.nextToken();
                  OracleDataTypeIntervalYear interval = new OracleDataTypeIntervalYear();
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     interval.addArgument(this.expr());
                     this.accept(Token.RPAREN);
                  }

                  this.accept(Token.TO);
                  this.acceptIdentifier("MONTH");
                  return interval;
               } else {
                  this.acceptIdentifier("DAY");
                  OracleDataTypeIntervalDay interval = new OracleDataTypeIntervalDay();
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     interval.addArgument(this.expr());
                     this.accept(Token.RPAREN);
                  }

                  this.accept(Token.TO);
                  this.acceptIdentifier("SECOND");
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     interval.getFractionalSeconds().add(this.expr());
                     this.accept(Token.RPAREN);
                  }

                  return interval;
               }
            } else {
               String typeName;
               if (this.lexer.token() == Token.EXCEPTION) {
                  typeName = "EXCEPTION";
                  this.lexer.nextToken();
               } else if (this.lexer.identifierEquals(FnvHash.Constants.LONG)) {
                  this.lexer.nextToken();
                  if (this.lexer.identifierEquals(FnvHash.Constants.RAW)) {
                     this.lexer.nextToken();
                     typeName = "LONG RAW";
                  } else {
                     typeName = "LONG";
                  }
               } else {
                  SQLName typeExpr = this.name();
                  typeName = typeExpr.toString();
               }

               if ("TIMESTAMP".equalsIgnoreCase(typeName)) {
                  SQLDataTypeImpl timestamp = new SQLDataTypeImpl(typeName);
                  timestamp.setDbType(this.dbType);
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     timestamp.addArgument(this.expr());
                     this.accept(Token.RPAREN);
                  }

                  if (this.lexer.token() == Token.WITH) {
                     this.lexer.nextToken();
                     if (this.lexer.identifierEquals("LOCAL")) {
                        this.lexer.nextToken();
                        timestamp.setWithLocalTimeZone(true);
                     }

                     timestamp.setWithTimeZone(true);
                     this.acceptIdentifier("TIME");
                     this.acceptIdentifier("ZONE");
                  }

                  return timestamp;
               } else if ("national".equalsIgnoreCase(typeName) && this.isCharType(this.lexer.hash_lower())) {
                  typeName = typeName + ' ' + this.lexer.stringVal();
                  this.lexer.nextToken();
                  if (this.lexer.identifierEquals("VARYING")) {
                     typeName = typeName + ' ' + this.lexer.stringVal();
                     this.lexer.nextToken();
                  }

                  SQLCharacterDataType charType = new SQLCharacterDataType(typeName);
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     SQLExpr arg = this.expr();
                     arg.setParent(charType);
                     charType.addArgument(arg);
                     this.accept(Token.RPAREN);
                  }

                  charType = (SQLCharacterDataType)this.parseCharTypeRest(charType);
                  if (this.lexer.token() == Token.HINT) {
                     List<SQLCommentHint> hints = this.parseHints();
                     charType.setHints(hints);
                  }

                  return charType;
               } else if (this.isCharType(typeName)) {
                  if (this.lexer.identifierEquals("VARYING")) {
                     typeName = typeName + ' ' + this.lexer.stringVal();
                     this.lexer.nextToken();
                  }

                  SQLCharacterDataType charType = new SQLCharacterDataType(typeName);
                  if (this.lexer.token() == Token.LPAREN) {
                     this.lexer.nextToken();
                     charType.addArgument(this.expr());
                     if (this.lexer.identifierEquals("CHAR")) {
                        this.lexer.nextToken();
                        charType.setCharType("CHAR");
                     } else if (this.lexer.identifierEquals("BYTE")) {
                        this.lexer.nextToken();
                        charType.setCharType("BYTE");
                     }

                     this.accept(Token.RPAREN);
                  } else {
                     if (this.lexer.token() == Token.COMMA) {
                        return this.parseCharTypeRest(charType);
                     }

                     if (restrict) {
                        this.accept(Token.LPAREN);
                     }
                  }

                  return this.parseCharTypeRest(charType);
               } else {
                  if (this.lexer.token() == Token.PERCENT) {
                     this.lexer.nextToken();
                     if (this.lexer.identifierEquals("TYPE")) {
                        this.lexer.nextToken();
                        typeName = typeName + "%TYPE";
                     } else {
                        if (!this.lexer.identifierEquals("ROWTYPE")) {
                           throw new ParserException("syntax error : " + this.lexer.info());
                        }

                        this.lexer.nextToken();
                        typeName = typeName + "%ROWTYPE";
                     }
                  }

                  SQLDataTypeImpl dataType = new SQLDataTypeImpl(typeName);
                  dataType.setDbType(this.dbType);
                  return this.parseDataTypeRest(dataType);
               }
            }
         } else {
            return null;
         }
      } else {
         return null;
      }
   }

   public SQLExpr primary() {
      Token tok = this.lexer.token();
      SQLExpr sqlExpr = null;
      switch (tok) {
         case BINARY_FLOAT:
            OracleBinaryFloatExpr floatExpr = new OracleBinaryFloatExpr();
            floatExpr.setValue(Float.parseFloat(this.lexer.numberString()));
            this.lexer.nextToken();
            return this.primaryRest(floatExpr);
         case BINARY_DOUBLE:
            OracleBinaryDoubleExpr doubleExpr = new OracleBinaryDoubleExpr();
            doubleExpr.setValue(Double.parseDouble(this.lexer.numberString()));
            this.lexer.nextToken();
            return this.primaryRest(doubleExpr);
         case LPAREN:
         case IDENTIFIER:
         case VARIANT:
         case QUES:
         default:
            return super.primary();
         case LITERAL_ALIAS:
            String alias = this.lexer.stringVal();
            this.lexer.nextToken();
            return this.primaryRest(new SQLIdentifierExpr(alias));
         case SYSDATE:
            this.lexer.nextToken();
            OracleSysdateExpr sysdate = new OracleSysdateExpr();
            if (this.lexer.token() == Token.MONKEYS_AT) {
               this.lexer.nextToken();
               this.accept(Token.BANG);
               sysdate.setOption("!");
            }

            return this.primaryRest(sysdate);
         case PRIOR:
            this.lexer.nextToken();
            sqlExpr = this.expr();
            SQLExpr var18 = new SQLUnaryExpr(SQLUnaryOperator.Prior, sqlExpr);
            return this.primaryRest(var18);
         case COLON:
            this.lexer.nextToken();
            if (this.lexer.token() == Token.LITERAL_INT) {
               String name = ":" + this.lexer.numberString();
               this.lexer.nextToken();
               return new SQLVariantRefExpr(name);
            } else {
               if (this.lexer.token() == Token.IDENTIFIER) {
                  String name = this.lexer.stringVal();
                  if (name.charAt(0) != 'B' && name.charAt(0) != 'b') {
                     throw new ParserException("syntax error : " + this.lexer.info());
                  }

                  this.lexer.nextToken();
                  return new SQLVariantRefExpr(":" + name);
               }

               throw new ParserException("syntax error : " + this.lexer.info());
            }
         case TABLE:
            this.lexer.nextToken();
            return this.primaryRest(new SQLIdentifierExpr("TABLE"));
         case PLUS:
            this.lexer.nextToken();
            switch (this.lexer.token()) {
               case LITERAL_INT:
                  sqlExpr = new SQLIntegerExpr(this.lexer.integerValue());
                  this.lexer.nextToken();
                  break;
               case LITERAL_FLOAT:
                  sqlExpr = this.lexer.numberExpr();
                  this.lexer.nextToken();
                  break;
               case BINARY_FLOAT:
                  sqlExpr = new OracleBinaryFloatExpr(Float.parseFloat(this.lexer.numberString()));
                  this.lexer.nextToken();
                  break;
               case BINARY_DOUBLE:
                  sqlExpr = new OracleBinaryDoubleExpr(Double.parseDouble(this.lexer.numberString()));
                  this.lexer.nextToken();
                  break;
               case LPAREN:
                  this.lexer.nextToken();
                  sqlExpr = this.expr();
                  this.accept(Token.RPAREN);
                  sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Plus, sqlExpr);
                  break;
               case IDENTIFIER:
                  sqlExpr = this.expr();
                  sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Plus, sqlExpr);
                  break;
               default:
                  throw new ParserException("TODO " + this.lexer.info());
            }

            return this.primaryRest(sqlExpr);
         case SUB:
            this.lexer.nextToken();
            switch (this.lexer.token()) {
               case LITERAL_INT:
                  Number integerValue = this.lexer.integerValue();
                  Object var22;
                  if (integerValue instanceof Integer) {
                     int intVal = (Integer)integerValue;
                     if (intVal == Integer.MIN_VALUE) {
                        var22 = (long)intVal * -1L;
                     } else {
                        var22 = intVal * -1;
                     }
                  } else if (integerValue instanceof Long) {
                     long longVal = (Long)integerValue;
                     if (longVal == 2147483648L) {
                        var22 = (int)(longVal * -1L);
                     } else {
                        var22 = longVal * -1L;
                     }
                  } else {
                     var22 = ((BigInteger)integerValue).negate();
                  }

                  sqlExpr = new SQLIntegerExpr((Number)var22);
                  this.lexer.nextToken();
                  break;
               case LITERAL_FLOAT:
                  sqlExpr = this.lexer.numberExpr(true);
                  this.lexer.nextToken();
                  break;
               case BINARY_FLOAT:
                  sqlExpr = new OracleBinaryFloatExpr(Float.parseFloat(this.lexer.numberString()) * -1.0F);
                  this.lexer.nextToken();
                  break;
               case BINARY_DOUBLE:
                  sqlExpr = new OracleBinaryDoubleExpr(Double.parseDouble(this.lexer.numberString()) * (double)-1.0F);
                  this.lexer.nextToken();
                  break;
               case LPAREN:
                  this.lexer.nextToken();
                  sqlExpr = this.expr();
                  this.accept(Token.RPAREN);
                  sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Negative, sqlExpr);
                  break;
               case IDENTIFIER:
               case VARIANT:
               case QUES:
               case LITERAL_ALIAS:
                  sqlExpr = this.expr();
                  sqlExpr = new SQLUnaryExpr(SQLUnaryOperator.Negative, sqlExpr);
                  break;
               default:
                  throw new ParserException("TODO " + this.lexer.info());
            }

            return this.primaryRest(sqlExpr);
         case CURSOR:
            this.lexer.nextToken();
            this.accept(Token.LPAREN);
            SQLSelect select = this.createSelectParser().select();
            OracleCursorExpr cursorExpr = new OracleCursorExpr(select);
            this.accept(Token.RPAREN);
            return this.primaryRest(cursorExpr);
         case PCTFREE:
         case INITRANS:
         case MAXTRANS:
         case SEGMENT:
         case CREATION:
         case IMMEDIATE:
         case DEFERRED:
         case STORAGE:
         case NEXT:
         case MINEXTENTS:
         case MAXEXTENTS:
         case MAXSIZE:
         case PCTINCREASE:
         case FLASH_CACHE:
         case CELL_FLASH_CACHE:
         case NONE:
         case LOB:
         case STORE:
         case ROW:
         case CHUNK:
         case CACHE:
         case NOCACHE:
         case LOGGING:
         case NOCOMPRESS:
         case KEEP_DUPLICATES:
         case EXCEPTIONS:
         case PURGE:
         case OUTER:
            SQLExpr var10 = new SQLIdentifierExpr(this.lexer.stringVal());
            this.lexer.nextToken();
            return this.primaryRest(var10);
         case DOCUMENT:
         case CONTENT:
            OracleXmlContentExpr expr = new OracleXmlContentExpr();
            expr.setPlaceholder(new SQLIdentifierExpr(this.lexer.stringVal()));
            this.lexer.nextToken();
            expr.setValue(this.expr());
            if (this.lexer.token() == Token.WELLFORMED) {
               expr.setWellformed(true);
               this.lexer.nextToken();
            }

            return expr;
         case MULTISET:
            return this.parseMultisetExpr();
      }
   }

   protected SQLExpr methodRest(SQLExpr expr, boolean acceptLPAREN) {
      if (acceptLPAREN) {
         this.accept(Token.LPAREN);
      }

      if (this.lexer.token() == Token.PLUS) {
         this.lexer.nextToken();
         this.accept(Token.RPAREN);
         return new OracleOuterExpr(expr);
      } else if (expr != null && expr.toString().equalsIgnoreCase("json_arrayagg")) {
         return this.parseJsonArrayAggExpr(expr);
      } else if (expr != null && expr.toString().equalsIgnoreCase("json_object")) {
         return this.parseJsonObjectExpr(expr);
      } else {
         if (expr instanceof SQLIdentifierExpr) {
            String methodName = ((SQLIdentifierExpr)expr).getName();
            new SQLMethodInvokeExpr(methodName);
            if ("treat".equalsIgnoreCase(methodName)) {
               OracleTreatExpr treatExpr = new OracleTreatExpr();
               treatExpr.setExpr(this.expr());
               this.accept(Token.AS);
               if (this.lexer.identifierEquals("REF")) {
                  treatExpr.setRef(true);
                  this.lexer.nextToken();
               }

               treatExpr.setType(this.expr());
               this.accept(Token.RPAREN);
               return this.primaryRest(treatExpr);
            }
         }

         SQLExpr method = super.methodRest(expr, false);
         this.lexer.mark();
         this.lexer.nextToken();
         if (this.lexer.token() == Token.DOT) {
            SQLExpr call = this.expr();
            OracleMethodInvokeAccessExpr omiaExpr = new OracleMethodInvokeAccessExpr();
            omiaExpr.setMethod(method);
            omiaExpr.setCall(call);
            return omiaExpr;
         } else {
            this.lexer.reset();
            return method;
         }
      }
   }

   public SQLExpr parseJsonArrayAggExpr(SQLExpr expr) {
      SQLMethodInvokeExpr method = new SQLMethodInvokeExpr();
      OracleJsonArrayAggExpr aggExpr = new OracleJsonArrayAggExpr();
      method.setMethodName(expr.toString());
      method.addArgument(aggExpr);
      SQLExpr e = this.expr();
      aggExpr.setExpr(e);
      if (this.lexer.token() == Token.FORMAT) {
         aggExpr.setFormatJson(true);
         this.lexer.nextToken();
         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.ORDER) {
         SQLOrderBy sqlOrderBy = this.parseOrderBy();
         aggExpr.setOrderBy(sqlOrderBy);
      }

      if (this.lexer.token() == Token.NULL) {
         OracleJsonArrayAggExpr.OracleJsonAggArrayOnNullClause nullClause = new OracleJsonArrayAggExpr.OracleJsonAggArrayOnNullClause();
         nullClause.setNullOrAbsent(new SQLIdentifierExpr(this.lexer.stringVal()));
         aggExpr.setNullClause(nullClause);
         this.lexer.nextToken();
         this.lexer.nextToken();
         this.lexer.nextToken();
      }

      if (this.lexer.token() == Token.RETURNING) {
         OracleJsonArrayAggExpr.OracleJsonAggArrayReturnClause returnClause = new OracleJsonArrayAggExpr.OracleJsonAggArrayReturnClause();
         this.lexer.nextToken();
         returnClause.setType(new SQLIdentifierExpr(this.lexer.stringVal()));
         this.lexer.nextToken();
         if (this.lexer.token() == Token.LPAREN) {
            SQLExpr size = this.expr();
            SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(this.lexer.stringVal());
            returnClause.setSize(size);
            returnClause.setByteOrChar(identifierExpr);
            this.accept(Token.RPAREN);
         }

         aggExpr.setReturnClause(returnClause);
      }

      this.accept(Token.RPAREN);
      return method;
   }

   public SQLExpr parseJsonObjectExpr(SQLExpr expr) {
      OracleJsonObjectExpr jsonObjectExpr = new OracleJsonObjectExpr();
      SQLMethodInvokeExpr method = new SQLMethodInvokeExpr();
      method.setMethodName(expr.toString());
      method.addArgument(jsonObjectExpr);

      do {
         boolean hasKey = false;
         if (this.lexer.token() == Token.KEY) {
            hasKey = true;
            this.lexer.nextToken();
         }

         SQLExpr key = this.expr();
         if (this.lexer.token() == Token.VALUE) {
            this.lexer.nextToken();
         }

         SQLExpr value = this.expr();
         boolean isJsonFormat = false;
         if (this.lexer.token() == Token.FORMAT) {
            this.lexer.nextToken();
            this.lexer.nextToken();
            isJsonFormat = true;
         }

         OracleJsonObjectExpr.OracleJsonObjectExprItem item = new OracleJsonObjectExpr.OracleJsonObjectExprItem();
         item.setFormatJson(isJsonFormat);
         item.setValue(value);
         item.setKey(key);
         item.setHasKey(hasKey);
         jsonObjectExpr.getItems().add(item);
      } while(this.lexer.token() != Token.RPAREN && this.lexer.token() != Token.RETURNING && this.lexer.token() != Token.NULL && this.lexer.token() != Token.ABSENT);

      if (this.lexer.token() == Token.NULL || this.lexer.token() == Token.ABSENT) {
         OracleJsonObjectExpr.OracleJsonObjectOnNullClause nullClause = new OracleJsonObjectExpr.OracleJsonObjectOnNullClause();
         SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(this.lexer.stringVal());
         nullClause.setNullOrAbsent(identifierExpr);
         this.lexer.nextToken();
         this.lexer.nextToken();
         this.lexer.nextToken();
         jsonObjectExpr.setNullClause(nullClause);
      }

      if (this.lexer.token() == Token.RETURNING) {
         OracleJsonObjectExpr.OracleJsonObjectReturnClause returnClause = new OracleJsonObjectExpr.OracleJsonObjectReturnClause();
         this.lexer.nextToken();
         this.lexer.nextToken();
         if (this.lexer.token() == Token.LPAREN) {
            SQLExpr size = this.expr();
            SQLIdentifierExpr identifierExpr = new SQLIdentifierExpr(this.lexer.stringVal());
            returnClause.setSize(size);
            returnClause.setByteOrChar(identifierExpr);
            this.accept(Token.RPAREN);
         }

         jsonObjectExpr.setReturnClause(returnClause);
      }

      this.accept(Token.RPAREN);
      return method;
   }

   public SQLExpr primaryRest(SQLExpr expr) {
      if (expr instanceof SQLIdentifierExpr) {
         SQLIdentifierExpr identExpr = (SQLIdentifierExpr)expr;
         String ident = ((SQLIdentifierExpr)expr).getName();
         long hashCode64 = identExpr.hashCode64();
         if (hashCode64 == FnvHash.Constants.TIMESTAMP) {
            if (this.lexer.token() != Token.LITERAL_ALIAS && this.lexer.token() != Token.LITERAL_CHARS) {
               return new SQLIdentifierExpr("TIMESTAMP");
            }

            SQLTimestampExpr timestamp = new SQLTimestampExpr();
            String literal = this.lexer.stringVal();
            timestamp.setLiteral(literal);
            this.accept(Token.LITERAL_CHARS);
            if (this.lexer.identifierEquals("AT")) {
               this.lexer.nextToken();
               this.acceptIdentifier("TIME");
               this.acceptIdentifier("ZONE");
               String timezone = this.lexer.stringVal();
               timestamp.setTimeZone(timezone);
               this.accept(Token.LITERAL_CHARS);
            }

            return this.primaryRest(timestamp);
         }
      }

      if (this.lexer.token() == Token.IDENTIFIER && expr instanceof SQLNumericLiteralExpr) {
         String ident = this.lexer.stringVal();
         if (ident.length() == 1) {
            char unit = ident.charAt(0);
            switch (unit) {
               case 'E':
               case 'G':
               case 'K':
               case 'M':
               case 'P':
               case 'T':
               case 'e':
               case 'g':
               case 'k':
               case 'm':
               case 'p':
               case 't':
                  expr = new SQLSizeExpr(expr, SQLSizeExpr.Unit.valueOf(ident.toUpperCase()));
                  this.lexer.nextToken();
               case 'F':
               case 'H':
               case 'I':
               case 'J':
               case 'L':
               case 'N':
               case 'O':
               case 'Q':
               case 'R':
               case 'S':
               case 'U':
               case 'V':
               case 'W':
               case 'X':
               case 'Y':
               case 'Z':
               case '[':
               case '\\':
               case ']':
               case '^':
               case '_':
               case '`':
               case 'a':
               case 'b':
               case 'c':
               case 'd':
               case 'f':
               case 'h':
               case 'i':
               case 'j':
               case 'l':
               case 'n':
               case 'o':
               case 'q':
               case 'r':
               case 's':
            }
         }
      }

      if (this.lexer.token() == Token.DOTDOT) {
         this.lexer.nextToken();
         SQLExpr upBound = this.expr();
         return new OracleRangeExpr(expr, upBound);
      } else {
         if (this.lexer.token() == Token.MONKEYS_AT) {
            this.lexer.nextToken();
            SQLDbLinkExpr dblink = new SQLDbLinkExpr();
            dblink.setExpr(expr);
            if (this.lexer.token() == Token.BANG) {
               dblink.setDbLink("!");
               this.lexer.nextToken();
            } else {
               String link = this.lexer.stringVal();
               if (this.lexer.token() == Token.LITERAL_ALIAS) {
                  this.accept(Token.LITERAL_ALIAS);
               } else {
                  this.accept(Token.IDENTIFIER);
               }

               dblink.setDbLink(link);
               if (this.lexer.token() == Token.LPAREN) {
                  SQLExpr dblinkExpr = dblink.getExpr();
                  if (dblinkExpr.getClass() == SQLPropertyExpr.class) {
                     SQLExpr methodInvokeExpr = new SQLMethodInvokeExpr();
                     SQLMethodInvokeExpr expr2 = (SQLMethodInvokeExpr)this.methodRest(methodInvokeExpr, true);
                     expr2.setOwner(((SQLPropertyExpr)expr).getOwner());
                     expr2.setMethodName(((SQLPropertyExpr)expr).getName());
                     dblink.setExpr(expr2);
                  }
               }
            }

            expr = dblink;
         }

         if (this.lexer.token() == Token.LBRACKET) {
            SQLArrayExpr arrayExpr = new SQLArrayExpr();
            arrayExpr.setExpr(expr);
            this.lexer.nextToken();
            this.exprList(arrayExpr.getValues(), arrayExpr);
            this.accept(Token.RBRACKET);
            expr = this.primaryRest(arrayExpr);
         }

         if (this.lexer.identifierEquals("SECOND") || this.lexer.identifierEquals("MINUTE") || this.lexer.identifierEquals("HOUR") || this.lexer.identifierEquals("DAY") || this.lexer.identifierEquals("MONTH") || this.lexer.identifierEquals("YEAR")) {
            Lexer.SavePoint savePoint = this.lexer.mark();
            String name = this.lexer.stringVal();
            this.lexer.nextToken();
            if (this.lexer.token() == Token.COMMA) {
               this.lexer.reset(savePoint);
               return expr;
            }

            OracleIntervalExpr interval = new OracleIntervalExpr();
            interval.setValue(expr);
            OracleIntervalType type = OracleIntervalType.valueOf(name.toUpperCase());
            interval.setType(type);
            if (this.lexer.token() == Token.LPAREN) {
               this.lexer.nextToken();
               if (this.lexer.token() != Token.LITERAL_INT) {
                  throw new ParserException("syntax error. " + this.lexer.info());
               }

               interval.setPrecision(this.lexer.integerValue().intValue());
               this.lexer.nextToken();
               this.accept(Token.RPAREN);
            }

            if (this.lexer.token() != Token.TO) {
               this.lexer.reset(savePoint);
               return expr;
            }

            this.lexer.nextToken();
            if (this.lexer.identifierEquals("SECOND")) {
               this.lexer.nextToken();
               interval.setToType(OracleIntervalType.SECOND);
               if (this.lexer.token() == Token.LPAREN) {
                  this.lexer.nextToken();
                  if (this.lexer.token() != Token.LITERAL_INT) {
                     throw new ParserException("syntax error. " + this.lexer.info());
                  }

                  interval.setFactionalSecondsPrecision(this.lexer.integerValue().intValue());
                  this.lexer.nextToken();
                  this.accept(Token.RPAREN);
               }
            } else {
               interval.setToType(OracleIntervalType.MONTH);
               this.lexer.nextToken();
            }

            expr = interval;
         }

         if (this.lexer.identifierEquals("AT")) {
            Lexer.SavePoint mark = this.lexer.mark();
            this.lexer.nextToken();
            if (this.lexer.identifierEquals("LOCAL")) {
               this.lexer.nextToken();
               expr = new OracleDatetimeExpr(expr, new SQLIdentifierExpr("LOCAL"));
            } else {
               if (!this.lexer.identifierEquals("TIME")) {
                  this.lexer.reset(mark);
                  return expr;
               }

               this.lexer.nextToken();
               this.acceptIdentifier("ZONE");
               SQLExpr timeZone = this.primary();
               expr = new OracleDatetimeExpr(expr, timeZone);
            }
         }

         SQLExpr restExpr = super.primaryRest(expr);
         if (restExpr != expr && restExpr instanceof SQLMethodInvokeExpr) {
            SQLMethodInvokeExpr methodInvoke = (SQLMethodInvokeExpr)restExpr;
            if (methodInvoke.getArguments().size() == 1) {
               SQLExpr paramExpr = (SQLExpr)methodInvoke.getArguments().get(0);
               if (paramExpr instanceof SQLIdentifierExpr && "+".equals(((SQLIdentifierExpr)paramExpr).getName())) {
                  OracleOuterExpr outerExpr = new OracleOuterExpr();
                  if (methodInvoke.getOwner() == null) {
                     outerExpr.setExpr(new SQLIdentifierExpr(methodInvoke.getMethodName()));
                  } else {
                     outerExpr.setExpr(new SQLPropertyExpr(methodInvoke.getOwner(), methodInvoke.getMethodName()));
                  }

                  return outerExpr;
               }
            }
         }

         return restExpr;
      }
   }

   protected SQLExpr dotRest(SQLExpr expr) {
      if (this.lexer.token() == Token.LITERAL_ALIAS) {
         String name = this.lexer.stringVal();
         this.lexer.nextToken();
         expr = new SQLPropertyExpr(expr, name);
         if (this.lexer.token() == Token.DOT) {
            this.lexer.nextToken();
            expr = this.dotRest(expr);
         }

         return expr;
      } else {
         if (this.lexer.identifierEquals(FnvHash.Constants.NEXTVAL)) {
            if (expr instanceof SQLIdentifierExpr) {
               SQLIdentifierExpr identExpr = (SQLIdentifierExpr)expr;
               SQLSequenceExpr seqExpr = new SQLSequenceExpr(identExpr, SQLSequenceExpr.Function.NextVal);
               this.lexer.nextToken();
               return seqExpr;
            }
         } else if (this.lexer.identifierEquals(FnvHash.Constants.CURRVAL) && expr instanceof SQLIdentifierExpr) {
            SQLIdentifierExpr identExpr = (SQLIdentifierExpr)expr;
            SQLSequenceExpr seqExpr = new SQLSequenceExpr(identExpr, SQLSequenceExpr.Function.CurrVal);
            this.lexer.nextToken();
            return seqExpr;
         }

         return super.dotRest(expr);
      }
   }

   protected SQLAggregateExpr parseAggregateExpr(String methodName) {
      SQLAggregateExpr aggregateExpr;
      if (this.lexer.token() == Token.UNIQUE) {
         aggregateExpr = new SQLAggregateExpr(methodName, SQLAggregateOption.UNIQUE);
         this.lexer.nextToken();
      } else if (this.lexer.token() == Token.ALL) {
         aggregateExpr = new SQLAggregateExpr(methodName, SQLAggregateOption.ALL);
         this.lexer.nextToken();
      } else if (this.lexer.token() == Token.DISTINCT) {
         aggregateExpr = new SQLAggregateExpr(methodName, SQLAggregateOption.DISTINCT);
         this.lexer.nextToken();
      } else {
         aggregateExpr = new SQLAggregateExpr(methodName);
      }

      this.exprList(aggregateExpr.getArguments(), aggregateExpr);
      if (this.lexer.stringVal().equalsIgnoreCase("IGNORE")) {
         this.lexer.nextToken();
         this.acceptIdentifier("NULLS");
         aggregateExpr.setIgnoreNulls(true);
      } else if (this.lexer.identifierEquals(FnvHash.Constants.RESPECT)) {
         this.lexer.nextToken();
         this.acceptIdentifier("NULLS");
         aggregateExpr.setIgnoreNulls(false);
      }

      this.accept(Token.RPAREN);
      if (this.lexer.stringVal().equalsIgnoreCase("IGNORE")) {
         this.lexer.nextToken();
         this.acceptIdentifier("NULLS");
         aggregateExpr.setIgnoreNulls(true);
      } else if (this.lexer.identifierEquals(FnvHash.Constants.RESPECT)) {
         this.lexer.nextToken();
         this.acceptIdentifier("NULLS");
         aggregateExpr.setIgnoreNulls(false);
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.WITHIN)) {
         this.lexer.nextToken();
         this.accept(Token.GROUP);
         this.accept(Token.LPAREN);
         SQLOrderBy orderBy = this.parseOrderBy();
         aggregateExpr.setWithinGroup(true);
         aggregateExpr.setOrderBy(orderBy);
         this.accept(Token.RPAREN);
      }

      if (this.lexer.identifierEquals("KEEP")) {
         this.lexer.nextToken();
         SQLKeep keep = new SQLKeep();
         this.accept(Token.LPAREN);
         this.acceptIdentifier("DENSE_RANK");
         if (this.lexer.identifierEquals("FIRST")) {
            this.lexer.nextToken();
            keep.setDenseRank(SQLKeep.DenseRank.FIRST);
         } else {
            this.acceptIdentifier("LAST");
            keep.setDenseRank(SQLKeep.DenseRank.LAST);
         }

         SQLOrderBy orderBy = this.parseOrderBy();
         keep.setOrderBy(orderBy);
         aggregateExpr.setKeep(keep);
         this.accept(Token.RPAREN);
      }

      if (this.lexer.token() == Token.OVER) {
         OracleAnalytic over = new OracleAnalytic();
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         if (this.lexer.token() == Token.PARTITION) {
            this.lexer.nextToken();
            this.accept(Token.BY);
            if (this.lexer.token() == Token.LPAREN) {
               this.lexer.nextToken();
               this.exprList(over.getPartitionBy(), over);
               this.accept(Token.RPAREN);
            } else {
               this.exprList(over.getPartitionBy(), over);
            }
         }

         SQLOrderBy orderBy = this.parseOrderBy();
         if (orderBy != null) {
            over.setOrderBy(orderBy);
            OracleAnalyticWindowing windowing = null;
            if (this.lexer.identifierEquals(FnvHash.Constants.ROWS)) {
               this.lexer.nextToken();
               windowing = new OracleAnalyticWindowing();
               windowing.setType(OracleAnalyticWindowing.Type.ROWS);
            } else if (this.lexer.identifierEquals(FnvHash.Constants.RANGE)) {
               this.lexer.nextToken();
               windowing = new OracleAnalyticWindowing();
               windowing.setType(OracleAnalyticWindowing.Type.RANGE);
            }

            if (windowing != null) {
               if (this.lexer.identifierEquals(FnvHash.Constants.CURRENT)) {
                  this.lexer.nextToken();
                  this.accept(Token.ROW);
                  windowing.setExpr(new SQLIdentifierExpr("CURRENT ROW"));
                  over.setWindowing(windowing);
               }

               if (this.lexer.identifierEquals(FnvHash.Constants.UNBOUNDED)) {
                  this.lexer.nextToken();
                  if (!this.lexer.stringVal().equalsIgnoreCase("PRECEDING")) {
                     throw new ParserException("syntax error. " + this.lexer.info());
                  }

                  this.lexer.nextToken();
                  windowing.setExpr(new SQLIdentifierExpr("UNBOUNDED PRECEDING"));
               } else if (this.lexer.token() == Token.BETWEEN) {
                  this.lexer.nextToken();
                  SQLExpr beginExpr;
                  if (this.lexer.identifierEquals(FnvHash.Constants.CURRENT)) {
                     this.lexer.nextToken();
                     this.accept(Token.ROW);
                     beginExpr = new SQLIdentifierExpr("CURRENT ROW");
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.UNBOUNDED)) {
                     this.lexer.nextToken();
                     if (!this.lexer.stringVal().equalsIgnoreCase("PRECEDING")) {
                        throw new ParserException("syntax error. " + this.lexer.info());
                     }

                     this.lexer.nextToken();
                     beginExpr = new SQLIdentifierExpr("UNBOUNDED PRECEDING");
                  } else {
                     beginExpr = this.relational();
                  }

                  SQLOver.WindowingBound beginBound = this.parseWindowingBound();
                  if (beginBound != null) {
                     over.setWindowingBetweenBeginBound(beginBound);
                  }

                  this.accept(Token.AND);
                  SQLExpr endExpr;
                  if (this.lexer.identifierEquals(FnvHash.Constants.CURRENT)) {
                     this.lexer.nextToken();
                     this.accept(Token.ROW);
                     endExpr = new SQLIdentifierExpr("CURRENT ROW");
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.UNBOUNDED)) {
                     this.lexer.nextToken();
                     if (!this.lexer.stringVal().equalsIgnoreCase("PRECEDING")) {
                        throw new ParserException("syntax error. " + this.lexer.info());
                     }

                     this.lexer.nextToken();
                     endExpr = new SQLIdentifierExpr("UNBOUNDED PRECEDING");
                  } else {
                     endExpr = this.relational();
                  }

                  SQLExpr expr = new SQLBetweenExpr((SQLExpr)null, beginExpr, endExpr);
                  windowing.setExpr(expr);
               } else {
                  SQLExpr expr = this.expr();
                  windowing.setExpr(expr);
                  this.acceptIdentifier("PRECEDING");
                  over.setWindowingPreceding(true);
               }

               over.setWindowing(windowing);
            }
         }

         this.accept(Token.RPAREN);
         aggregateExpr.setOver(over);
      }

      return aggregateExpr;
   }

   private OracleIntervalType parseIntervalType() {
      String currentTokenUpperValue = this.lexer.stringVal();
      this.lexer.nextToken();
      if (currentTokenUpperValue.equals("YEAR")) {
         return OracleIntervalType.YEAR;
      } else if (currentTokenUpperValue.equals("MONTH")) {
         return OracleIntervalType.MONTH;
      } else if (currentTokenUpperValue.equals("HOUR")) {
         return OracleIntervalType.HOUR;
      } else if (currentTokenUpperValue.equals("MINUTE")) {
         return OracleIntervalType.MINUTE;
      } else if (currentTokenUpperValue.equals("SECOND")) {
         return OracleIntervalType.SECOND;
      } else {
         throw new ParserException("syntax error. " + this.lexer.info());
      }
   }

   public OracleSelectParser createSelectParser() {
      return new OracleSelectParser(this);
   }

   protected SQLExpr parseInterval() {
      this.accept(Token.INTERVAL);
      OracleIntervalExpr interval = new OracleIntervalExpr();
      if (this.lexer.token() == Token.LITERAL_CHARS) {
         interval.setValue(new SQLCharExpr(this.lexer.stringVal()));
      } else if (this.lexer.token() == Token.VARIANT) {
         interval.setValue(new SQLVariantRefExpr(this.lexer.stringVal()));
      } else {
         if (this.lexer.token() != Token.QUES) {
            return new SQLIdentifierExpr("INTERVAL");
         }

         interval.setValue(new SQLVariantRefExpr("?"));
      }

      this.lexer.nextToken();
      OracleIntervalType type;
      if (this.lexer.identifierEquals(FnvHash.Constants.YEAR)) {
         this.lexer.nextToken();
         type = OracleIntervalType.YEAR;
      } else if (this.lexer.identifierEquals(FnvHash.Constants.MONTH)) {
         this.lexer.nextToken();
         type = OracleIntervalType.MONTH;
      } else if (this.lexer.identifierEquals(FnvHash.Constants.DAY)) {
         this.lexer.nextToken();
         type = OracleIntervalType.DAY;
      } else if (this.lexer.identifierEquals(FnvHash.Constants.HOUR)) {
         this.lexer.nextToken();
         type = OracleIntervalType.HOUR;
      } else if (this.lexer.identifierEquals(FnvHash.Constants.MINUTE)) {
         this.lexer.nextToken();
         type = OracleIntervalType.MINUTE;
      } else {
         if (!this.lexer.identifierEquals(FnvHash.Constants.SECOND)) {
            throw new ParserException("illegal interval type. " + this.lexer.info());
         }

         this.lexer.nextToken();
         type = OracleIntervalType.SECOND;
      }

      interval.setType(type);
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         if (this.lexer.token() != Token.LITERAL_INT && this.lexer.token() != Token.VARIANT) {
            throw new ParserException("syntax error. " + this.lexer.info());
         }

         interval.setPrecision(this.primary());
         if (this.lexer.token() == Token.COMMA) {
            this.lexer.nextToken();
            if (this.lexer.token() != Token.LITERAL_INT) {
               throw new ParserException("syntax error. " + this.lexer.info());
            }

            interval.setFactionalSecondsPrecision(this.lexer.integerValue().intValue());
            this.lexer.nextToken();
         }

         this.accept(Token.RPAREN);
      }

      if (this.lexer.token() == Token.TO) {
         this.lexer.nextToken();
         if (this.lexer.identifierEquals("SECOND")) {
            this.lexer.nextToken();
            interval.setToType(OracleIntervalType.SECOND);
            if (this.lexer.token() == Token.LPAREN) {
               this.lexer.nextToken();
               if (this.lexer.token() != Token.LITERAL_INT && this.lexer.token() != Token.VARIANT) {
                  throw new ParserException("syntax error. " + this.lexer.info());
               }

               interval.setToFactionalSecondsPrecision(this.primary());
               this.accept(Token.RPAREN);
            }
         } else {
            interval.setToType(OracleIntervalType.MONTH);
            this.lexer.nextToken();
         }
      }

      return interval;
   }

   public SQLExpr relationalRest(SQLExpr expr) {
      if (this.lexer.token() != Token.IS) {
         return super.relationalRest(expr);
      } else {
         this.lexer.nextToken();
         if (this.lexer.token() == Token.NOT) {
            this.lexer.nextToken();
            if (this.lexer.identifierEquals("A")) {
               this.lexer.nextToken();
               this.accept(Token.SET);
               return new SQLUnaryExpr(SQLUnaryOperator.IsNotASet, expr);
            }

            if (this.lexer.identifierEquals("EMPTY")) {
               this.lexer.nextToken();
               return new SQLUnaryExpr(SQLUnaryOperator.IsNotEmpty, expr);
            }

            SQLExpr rightExpr = this.primary();
            expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.IsNot, rightExpr, this.getDbType());
         } else if (this.lexer.identifierEquals("A")) {
            this.lexer.nextToken();
            this.accept(Token.SET);
            expr = new OracleIsSetExpr(expr);
         } else {
            if (this.lexer.identifierEquals("EMPTY")) {
               this.lexer.nextToken();
               return new SQLUnaryExpr(SQLUnaryOperator.IsEmpty, expr);
            }

            if (this.lexer.token() == Token.OF) {
               this.lexer.nextToken();
               if (this.lexer.identifierEquals(FnvHash.Constants.TYPE)) {
                  this.lexer.nextToken();
               }

               OracleIsOfTypeExpr isOf = new OracleIsOfTypeExpr();
               isOf.setExpr(expr);
               this.accept(Token.LPAREN);

               while(true) {
                  boolean only = this.lexer.identifierEquals(FnvHash.Constants.ONLY);
                  if (only) {
                     this.lexer.nextToken();
                  }

                  SQLExpr type = this.name();
                  if (only) {
                     type.putAttribute("ONLY", true);
                  }

                  type.setParent(isOf);
                  isOf.getTypes().add(type);
                  if (this.lexer.token() != Token.COMMA) {
                     this.accept(Token.RPAREN);
                     expr = isOf;
                     break;
                  }

                  this.lexer.nextToken();
               }
            } else {
               SQLExpr rightExpr = this.primary();
               expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Is, rightExpr, this.getDbType());
            }
         }

         return expr;
      }
   }

   public SQLName name() {
      SQLName name = super.name();
      if (this.lexer.token() != Token.MONKEYS_AT) {
         return name;
      } else {
         this.lexer.nextToken();
         if (this.lexer.token() != Token.IDENTIFIER) {
            throw new ParserException("syntax error, expect identifier, but " + this.lexer.token() + ", " + this.lexer.info());
         } else {
            SQLDbLinkExpr dbLink = new SQLDbLinkExpr();
            dbLink.setExpr(name);
            String link = this.lexer.stringVal();
            this.lexer.nextToken();

            while(this.lexer.token() == Token.DOT) {
               this.lexer.nextToken();
               String stringVal = this.lexer.stringVal();
               this.accept(Token.IDENTIFIER);
               link = link + "." + stringVal;
            }

            dbLink.setDbLink(link);
            return dbLink;
         }
      }
   }

   public OraclePrimaryKey parsePrimaryKey() {
      this.lexer.nextToken();
      this.accept(Token.KEY);
      OraclePrimaryKey primaryKey = new OraclePrimaryKey();
      this.accept(Token.LPAREN);
      this.orderBy(primaryKey.getColumns(), primaryKey);
      this.accept(Token.RPAREN);
      if (this.lexer.token() == Token.USING) {
         OracleUsingIndexClause using = this.parseUsingIndex();
         primaryKey.setUsing(using);
      }

      while(true) {
         while(this.lexer.token() != Token.ENABLE) {
            if (this.lexer.token() == Token.DISABLE) {
               this.lexer.nextToken();
               primaryKey.setEnable(Boolean.FALSE);
            } else if (this.lexer.identifierEquals("VALIDATE")) {
               this.lexer.nextToken();
               primaryKey.setValidate(Boolean.TRUE);
            } else if (this.lexer.identifierEquals("NOVALIDATE")) {
               this.lexer.nextToken();
               primaryKey.setValidate(Boolean.FALSE);
            } else if (this.lexer.identifierEquals("RELY")) {
               this.lexer.nextToken();
               primaryKey.setRely(Boolean.TRUE);
            } else {
               if (!this.lexer.identifierEquals("NORELY")) {
                  return primaryKey;
               }

               this.lexer.nextToken();
               primaryKey.setRely(Boolean.FALSE);
            }
         }

         this.lexer.nextToken();
         primaryKey.setEnable(Boolean.TRUE);
      }
   }

   private OracleUsingIndexClause parseUsingIndex() {
      this.accept(Token.USING);
      this.accept(Token.INDEX);
      OracleUsingIndexClause using = new OracleUsingIndexClause();
      if (this.lexer.token() == Token.LPAREN) {
         this.lexer.nextToken();
         OracleCreateIndexStatement createIndex = (new OracleStatementParser(this.lexer)).parseCreateIndex(true);
         using.setIndex((SQLCreateIndexStatement)createIndex);
         this.accept(Token.RPAREN);
      }

      while(true) {
         this.parseSegmentAttributes(using);
         if (this.lexer.token() == Token.COMPUTE) {
            this.lexer.nextToken();
            this.acceptIdentifier("STATISTICS");
            using.setComputeStatistics(true);
         } else if (this.lexer.token() == Token.ENABLE) {
            this.lexer.nextToken();
            using.setEnable(true);
         } else if (this.lexer.identifierEquals("REVERSE")) {
            this.lexer.nextToken();
            using.setReverse(true);
         } else if (this.lexer.token() == Token.DISABLE) {
            this.lexer.nextToken();
            using.setEnable(false);
         } else {
            if (!this.lexer.identifierEquals("LOCAL")) {
               if (this.lexer.token() == Token.IDENTIFIER) {
                  using.setTablespace(this.name());
               }

               return using;
            }

            this.lexer.nextToken();
            this.accept(Token.LPAREN);

            while(true) {
               SQLPartition partition = this.parsePartition();
               partition.setParent(using);
               using.getLocalPartitionIndex().add(partition);
               if (this.lexer.token() != Token.COMMA) {
                  if (this.lexer.token() != Token.RPAREN) {
                     throw new ParserException("TODO " + this.lexer.info());
                  }

                  this.accept(Token.RPAREN);
                  break;
               }

               this.lexer.nextToken();
            }
         }
      }
   }

   public SQLColumnDefinition parseColumnRest(SQLColumnDefinition column) {
      column = super.parseColumnRest(column);
      if (this.lexer.identifierEquals(FnvHash.Constants.GENERATED)) {
         this.lexer.nextToken();
         if (!this.lexer.identifierEquals(FnvHash.Constants.ALWAYS)) {
            throw new ParserException("TODO " + this.lexer.info());
         }

         this.lexer.nextToken();
         this.accept(Token.AS);
         SQLExpr expr = this.expr();
         column.setGeneratedAlawsAs(expr);
      }

      while(true) {
         while(this.lexer.token() != Token.ENABLE) {
            if (this.lexer.token() == Token.DISABLE) {
               this.lexer.nextToken();
               column.setEnable(Boolean.FALSE);
            } else if (this.lexer.identifierEquals("VALIDATE")) {
               this.lexer.nextToken();
               column.setValidate(Boolean.TRUE);
            } else if (this.lexer.identifierEquals("NOVALIDATE")) {
               this.lexer.nextToken();
               column.setValidate(Boolean.FALSE);
            } else if (this.lexer.identifierEquals("RELY")) {
               this.lexer.nextToken();
               column.setRely(Boolean.TRUE);
            } else if (this.lexer.identifierEquals("NORELY")) {
               this.lexer.nextToken();
               column.setRely(Boolean.FALSE);
            } else if (this.lexer.identifierEquals(FnvHash.Constants.VISIBLE)) {
               this.lexer.nextToken();
               column.setVisible(true);
            } else {
               if (!this.lexer.identifierEquals(FnvHash.Constants.VIRTUAL)) {
                  return column;
               }

               this.lexer.nextToken();
               column.setVirtual(true);
            }
         }

         this.lexer.nextToken();
         column.setEnable(Boolean.TRUE);
      }
   }

   public SQLExpr exprRest(SQLExpr expr) {
      expr = super.exprRest(expr);
      if (this.lexer.token() == Token.COLONEQ) {
         this.lexer.nextToken();
         SQLExpr right = this.expr();
         expr = new SQLBinaryOpExpr(expr, SQLBinaryOperator.Assignment, right, this.getDbType());
      }

      return expr;
   }

   public SQLObject parseOpaque() {
      this.acceptIdentifier("OPAQUE");
      this.acceptIdentifier("TYPE");
      SQLExpr expr = this.primary();
      OracleLobStorageClause clause = new OracleLobStorageClause();
      this.accept(Token.STORE);
      this.accept(Token.AS);
      if (this.lexer.identifierEquals("SECUREFILE")) {
         this.lexer.nextToken();
         clause.setSecureFile(true);
      }

      if (this.lexer.identifierEquals("BASICFILE")) {
         this.lexer.nextToken();
         clause.setBasicFile(true);
      }

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

         while(true) {
            this.parseSegmentAttributes(clause);
            if (this.lexer.token() == Token.ENABLE) {
               this.lexer.nextToken();
               this.accept(Token.STORAGE);
               this.accept(Token.IN);
               this.accept(Token.ROW);
               clause.setEnable(true);
            } else if (this.lexer.token() == Token.DISABLE) {
               this.lexer.nextToken();
               this.accept(Token.STORAGE);
               this.accept(Token.IN);
               this.accept(Token.ROW);
               clause.setEnable(false);
            } else if (this.lexer.token() == Token.CHUNK) {
               this.lexer.nextToken();
               clause.setChunk(this.primary());
            } else if (this.lexer.token() == Token.NOCACHE) {
               this.lexer.nextToken();
               clause.setCache(false);
               if (this.lexer.token() == Token.LOGGING) {
                  this.lexer.nextToken();
                  clause.setLogging(true);
               }
            } else if (this.lexer.token() == Token.CACHE) {
               this.lexer.nextToken();
               clause.setCache(true);
            } else if (this.lexer.token() == Token.KEEP_DUPLICATES) {
               this.lexer.nextToken();
               clause.setKeepDuplicate(true);
            } else if (this.lexer.identifierEquals("PCTVERSION")) {
               this.lexer.nextToken();
               clause.setPctversion(this.expr());
            } else if (this.lexer.identifierEquals("RETENTION")) {
               this.lexer.nextToken();
               clause.setRetention(true);
            } else {
               if (this.lexer.token() != Token.STORAGE) {
                  this.accept(Token.RPAREN);
                  break;
               }

               OracleStorageClause storageClause = this.parseStorage();
               clause.setStorageClause(storageClause);
            }
         }
      }

      return clause;
   }

   public OracleLobStorageClause parseLobStorage() {
      this.lexer.nextToken();
      OracleLobStorageClause clause = new OracleLobStorageClause();
      this.accept(Token.LPAREN);
      this.names(clause.getItems());
      this.accept(Token.RPAREN);
      this.accept(Token.STORE);
      this.accept(Token.AS);
      if (this.lexer.identifierEquals("SECUREFILE")) {
         this.lexer.nextToken();
         clause.setSecureFile(true);
      }

      if (this.lexer.identifierEquals("BASICFILE")) {
         this.lexer.nextToken();
         clause.setBasicFile(true);
      }

      if (this.lexer.token() == Token.IDENTIFIER || this.lexer.token() == Token.LITERAL_ALIAS) {
         SQLName segmentName = this.name();
         clause.setSegementName(segmentName);
      }

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

         while(true) {
            this.parseSegmentAttributes(clause);
            if (this.lexer.token() == Token.ENABLE) {
               this.lexer.nextToken();
               this.accept(Token.STORAGE);
               this.accept(Token.IN);
               this.accept(Token.ROW);
               clause.setEnable(true);
            } else if (this.lexer.token() == Token.DISABLE) {
               this.lexer.nextToken();
               this.accept(Token.STORAGE);
               this.accept(Token.IN);
               this.accept(Token.ROW);
               clause.setEnable(false);
            } else if (this.lexer.token() == Token.CHUNK) {
               this.lexer.nextToken();
               clause.setChunk(this.primary());
            } else if (this.lexer.token() == Token.NOCACHE) {
               this.lexer.nextToken();
               clause.setCache(false);
               if (this.lexer.token() == Token.LOGGING) {
                  this.lexer.nextToken();
                  clause.setLogging(true);
               }
            } else if (this.lexer.token() == Token.CACHE) {
               this.lexer.nextToken();
               clause.setCache(true);
            } else if (this.lexer.token() == Token.KEEP_DUPLICATES) {
               this.lexer.nextToken();
               clause.setKeepDuplicate(true);
            } else if (this.lexer.identifierEquals("PCTVERSION")) {
               this.lexer.nextToken();
               clause.setPctversion(this.expr());
            } else if (this.lexer.identifierEquals("RETENTION")) {
               this.lexer.nextToken();
               clause.setRetention(true);
            } else {
               if (this.lexer.token() != Token.STORAGE) {
                  this.accept(Token.RPAREN);
                  break;
               }

               OracleStorageClause storageClause = this.parseStorage();
               clause.setStorageClause(storageClause);
            }
         }
      }

      return clause;
   }

   public OracleStorageClause parseStorage() {
      this.lexer.nextToken();
      this.accept(Token.LPAREN);
      OracleStorageClause storage = new OracleStorageClause();

      while(true) {
         while(!this.lexer.identifierEquals("INITIAL")) {
            if (this.lexer.token() == Token.NEXT) {
               this.lexer.nextToken();
               storage.setNext(this.expr());
            } else if (this.lexer.token() == Token.MINEXTENTS) {
               this.lexer.nextToken();
               storage.setMinExtents(this.expr());
            } else if (this.lexer.token() == Token.MAXEXTENTS) {
               this.lexer.nextToken();
               storage.setMaxExtents(this.expr());
            } else if (this.lexer.token() == Token.MAXSIZE) {
               this.lexer.nextToken();
               storage.setMaxSize(this.expr());
            } else if (this.lexer.token() == Token.PCTINCREASE) {
               this.lexer.nextToken();
               storage.setPctIncrease(this.expr());
            } else if (this.lexer.identifierEquals("FREELISTS")) {
               this.lexer.nextToken();
               storage.setFreeLists(this.expr());
            } else if (this.lexer.identifierEquals("FREELIST")) {
               this.lexer.nextToken();
               this.acceptIdentifier("GROUPS");
               storage.setFreeListGroups(this.expr());
            } else if (this.lexer.identifierEquals("BUFFER_POOL")) {
               this.lexer.nextToken();
               storage.setBufferPool(this.expr());
            } else if (this.lexer.identifierEquals("OBJNO")) {
               this.lexer.nextToken();
               storage.setObjno(this.expr());
            } else if (this.lexer.token() == Token.FLASH_CACHE) {
               this.lexer.nextToken();
               OracleStorageClause.FlashCacheType flashCacheType;
               if (this.lexer.identifierEquals("KEEP")) {
                  flashCacheType = OracleStorageClause.FlashCacheType.KEEP;
                  this.lexer.nextToken();
               } else if (this.lexer.token() == Token.NONE) {
                  flashCacheType = OracleStorageClause.FlashCacheType.NONE;
                  this.lexer.nextToken();
               } else {
                  this.accept(Token.DEFAULT);
                  flashCacheType = OracleStorageClause.FlashCacheType.DEFAULT;
               }

               storage.setFlashCache(flashCacheType);
            } else {
               if (this.lexer.token() != Token.CELL_FLASH_CACHE) {
                  this.accept(Token.RPAREN);
                  return storage;
               }

               this.lexer.nextToken();
               OracleStorageClause.FlashCacheType flashCacheType;
               if (this.lexer.identifierEquals("KEEP")) {
                  flashCacheType = OracleStorageClause.FlashCacheType.KEEP;
                  this.lexer.nextToken();
               } else if (this.lexer.token() == Token.NONE) {
                  flashCacheType = OracleStorageClause.FlashCacheType.NONE;
                  this.lexer.nextToken();
               } else {
                  this.accept(Token.DEFAULT);
                  flashCacheType = OracleStorageClause.FlashCacheType.DEFAULT;
               }

               storage.setCellFlashCache(flashCacheType);
            }
         }

         this.lexer.nextToken();
         storage.setInitial(this.expr());
      }
   }

   public SQLUnique parseUnique() {
      this.accept(Token.UNIQUE);
      OracleUnique unique = new OracleUnique();
      this.accept(Token.LPAREN);
      this.orderBy(unique.getColumns(), unique);
      this.accept(Token.RPAREN);
      if (this.lexer.token() == Token.USING) {
         OracleUsingIndexClause using = this.parseUsingIndex();
         unique.setUsing(using);
      }

      return unique;
   }

   public OracleConstraint parseConstaint() {
      OracleConstraint constraint = (OracleConstraint)super.parseConstaint();

      while(true) {
         while(this.lexer.token() != Token.EXCEPTIONS) {
            if (this.lexer.token() == Token.DISABLE) {
               this.lexer.nextToken();
               constraint.setEnable(false);
            } else if (this.lexer.token() == Token.ENABLE) {
               this.lexer.nextToken();
               constraint.setEnable(true);
            } else if (this.lexer.identifierEquals(FnvHash.Constants.VALIDATE)) {
               this.lexer.nextToken();
               constraint.setValidate(Boolean.TRUE);
            } else if (this.lexer.identifierEquals(FnvHash.Constants.NOVALIDATE)) {
               this.lexer.nextToken();
               constraint.setValidate(Boolean.FALSE);
            } else if (this.lexer.token() == Token.INITIALLY) {
               this.lexer.nextToken();
               if (this.lexer.token() == Token.IMMEDIATE) {
                  this.lexer.nextToken();
                  constraint.setInitially(OracleConstraint.Initially.IMMEDIATE);
               } else {
                  this.accept(Token.DEFERRED);
                  constraint.setInitially(OracleConstraint.Initially.DEFERRED);
               }
            } else if (this.lexer.token() == Token.NOT) {
               this.lexer.nextToken();
               if (!this.lexer.identifierEquals(FnvHash.Constants.DEFERRABLE)) {
                  throw new ParserException("TODO " + this.lexer.info());
               }

               this.lexer.nextToken();
               constraint.setDeferrable(false);
            } else {
               if (!this.lexer.identifierEquals(FnvHash.Constants.DEFERRABLE)) {
                  if (this.lexer.token() == Token.USING) {
                     OracleUsingIndexClause using = this.parseUsingIndex();
                     constraint.setUsing(using);
                  }

                  return constraint;
               }

               this.lexer.nextToken();
               constraint.setDeferrable(true);
            }
         }

         this.lexer.nextToken();
         this.accept(Token.INTO);
         SQLName exceptionsInto = this.name();
         constraint.setExceptionsInto(exceptionsInto);
      }
   }

   protected OracleForeignKey createForeignKey() {
      return new OracleForeignKey();
   }

   protected SQLCheck createCheck() {
      return new OracleCheck();
   }

   public SQLPartition parsePartition() {
      this.accept(Token.PARTITION);
      SQLPartition partition = new SQLPartition();
      partition.setName(this.name());
      SQLPartitionValue values = this.parsePartitionValues();
      if (values != null) {
         this.parseSegmentAttributes(values);
      }

      if (values != null) {
         partition.setValues(values);
      }

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

         while(true) {
            SQLSubPartition subPartition = this.parseSubPartition();
            this.parseSegmentAttributes(subPartition);
            partition.addSubPartition(subPartition);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      } else if (this.lexer.identifierEquals(FnvHash.Constants.SUBPARTITIONS)) {
         this.lexer.nextToken();
         SQLExpr subPartitionsCount = this.primary();
         partition.setSubPartitionsCount(subPartitionsCount);
      }

      while(true) {
         this.parseSegmentAttributes(partition);
         if (this.lexer.token() == Token.LOB) {
            OracleLobStorageClause lobStorage = this.parseLobStorage();
            partition.setLobStorage(lobStorage);
         } else {
            if (this.lexer.token() != Token.SEGMENT && !this.lexer.identifierEquals("SEGMENT")) {
               if (this.lexer.token() == Token.LPAREN) {
                  this.lexer.nextToken();

                  while(true) {
                     SQLSubPartition subPartition = this.parseSubPartition();
                     this.parseSegmentAttributes(subPartition);
                     partition.addSubPartition(subPartition);
                     if (this.lexer.token() != Token.COMMA) {
                        this.accept(Token.RPAREN);
                        break;
                     }

                     this.lexer.nextToken();
                  }
               }

               return partition;
            }

            this.lexer.nextToken();
            this.accept(Token.CREATION);
            if (this.lexer.token() == Token.IMMEDIATE) {
               this.lexer.nextToken();
               partition.setSegmentCreationImmediate(true);
            } else if (this.lexer.token() == Token.DEFERRED) {
               this.lexer.nextToken();
               partition.setSegmentCreationDeferred(true);
            }
         }
      }
   }

   protected SQLPartitionBy parsePartitionBy() {
      this.lexer.nextToken();
      this.accept(Token.BY);
      if (this.lexer.identifierEquals("RANGE")) {
         return this.partitionByRange();
      } else if (!this.lexer.identifierEquals("HASH")) {
         if (this.lexer.identifierEquals("LIST")) {
            SQLPartitionByList partitionByList = this.partitionByList();
            this.partitionClauseRest(partitionByList);
            return partitionByList;
         } else {
            throw new ParserException("TODO : " + this.lexer.info());
         }
      } else {
         SQLPartitionByHash partitionByHash = this.partitionByHash();
         this.partitionClauseRest(partitionByHash);
         if (this.lexer.token() == Token.LPAREN) {
            this.lexer.nextToken();

            while(true) {
               SQLPartition partition = this.parsePartition();
               partitionByHash.addPartition(partition);
               if (this.lexer.token() != Token.COMMA) {
                  if (this.lexer.token() != Token.RPAREN) {
                     throw new ParserException("TODO : " + this.lexer.info());
                  }

                  this.lexer.nextToken();
                  break;
               }

               this.lexer.nextToken();
            }
         }

         return partitionByHash;
      }
   }

   protected SQLPartitionByList partitionByList() {
      this.acceptIdentifier("LIST");
      SQLPartitionByList partitionByList = new SQLPartitionByList();
      this.accept(Token.LPAREN);
      partitionByList.addColumn(this.expr());
      this.accept(Token.RPAREN);
      this.parsePartitionByRest(partitionByList);
      return partitionByList;
   }

   protected SQLSubPartition parseSubPartition() {
      this.acceptIdentifier("SUBPARTITION");
      SQLSubPartition subPartition = new SQLSubPartition();
      SQLName name = this.name();
      subPartition.setName(name);
      SQLPartitionValue values = this.parsePartitionValues();
      if (values != null) {
         subPartition.setValues(values);
      }

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

      return subPartition;
   }

   public void parseSegmentAttributes(OracleSegmentAttributes attributes) {
      while(true) {
         if (this.lexer.token() == Token.TABLESPACE) {
            this.lexer.nextToken();
            attributes.setTablespace(this.name());
         } else if (this.lexer.token() != Token.NOCOMPRESS && !this.lexer.identifierEquals("NOCOMPRESS")) {
            if (this.lexer.identifierEquals(FnvHash.Constants.COMPRESS)) {
               this.lexer.nextToken();
               attributes.setCompress(Boolean.TRUE);
               if (this.lexer.token() == Token.LITERAL_INT) {
                  int compressLevel = this.parseIntValue();
                  attributes.setCompressLevel(compressLevel);
               } else if (this.lexer.identifierEquals("BASIC")) {
                  this.lexer.nextToken();
               } else if (this.lexer.token() == Token.FOR) {
                  this.lexer.nextToken();
                  if (!this.lexer.identifierEquals("OLTP")) {
                     throw new ParserException("TODO : " + this.lexer.info());
                  }

                  this.lexer.nextToken();
                  attributes.setCompressForOltp(true);
               }
            } else if (this.lexer.identifierEquals("NOCOMPRESS")) {
               this.lexer.nextToken();
               attributes.setCompress(Boolean.FALSE);
            } else if (this.lexer.token() != Token.LOGGING && !this.lexer.identifierEquals("LOGGING")) {
               if (this.lexer.identifierEquals("NOLOGGING")) {
                  this.lexer.nextToken();
                  attributes.setLogging(Boolean.FALSE);
               } else if (this.lexer.token() == Token.INITRANS) {
                  this.lexer.nextToken();
                  attributes.setInitrans(this.parseIntValue());
               } else if (this.lexer.token() == Token.MAXTRANS) {
                  this.lexer.nextToken();
                  attributes.setMaxtrans(this.parseIntValue());
               } else if (this.lexer.token() == Token.PCTINCREASE) {
                  this.lexer.nextToken();
                  attributes.setPctincrease(this.parseIntValue());
               } else if (this.lexer.token() == Token.PCTFREE) {
                  this.lexer.nextToken();
                  attributes.setPctfree(this.parseIntValue());
               } else if (this.lexer.token() != Token.STORAGE && !this.lexer.identifierEquals("STORAGE")) {
                  if (this.lexer.identifierEquals(FnvHash.Constants.PCTUSED)) {
                     this.lexer.nextToken();
                     attributes.setPctused(this.parseIntValue());
                  } else if (this.lexer.identifierEquals(FnvHash.Constants.USAGE)) {
                     this.lexer.nextToken();
                     this.acceptIdentifier("QUEUE");
                  } else {
                     if (!this.lexer.identifierEquals(FnvHash.Constants.OPAQUE)) {
                        return;
                     }

                     this.parseOpaque();
                  }
               } else {
                  OracleStorageClause storage = this.parseStorage();
                  attributes.setStorage(storage);
               }
            } else {
               this.lexer.nextToken();
               attributes.setLogging(Boolean.TRUE);
            }
         } else {
            this.lexer.nextToken();
            attributes.setCompress(Boolean.FALSE);
         }
      }
   }

   protected SQLPartitionByRange partitionByRange() {
      this.acceptIdentifier("RANGE");
      this.accept(Token.LPAREN);
      SQLPartitionByRange clause = new SQLPartitionByRange();

      while(true) {
         SQLName column = this.name();
         clause.addColumn(column);
         if (this.lexer.token() != Token.COMMA) {
            this.accept(Token.RPAREN);
            if (this.lexer.token() == Token.INTERVAL) {
               this.lexer.nextToken();
               this.accept(Token.LPAREN);
               clause.setInterval(this.expr());
               this.accept(Token.RPAREN);
            }

            this.parsePartitionByRest(clause);
            return clause;
         }

         this.lexer.nextToken();
      }
   }

   protected void parsePartitionByRest(SQLPartitionBy clause) {
      if (this.lexer.token() == Token.STORE) {
         this.lexer.nextToken();
         this.accept(Token.IN);
         this.accept(Token.LPAREN);

         while(true) {
            SQLName tablespace = this.name();
            clause.getStoreIn().add(tablespace);
            if (this.lexer.token() != Token.COMMA) {
               this.accept(Token.RPAREN);
               break;
            }

            this.lexer.nextToken();
         }
      }

      if (this.lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
         SQLSubPartitionBy subPartitionBy = this.subPartitionBy();
         clause.setSubPartitionBy(subPartitionBy);
         if (this.lexer.identifierEquals(FnvHash.Constants.SUBPARTITIONS)) {
            this.lexer.nextToken();
            Number intValue = this.lexer.integerValue();
            SQLNumberExpr numExpr = new SQLNumberExpr(intValue);
            subPartitionBy.setSubPartitionsCount(numExpr);
            this.lexer.nextToken();
         }
      }

      this.accept(Token.LPAREN);

      while(true) {
         SQLPartition partition = this.parsePartition();
         clause.addPartition(partition);
         if (this.lexer.token() != Token.COMMA) {
            this.accept(Token.RPAREN);
            return;
         }

         this.lexer.nextToken();
      }
   }

   protected SQLSubPartitionBy subPartitionBy() {
      this.lexer.nextToken();
      this.accept(Token.BY);
      if (this.lexer.identifierEquals(FnvHash.Constants.HASH)) {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         SQLSubPartitionByHash byHash = new SQLSubPartitionByHash();
         SQLExpr expr = this.expr();
         byHash.setExpr(expr);
         this.accept(Token.RPAREN);
         return byHash;
      } else if (!this.lexer.identifierEquals(FnvHash.Constants.LIST)) {
         throw new ParserException("TODO : " + this.lexer.info());
      } else {
         this.lexer.nextToken();
         this.accept(Token.LPAREN);
         SQLSubPartitionByList byList = new SQLSubPartitionByList();
         SQLName column = this.name();
         byList.setColumn(column);
         this.accept(Token.RPAREN);
         if (this.lexer.identifierEquals(FnvHash.Constants.SUBPARTITION)) {
            this.lexer.nextToken();
            this.acceptIdentifier("TEMPLATE");
            this.accept(Token.LPAREN);

            while(true) {
               SQLSubPartition subPartition = this.parseSubPartition();
               subPartition.setParent(byList);
               byList.getSubPartitionTemplate().add(subPartition);
               if (this.lexer.token() != Token.COMMA) {
                  this.accept(Token.RPAREN);
                  break;
               }

               this.lexer.nextToken();
            }
         }

         if (this.lexer.identifierEquals(FnvHash.Constants.SUBPARTITIONS)) {
            this.lexer.nextToken();
            Number intValue = this.lexer.integerValue();
            SQLNumberExpr numExpr = new SQLNumberExpr(intValue);
            byList.setSubPartitionsCount(numExpr);
            this.lexer.nextToken();
         }

         return byList;
      }
   }

   protected void partitionClauseRest(SQLPartitionBy clause) {
      if (this.lexer.identifierEquals(FnvHash.Constants.PARTITIONS)) {
         this.lexer.nextToken();
         SQLIntegerExpr countExpr = this.integerExpr();
         clause.setPartitionsCount(countExpr);
      }

      if (this.lexer.token() == Token.STORE) {
         this.lexer.nextToken();
         this.accept(Token.IN);
         this.accept(Token.LPAREN);
         this.names(clause.getStoreIn(), clause);
         this.accept(Token.RPAREN);
      }

   }

   protected SQLPartitionByHash partitionByHash() {
      this.acceptIdentifier("HASH");
      SQLPartitionByHash partitionByHash = new SQLPartitionByHash();
      if (this.lexer.token() == Token.KEY) {
         this.lexer.nextToken();
         partitionByHash.setKey(true);
      }

      this.accept(Token.LPAREN);
      this.exprList(partitionByHash.getColumns(), partitionByHash);
      this.accept(Token.RPAREN);
      return partitionByHash;
   }

   private OracleMultisetExpr parseMultisetExpr() {
      OracleMultisetExpr oracleMultisetExpr = new OracleMultisetExpr();
      this.lexer.nextToken();
      SQLExpr type = new SQLIdentifierExpr(this.lexer.stringVal());
      this.lexer.nextToken();
      SQLExpr distinct = null;
      if (this.lexer.token() == Token.DISTINCT) {
         distinct = new SQLIdentifierExpr(Token.DISTINCT.name);
         this.lexer.nextToken();
      }

      SQLExpr rightNestedTable = this.expr();
      oracleMultisetExpr.setType(type);
      oracleMultisetExpr.setDistinct(distinct);
      oracleMultisetExpr.setRightNestedTable(rightNestedTable);
      return oracleMultisetExpr;
   }

   static {
      String[] strings = new String[]{"AVG", "CORR", "COVAR_POP", "COVAR_SAMP", "COUNT", "CUME_DIST", "DENSE_RANK", "FIRST", "FIRST_VALUE", "LAG", "LAST", "LAST_VALUE", "LISTAGG", "LEAD", "MAX", "MIN", "NTILE", "PERCENT_RANK", "PERCENTILE_CONT", "PERCENTILE_DISC", "RANK", "RATIO_TO_REPORT", "REGR_SLOPE", "REGR_INTERCEPT", "REGR_COUNT", "REGR_R2", "REGR_AVGX", "REGR_AVGY", "REGR_SXX", "REGR_SYY", "REGR_SXY", "ROW_NUMBER", "STDDEV", "STDDEV_POP", "STDDEV_SAMP", "SUM", "VAR_POP", "VAR_SAMP", "VARIANCE", "WM_CONCAT"};
      AGGREGATE_FUNCTIONS_CODES = FnvHash.fnv1a_64_lower(strings, true);
      AGGREGATE_FUNCTIONS = new String[AGGREGATE_FUNCTIONS_CODES.length];

      for(String str : strings) {
         long hash = FnvHash.fnv1a_64_lower(str);
         int index = Arrays.binarySearch(AGGREGATE_FUNCTIONS_CODES, hash);
         AGGREGATE_FUNCTIONS[index] = str;
      }

   }
}
