Skip to content

Commit f55bb5d

Browse files
committed
Refine exception specification when accessing items by name/by index.
We now define IndexOutOfBoundsException to signal out of range indexes for by-index access and NoSuchElementException to signal unknown/illegal identifiers for by-name access to columns, out parameters, and bind values. [resolves #184][#240] Signed-off-by: Mark Paluch <[email protected]>
1 parent f41ed12 commit f55bb5d

File tree

6 files changed

+75
-6
lines changed

6 files changed

+75
-6
lines changed

r2dbc-spec/src/main/asciidoc/exceptions.adoc

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,13 @@ R2DBC defines the following general exceptions:
2020
[[exceptions.iae]]
2121
=== `IllegalArgumentException`
2222

23-
Drivers throw `IllegalArgumentException` if a method has been received an illegal or inappropriate argument (such as values that are out of bounds or an expected parameter is `null`).
23+
Drivers throw `IllegalArgumentException` if a method has received an illegal or inappropriate argument (such as values that are invalid or an expected parameter is `null`).
24+
This exception is a generic exception that is not associated with an error code or an `SQLState`.
25+
26+
[[exceptions.iob]]
27+
=== `IndexOutOfBoundsException`
28+
29+
Drivers throw `IndexOutOfBoundsException` if a method has received an illegal or inappropriate index value (such as negative values or indexes that are greater or equal to the number of elements).
2430
This exception is a generic exception that is not associated with an error code or an `SQLState`.
2531

2632
[[exceptions.ise]]
@@ -29,6 +35,12 @@ This exception is a generic exception that is not associated with an error code
2935
Drivers throw `IllegalStateException` if a method has received an argument that is invalid in the current state or when an argument-less method is invoked in a state that does not allow execution in the current state (such as interacting with a closed connection object).
3036
This exception is a generic exception that is not associated with an error code or an `SQLState`.
3137

38+
[[exceptions.nse]]
39+
=== `NoSuchElementException`
40+
41+
Drivers throw `NoSuchElementException` if a method has received a column/parameter name that doesn't exist in the collection of items.
42+
This exception is a generic exception that is not associated with an error code or an `SQLState`.
43+
3244
[[exceptions.nsoe]]
3345
=== `NoSuchOptionException`
3446

r2dbc-spi-test/src/main/java/io/r2dbc/spi/test/TestKit.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import java.util.Arrays;
4646
import java.util.Collection;
4747
import java.util.HashSet;
48+
import java.util.NoSuchElementException;
49+
import java.util.concurrent.Callable;
4850
import java.util.function.Function;
4951
import java.util.stream.Collectors;
5052
import java.util.stream.IntStream;
@@ -318,7 +320,7 @@ default void bindFails() {
318320
assertThrows(IndexOutOfBoundsException.class, () -> statement.bind(99, ""), "bind(nonexistent-index, null) should fail");
319321
assertThrows(IllegalArgumentException.class, () -> bind(statement, getIdentifier(0), null), "bind(identifier, null) should fail");
320322
assertThrows(IllegalArgumentException.class, () -> bind(statement, getIdentifier(0), Class.class), "bind(identifier, Class.class) should fail");
321-
assertThrows(IllegalArgumentException.class, () -> statement.bind("unknown", ""), "bind(unknown-placeholder, \"\") should fail");
323+
assertThrows(NoSuchElementException.class, () -> statement.bind("unknown-placeholder", ""), "bind(unknown-placeholder, \"\") should fail");
322324
return Mono.empty();
323325
},
324326
Connection::close)
@@ -492,14 +494,20 @@ default void columnMetadata() {
492494
.execute())
493495
.flatMap(result -> {
494496
return result.map((row, rowMetadata) -> {
495-
return Arrays.asList(rowMetadata.contains("value"), rowMetadata.contains("VALUE"));
497+
return Arrays.asList(rowMetadata.contains("value"), rowMetadata.contains("VALUE"),
498+
captureException(() -> rowMetadata.getColumnMetadata(-1)),
499+
captureException(() -> rowMetadata.getColumnMetadata(100)),
500+
captureException(() -> rowMetadata.getColumnMetadata("unknown")));
496501
});
497502
})
498503
.flatMapIterable(Function.identity()),
499504
Connection::close)
500505
.as(StepVerifier::create)
501506
.expectNext(true).as("rowMetadata.contains(value)")
502507
.expectNext(true).as("rowMetadata.contains(VALUE)")
508+
.expectNextMatches(IndexOutOfBoundsException.class::isInstance).as("getColumnMetadata(-1) throws IndexOutOfBoundsException")
509+
.expectNextMatches(IndexOutOfBoundsException.class::isInstance).as("getColumnMetadata(100) throws IndexOutOfBoundsException")
510+
.expectNextMatches(NoSuchElementException.class::isInstance).as("getColumnMetadata(unknown) throws NoSuchElementException")
503511
.verifyComplete();
504512
}
505513

@@ -522,6 +530,27 @@ default void rowMetadata() {
522530
.verifyComplete();
523531
}
524532

533+
@Test
534+
default void row() {
535+
getJdbcOperations().execute(expand(TestStatement.INSERT_TWO_COLUMNS));
536+
537+
Flux.usingWhen(getConnectionFactory().create(),
538+
connection -> Flux.from(connection
539+
540+
.createStatement(expand(TestStatement.SELECT_VALUE_ALIASED_COLUMNS))
541+
.execute())
542+
.flatMap(result -> result.map(readable -> Arrays.asList(captureException(() -> readable.get(-1)),
543+
captureException(() -> readable.get(100)),
544+
captureException(() -> readable.get("unknown")))))
545+
.flatMapIterable(Function.identity()),
546+
Connection::close)
547+
.as(StepVerifier::create)
548+
.expectNextMatches(IndexOutOfBoundsException.class::isInstance).as("get(-1) throws IndexOutOfBoundsException")
549+
.expectNextMatches(IndexOutOfBoundsException.class::isInstance).as("get(100) throws IndexOutOfBoundsException")
550+
.expectNextMatches(NoSuchElementException.class::isInstance).as("get(unknown) throws NoSuchElementException")
551+
.verifyComplete();
552+
}
553+
525554
@Test
526555
default void compoundStatement() {
527556
getJdbcOperations().execute(expand(TestStatement.INSERT_VALUE100));
@@ -868,6 +897,24 @@ static <T> Collection<T> collectionOf(T... values) {
868897
return new HashSet<>(Arrays.asList(values));
869898
}
870899

900+
/**
901+
* Returns an {@link Exception} from a {@link Callable}.
902+
*
903+
* @param throwingCallable
904+
* @return
905+
* @throws IllegalStateException if {@code throwingCallable} did not throw an exception.
906+
*/
907+
static Exception captureException(Callable<?> throwingCallable) {
908+
909+
try {
910+
throwingCallable.call();
911+
} catch (Exception e) {
912+
return e;
913+
}
914+
915+
throw new IllegalStateException("Callable did not throw an exception.");
916+
}
917+
871918
/**
872919
* Enumeration of TCK statements. Can be customized by overriding {@link #expand(TestStatement, Object...)}.
873920
*/

r2dbc-spi/src/main/java/io/r2dbc/spi/OutParametersMetadata.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public interface OutParametersMetadata {
3131
*
3232
* @param index the out parameter index starting at 0
3333
* @return the {@link OutParameterMetadata} for one out parameter
34-
* @throws ArrayIndexOutOfBoundsException if the {@code index} is less than zero or greater than the number of available out parameters.
34+
* @throws IndexOutOfBoundsException if {@code index} is out of range (negative or equals/exceeds {@code getParameterMetadatas().size()})
3535
*/
3636
OutParameterMetadata getParameterMetadata(int index);
3737

r2dbc-spi/src/main/java/io/r2dbc/spi/Readable.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package io.r2dbc.spi;
1818

19+
import java.util.NoSuchElementException;
20+
1921
/**
2022
* Represents a readable object, for example a set of columns or {@code OUT} parameters from a database query, later on referred to as items.
2123
* Values can for columns or {@code OUT} parameters be either retrieved by specifying a name or the index.
@@ -44,6 +46,7 @@ public interface Readable {
4446
*
4547
* @param index the index of the parameter starting at {@code 0}
4648
* @return the value. Value can be {@code null}.
49+
* @throws IndexOutOfBoundsException if {@code index} if the index is out of range (negative or equals/exceeds the number of readable objects)
4750
*/
4851
@Nullable
4952
default Object get(int index) {
@@ -57,7 +60,8 @@ default Object get(int index) {
5760
* @param type the type of item to return. This type must be assignable to, and allows for variance.
5861
* @param <T> the type of the item being returned.
5962
* @return the value. Value can be {@code null}.
60-
* @throws IllegalArgumentException if {@code type} is {@code null}
63+
* @throws IllegalArgumentException if {@code type} is {@code null}
64+
* @throws IndexOutOfBoundsException if {@code index} is out of range (negative or equals/exceeds the number of readable objects)
6165
*/
6266
@Nullable
6367
<T> T get(int index, Class<T> type);
@@ -69,6 +73,7 @@ default Object get(int index) {
6973
* @param name the name
7074
* @return the value. Value can be {@code null}.
7175
* @throws IllegalArgumentException if {@code name} is {@code null}
76+
* @throws NoSuchElementException if {@code name} is not a known readable column or out parameter
7277
*/
7378
@Nullable
7479
default Object get(String name) {
@@ -83,6 +88,7 @@ default Object get(String name) {
8388
* @param <T> the type of the item being returned.
8489
* @return the value. Value can be {@code null}.
8590
* @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}
91+
* @throws NoSuchElementException if {@code name} is not a known readable column or out parameter
8692
*/
8793
@Nullable
8894
<T> T get(String name, Class<T> type);

r2dbc-spi/src/main/java/io/r2dbc/spi/RowMetadata.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public interface RowMetadata {
3333
*
3434
* @param index the column index starting at 0
3535
* @return the {@link ColumnMetadata} for one column in this row
36-
* @throws ArrayIndexOutOfBoundsException if the {@code index} is less than zero or greater than the number of available columns.
36+
* @throws IndexOutOfBoundsException if {@code index} is out of range (negative or equals/exceeds {@code getColumnMetadatas().size()})
3737
*/
3838
ColumnMetadata getColumnMetadata(int index);
3939

r2dbc-spi/src/main/java/io/r2dbc/spi/Statement.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import org.reactivestreams.Publisher;
2020

21+
import java.util.NoSuchElementException;
22+
2123
/**
2224
* A statement that can be executed multiple times in a prepared and optimized way. Bound parameters can be either scalar values (using type inference for the database parameter type) or
2325
* {@link Parameter} objects.
@@ -64,6 +66,7 @@ public interface Statement {
6466
* @param value the value to bind
6567
* @return this {@link Statement}
6668
* @throws IllegalArgumentException if {@code name} or {@code value} is {@code null}
69+
* @throws NoSuchElementException if {@code name} is not a known name to bind
6770
*/
6871
Statement bind(String name, Object value);
6972

@@ -85,6 +88,7 @@ public interface Statement {
8588
* @param type the type of null value
8689
* @return this {@link Statement}
8790
* @throws IllegalArgumentException if {@code name} or {@code type} is {@code null}
91+
* @throws NoSuchElementException if {@code name} is not a known name to bind
8892
*/
8993
Statement bindNull(String name, Class<?> type);
9094

0 commit comments

Comments
 (0)