static T parse()

in src/main/java/com/google/cloud/spanner/pgadapter/statements/AbstractFetchOrMoveStatement.java [193:267]


  static <T extends ParsedFetchOrMoveStatement> T parse(String sql, String type, Class<T> clazz) {
    Preconditions.checkNotNull(sql);
    Preconditions.checkNotNull(type);

    // {MOVE | FETCH} [ direction ] [ FROM | IN ] cursor_name
    SimpleParser parser = new SimpleParser(sql);
    if (!parser.eatKeyword(type)) {
      throw PGExceptionFactory.newPGException(
          "not a valid " + type.toUpperCase() + " statement: " + sql, SQLState.SyntaxError);
    }
    Direction direction;
    if (parser.eatKeyword("forward")) {
      if (parser.eatKeyword("all")) {
        direction = Direction.FORWARD_ALL;
      } else {
        direction = Direction.FORWARD;
      }
    } else if (parser.eatKeyword("backward")) {
      if (parser.eatKeyword("all")) {
        direction = Direction.BACKWARD_ALL;
      } else {
        direction = Direction.BACKWARD;
      }
    } else {
      direction =
          Arrays.stream(Direction.values())
              // This ensures that the stream will return the first valid direction keyword that it
              // finds, or null if no valid direction keyword is found. If no valid direction
              // keyword is found, then the position of the parser will also not be moved.
              .filter(dir -> parser.eatKeyword(dir.name()))
              .findFirst()
              .orElse(null);
    }
    Long count = null;
    if (parser.peekNumericLiteral()) {
      count = parser.readIntegerLiteral();
      if (count == null) {
        throw PGExceptionFactory.newPGException("syntax error: " + sql, SQLState.SyntaxError);
      }
      if (count > Integer.MAX_VALUE || count < Integer.MIN_VALUE) {
        throw PGExceptionFactory.newPGException(
            "count out of range: " + count, SQLState.NumericValueOutOfRange);
      }
    }
    if (count != null && direction != null && !direction.supportsCount) {
      throw PGExceptionFactory.newPGException(
          "unexpected <count> argument: " + sql, SQLState.SyntaxError);
    } else if (count == null && direction == Direction.ABSOLUTE) {
      throw PGExceptionFactory.newPGException(
          "missing or invalid <count> argument for ABSOLUTE: " + sql, SQLState.SyntaxError);
    } else if (count == null && direction == Direction.RELATIVE) {
      throw PGExceptionFactory.newPGException(
          "missing or invalid <count> argument for RELATIVE: " + sql, SQLState.SyntaxError);
    }
    // Skip 'from' or 'in'.
    boolean ignore = parser.eatKeyword("from") || parser.eatKeyword("in");
    TableOrIndexName name = parser.readTableOrIndexName();
    if (name == null || name.schema != null) {
      throw PGExceptionFactory.newPGException("invalid cursor name: " + sql, SQLState.SyntaxError);
    }
    if (parser.hasMoreTokens()) {
      throw PGExceptionFactory.newPGException(
          "unexpected tokens after cursor name: " + sql, SQLState.SyntaxError);
    }

    Integer integerCount = count == null ? null : count.intValue();
    try {
      return clazz
          .getDeclaredConstructor(String.class, Direction.class, Integer.class)
          .newInstance(unquoteOrFoldIdentifier(name.name), direction, integerCount);
    } catch (Exception exception) {
      throw PGExceptionFactory.newPGException(
          "internal error: " + exception.getMessage(), SQLState.InternalError);
    }
  }