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);
}
}