Skip to content

Commit f05443c

Browse files
Add Segment type for warnings
1 parent 45bc3bf commit f05443c

File tree

6 files changed

+142
-96
lines changed

6 files changed

+142
-96
lines changed

src/main/java/oracle/r2dbc/OracleR2dbcWarning.java

Lines changed: 48 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -3,85 +3,79 @@
33
import io.r2dbc.spi.R2dbcException;
44
import io.r2dbc.spi.Result;
55

6-
import java.sql.SQLWarning;
76
import java.util.function.Function;
87
import java.util.function.Predicate;
98

10-
import static io.r2dbc.spi.Result.*;
11-
129
/**
1310
* <p>
14-
* An exception that provides information on warnings raised by Oracle Database.
11+
* A subtype of the {@link Result.Segment} interface that provides information
12+
* on warnings raised by Oracle Database.
13+
* </p><p>
14+
* When a SQL command results in a warning, Oracle R2DBC emits a {@link Result}
15+
* with an {@code OracleR2dbcWarning} segment in addition to any other segments
16+
* that resulted from the SQL command. For example, if a SQL {@code SELECT}
17+
* command results in a warning, then an {@code OracleR2dbcWarning} segment is
18+
* included with the result, along with any {@link Result.RowSegment}s returned
19+
* by the {@code SELECT}.
1520
* </p><p>
16-
* When a SQL command results in a warning, Oracle R2DBC generates
17-
* {@link Message} segments having an {@code OracleR2dbcWarning} as the
18-
* {@linkplain Message#exception() exception}. These segments may be consumed
19-
* with {@link Result#flatMap(Function)}:
21+
* R2DBC drivers typically emit {@code onError} signals for {@code Message}
22+
* segments that are not consumed by {@link Result#filter(Predicate)} or
23+
* {@link Result#flatMap(Function)}. Oracle R2DBC does not apply this behavior
24+
* for warning messages. If an {@code OracleR2dbcWarning}
25+
* segment is not consumed by the {@code filter} or {@code flatMap} methods of
26+
* a {@code Result}, then the warning is discarded and the result may be
27+
* consumed as normal with with the {@code map} or {@code getRowsUpdated}
28+
* methods.
29+
* </p><p>
30+
* Warning messages may be consumed with {@link Result#flatMap(Function)}:
2031
* </p><pre>{@code
2132
* result.flatMap(segment -> {
22-
* if (OracleR2dbcWarning.isWarning(segment)) {
23-
* logWarning(OracleR2dbcWarning.getWarning(segment));
33+
* if (segment instanceof OracleR2dbcWarning) {
34+
* logWarning(((OracleR2dbcWarning)segment).getMessage());
2435
* return emptyPublisher();
2536
* }
2637
* else {
2738
* ... handle other segment types ...
2839
* }
2940
* })
3041
* }</pre><p>
31-
* Alternatively, these segments may be ignored using
32-
* {@link Result#filter(Predicate)}:
33-
* </p> <pre>{@code
34-
* result.filter(segment -> !OracleR2dbcWarning.isWarning(segment))
42+
* A {@code flatMap} function may also be used to convert a warning into an
43+
* {@code onError} signal:
44+
* </p><pre>{@code
45+
* result.flatMap(segment -> {
46+
* if (segment instanceof OracleR2dbcWarning) {
47+
* return errorPublisher(((OracleR2dbcWarning)segment).warning());
48+
* }
49+
* else {
50+
* ... handle other segment types ...
51+
* }
52+
* })
3553
* }</pre>
36-
* If these segments are not flat-mapped or filtered from a {@code Result}, then
37-
* an {@code onError} signal with the {@code OracleR2dbcWarning} is emitted
38-
* </p>
54+
* @since 1.1.0
3955
*/
40-
public class OracleR2dbcWarning extends R2dbcException {
56+
public interface OracleR2dbcWarning extends Result.Segment {
4157

4258
/**
43-
* Constructs a new warning having a {@code SQLWarning} from JDBC as its
44-
* cause. The constructed warning has the same message, SQL state, and error
45-
* code as the given {@code SQLWarning}.
46-
* @param sql The SQL command that resulted in a warning. May be null.
47-
* @param sqlWarning The SQLWarning thrown by JDBC. Not null.
59+
* Returns the warning as an {@link R2dbcException}.
60+
* @return The warning as an {@link R2dbcException}. Not null.
4861
*/
49-
public OracleR2dbcWarning(String sql, SQLWarning sqlWarning) {
50-
super(sqlWarning.getMessage(), sqlWarning.getSQLState(),
51-
sqlWarning.getErrorCode(), sql, sqlWarning);
52-
}
62+
R2dbcException exception();
5363

5464
/**
55-
* Checks if a segment is a {@link Message} having an
56-
* {@code OracleR2dbcException} as an
57-
* {@linkplain Message#exception() exception}.
58-
* @param segment Segment to check. May be null, in which case {@code false}
59-
* is returned.
60-
* @return {@code true} if the segment is a {@code Message} with an
61-
* {@code OracleR2dbcException}, or {@code false} if not.
65+
* Returns the error code of the warning.
66+
* @return The error code of the warning.
6267
*/
63-
public static boolean isWarning(Segment segment) {
64-
return segment instanceof Message
65-
&& ((Message)segment).exception() instanceof OracleR2dbcWarning;
66-
}
68+
int errorCode();
6769

6870
/**
69-
* Returns the {@code OracleR2dbcWarning} of a {@link Message} segment. This
70-
* method should only be called if {@link #isWarning(Segment)} returned
71-
* {@code true} for the given segment.
72-
* @param segment A {@code Message} segment with an
73-
* {@code OracleR2dbcWarning}. Not null.
74-
* @return The {@code OracleR2dbcWarning} of the {@code Message} segment.
75-
* @throws IllegalArgumentException If the segment is not a {@code Message}
76-
* with an {@code OracleR2dbcWarning}.
71+
* Returns the SQLState of the warning.
72+
* @return The SQLState of the warning. Not null.
7773
*/
78-
public static OracleR2dbcWarning getWarning(Segment segment) {
79-
if (!isWarning(segment)) {
80-
throw new IllegalArgumentException(
81-
"Not a Message segment with an OracleR2dbcWarning: " + segment);
82-
}
83-
84-
return (OracleR2dbcWarning)((Message)segment).exception();
85-
}
74+
String sqlState();
8675

76+
/**
77+
* Returns the text of the warning message.
78+
* @return The text of the warning message. Not null.
79+
*/
80+
String message();
8781
}

src/main/java/oracle/r2dbc/impl/OracleR2dbcExceptions.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import io.r2dbc.spi.R2dbcTransientException;
3232
import io.r2dbc.spi.R2dbcTransientResourceException;
3333
import oracle.jdbc.OracleDatabaseException;
34-
import oracle.r2dbc.OracleR2dbcWarning;
3534

3635
import java.sql.SQLException;
3736
import java.sql.SQLIntegrityConstraintViolationException;
@@ -43,7 +42,6 @@
4342
import java.sql.SQLTransactionRollbackException;
4443
import java.sql.SQLTransientConnectionException;
4544
import java.sql.SQLTransientException;
46-
import java.sql.SQLWarning;
4745
import java.util.function.Supplier;
4846

4947
/**
@@ -165,12 +163,26 @@ static void requireOpenConnection(java.sql.Connection jdbcConnection) {
165163
* as the specified {@code sqlException}. Not null.
166164
*/
167165
static R2dbcException toR2dbcException(SQLException sqlException) {
166+
return toR2dbcException(sqlException, getSql(sqlException));
167+
}
168+
169+
/**
170+
* Converts a {@link SQLException} into an {@link R2dbcException}, as
171+
* specified by {@link #toR2dbcException(SQLException)}. This method accepts
172+
* a SQL string argument. It should be used in cases where the SQL can not
173+
* be extracted by {@link #getSql(SQLException)}.
174+
* @param sqlException A {@code SQLException} to convert. Not null.
175+
* @param sql SQL that caused the exception
176+
* @return an {@code R2dbcException} that indicates the same error conditions
177+
* as the specified {@code sqlException}. Not null.
178+
*/
179+
static R2dbcException toR2dbcException(
180+
SQLException sqlException, String sql) {
168181
assert sqlException != null : "sqlException is null";
169182

170183
final String message = sqlException.getMessage();
171184
final String sqlState = sqlException.getSQLState();
172185
final int errorCode = sqlException.getErrorCode();
173-
final String sql = getSql(sqlException);
174186

175187
if (sqlException instanceof SQLNonTransientException) {
176188
if (sqlException instanceof SQLSyntaxErrorException) {
@@ -216,9 +228,6 @@ else if (sqlException instanceof SQLRecoverableException) {
216228
return new R2dbcTransientResourceException(
217229
message, sqlState, errorCode, sql, sqlException);
218230
}
219-
else if (sqlException instanceof SQLWarning) {
220-
return new OracleR2dbcWarning(sql, (SQLWarning)sqlException);
221-
}
222231
else {
223232
return new OracleR2dbcException(
224233
message, sqlState, errorCode, sql, sqlException);

src/main/java/oracle/r2dbc/impl/OracleResultImpl.java

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import io.r2dbc.spi.Result;
2828
import io.r2dbc.spi.Row;
2929
import io.r2dbc.spi.RowMetadata;
30+
import oracle.r2dbc.OracleR2dbcWarning;
3031
import oracle.r2dbc.impl.ReadablesMetadata.RowMetadataImpl;
3132
import org.reactivestreams.Publisher;
3233
import reactor.core.publisher.Flux;
@@ -398,14 +399,15 @@ static OracleResultImpl createErrorResult(R2dbcException r2dbcException) {
398399
* Creates a {@code Result} that publishes a {@code warning} as a
399400
* {@link Message} segment, followed by any {@code Segment}s of a
400401
* {@code result}.
402+
* @param sql The SQL that resulted in a waring. Not null.
401403
* @param warning Warning to publish. Not null.
402404
* @param result Result to publisher. Not null.
403405
* @return A {@code Result} for a {@code Statement} execution that
404406
* completed with a warning.
405407
*/
406408
static OracleResultImpl createWarningResult(
407-
SQLWarning warning, OracleResultImpl result) {
408-
return new WarningResult(warning, result);
409+
String sql, SQLWarning warning, OracleResultImpl result) {
410+
return new WarningResult(sql, warning, result);
409411
}
410412

411413
/**
@@ -627,6 +629,9 @@ <T> Publisher<T> publishSegments(Function<Segment, T> mappingFunction) {
627629
*/
628630
private static final class WarningResult extends OracleResultImpl {
629631

632+
/** The SQL that resulted in a warning */
633+
private final String sql;
634+
630635
/** The warning of this result */
631636
private final SQLWarning warning;
632637

@@ -636,11 +641,13 @@ private static final class WarningResult extends OracleResultImpl {
636641
/**
637642
* Constructs a result that publishes a {@code warning} as a
638643
* {@link Message}, and then publishes the segments of a {@code result}.
644+
* @param sql The SQL that resulted in a warning
639645
* @param warning Warning to publish. Not null.
640646
* @param result Result of segments to publish after the warning. Not null.
641647
*/
642648
private WarningResult(
643-
SQLWarning warning, OracleResultImpl result) {
649+
String sql, SQLWarning warning, OracleResultImpl result) {
650+
this.sql = sql;
644651
this.warning = warning;
645652
this.result = result;
646653
}
@@ -649,8 +656,11 @@ private WarningResult(
649656
<T> Publisher<T> publishSegments(Function<Segment, T> mappingFunction) {
650657
return Flux.fromStream(Stream.iterate(
651658
warning, Objects::nonNull, SQLWarning::getNextWarning)
652-
.map(OracleR2dbcExceptions::toR2dbcException)
653-
.map(MessageImpl::new))
659+
.map(nextWarning ->
660+
// It is noted that SQL can not be extracted from Oracle JDBC's
661+
// SQLWarning objects, so it must be explicitly provided here.
662+
OracleR2dbcExceptions.toR2dbcException(warning, sql))
663+
.map(WarningImpl::new))
654664
.map(mappingFunction)
655665
// Invoke publishSegments(Class, Function) rather than
656666
// publishSegments(Function) to update the state of the result; Namely,
@@ -803,6 +813,38 @@ public String message() {
803813
}
804814
}
805815

816+
/**
817+
* Implementation of {@link OracleR2dbcWarning}.
818+
*/
819+
private static final class WarningImpl implements OracleR2dbcWarning {
820+
821+
private final R2dbcException warning;
822+
823+
private WarningImpl(R2dbcException exception) {
824+
this.warning = exception;
825+
}
826+
827+
@Override
828+
public R2dbcException exception() {
829+
return warning;
830+
}
831+
832+
@Override
833+
public int errorCode() {
834+
return warning.getErrorCode();
835+
}
836+
837+
@Override
838+
public String sqlState() {
839+
return warning.getSqlState();
840+
}
841+
842+
@Override
843+
public String message() {
844+
return warning.getMessage();
845+
}
846+
}
847+
806848
/**
807849
* Returns a {@code Publisher} that emits the signals of a {@code publisher}
808850
* to a single {@link org.reactivestreams.Subscriber}, and rejects additional

src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1132,7 +1132,7 @@ private OracleResultImpl getWarnings(OracleResultImpl result) {
11321132
preparedStatement.clearWarnings();
11331133
return warning == null
11341134
? result
1135-
: OracleResultImpl.createWarningResult(warning, result);
1135+
: OracleResultImpl.createWarningResult(sql, warning, result);
11361136
});
11371137
}
11381138

src/test/java/oracle/r2dbc/impl/OracleResultImplTest.java

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
package oracle.r2dbc.impl;
2323

2424
import io.r2dbc.spi.Connection;
25+
import io.r2dbc.spi.R2dbcException;
2526
import io.r2dbc.spi.Result;
2627
import io.r2dbc.spi.Result.Message;
2728
import io.r2dbc.spi.Result.RowSegment;
@@ -638,7 +639,7 @@ public void testOracleR2dbcWarning() {
638639
Connection connection = awaitOne(sharedConnection());
639640
try {
640641

641-
// Expect a warning for forcing a view that references a non-existant
642+
// Expect a warning for forcing a view that references a non-existent
642643
// table
643644
String sql = "CREATE OR REPLACE FORCE VIEW testOracleR2dbcWarning AS" +
644645
" SELECT x FROM thisdoesnotexist";
@@ -649,31 +650,29 @@ public void testOracleR2dbcWarning() {
649650
awaitMany(Flux.from(warningStatement.execute())
650651
.flatMap(result -> result.flatMap(Mono::just)));
651652

652-
// Expect the update count segment first. Warnings are always emitted
653-
// after any result values.
654-
Result.Segment firstSegment = segments.get(0);
655-
assertEquals(0,
656-
assertInstanceOf(UpdateCount.class, firstSegment).value());
657-
assertFalse(OracleR2dbcWarning.isWarning(firstSegment));
658-
assertThrows(IllegalArgumentException.class, () ->
659-
OracleR2dbcWarning.getWarning(firstSegment));
660-
661-
// Expect the message segment next. Expect it to have the fixed message
653+
// Expect the warning segment first. Expect it to have the fixed message
662654
// and error number used by Oracle JDBC for all warnings.
663-
Result.Segment secondSegment = segments.get(1);
664-
Message message = assertInstanceOf(Message.class, secondSegment);
665-
assertEquals(
666-
message.message(), "Warning: execution completed with warning");
667-
assertEquals(message.errorCode(), 17110);
668-
assertEquals("", message.sqlState());
655+
Result.Segment secondSegment = segments.get(0);
669656
OracleR2dbcWarning warning =
670-
assertInstanceOf(OracleR2dbcWarning.class, message.exception());
671-
assertEquals(message.message(), warning.getMessage());
672-
assertEquals(message.errorCode(), warning.getErrorCode());
673-
assertEquals(message.sqlState(), warning.getSqlState());
674-
assertEquals(sql, warning.getSql());
675-
assertTrue(OracleR2dbcWarning.isWarning(secondSegment));
676-
assertSame(warning, OracleR2dbcWarning.getWarning(secondSegment));
657+
assertInstanceOf(OracleR2dbcWarning.class, secondSegment);
658+
assertEquals(
659+
warning.message(), "Warning: execution completed with warning");
660+
assertEquals(warning.errorCode(), 17110);
661+
assertEquals("99999", warning.sqlState()); // Default SQL state
662+
R2dbcException exception =
663+
assertInstanceOf(R2dbcException.class, warning.exception());
664+
assertEquals(warning.message(), exception.getMessage());
665+
assertEquals(warning.errorCode(), exception.getErrorCode());
666+
assertEquals(warning.sqlState(), exception.getSqlState());
667+
assertEquals(sql, exception.getSql());
668+
669+
670+
// Expect the update count segment last. Warnings are always emitted
671+
// first.
672+
Result.Segment firstSegment = segments.get(1);
673+
assertEquals(0,
674+
assertInstanceOf(UpdateCount.class, firstSegment).value());
675+
assertFalse(firstSegment instanceof OracleR2dbcWarning);
677676

678677
// Verify that there are not any more segments
679678
assertEquals(2, segments.size());

0 commit comments

Comments
 (0)