protected SQLExpr parseInterval() {
    accept(Token.INTERVAL);

    OracleIntervalExpr interval = new OracleIntervalExpr();
    if (lexer.token() != Token.LITERAL_CHARS) {
      return new SQLIdentifierExpr("INTERVAL");
    }
    interval.setValue(new SQLCharExpr(lexer.stringVal()));
    lexer.nextToken();

    OracleIntervalType type = OracleIntervalType.valueOf(lexer.stringVal());
    interval.setType(type);
    lexer.nextToken();

    if (lexer.token() == Token.LPAREN) {
      lexer.nextToken();
      if (lexer.token() != Token.LITERAL_INT) {
        throw new ParserException("syntax error");
      }
      interval.setPrecision(lexer.integerValue().intValue());
      lexer.nextToken();

      if (lexer.token() == Token.COMMA) {
        lexer.nextToken();
        if (lexer.token() != Token.LITERAL_INT) {
          throw new ParserException("syntax error");
        }
        interval.setFactionalSecondsPrecision(lexer.integerValue().intValue());
        lexer.nextToken();
      }
      accept(Token.RPAREN);
    }

    if (lexer.token() == Token.TO) {
      lexer.nextToken();
      if (identifierEquals("SECOND")) {
        lexer.nextToken();
        interval.setToType(OracleIntervalType.SECOND);
        if (lexer.token() == Token.LPAREN) {
          lexer.nextToken();
          if (lexer.token() != Token.LITERAL_INT) {
            throw new ParserException("syntax error");
          }
          interval.setToFactionalSecondsPrecision(lexer.integerValue().intValue());
          lexer.nextToken();
          accept(Token.RPAREN);
        }
      } else {
        interval.setToType(OracleIntervalType.MONTH);
        lexer.nextToken();
      }
    }

    return interval;
  }
  public SQLExpr primaryRest(SQLExpr expr) {
    if (expr.getClass() == SQLIdentifierExpr.class) {
      String ident = ((SQLIdentifierExpr) expr).getName();

      if ("DATE".equalsIgnoreCase(ident)) {
        OracleDateExpr timestamp = new OracleDateExpr();

        String literal = lexer.stringVal();
        timestamp.setLiteral(literal);
        accept(Token.LITERAL_CHARS);

        return primaryRest(timestamp);
      }
      if ("TIMESTAMP".equalsIgnoreCase(ident)) {
        if (lexer.token() != Token.LITERAL_ALIAS && lexer.token() != Token.LITERAL_CHARS) {
          return new SQLIdentifierExpr("TIMESTAMP");
        }

        OracleTimestampExpr timestamp = new OracleTimestampExpr();

        String literal = lexer.stringVal();
        timestamp.setLiteral(literal);
        accept(Token.LITERAL_CHARS);

        if (identifierEquals("AT")) {
          lexer.nextToken();
          acceptIdentifier("TIME");
          acceptIdentifier("ZONE");

          String timezone = lexer.stringVal();
          timestamp.setTimeZone(timezone);
          accept(Token.LITERAL_CHARS);
        }

        return primaryRest(timestamp);
      }
    }
    if (lexer.token() == Token.IDENTIFIER && expr instanceof SQLNumericLiteralExpr) {
      String ident = lexer.stringVal();

      if (ident.length() == 1) {
        char unit = ident.charAt(0);
        switch (unit) {
          case 'K':
          case 'M':
          case 'G':
          case 'T':
          case 'P':
          case 'E':
          case 'k':
          case 'm':
          case 'g':
          case 't':
          case 'p':
          case 'e':
            expr = new OracleSizeExpr(expr, OracleSizeExpr.Unit.valueOf(ident.toUpperCase()));
            lexer.nextToken();
            break;
          default:
            break;
        }
      }
    }

    if (lexer.token() == Token.DOTDOT) {
      lexer.nextToken();
      SQLExpr upBound = expr();

      return new OracleRangeExpr(expr, upBound);
    }

    if (lexer.token() == Token.MONKEYS_AT) {
      lexer.nextToken();

      OracleDbLinkExpr dblink = new OracleDbLinkExpr();
      dblink.setExpr(expr);

      if (lexer.token() == Token.BANG) {
        dblink.setDbLink("!");
        lexer.nextToken();
      } else {
        String link = lexer.stringVal();
        accept(Token.IDENTIFIER);
        dblink.setDbLink(link);
      }

      expr = dblink;
    }

    if (identifierEquals("DAY") || identifierEquals("YEAR")) {
      lexer.mark();

      String name = lexer.stringVal();
      lexer.nextToken();

      if (lexer.token() == Token.COMMA) {
        lexer.reset();
        return expr;
      }

      OracleIntervalExpr interval = new OracleIntervalExpr();
      interval.setValue(expr);
      OracleIntervalType type = OracleIntervalType.valueOf(name);
      interval.setType(type);

      if (lexer.token() == Token.LPAREN) {
        lexer.nextToken();
        if (lexer.token() != Token.LITERAL_INT) {
          throw new ParserException("syntax error");
        }
        interval.setPrecision(lexer.integerValue().intValue());
        lexer.nextToken();
        accept(Token.RPAREN);
      }

      accept(Token.TO);
      if (identifierEquals("SECOND")) {
        lexer.nextToken();
        interval.setToType(OracleIntervalType.SECOND);
        if (lexer.token() == Token.LPAREN) {
          lexer.nextToken();
          if (lexer.token() != Token.LITERAL_INT) {
            throw new ParserException("syntax error");
          }
          interval.setFactionalSecondsPrecision(lexer.integerValue().intValue());
          lexer.nextToken();
          accept(Token.RPAREN);
        }
      } else {
        interval.setToType(OracleIntervalType.MONTH);
        lexer.nextToken();
      }

      expr = interval;
    }

    if (identifierEquals("AT")) {
      char markChar = lexer.current();
      int markBp = lexer.bp();
      lexer.nextToken();
      if (identifierEquals("LOCAL")) {
        lexer.nextToken();
        expr = new OracleDatetimeExpr(expr, new SQLIdentifierExpr("LOCAL"));
      } else {
        if (identifierEquals("TIME")) {
          lexer.nextToken();
        } else {
          lexer.reset(markBp, markChar, Token.IDENTIFIER);
          return expr;
        }
        acceptIdentifier("ZONE");

        SQLExpr timeZone = primary();
        expr = new OracleDatetimeExpr(expr, timeZone);
      }
    }

    SQLExpr restExpr = super.primaryRest(expr);

    if (restExpr != expr && restExpr instanceof SQLMethodInvokeExpr) {
      SQLMethodInvokeExpr methodInvoke = (SQLMethodInvokeExpr) restExpr;
      if (methodInvoke.getParameters().size() == 1) {
        SQLExpr paramExpr = methodInvoke.getParameters().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;
  }