Skip to content

Commit aada73f

Browse files
Merge pull request #98 from oracle/93-oracle-warning
Oracle Warning Segments
2 parents e8fc172 + 496e75d commit aada73f

File tree

8 files changed

+271
-23
lines changed

8 files changed

+271
-23
lines changed

.github/workflows/startup/01_createUser.sql

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,13 @@
3636
-- v$transaction (to verify if TransactionDefinitions are applied).
3737
-- v$session (to verify if VSESSION_* Options are applied).
3838
ALTER SESSION SET CONTAINER=xepdb1;
39-
CREATE ROLE r2dbc_test_user;
40-
GRANT SELECT ON v_$open_cursor TO r2dbc_test_user;
41-
GRANT SELECT ON v_$transaction TO r2dbc_test_user;
42-
GRANT SELECT ON v_$session TO r2dbc_test_user;
39+
CREATE ROLE r2dbc_test_role;
40+
GRANT SELECT ON v_$open_cursor TO r2dbc_test_role;
41+
GRANT SELECT ON v_$transaction TO r2dbc_test_role;
42+
GRANT SELECT ON v_$session TO r2dbc_test_role;
43+
GRANT CREATE VIEW TO r2dbc_test_role;
4344

4445
CREATE USER test IDENTIFIED BY test;
45-
GRANT connect, resource, unlimited tablespace, r2dbc_test_user TO test;
46+
GRANT connect, resource, unlimited tablespace, r2dbc_test_role TO test;
4647
ALTER USER test DEFAULT TABLESPACE users;
4748
ALTER USER test TEMPORARY TABLESPACE temp;

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,32 @@ Oracle R2DBC's implementation of Publishers that emit multiple items will
344344
typically defer execution until a Subscriber signals demand, and not support
345345
multiple subscribers.
346346

347-
### Errors
347+
### Errors and Warnings
348348
Oracle R2DBC creates R2dbcExceptions having the same ORA-XXXXX error codes
349-
used by Oracle Database and Oracle JDBC.
349+
used by Oracle Database and Oracle JDBC. The
350+
[Database Error Messages](https://docs.oracle.com/en/database/oracle/oracle-database/21/errmg/ORA-00000.html#GUID-27437B7F-F0C3-4F1F-9C6E-6780706FB0F6)
351+
document provides a reference for all ORA-XXXXX error codes.
350352

351-
A reference for the ORA-XXXXX error codes can be found
352-
[here](https://docs.oracle.com/en/database/oracle/oracle-database/21/errmg/ORA-00000.html#GUID-27437B7F-F0C3-4F1F-9C6E-6780706FB0F6)
353+
Warning messages from Oracle Database are emitted as
354+
`oracle.r2dbc.OracleR2dbcWarning` segments. These segments may be consumed using
355+
`Result.flatMap(Function)`:
356+
```java
357+
result.flatMap(segment -> {
358+
if (segment instanceof OracleR2dbcWarning) {
359+
logWarning(((OracleR2dbcWarning)segment).getMessage());
360+
return emptyPublisher();
361+
}
362+
else if (segment instanceof Result.Message){
363+
... handle an error ...
364+
}
365+
else {
366+
... handle other segment types ...
367+
}
368+
})
369+
```
370+
Unlike the errors of standard `Result.Message` segments, if a warning is not
371+
consumed by `flatMap`, then it will be silently discarded when a `Result` is
372+
consumed using the `map` or `getRowsUpdated` methods.
353373

354374
### Transactions
355375
Oracle R2DBC uses READ COMMITTED as the default transaction isolation level.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package oracle.r2dbc;
2+
3+
import io.r2dbc.spi.Result;
4+
5+
import java.util.function.Function;
6+
import java.util.function.Predicate;
7+
8+
/**
9+
* <p>
10+
* A subtype of {@link Result.Message} that provides information on warnings
11+
* raised by Oracle Database.
12+
* </p><p>
13+
* When a SQL command results in a warning, Oracle R2DBC emits a {@link Result}
14+
* with an {@code OracleR2dbcWarning} segment in addition to any other segments
15+
* that resulted from the SQL command. For example, if a SQL {@code SELECT}
16+
* command results in a warning, then an {@code OracleR2dbcWarning} segment is
17+
* included with the result, along with any {@link Result.RowSegment}s returned
18+
* by the {@code SELECT}.
19+
* </p><p>
20+
* R2DBC drivers typically emit {@code onError} signals for {@code Message}
21+
* segments that are not consumed by {@link Result#filter(Predicate)} or
22+
* {@link Result#flatMap(Function)}. Oracle R2DBC does not apply this behavior
23+
* for warning messages. If an {@code OracleR2dbcWarning}
24+
* segment is not consumed by the {@code filter} or {@code flatMap} methods of
25+
* a {@code Result}, then the warning is discarded and the result may be
26+
* consumed as normal with with the {@code map} or {@code getRowsUpdated}
27+
* methods.
28+
* </p><p>
29+
* Warning messages may be consumed with {@link Result#flatMap(Function)}:
30+
* </p><pre>{@code
31+
* result.flatMap(segment -> {
32+
* if (segment instanceof OracleR2dbcWarning) {
33+
* logWarning(((OracleR2dbcWarning)segment).getMessage());
34+
* return emptyPublisher();
35+
* }
36+
* else {
37+
* ... handle other segment types ...
38+
* }
39+
* })
40+
* }</pre><p>
41+
* A {@code flatMap} function may also be used to convert a warning into an
42+
* {@code onError} signal:
43+
* </p><pre>{@code
44+
* result.flatMap(segment -> {
45+
* if (segment instanceof OracleR2dbcWarning) {
46+
* return errorPublisher(((OracleR2dbcWarning)segment).warning());
47+
* }
48+
* else {
49+
* ... handle other segment types ...
50+
* }
51+
* })
52+
* }</pre>
53+
* @since 1.1.0
54+
*/
55+
public interface OracleR2dbcWarning extends Result.Message {
56+
57+
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,12 +163,26 @@ static void requireOpenConnection(java.sql.Connection jdbcConnection) {
163163
* as the specified {@code sqlException}. Not null.
164164
*/
165165
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) {
166181
assert sqlException != null : "sqlException is null";
167182

168183
final String message = sqlException.getMessage();
169184
final String sqlState = sqlException.getSQLState();
170185
final int errorCode = sqlException.getErrorCode();
171-
final String sql = getSql(sqlException);
172186

173187
if (sqlException instanceof SQLNonTransientException) {
174188
if (sqlException instanceof SQLSyntaxErrorException) {

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

Lines changed: 36 additions & 6 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;
@@ -150,6 +151,8 @@ private <T extends Segment, U> Publisher<U> publishSegments(
150151
Flux.from(publishSegments(segment -> {
151152
if (type.isInstance(segment))
152153
return mappingFunction.apply(type.cast(segment));
154+
else if (segment instanceof OracleR2dbcWarning)
155+
return (U)FILTERED;
153156
else if (segment instanceof Message)
154157
throw ((Message)segment).exception();
155158
else
@@ -398,14 +401,15 @@ static OracleResultImpl createErrorResult(R2dbcException r2dbcException) {
398401
* Creates a {@code Result} that publishes a {@code warning} as a
399402
* {@link Message} segment, followed by any {@code Segment}s of a
400403
* {@code result}.
404+
* @param sql The SQL that resulted in a waring. Not null.
401405
* @param warning Warning to publish. Not null.
402406
* @param result Result to publisher. Not null.
403407
* @return A {@code Result} for a {@code Statement} execution that
404408
* completed with a warning.
405409
*/
406410
static OracleResultImpl createWarningResult(
407-
SQLWarning warning, OracleResultImpl result) {
408-
return new WarningResult(warning, result);
411+
String sql, SQLWarning warning, OracleResultImpl result) {
412+
return new WarningResult(sql, warning, result);
409413
}
410414

411415
/**
@@ -627,6 +631,9 @@ <T> Publisher<T> publishSegments(Function<Segment, T> mappingFunction) {
627631
*/
628632
private static final class WarningResult extends OracleResultImpl {
629633

634+
/** The SQL that resulted in a warning */
635+
private final String sql;
636+
630637
/** The warning of this result */
631638
private final SQLWarning warning;
632639

@@ -636,11 +643,13 @@ private static final class WarningResult extends OracleResultImpl {
636643
/**
637644
* Constructs a result that publishes a {@code warning} as a
638645
* {@link Message}, and then publishes the segments of a {@code result}.
646+
* @param sql The SQL that resulted in a warning
639647
* @param warning Warning to publish. Not null.
640648
* @param result Result of segments to publish after the warning. Not null.
641649
*/
642650
private WarningResult(
643-
SQLWarning warning, OracleResultImpl result) {
651+
String sql, SQLWarning warning, OracleResultImpl result) {
652+
this.sql = sql;
644653
this.warning = warning;
645654
this.result = result;
646655
}
@@ -649,8 +658,11 @@ private WarningResult(
649658
<T> Publisher<T> publishSegments(Function<Segment, T> mappingFunction) {
650659
return Flux.fromStream(Stream.iterate(
651660
warning, Objects::nonNull, SQLWarning::getNextWarning)
652-
.map(OracleR2dbcExceptions::toR2dbcException)
653-
.map(MessageImpl::new))
661+
.map(nextWarning ->
662+
// It is noted that SQL can not be extracted from Oracle JDBC's
663+
// SQLWarning objects, so it must be explicitly provided here.
664+
OracleR2dbcExceptions.toR2dbcException(warning, sql))
665+
.map(WarningImpl::new))
654666
.map(mappingFunction)
655667
// Invoke publishSegments(Class, Function) rather than
656668
// publishSegments(Function) to update the state of the result; Namely,
@@ -774,7 +786,7 @@ public long value() {
774786
/**
775787
* Implementation of {@link Message}.
776788
*/
777-
private static final class MessageImpl implements Message {
789+
private static class MessageImpl implements Message {
778790

779791
private final R2dbcException exception;
780792

@@ -801,6 +813,24 @@ public String sqlState() {
801813
public String message() {
802814
return exception.getMessage();
803815
}
816+
817+
@Override
818+
public String toString() {
819+
return exception.toString();
820+
}
821+
}
822+
823+
/**
824+
* Implementation of {@link OracleR2dbcWarning}.
825+
*/
826+
private static final class WarningImpl
827+
extends MessageImpl
828+
implements OracleR2dbcWarning {
829+
830+
private WarningImpl(R2dbcException exception) {
831+
super(exception);
832+
}
833+
804834
}
805835

806836
/**

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

0 commit comments

Comments
 (0)