From 06021b52571a5cfc1569247f774e5b8663fd26a3 Mon Sep 17 00:00:00 2001
From: Michael McMahon
Date: Fri, 18 Oct 2024 14:44:22 -0700
Subject: [PATCH 1/3] Update to ojdbc 23.5
---
pom.xml | 2 +-
.../oracle/r2dbc/impl/OracleBatchImpl.java | 14 ++--
.../r2dbc/impl/OracleConnectionImpl.java | 74 ++++++++++++++++++-
.../r2dbc/impl/OracleStatementImpl.java | 73 +++++++++++++++---
.../r2dbc/impl/OracleConnectionImplTest.java | 8 ++
.../r2dbc/impl/OracleLargeObjectsTest.java | 4 +
.../impl/OracleReactiveJdbcAdapterTest.java | 9 +++
.../r2dbc/impl/OracleStatementImplTest.java | 17 ++---
8 files changed, 172 insertions(+), 29 deletions(-)
diff --git a/pom.xml b/pom.xml
index a727e54..180d75b 100755
--- a/pom.xml
+++ b/pom.xml
@@ -65,7 +65,7 @@
11
- 23.4.0.24.05
+ 23.5.0.24.07
1.0.0.RELEASE
3.5.11
1.0.3
diff --git a/src/main/java/oracle/r2dbc/impl/OracleBatchImpl.java b/src/main/java/oracle/r2dbc/impl/OracleBatchImpl.java
index 94c1ad8..e45cb56 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleBatchImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleBatchImpl.java
@@ -56,6 +56,9 @@
*/
final class OracleBatchImpl implements Batch {
+ /** The OracleConnectionImpl that created this Batch */
+ private final OracleConnectionImpl r2dbcConnection;
+
/** Adapts Oracle JDBC Driver APIs into Reactive Streams APIs */
private final ReactiveJdbcAdapter adapter;
@@ -83,12 +86,11 @@ final class OracleBatchImpl implements Batch {
* @param jdbcConnection JDBC connection to an Oracle Database. Not null.
* @param adapter Adapts JDBC calls into reactive streams. Not null.
*/
- OracleBatchImpl(
- Duration timeout, Connection jdbcConnection, ReactiveJdbcAdapter adapter) {
+ OracleBatchImpl(Duration timeout, OracleConnectionImpl r2dbcConnection) {
this.timeout = timeout;
- this.jdbcConnection =
- requireNonNull(jdbcConnection, "jdbcConnection is null");
- this.adapter = requireNonNull(adapter, "adapter is null");
+ this.r2dbcConnection = r2dbcConnection;
+ this.jdbcConnection = r2dbcConnection.jdbcConnection();
+ this.adapter = r2dbcConnection.adapter();
}
/**
@@ -103,7 +105,7 @@ public Batch add(String sql) {
requireOpenConnection(jdbcConnection);
requireNonNull(sql, "sql is null");
statements.add(
- new OracleStatementImpl(sql, timeout, jdbcConnection, adapter));
+ new OracleStatementImpl(sql, timeout, r2dbcConnection));
return this;
}
diff --git a/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java b/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java
index 8d82538..c453c5d 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java
@@ -38,6 +38,8 @@
import java.sql.SQLException;
import java.sql.Savepoint;
import java.time.Duration;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import static io.r2dbc.spi.IsolationLevel.READ_COMMITTED;
import static io.r2dbc.spi.IsolationLevel.SERIALIZABLE;
@@ -126,6 +128,12 @@ final class OracleConnectionImpl implements Connection, Lifecycle {
*/
private TransactionDefinition currentTransaction = null;
+ /**
+ * A queue of tasks that must complete before the {@link #jdbcConnection} is
+ * closed.
+ */
+ private final Queue> closeTasks = new ConcurrentLinkedQueue<>();
+
/**
* Constructs a new connection that uses the specified {@code adapter} to
* perform database operations with the specified {@code jdbcConnection}.
@@ -369,7 +377,46 @@ else if (isReadOnly == null && name == null) {
*/
@Override
public Publisher close() {
- return adapter.publishClose(jdbcConnection);
+
+ Publisher closeTasksPublisher = Mono.defer(() -> {
+ Publisher>[] closeTasksArray = closeTasks.toArray(Publisher>[]::new);
+ closeTasks.clear();
+
+ return Flux.concatDelayError(closeTasksArray).then();
+ });
+
+ return Flux.concatDelayError(
+ closeTasksPublisher,
+ adapter.publishClose(jdbcConnection));
+ }
+
+ /**
+ *
+ * Adds a publisher that must be subscribed to and must terminate before
+ * closing the JDBC connection. This can be used to ensure that a publisher
+ * has completed a task before the {@link #jdbcConnection()} has been closed
+ * and becomes unusable.
+ *
+ * A call to this method should always be accompanied with a call to
+ * {@link #removeCloseTask(Publisher)}: If the publisher is subscribed to
+ * and it terminates before {@link #close()} is called, then any reference
+ * to the Publisher must be removed so that it can be garbage collected.
+ *
+ * @param publisher Publisher that must terminate before closing the JDBC
+ * connection. Not null.
+ */
+ void addCloseTask(Publisher> publisher) {
+ closeTasks.add(publisher);
+ }
+
+ /**
+ * Removes a publisher that was previously added with
+ * {@link #addCloseTask(Publisher)}.
+ *
+ * @param publisher Publisher to remove. Not null.
+ */
+ void removeCloseTask(Publisher> publisher) {
+ closeTasks.remove(publisher);
}
/**
@@ -417,7 +464,7 @@ public Publisher commitTransaction() {
@Override
public Batch createBatch() {
requireOpenConnection(jdbcConnection);
- return new OracleBatchImpl(statementTimeout, jdbcConnection, adapter);
+ return new OracleBatchImpl(statementTimeout, this);
}
/**
@@ -441,8 +488,7 @@ public Batch createBatch() {
public Statement createStatement(String sql) {
requireNonNull(sql, "sql is null");
requireOpenConnection(jdbcConnection);
- return new OracleStatementImpl(
- sql, statementTimeout, jdbcConnection, adapter);
+ return new OracleStatementImpl(sql, statementTimeout, this);
}
/**
@@ -826,4 +872,24 @@ public Publisher preRelease() {
});
}
+ /**
+ * Returns the JDBC connection that this R2DBC connection executes database
+ * calls with.
+ *
+ * @return The JDBC connection that backs this R2DBC connection. Not null.
+ */
+ java.sql.Connection jdbcConnection() {
+ return jdbcConnection;
+ }
+
+ /**
+ * Returns the adapter that adapts the asynchronous API of the
+ * {@link #jdbcConnection()} that backs this R2DBC connection.
+ *
+ * @return The JDBC connection that backs this R2DBC connection. Not null.
+ */
+ ReactiveJdbcAdapter adapter() {
+ return adapter;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java b/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java
index 0651f80..0b3eedb 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java
@@ -197,6 +197,11 @@ final class OracleStatementImpl implements Statement {
/** Adapts Oracle JDBC Driver APIs into Reactive Streams APIs */
private final ReactiveJdbcAdapter adapter;
+ /**
+ * The instance of OracleConnectionImpl that created this statement.
+ */
+ private final OracleConnectionImpl r2dbcConnection;
+
/**
* SQL Language command that this statement executes. The command is
* provided by user code and may include parameter markers.
@@ -255,15 +260,15 @@ final class OracleStatementImpl implements Statement {
* @param sql SQL Language statement that may include parameter markers.
* @param timeout Timeout applied to the execution of the constructed
* {@code Statement}. Not null. Not negative.
- * @param jdbcConnection JDBC connection to an Oracle Database.
- * @param adapter Adapts JDBC calls into reactive streams.
+ * @param r2dbcConnection The OracleConnectionImpl that is creating this
+ * statement. Not null.
*/
OracleStatementImpl(
- String sql, Duration timeout, Connection jdbcConnection,
- ReactiveJdbcAdapter adapter) {
+ String sql, Duration timeout, OracleConnectionImpl r2dbcConnection) {
this.sql = sql;
- this.jdbcConnection = jdbcConnection;
- this.adapter = adapter;
+ this.r2dbcConnection = r2dbcConnection;
+ this.jdbcConnection = r2dbcConnection.jdbcConnection();
+ this.adapter = r2dbcConnection.adapter();
// The SQL string is parsed to identify parameter markers and allocate the
// bindValues array accordingly
@@ -987,13 +992,29 @@ private JdbcStatement(PreparedStatement preparedStatement, Object[] binds) {
this.preparedStatement = preparedStatement;
this.binds = binds;
+ // Work around for Oracle JDBC bug #37160069: The JDBC statement must be
+ // closed before closeAsyncOracle is called. This bug should be fixed
+ // by the 23.7 release of Oracle JDBC. The fix can be verified by the
+ // OracleConnectionImplTest.testSetStatementTimeout method (test won't
+ // fail, but look for an error in stderr). Typically, statement closing
+ // is a no-op if the connection is closed. However, if the statement
+ // executes SELECT ... FOR UPDATE, then JDBC will implicitly execute a
+ // commit() when the Statement (or really the ResultSet) is closed. This
+ // commit operation fails if the JDBC connection is already closed.
+ Publisher closePublisher = closeStatement();
+ r2dbcConnection.addCloseTask(closePublisher);
+
+ dependentCounter = new DependentCounter(Publishers.concatTerminal(
+ closePublisher,
+ Mono.fromRunnable(() ->
+ r2dbcConnection.removeCloseTask(closePublisher))));
+
// Add this statement as a "party" (think j.u.c.Phaser) to the dependent
// results by calling increment(). After the Result publisher returned by
// execute() terminates, this statement "arrives" by calling decrement().
// Calling decrement() after the Result publisher terminates ensures that
// the JDBC statement can not be closed until all results have had a
// chance to be emitted to user code.
- dependentCounter = new DependentCounter(closeStatement());
dependentCounter.increment();
}
@@ -1864,7 +1885,24 @@ private Publisher convertBlobBind(
Mono.from(adapter.publishBlobWrite(r2dbcBlob.stream(), jdbcBlob))
.thenReturn(jdbcBlob),
jdbcBlob -> {
- addDeallocation(adapter.publishBlobFree(jdbcBlob));
+ Publisher freePublisher = adapter.publishBlobFree(jdbcBlob);
+
+ // Work around for Oracle JDBC bug #37160069: All LOBs need to be
+ // freed before closeAsyncOracle is called. This bug should be fixed
+ // by the 23.7 release of Oracle JDBC. The fix can be verified by the
+ // clobInsert and blobInsert methods in the TestKit class of the R2DBC
+ // SPI test: These tests will subscribe to Connection.close() before
+ // this freePublisher is subscribed to.
+ r2dbcConnection.addCloseTask(freePublisher);
+
+ addDeallocation(
+ Publishers.concatTerminal(
+ freePublisher,
+ Mono.fromRunnable(() ->
+ r2dbcConnection.removeCloseTask(freePublisher))));
+
+ // TODO: Why is discard() called here? It should be called by the
+ // user who allocated the Blob, not by Oracle R2DBC.
return r2dbcBlob.discard();
});
}
@@ -1891,7 +1929,24 @@ private Publisher convertClobBind(
Mono.from(adapter.publishClobWrite(r2dbcClob.stream(), jdbcClob))
.thenReturn(jdbcClob),
jdbcClob -> {
- addDeallocation(adapter.publishClobFree(jdbcClob));
+ Publisher freePublisher = adapter.publishClobFree(jdbcClob);
+
+ // Work around for Oracle JDBC bug #37160069: All LOBs need to be
+ // freed before closeAsyncOracle is called. This bug should be fixed
+ // by the 23.7 release of Oracle JDBC. The fix can be verified by the
+ // clobInsert and blobInsert methods in the TestKit class of the R2DBC
+ // SPI test: These tests will subscribe to Connection.close() before
+ // this freePublisher is subscribed to.
+ r2dbcConnection.addCloseTask(freePublisher);
+
+ addDeallocation(
+ Publishers.concatTerminal(
+ freePublisher,
+ Mono.fromRunnable(() ->
+ r2dbcConnection.removeCloseTask(freePublisher))));
+
+ // TODO: Why is discard() called here? It should be called by the
+ // user who allocated the Clob, not by Oracle R2DBC.
return r2dbcClob.discard();
});
}
diff --git a/src/test/java/oracle/r2dbc/impl/OracleConnectionImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleConnectionImplTest.java
index 8452007..6996e73 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleConnectionImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleConnectionImplTest.java
@@ -1356,6 +1356,14 @@ public void testValidate() {
*/
@Test
public void testSetStatementTimeout() {
+ // Assume that oracle.jdbc.disablePipeline is only set to false when
+ // experimenting with pipelining on Mac OS. In this scenario, statement
+ // cancellation is known to not work.
+ String disabledProperty = System.getProperty("oracle.jdbc.disablePipeline");
+ assumeTrue(
+ disabledProperty == null || disabledProperty.equalsIgnoreCase("true"),
+ "oracle.jdbc.disablePipeline is set, and the value is not \"true\"");
+
Connection connection =
Mono.from(sharedConnection()).block(connectTimeout());
try {
diff --git a/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java b/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java
index a9ed093..144d29d 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java
@@ -29,7 +29,10 @@
import io.r2dbc.spi.Statement;
import oracle.r2dbc.OracleR2dbcObject;
import oracle.r2dbc.OracleR2dbcTypes;
+import oracle.r2dbc.test.DatabaseConfig;
+import oracle.r2dbc.test.TestUtils;
import org.junit.jupiter.api.Test;
+import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -38,6 +41,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList;
diff --git a/src/test/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapterTest.java b/src/test/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapterTest.java
index 8396da5..66ad6e2 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapterTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapterTest.java
@@ -98,6 +98,7 @@
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
/**
* Verifies that
@@ -382,6 +383,14 @@ public void testConnectTimeout()
*/
@Test
public void testStatementTimeout() {
+ // Assume that oracle.jdbc.disablePipeline is only set to false when
+ // experimenting with pipelining on Mac OS. In this scenario, statement
+ // cancellation is known to not work.
+ String disabledProperty = System.getProperty("oracle.jdbc.disablePipeline");
+ assumeTrue(
+ disabledProperty == null || disabledProperty.equalsIgnoreCase("true"),
+ "oracle.jdbc.disablePipeline is set, and the value is not \"true\"");
+
Connection connection0 =
Mono.from(ConnectionFactories.get(connectionFactoryOptions()
.mutate()
diff --git a/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java
index bde77ba..d0a2f1b 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java
@@ -31,6 +31,7 @@
import io.r2dbc.spi.Result;
import io.r2dbc.spi.Result.Message;
import io.r2dbc.spi.Result.UpdateCount;
+import io.r2dbc.spi.Row;
import io.r2dbc.spi.Statement;
import oracle.r2dbc.OracleR2dbcObject;
import oracle.r2dbc.OracleR2dbcOptions;
@@ -76,12 +77,11 @@
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
import static oracle.r2dbc.test.DatabaseConfig.databaseVersion;
import static oracle.r2dbc.test.DatabaseConfig.jdbcMinorVersion;
-import static oracle.r2dbc.test.DatabaseConfig.jdbcVersion;
import static oracle.r2dbc.test.DatabaseConfig.newConnection;
import static oracle.r2dbc.test.DatabaseConfig.sharedConnection;
+import static oracle.r2dbc.test.DatabaseConfig.sqlTimeout;
import static oracle.r2dbc.test.TestUtils.constructObject;
import static oracle.r2dbc.test.TestUtils.showErrors;
-import static oracle.r2dbc.test.DatabaseConfig.sqlTimeout;
import static oracle.r2dbc.util.Awaits.awaitError;
import static oracle.r2dbc.util.Awaits.awaitExecution;
import static oracle.r2dbc.util.Awaits.awaitMany;
@@ -3231,10 +3231,10 @@ public boolean equals(Object other) {
// Oracle JDBC 23.4 has a defect which prevents the Subscriber from
// receiving a terminal signal. The defect has been reported as bug
- // #36607804, and is expected to be fixed in the 23.5 release.
+ // #36607804, it will be fixed in the 23.6 release.
Assumptions.assumeTrue(
- jdbcMinorVersion() >= 5,
- "Oracle JDBC 23.4 does not support generated keys for VECTOR");
+ jdbcMinorVersion() >= 6,
+ "Oracle JDBC 23.5 does not support generated keys for VECTOR");
IdVector expected2 = new IdVector(
0,
@@ -3257,12 +3257,11 @@ public boolean equals(Object other) {
return Mono.just(((UpdateCount) segment).value());
}
else if (segment instanceof Result.RowSegment) {
- OutParameters outParameters =
- ((Result.OutSegment)segment).outParameters();
+ Row generatedRow = ((Result.RowSegment)segment).row();
return Mono.just(new IdVector(
- outParameters.get("outId", Integer.class),
- outParameters.get("outVector", VECTOR.class)));
+ generatedRow.get("id", Integer.class),
+ generatedRow.get("value", VECTOR.class)));
}
else if (segment instanceof Message) {
throw ((Message)segment).exception();
From ba7a5f20d7429a6d7844386fd9c8ad7896c7ecc0 Mon Sep 17 00:00:00 2001
From: Michael McMahon
Date: Fri, 8 Nov 2024 11:32:28 -0800
Subject: [PATCH 2/3] Updating ojdbc11 and README
---
README.md | 176 ++++++++----------
pom.xml | 6 +-
.../r2dbc/impl/OracleConnectionImpl.java | 58 +++---
.../oracle/r2dbc/impl/OracleReadableImpl.java | 94 +++++-----
.../oracle/r2dbc/impl/OracleResultImpl.java | 33 ++--
.../r2dbc/impl/OracleStatementImpl.java | 56 ++----
.../r2dbc/impl/OracleLargeObjectsTest.java | 140 +++++++++++++-
7 files changed, 336 insertions(+), 227 deletions(-)
diff --git a/README.md b/README.md
index 4ace718..a058c86 100644
--- a/README.md
+++ b/README.md
@@ -27,17 +27,14 @@ Project Reactor, RxJava, and Akka Streams.
# About This Version
The 1.2.0 release Oracle R2DBC implements version 1.0.0.RELEASE of the R2DBC SPI.
-Fixes in this release:
- - [Fixed "Operator has been terminated" message](https://github.com/oracle/oracle-r2dbc/pull/134)
- - [Checking for Zero Threads in the common ForkJoinPool](https://github.com/oracle/oracle-r2dbc/pull/131)
-
New features in this release:
-- [Supporting Option Values from Supplier and Publisher](https://github.com/oracle/oracle-r2dbc/pull/137)
-- [Added Options for Kerberos](https://github.com/oracle/oracle-r2dbc/pull/127)
+- [Pipelined Operations](https://github.com/oracle/oracle-r2dbc/pull/145)
+- [Vector Data Type](https://github.com/oracle/oracle-r2dbc/pull/146)
+- [Options for Fetch Size and Proxy Authentication](https://github.com/oracle/oracle-r2dbc/pull/155)
Updated dependencies:
-- Updated Oracle JDBC from 21.7.0.0 to 21.11.0.0
-- Updated Project Reactor from 3.5.0 to 3.5.11
+- Updated Oracle JDBC from 21.11.0.0 to 23.6.0.24.10
+- Updated Project Reactor from 3.5.11 to 3.6.11
## Installation
Oracle R2DBC can be obtained from Maven Central.
@@ -45,7 +42,7 @@ Oracle R2DBC can be obtained from Maven Central.
com.oracle.database.r2dbc
oracle-r2dbc
- 1.2.0
+ 1.3.0
```
@@ -60,8 +57,8 @@ Oracle R2DBC can also be built from source using Maven:
Oracle R2DBC is compatible with JDK 11 (or newer), and has the following runtime dependencies:
- R2DBC SPI 1.0.0.RELEASE
- Reactive Streams 1.0.3
-- Project Reactor 3.5.11
-- Oracle JDBC 21.11.0.0 for JDK 11 (ojdbc11.jar)
+- Project Reactor 3.6.11
+- Oracle JDBC 23.6.0.24.10 for JDK 11 (ojdbc11.jar)
- Oracle R2DBC relies on the Oracle JDBC Driver's [Reactive Extensions
](https://docs.oracle.com/en/database/oracle/oracle-database/23/jjdbc/jdbc-reactive-extensions.html#GUID-1C40C43B-3823-4848-8B5A-D2F97A82F79B) APIs.
@@ -73,7 +70,7 @@ Oracle R2DBC can only interoperate with libraries that support the 1.0.0.RELEASE
version of the R2DBC SPI. When using libraries like Spring and r2dbc-pool, be
sure to use a version which supports the 1.0.0.RELEASE of the SPI.
-Oracle R2DBC depends on the JDK 11 build of Oracle JDBC 21.11.0.0. Other
+Oracle R2DBC depends on the JDK 11 build of Oracle JDBC 23.6.0.24.10. Other
libraries may depend on a different version of Oracle JDBC, and this version may
be incompatible. To resolve incompatibilities, it may be necessary to explicitly
declare the dependency in your project, ie:
@@ -81,11 +78,11 @@ declare the dependency in your project, ie:
com.oracle.database.jdbc
ojdbc11
- 21.11.0.0
+ 23.6.0.24.10
```
-## Code Examples
+## Basic Code Examples
The following method returns an Oracle R2DBC `ConnectionFactory`
```java
@@ -150,7 +147,8 @@ a greeting message. "France" is set as the bind value for `locale_name`, so the
query should return a greeting like "Bonjour" when `row.get("greeting")`
is called.
-Additional code examples can be found [here](sample).
+Additional code examples appear throughout this document, and can also be found
+[here](sample).
## Help
For help programming with Oracle R2DBC, ask questions on Stack Overflow tagged with [[oracle] and [r2dbc]](https://stackoverflow.com/tags/oracle+r2dbc). The development team monitors Stack Overflow regularly.
@@ -169,7 +167,7 @@ vulnerability disclosure process.
## License
-Copyright (c) 2021, 2023 Oracle and/or its affiliates.
+Copyright (c) 2021, 2024 Oracle and/or its affiliates.
This software is dual-licensed to you under the Universal Permissive License
(UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License
@@ -366,9 +364,12 @@ ConnectionFactoryOptions.builder()
If this option is not configured, then the common
`java.util.concurrent.ForkJoinPool` is used as a default.
+
#### Configuring Oracle JDBC Connection Properties
-A subset of Oracle JDBC's connection properties are also supported by Oracle
-R2DBC. These connection properties may be configured as options having the same
+A subset of Oracle JDBC's connection properties are defined as `Option`
+constants in the
+[OracleR2dbcOptions](src/main/java/oracle/r2dbc/OracleR2dbcOptions.java) class.
+These connection properties may be configured as options having the same
name as the Oracle JDBC connection property, and may have `CharSequence` value
types.
@@ -406,23 +407,6 @@ Oracle R2DBC.
- [ssl.trustManagerFactory.algorithm](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_THIN_SSL_TRUSTMANAGERFACTORY_ALGORITHM)
- [oracle.net.ssl_context_protocol](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_SSL_CONTEXT_PROTOCOL)
-##### Miscellaneous Connection Properties
- - [oracle.jdbc.fanEnabled](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_FAN_ENABLED)
- - [oracle.jdbc.implicitStatementCacheSize](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE)
- - [oracle.jdbc.defaultLobPrefetchSize](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE)
- - [oracle.net.disableOob](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_THIN_NET_DISABLE_OUT_OF_BAND_BREAK)
- - Out of band (OOB) breaks effect statement timeouts. Set this to "true" if
- statement timeouts are not working correctly. OOB breaks are a
- - [requirement for pipelining](#requirements-for-pipelining)
- - [oracle.jdbc.enableQueryResultCache](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_ENABLE_QUERY_RESULT_CACHE)
- - Cached query results can cause phantom reads even if the serializable
- transaction isolation level is set. Set this to "false" if using the
- serializable isolation level.
- - [oracle.jdbc.timezoneAsRegion](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_TIMEZONE_AS_REGION)
- - Setting this option to "false" may resolve "ORA-01882: timezone region not
- found". The ORA-01882 error happens when Oracle Database doesn't recognize
- the name returned by `java.util.TimeZone.getDefault().getId()`.
-
##### Database Tracing Connection Properties
- [v$session.terminal](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_TERMINAL)
- [v$session.machine](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_MACHINE)
@@ -462,6 +446,25 @@ Oracle R2DBC.
- [oracle.net.ldap.ssl.trustManagerFactory.algorithm](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_LDAP_SSL_TRUSTMANAGER_FACTORY_ALGORITHM)
- [oracle.net.ldap.ssl.ssl_context_protocol](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_LDAP_SSL_CONTEXT_PROTOCOL)
+##### Miscellaneous Connection Properties
+- [oracle.jdbc.fanEnabled](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_FAN_ENABLED)
+- [oracle.jdbc.implicitStatementCacheSize](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE)
+- [oracle.jdbc.defaultLobPrefetchSize](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE)
+- [oracle.net.disableOob](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_THIN_NET_DISABLE_OUT_OF_BAND_BREAK)
+ - Out of band (OOB) breaks effect statement timeouts. Set this to "true" if
+ statement timeouts are not working correctly. OOB breaks are a
+ [requirement for pipelining](#requirements-for-pipelining)
+- [oracle.jdbc.enableQueryResultCache](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_ENABLE_QUERY_RESULT_CACHE)
+ - Cached query results can cause phantom reads even if the serializable
+ transaction isolation level is set. Set this to "false" if using the
+ serializable isolation level.
+- [oracle.jdbc.timezoneAsRegion](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_TIMEZONE_AS_REGION)
+ - Setting this option to "false" may resolve "ORA-01882: timezone region not
+ found". The ORA-01882 error happens when Oracle Database doesn't recognize
+ the name returned by `java.util.TimeZone.getDefault().getId()`.
+- [oracle.jdbc.defaultRowPrefetch](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_DEFAULT_ROW_PREFETCH)
+- [oracle.jdbc.proxyClientName](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_PROXY_CLIENT_NAME)
+
### Thread Safety
Oracle R2DBC's `ConnectionFactory` and `ConnectionFactoryProvider` are the only
classes that have a thread safe implementation. All other classes implemented
@@ -772,14 +775,14 @@ for the out parameters is emitted last, after the `Result` for each cursor.
Oracle R2DBC supports type mappings between Java and SQL for non-standard data
types of Oracle Database.
-| Oracle SQL Type | Java Type |
-|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------|
-| [JSON](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-E441F541-BA31-4E8C-B7B4-D2FB8C42D0DF) | `javax.json.JsonObject` or `oracle.sql.json.OracleJsonObject` |
-| [DATE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-5405B652-C30E-4F4F-9D33-9A4CB2110F1B) | `java.time.LocalDateTime` |
-| [INTERVAL DAY TO SECOND](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-B03DD036-66F8-4BD3-AF26-6D4433EBEC1C) | `java.time.Duration` |
-| [INTERVAL YEAR TO MONTH](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-ED59E1B3-BA8D-4711-B5C8-B0199C676A95) | `java.time.Period` |
-| [SYS_REFCURSOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/static-sql.html#GUID-470A7A99-888A-46C2-BDAF-D4710E650F27) | `io.r2dbc.spi.Result` |
-| [VECTOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-801FFE49-217D-4012-9C55-66DAE1BA806F) | `double[]`, `float[]`, `byte[]`, or `oracle.sql.VECTOR` |
+| Oracle SQL Type | Java Type |
+|---------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------|
+| [JSON](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-E441F541-BA31-4E8C-B7B4-D2FB8C42D0DF) | `javax.json.JsonObject` or `oracle.sql.json.OracleJsonObject` |
+| [DATE](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-5405B652-C30E-4F4F-9D33-9A4CB2110F1B) | `java.time.LocalDateTime` |
+| [INTERVAL DAY TO SECOND](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-B03DD036-66F8-4BD3-AF26-6D4433EBEC1C) | `java.time.Duration` |
+| [INTERVAL YEAR TO MONTH](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-ED59E1B3-BA8D-4711-B5C8-B0199C676A95) | `java.time.Period` |
+| [SYS_REFCURSOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/lnpls/static-sql.html#GUID-470A7A99-888A-46C2-BDAF-D4710E650F27) | `io.r2dbc.spi.Result` |
+| [VECTOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/Data-Types.html#GUID-801FFE49-217D-4012-9C55-66DAE1BA806F) | `double[]`, `float[]`, `byte[]`, `boolean[]` or `oracle.sql.VECTOR` |
> Unlike the standard SQL type named "DATE", the Oracle Database type named
> "DATE" stores values for year, month, day, hour, minute, and second. The
> standard SQL type only stores year, month, and day. LocalDateTime objects are able
@@ -1011,7 +1014,7 @@ Publisher mapRefCursorRows(Result refCursorResult) {
```
### VECTOR
-The default mapping for `VECTOR` is the
+The default mapping for the VECTOR data type is the
[oracle.sql.VECTOR](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/sql/VECTOR.html)
class. Instances of this class may be passed to
`Statement.bind(int/String, Object)`:
@@ -1027,24 +1030,16 @@ void bindVector(Statement statement, float[] floatArray) throws SQLException {
statement.bind("vector", vector);
}
```
-The `oracle.sql.VECTOR` class defines three factory methods: `ofFloat64Values`,
-`ofFloat32Values`, and `ofInt8Values`. These methods support Java to VECTOR
-conversions of `boolean[]`, `byte[]`, `short[]`, `int[]`, `long[]`,
-`float[]`, and `double[]`:
-```java
-void bindVector(Statement statement, int[] intArray) {
- final VECTOR vector;
- try {
- vector = VECTOR.ofFloat64Values(intArray);
- }
- catch (SQLException sqlException) {
- throw new IllegalArgumentException(sqlException);
- }
- statement.bind("vector", vector);
-}
-```
-The factory methods of `oracle.sql.VECTOR` may perform lossy conversions, such
-as when converting a `double[]` into a VECTOR of 32-bit floating point numbers.
+The `oracle.sql.VECTOR` class defines factory methods that convert a
+`double[]`, `float[]`, `long[]`, `int[]`, `short[]`, `byte[]`, or `boolean[]`
+into a VECTOR of a specific dimension type:
+- `ofFloat64Values`
+- `ofFloat32Values`
+- `ofInt8Values`
+- `ofBinaryValues` (only supports `boolean[]` and `byte[]`)
+
+These factory methods may perform a lossy conversion, such
+as when converting a `double[]` into a VECTOR of FLOAT32 dimensions.
[The JavaDocs of these methods specify which conversions are lossy](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/sql/VECTOR.html).
The `OracleR2dbcTypes.VECTOR` type descriptor can be used to register an OUT or
@@ -1052,60 +1047,35 @@ IN/OUT parameter:
```java
void registerOutVector(Statement statement) {
Parameter outVector = Parameters.out(OracleR2dbcTypes.VECTOR);
- statement.bind("vector", outVector);
+ statement.bind("out_vector", outVector);
}
```
-The `OracleR2dbcTypes.VECTOR` type descriptor can also be used as an alternative to
-`oracle.sql.VECTOR` when binding an IN parameter to a `double[]`, `float[]`, or
-`byte[]`:
+The `OracleR2dbcTypes.VECTOR` type descriptor can also be used as an alternative
+to `oracle.sql.VECTOR` for binding a `double[]`, `float[]`, `byte[]`, or
+`boolean[]`. Arrays of these types are respectively converted into a VECTOR of
+FLOAT64, FLOAT32, INT8, or BINARY dimensions.
```java
void bindVector(Statement statement, float[] floatArray) {
Parameter inVector = Parameters.in(OracleR2dbcTypes.VECTOR, floatArray);
- statement.bind("vector", inVector);
+ statement.bind("in_vector", inVector);
}
```
-Note that `double[]`, `float[]`, and `byte[]` can NOT be passed directly to
-`Statement.bind(int/String, Object)` when binding `VECTOR` data. The R2DBC
-Specification defines `ARRAY` as the default mapping for Java arrays.
-
-A `VECTOR` column or OUT parameter is converted to `oracle.sql.VECTOR` by
-default. The column or OUT parameter can also be converted to `double[]`,
-`float[]`, or `byte[]` by passing the corresponding array class to the `get`
-methods:
+Note that passing arrays directly into `Statement.bind(int/String, Object)` will
+NOT create a VECTOR bind. The R2DBC Specification already defines ARRAY as
+the default mapping for Java arrays, not VECTOR.
+
+A VECTOR column or OUT parameter is converted to `oracle.sql.VECTOR` by
+default. But a VECTOR column or OUT parameter having FLOAT64, FLOAT32, or INT8
+dimensions may also be converted to
+`double[]`, `float[]`, `long[]`, `int[]`, `short[]`, `byte[]`, or `boolean[]`.
+A VECTOR of BINARY dimensions may only be converted to `boolean[]` or `byte[]`.
+These array classes may be passed to the `get` methods of a `Readable`:
```java
float[] getVector(io.r2dbc.Readable readable) {
return readable.get("vector", float[].class);
}
```
-#### Returning VECTOR from DML
-Returning a VECTOR column with `Statement.returningGeneratedValues(String...)`
-is not supported due to a defect in the 23.4 release of Oracle JDBC. Attempting
-to return a `VECTOR` column will result in a `Subscriber` that never receives
-`onComplete` or `onError`. The defect will be fixed in the next release of
-Oracle JDBC.
-
-A `RETURNING ... INTO` clause can be used as a temporary workaround. This clause
-must appear within a PL/SQL block, denoted by the `BEGIN` and `END;` keywords.
-In the following example, a `VECTOR` column named "embedding" is returned:
-```java
-Publisher returningVectorExample(Connection connection, String vectorString) {
-
- Statement statement = connection.createStatement(
- "BEGIN INSERT INTO example(embedding)"
- + " VALUES (TO_VECTOR(:vectorString, 999, FLOAT64))"
- + " RETURNING embedding INTO :embedding;"
- + " END;")
- .bind("vectorString", vectorString)
- .bind("embedding", Parameters.out(OracleR2dbcTypes.VECTOR));
-
- return Flux.from(statement.execute())
- .flatMap(result ->
- result.map(outParameters ->
- outParameters.get("embedding", double[].class)));
-}
-```
-
## Secure Programming Guidelines
The following security related guidelines should be adhered to when programming
with the Oracle R2DBC Driver.
diff --git a/pom.xml b/pom.xml
index 180d75b..b33e710 100755
--- a/pom.xml
+++ b/pom.xml
@@ -26,7 +26,7 @@
com.oracle.database.r2dbc
oracle-r2dbc
- 1.2.0
+ 1.3.0
oracle-r2dbc
Oracle R2DBC Driver implementing version 1.0.0 of the R2DBC SPI for Oracle Database.
@@ -65,9 +65,9 @@
11
- 23.5.0.24.07
+ 23.6.0.24.10
1.0.0.RELEASE
- 3.5.11
+ 3.6.11
1.0.3
5.9.1
5.3.19
diff --git a/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java b/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java
index c453c5d..11b8bec 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleConnectionImpl.java
@@ -38,8 +38,8 @@
import java.sql.SQLException;
import java.sql.Savepoint;
import java.time.Duration;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import static io.r2dbc.spi.IsolationLevel.READ_COMMITTED;
import static io.r2dbc.spi.IsolationLevel.SERIALIZABLE;
@@ -47,8 +47,8 @@
import static io.r2dbc.spi.TransactionDefinition.LOCK_WAIT_TIMEOUT;
import static io.r2dbc.spi.TransactionDefinition.NAME;
import static io.r2dbc.spi.TransactionDefinition.READ_ONLY;
-import static oracle.r2dbc.impl.OracleR2dbcExceptions.requireNonNull;
import static oracle.r2dbc.impl.OracleR2dbcExceptions.fromJdbc;
+import static oracle.r2dbc.impl.OracleR2dbcExceptions.requireNonNull;
import static oracle.r2dbc.impl.OracleR2dbcExceptions.requireOpenConnection;
import static oracle.r2dbc.impl.OracleR2dbcExceptions.runJdbc;
import static oracle.r2dbc.impl.OracleR2dbcExceptions.toR2dbcException;
@@ -129,10 +129,10 @@ final class OracleConnectionImpl implements Connection, Lifecycle {
private TransactionDefinition currentTransaction = null;
/**
- * A queue of tasks that must complete before the {@link #jdbcConnection} is
- * closed.
+ * A set of tasks that must complete before the {@link #jdbcConnection} is
+ * closed. The tasks are executed by subscribing to a Publisher.
*/
- private final Queue> closeTasks = new ConcurrentLinkedQueue<>();
+ private final Set> closeTasks = ConcurrentHashMap.newKeySet();
/**
* Constructs a new connection that uses the specified {@code adapter} to
@@ -393,30 +393,36 @@ public Publisher close() {
/**
*
* Adds a publisher that must be subscribed to and must terminate before
- * closing the JDBC connection. This can be used to ensure that a publisher
- * has completed a task before the {@link #jdbcConnection()} has been closed
- * and becomes unusable.
+ * closing the JDBC connection. This method can be used to ensure that certain
+ * tasks are completed before the {@link #jdbcConnection()} is closed and
+ * becomes unusable.
*
- * A call to this method should always be accompanied with a call to
- * {@link #removeCloseTask(Publisher)}: If the publisher is subscribed to
- * and it terminates before {@link #close()} is called, then any reference
- * to the Publisher must be removed so that it can be garbage collected.
+ * The publisher returned by this method emits the same result as the
+ * publisher passed into this method. However, when the returned publisher
+ * terminates, it will also remove any reference to the publisher that was
+ * passed into this method. If the returned publisher is never subscribed
+ * to, then the reference will not be cleared until the connection is
+ * closed! So, this method should only be used in cases where the user is
+ * responsible for subscribing to the returned publisher, in the same way they
+ * would be responsible for calling close() on an AutoCloseable. If the user
+ * is not responsible for subscribing, then there is no reasonable way for
+ * them to reduce the number object references that this method will create;
+ * They would either need to close this connection, or subscribe to a
+ * publisher when there is no obligation to do so.
*
- * @param publisher Publisher that must terminate before closing the JDBC
- * connection. Not null.
+ *
+ * @param publisher Publisher that must be subscribed to before closing the
+ * JDBC connection. Not null.
+ *
+ * @return A publisher that emits the same result as the publisher passed into
+ * this method, and clears any reference to it when terminated. Not null.
*/
- void addCloseTask(Publisher> publisher) {
+ Publisher addCloseTask(Publisher publisher) {
closeTasks.add(publisher);
- }
- /**
- * Removes a publisher that was previously added with
- * {@link #addCloseTask(Publisher)}.
- *
- * @param publisher Publisher to remove. Not null.
- */
- void removeCloseTask(Publisher> publisher) {
- closeTasks.remove(publisher);
+ return Publishers.concatTerminal(
+ publisher,
+ Mono.fromRunnable(() -> closeTasks.remove(publisher)));
}
/**
@@ -876,7 +882,7 @@ public Publisher preRelease() {
* Returns the JDBC connection that this R2DBC connection executes database
* calls with.
*
- * @return The JDBC connection that backs this R2DBC connection. Not null.
+ * @return The JDBC connection which backs this R2DBC connection. Not null.
*/
java.sql.Connection jdbcConnection() {
return jdbcConnection;
diff --git a/src/main/java/oracle/r2dbc/impl/OracleReadableImpl.java b/src/main/java/oracle/r2dbc/impl/OracleReadableImpl.java
index c18aebd..850f886 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleReadableImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleReadableImpl.java
@@ -46,6 +46,7 @@
import oracle.sql.INTERVALYM;
import oracle.sql.TIMESTAMPLTZ;
import oracle.sql.TIMESTAMPTZ;
+import org.reactivestreams.Publisher;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
@@ -93,6 +94,9 @@
class OracleReadableImpl implements io.r2dbc.spi.Readable {
+ /** The R2DBC connection that created this readable */
+ private final OracleConnectionImpl r2dbcConnection;
+
/** The JDBC connection that created this readable */
private final java.sql.Connection jdbcConnection;
@@ -117,21 +121,21 @@ class OracleReadableImpl implements io.r2dbc.spi.Readable {
* {@code jdbcReadable} and obtains metadata of the values from
* {@code resultMetadata}.
*
- * @param jdbcConnection JDBC connection that created the
+ * @param r2dbcConnection R2DBC connection that created the
* {@code jdbcReadable}. Not null.
* @param jdbcReadable Readable values from a JDBC Driver. Not null.
* @param readablesMetadata Metadata of each value. Not null.
* @param adapter Adapts JDBC calls into reactive streams. Not null.
*/
private OracleReadableImpl(
- java.sql.Connection jdbcConnection, DependentCounter dependentCounter,
- JdbcReadable jdbcReadable, ReadablesMetadata> readablesMetadata,
- ReactiveJdbcAdapter adapter) {
- this.jdbcConnection = jdbcConnection;
+ OracleConnectionImpl r2dbcConnection, DependentCounter dependentCounter,
+ JdbcReadable jdbcReadable, ReadablesMetadata> readablesMetadata) {
+ this.r2dbcConnection = r2dbcConnection;
+ this.jdbcConnection = r2dbcConnection.jdbcConnection();
this.dependentCounter = dependentCounter;
this.jdbcReadable = jdbcReadable;
this.readablesMetadata = readablesMetadata;
- this.adapter = adapter;
+ this.adapter = r2dbcConnection.adapter();
}
/**
@@ -151,11 +155,10 @@ private OracleReadableImpl(
* {@code metadata}. Not null.
*/
static Row createRow(
- java.sql.Connection jdbcConnection, DependentCounter dependentCounter,
- JdbcReadable jdbcReadable, RowMetadataImpl metadata,
- ReactiveJdbcAdapter adapter) {
+ OracleConnectionImpl r2dbcConnection, DependentCounter dependentCounter,
+ JdbcReadable jdbcReadable, RowMetadataImpl metadata) {
return new RowImpl(
- jdbcConnection, dependentCounter, jdbcReadable, metadata, adapter);
+ r2dbcConnection, dependentCounter, jdbcReadable, metadata);
}
/**
@@ -164,7 +167,7 @@ static Row createRow(
* the provided {@code jdbcReadable} and {@code rowMetadata}. The metadata
* object is used to determine the default type mapping of column values.
*
- * @param jdbcConnection JDBC connection that created the
+ * @param r2dbcConnection R2DBC connection that created the
* {@code jdbcReadable}. Not null.
* @param dependentCounter Counter that is increased for each dependent
* {@code Result} created by the returned {@code OutParameters}
@@ -175,11 +178,10 @@ static Row createRow(
* {@code metadata}. Not null.
*/
static OutParameters createOutParameters(
- java.sql.Connection jdbcConnection, DependentCounter dependentCounter,
- JdbcReadable jdbcReadable, OutParametersMetadataImpl metadata,
- ReactiveJdbcAdapter adapter) {
+ OracleConnectionImpl r2dbcConnection, DependentCounter dependentCounter,
+ JdbcReadable jdbcReadable, OutParametersMetadataImpl metadata) {
return new OutParametersImpl(
- jdbcConnection, dependentCounter, jdbcReadable, metadata, adapter);
+ r2dbcConnection, dependentCounter, jdbcReadable, metadata);
}
/**
@@ -335,11 +337,16 @@ private ByteBuffer getByteBuffer(int index) {
*/
private Blob getBlob(int index) {
java.sql.Blob jdbcBlob = jdbcReadable.getObject(index, java.sql.Blob.class);
- return jdbcBlob == null
- ? null
- : OracleLargeObjects.createBlob(
- adapter.publishBlobRead(jdbcBlob),
- adapter.publishBlobFree(jdbcBlob));
+
+ if (jdbcBlob == null)
+ return null;
+
+ Publisher freePublisher =
+ r2dbcConnection.addCloseTask(adapter.publishBlobFree(jdbcBlob));
+
+ return OracleLargeObjects.createBlob(
+ adapter.publishBlobRead(jdbcBlob),
+ freePublisher);
}
/**
@@ -367,11 +374,15 @@ private Clob getClob(int index) {
jdbcClob = jdbcReadable.getObject(index, java.sql.Clob.class);
}
- return jdbcClob == null
- ? null
- : OracleLargeObjects.createClob(
- adapter.publishClobRead(jdbcClob),
- adapter.publishClobFree(jdbcClob));
+ if (jdbcClob == null)
+ return null;
+
+ Publisher freePublisher =
+ r2dbcConnection.addCloseTask(adapter.publishClobFree(jdbcClob));
+
+ return OracleLargeObjects.createClob(
+ adapter.publishClobRead(jdbcClob),
+ freePublisher);
}
/**
@@ -685,11 +696,10 @@ private OracleR2dbcObjectImpl getOracleR2dbcObject(int index) {
return null;
return new OracleR2dbcObjectImpl(
- jdbcConnection,
+ r2dbcConnection,
dependentCounter,
new StructJdbcReadable(oracleStruct),
- ReadablesMetadata.createAttributeMetadata(oracleStruct),
- adapter);
+ ReadablesMetadata.createAttributeMetadata(oracleStruct));
}
/**
@@ -956,7 +966,7 @@ private Result getResult(int index) {
dependentCounter.increment();
return OracleResultImpl.createQueryResult(
- dependentCounter, resultSet, adapter);
+ r2dbcConnection, dependentCounter, resultSet);
}
/**
@@ -994,17 +1004,16 @@ private static final class RowImpl
* {@code jdbcReadable}, and uses the specified {@code rowMetadata} to
* determine the default type mapping of column values.
*
- * @param jdbcConnection JDBC connection that created the
+ * @param r2dbcConnection R2DBC connection that created the
* {@code jdbcReadable}. Not null.
* @param jdbcReadable Row data from the Oracle JDBC Driver. Not null.
* @param metadata Meta-data for the specified row. Not null.
* @param adapter Adapts JDBC calls into reactive streams. Not null.
*/
private RowImpl(
- java.sql.Connection jdbcConnection, DependentCounter dependentCounter,
- JdbcReadable jdbcReadable, RowMetadataImpl metadata,
- ReactiveJdbcAdapter adapter) {
- super(jdbcConnection, dependentCounter, jdbcReadable, metadata, adapter);
+ OracleConnectionImpl r2dbcConnection, DependentCounter dependentCounter,
+ JdbcReadable jdbcReadable, RowMetadataImpl metadata) {
+ super(r2dbcConnection, dependentCounter, jdbcReadable, metadata);
this.metadata = metadata;
}
@@ -1044,10 +1053,9 @@ private static final class OutParametersImpl
* @param adapter Adapts JDBC calls into reactive streams. Not null.
*/
private OutParametersImpl(
- java.sql.Connection jdbcConnection, DependentCounter dependentCounter,
- JdbcReadable jdbcReadable, OutParametersMetadataImpl metadata,
- ReactiveJdbcAdapter adapter) {
- super(jdbcConnection, dependentCounter, jdbcReadable, metadata, adapter);
+ OracleConnectionImpl r2dbcConnection, DependentCounter dependentCounter,
+ JdbcReadable jdbcReadable, OutParametersMetadataImpl metadata) {
+ super(r2dbcConnection, dependentCounter, jdbcReadable, metadata);
this.metadata = metadata;
}
@@ -1068,20 +1076,18 @@ private final class OracleR2dbcObjectImpl
* {@code jdbcReadable} and obtains metadata of the values from
* {@code outParametersMetaData}.
*
- * @param jdbcConnection JDBC connection that created the
+ * @param r2dbcConnection R2DBC connection that created the
* {@code jdbcReadable}. Not null.
* @param structJdbcReadable Readable values from a JDBC Driver. Not null.
* @param metadata Metadata of each value. Not null.
* @param adapter Adapts JDBC calls into reactive streams. Not null.
*/
private OracleR2dbcObjectImpl(
- java.sql.Connection jdbcConnection,
+ OracleConnectionImpl r2dbcConnection,
DependentCounter dependentCounter,
StructJdbcReadable structJdbcReadable,
- OracleR2dbcObjectMetadataImpl metadata,
- ReactiveJdbcAdapter adapter) {
- super(
- jdbcConnection, dependentCounter, structJdbcReadable, metadata, adapter);
+ OracleR2dbcObjectMetadataImpl metadata) {
+ super(r2dbcConnection, dependentCounter, structJdbcReadable, metadata);
this.metadata = metadata;
}
diff --git a/src/main/java/oracle/r2dbc/impl/OracleResultImpl.java b/src/main/java/oracle/r2dbc/impl/OracleResultImpl.java
index 5757a6b..6cd7327 100644
--- a/src/main/java/oracle/r2dbc/impl/OracleResultImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleResultImpl.java
@@ -262,6 +262,8 @@ protected void setPublished() {
/**
* Creates a {@code Result} that publishes a JDBC {@code resultSet} as
* {@link RowSegment}s
+ * @param r2dbcConnection The R2DBC connection that created this result. Not
+ * null.
* @param dependentCounter Collection of results that depend on the JDBC
* statement which created the {@code ResultSet} to remain open until all
* results are consumed.
@@ -270,9 +272,9 @@ protected void setPublished() {
* @return A {@code Result} for a ResultSet
*/
public static OracleResultImpl createQueryResult(
- DependentCounter dependentCounter, ResultSet resultSet,
- ReactiveJdbcAdapter adapter) {
- return new ResultSetResult(dependentCounter, resultSet, adapter);
+ OracleConnectionImpl r2dbcConnection,
+ DependentCounter dependentCounter, ResultSet resultSet) {
+ return new ResultSetResult(r2dbcConnection, dependentCounter, resultSet);
}
/**
@@ -296,6 +298,8 @@ static OracleResultImpl createCallResult(
* {@link UpdateCount} segment, followed by a {@code generatedKeys}
* {@code ResultSet} as {@link RowSegment}s
* @return A {@code Result} for values generated by DML
+ * @param r2dbcConnection The R2DBC connection that created this result. Not
+ * null.
* @param updateCount Update count to publish
* @param dependentCounter Collection of results that depend on the JDBC
* statement which created the {@code generatedKeys} {@code ResultSet} to
@@ -304,10 +308,10 @@ static OracleResultImpl createCallResult(
* @param adapter Adapts JDBC calls into reactive streams. Not null.
*/
static OracleResultImpl createGeneratedValuesResult(
- long updateCount, DependentCounter dependentCounter,
- ResultSet generatedKeys, ReactiveJdbcAdapter adapter) {
+ OracleConnectionImpl r2dbcConnection, long updateCount,
+ DependentCounter dependentCounter, ResultSet generatedKeys) {
return new GeneratedKeysResult(
- updateCount, dependentCounter, generatedKeys, adapter);
+ r2dbcConnection, updateCount, dependentCounter, generatedKeys);
}
/**
@@ -434,17 +438,20 @@ protected Publisher mapSegments(
*/
private static final class ResultSetResult extends DependentResult {
+ private final OracleConnectionImpl r2dbcConnection;
private final ResultSet resultSet;
private final RowMetadataImpl metadata;
private final ReactiveJdbcAdapter adapter;
private ResultSetResult(
- DependentCounter dependentCounter, ResultSet resultSet,
- ReactiveJdbcAdapter adapter) {
+ OracleConnectionImpl r2dbcConnection,
+ DependentCounter dependentCounter,
+ ResultSet resultSet) {
super(dependentCounter);
+ this.r2dbcConnection = r2dbcConnection;
this.resultSet = resultSet;
this.metadata = createRowMetadata(fromJdbc(resultSet::getMetaData));
- this.adapter = adapter;
+ this.adapter = r2dbcConnection.adapter();
}
@Override
@@ -457,8 +464,7 @@ protected Publisher mapDependentSegments(
// Avoiding object allocation by reusing the same Row object
ReusableJdbcReadable reusableJdbcReadable = new ReusableJdbcReadable();
Row row = createRow(
- fromJdbc(() -> resultSet.getStatement().getConnection()),
- dependentCounter, reusableJdbcReadable, metadata, adapter);
+ r2dbcConnection, dependentCounter, reusableJdbcReadable, metadata);
return adapter.publishRows(resultSet, jdbcReadable -> {
reusableJdbcReadable.current = jdbcReadable;
@@ -500,11 +506,12 @@ private static final class GeneratedKeysResult extends OracleResultImpl {
private final OracleResultImpl generatedKeysResult;
private GeneratedKeysResult(
+ OracleConnectionImpl r2dbcConnection,
long updateCount, DependentCounter dependentCounter,
- ResultSet generatedKeys, ReactiveJdbcAdapter adapter) {
+ ResultSet generatedKeys) {
updateCountResult = createUpdateCountResult(updateCount);
generatedKeysResult =
- createQueryResult(dependentCounter, generatedKeys, adapter);
+ createQueryResult(r2dbcConnection, dependentCounter, generatedKeys);
}
@Override
diff --git a/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java b/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java
index 0b3eedb..8cd2771 100755
--- a/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java
+++ b/src/main/java/oracle/r2dbc/impl/OracleStatementImpl.java
@@ -1001,13 +1001,9 @@ private JdbcStatement(PreparedStatement preparedStatement, Object[] binds) {
// executes SELECT ... FOR UPDATE, then JDBC will implicitly execute a
// commit() when the Statement (or really the ResultSet) is closed. This
// commit operation fails if the JDBC connection is already closed.
- Publisher closePublisher = closeStatement();
- r2dbcConnection.addCloseTask(closePublisher);
-
- dependentCounter = new DependentCounter(Publishers.concatTerminal(
- closePublisher,
- Mono.fromRunnable(() ->
- r2dbcConnection.removeCloseTask(closePublisher))));
+ Publisher closePublisher =
+ r2dbcConnection.addCloseTask(closeStatement());
+ dependentCounter = new DependentCounter(closePublisher);
// Add this statement as a "party" (think j.u.c.Phaser) to the dependent
// results by calling increment(). After the Result publisher returned by
@@ -1266,7 +1262,8 @@ private OracleResultImpl getCurrentResult(boolean isResultSet) {
return fromJdbc(() -> {
if (isResultSet) {
return createQueryResult(
- dependentCounter, preparedStatement.getResultSet(), adapter);
+ r2dbcConnection, dependentCounter,
+ preparedStatement.getResultSet());
}
else {
long updateCount = preparedStatement.getLargeUpdateCount();
@@ -1885,25 +1882,17 @@ private Publisher convertBlobBind(
Mono.from(adapter.publishBlobWrite(r2dbcBlob.stream(), jdbcBlob))
.thenReturn(jdbcBlob),
jdbcBlob -> {
- Publisher freePublisher = adapter.publishBlobFree(jdbcBlob);
-
// Work around for Oracle JDBC bug #37160069: All LOBs need to be
// freed before closeAsyncOracle is called. This bug should be fixed
// by the 23.7 release of Oracle JDBC. The fix can be verified by the
// clobInsert and blobInsert methods in the TestKit class of the R2DBC
// SPI test: These tests will subscribe to Connection.close() before
// this freePublisher is subscribed to.
- r2dbcConnection.addCloseTask(freePublisher);
-
- addDeallocation(
- Publishers.concatTerminal(
- freePublisher,
- Mono.fromRunnable(() ->
- r2dbcConnection.removeCloseTask(freePublisher))));
+ Publisher freePublisher =
+ r2dbcConnection.addCloseTask(adapter.publishBlobFree(jdbcBlob));
+ addDeallocation(freePublisher);
- // TODO: Why is discard() called here? It should be called by the
- // user who allocated the Blob, not by Oracle R2DBC.
- return r2dbcBlob.discard();
+ return Mono.empty();
});
}
@@ -1929,25 +1918,17 @@ private Publisher convertClobBind(
Mono.from(adapter.publishClobWrite(r2dbcClob.stream(), jdbcClob))
.thenReturn(jdbcClob),
jdbcClob -> {
- Publisher freePublisher = adapter.publishClobFree(jdbcClob);
-
// Work around for Oracle JDBC bug #37160069: All LOBs need to be
// freed before closeAsyncOracle is called. This bug should be fixed
// by the 23.7 release of Oracle JDBC. The fix can be verified by the
// clobInsert and blobInsert methods in the TestKit class of the R2DBC
// SPI test: These tests will subscribe to Connection.close() before
// this freePublisher is subscribed to.
- r2dbcConnection.addCloseTask(freePublisher);
-
- addDeallocation(
- Publishers.concatTerminal(
- freePublisher,
- Mono.fromRunnable(() ->
- r2dbcConnection.removeCloseTask(freePublisher))));
+ Publisher freePublisher =
+ r2dbcConnection.addCloseTask(adapter.publishClobFree(jdbcClob));
+ addDeallocation(freePublisher);
- // TODO: Why is discard() called here? It should be called by the
- // user who allocated the Clob, not by Oracle R2DBC.
- return r2dbcClob.discard();
+ return Mono.empty();
});
}
@@ -2065,9 +2046,10 @@ protected Publisher executeJdbc() {
Mono.just(createCallResult(
dependentCounter,
createOutParameters(
- fromJdbc(preparedStatement::getConnection),
+ r2dbcConnection,
dependentCounter,
- new JdbcOutParameters(), metadata, adapter),
+ new JdbcOutParameters(),
+ metadata),
adapter)));
}
@@ -2251,8 +2233,10 @@ protected Publisher executeJdbc() {
if (generatedKeys.isBeforeFirst()) {
return Mono.just(createGeneratedValuesResult(
- preparedStatement.getLargeUpdateCount(),
- dependentCounter, generatedKeys, adapter))
+ r2dbcConnection,
+ preparedStatement.getLargeUpdateCount(),
+ dependentCounter,
+ generatedKeys))
.concatWith(super.getResults(
preparedStatement.getMoreResults(KEEP_CURRENT_RESULT)));
}
diff --git a/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java b/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java
index 144d29d..5645586 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleLargeObjectsTest.java
@@ -30,18 +30,17 @@
import oracle.r2dbc.OracleR2dbcObject;
import oracle.r2dbc.OracleR2dbcTypes;
import oracle.r2dbc.test.DatabaseConfig;
-import oracle.r2dbc.test.TestUtils;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static java.util.Arrays.asList;
@@ -51,10 +50,13 @@
import static oracle.r2dbc.util.Awaits.awaitMany;
import static oracle.r2dbc.util.Awaits.awaitNone;
import static oracle.r2dbc.util.Awaits.awaitOne;
+import static oracle.r2dbc.util.Awaits.awaitQuery;
import static oracle.r2dbc.util.Awaits.awaitUpdate;
import static oracle.r2dbc.util.Awaits.tryAwaitExecution;
import static oracle.r2dbc.util.Awaits.tryAwaitNone;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Verifies the Oracle R2DBC Driver implements behavior related to {@link Blob}
@@ -504,4 +506,138 @@ public void testClobObject() {
}
}
+ /**
+ * Verifies behavior around discarding LOBs.
+ *
+ * A Blob/Clob bind should not be discarded until a user calls discard() and
+ * subscribes. Oracle R2DBC used to call discard() itself on Blob/Clob binds,
+ * which is not correct.
+ *
+ * When Connection.close() is called and subscribed to, it should not fail
+ * due to Oracle JDBC bug #37160069 which requires all LOBs to be freed before
+ * closeAsyncOracle is called.
+ */
+ @Test
+ public void testLobDiscard() {
+ byte[] data = getBytes(64 * 1024);
+
+ class TestBlob implements Blob {
+ final ByteBuffer blobData = ByteBuffer.wrap(data);
+
+ boolean isDiscarded = false;
+
+ @Override
+ public Publisher stream() {
+ return Mono.just(blobData);
+ }
+
+ @Override
+ public Publisher discard() {
+ return Mono.empty()
+ .doOnSuccess(nil -> isDiscarded = true);
+ }
+ }
+
+ class TestClob implements Clob {
+ final CharBuffer clobData =
+ CharBuffer.wrap(new String(data, StandardCharsets.US_ASCII));
+
+ boolean isDiscarded = false;
+
+ @Override
+ public Publisher stream() {
+ return Mono.just(clobData);
+ }
+
+ @Override
+ public Publisher discard() {
+ return Mono.empty()
+ .doOnSuccess(nil -> isDiscarded = true);
+ }
+ }
+
+ Connection connection =
+ Mono.from(DatabaseConfig.newConnection()).block(connectTimeout());
+ try {
+ awaitExecution(
+ connection.createStatement(
+ "CREATE TABLE testLobDiscard (blobValue BLOB, clobValue CLOB)"));
+
+ // Verify that LOBs are not discarded until discard() is subscribed to
+ TestBlob testBlob = new TestBlob();
+ TestClob testClob = new TestClob();
+ awaitUpdate(1, connection.createStatement(
+ "INSERT INTO testLobDiscard VALUES (?, ?)")
+ .bind(0, testBlob)
+ .bind(1, testClob));
+ assertFalse(testBlob.isDiscarded);
+ assertFalse(testClob.isDiscarded);
+ awaitNone(testBlob.discard());
+ awaitNone(testClob.discard());
+ assertTrue(testBlob.isDiscarded);
+ assertTrue(testClob.isDiscarded);
+
+ // Query temporary LOBs, and don't discard them
+ Object[] blobAndClob =
+ awaitOne(Flux.from(connection.createStatement(
+ "SELECT TO_BLOB(HEXTORAW('ABCDEF')), TO_CLOB('ABCDEF') FROM sys.dual")
+ .execute())
+ .flatMap(result ->
+ result.map(row ->
+ new Object[]{
+ row.get(0, Blob.class),
+ row.get(1, Clob.class)})));
+
+ awaitExecution(connection.createStatement(
+ "DROP TABLE testLobDiscard"));
+
+ // Close the connection. It should not fail due to Oracle JDBC bug
+ // #37160069.
+ awaitNone(connection.close());
+ }
+ catch (Exception exception) {
+ try {
+ tryAwaitExecution(connection.createStatement(
+ "DROP TABLE testLobDiscard"));
+ tryAwaitNone(connection.close());
+ }
+ catch (Exception closeException) {
+ exception.addSuppressed(closeException);
+ }
+ throw exception;
+ }
+ }
+
+ /**
+ * Verifies inserts and selects on NULL valued BLOBs and CLOBs
+ */
+ @Test
+ public void testNullLob() {
+ Connection connection = awaitOne(sharedConnection());
+ try {
+ awaitExecution(connection.createStatement(
+ "CREATE TABLE testNullLob(blobValue BLOB, clobValue CLOB)"));
+
+ awaitUpdate(1, connection.createStatement(
+ "INSERT INTO testNullLob VALUES (?, ?)")
+ .bindNull(0, Blob.class)
+ .bindNull(1, Clob.class));
+
+ awaitQuery(
+ List.of(
+ Arrays.asList(null, null)),
+ row -> Arrays.asList(
+ row.get(0, Blob.class),
+ row.get(1, Clob.class)),
+ connection.createStatement(
+ "SELECT blobValue, clobValue FROM testNullLob"));
+
+ }
+ finally {
+ tryAwaitExecution(connection.createStatement(
+ "DROP TABLE testNullLob"));
+ tryAwaitNone(connection.close());
+ }
+ }
+
}
From ad2d0233830d6341b26b14e965fd5c63a010c581 Mon Sep 17 00:00:00 2001
From: Michael McMahon
Date: Fri, 8 Nov 2024 13:37:27 -0800
Subject: [PATCH 3/3] DB 19 test compatiblity and README updates
---
README.md | 31 +++++++++++--------
...acleConnectionFactoryProviderImplTest.java | 7 ++---
.../r2dbc/impl/OracleResultImplTest.java | 28 ++++++++---------
.../r2dbc/impl/OracleStatementImplTest.java | 15 +++++++++
.../oracle/r2dbc/test/DatabaseConfig.java | 18 +++++++++--
.../java/oracle/r2dbc/test/OracleTestKit.java | 7 ++---
.../r2dbc/util/SharedConnectionFactory.java | 2 +-
7 files changed, 68 insertions(+), 40 deletions(-)
diff --git a/README.md b/README.md
index fb26618..b1407a4 100644
--- a/README.md
+++ b/README.md
@@ -25,12 +25,12 @@ Project Reactor, RxJava, and Akka Streams.
[Reactive Streams Specification v1.0.3](https://github.com/reactive-streams/reactive-streams-jvm/blob/v1.0.3/README.md)
# About This Version
-The 1.2.0 release Oracle R2DBC implements version 1.0.0.RELEASE of the R2DBC SPI.
+The 1.3.0 release Oracle R2DBC implements version 1.0.0.RELEASE of the R2DBC SPI.
New features in this release:
-- [Pipelined Operations](https://github.com/oracle/oracle-r2dbc/pull/145)
-- [Vector Data Type](https://github.com/oracle/oracle-r2dbc/pull/146)
-- [Options for Fetch Size and Proxy Authentication](https://github.com/oracle/oracle-r2dbc/pull/155)
+- [Pipelining](#pipelining)
+- [Vector Data Type](#vector)
+- [Fetch Size and Proxy Authentication Options](https://github.com/oracle/oracle-r2dbc/pull/155)
Updated dependencies:
- Updated Oracle JDBC from 21.11.0.0 to 23.6.0.24.10
@@ -369,23 +369,28 @@ If this option is not configured, then the common
A subset of Oracle JDBC's connection properties are defined as `Option`
constants in the
[OracleR2dbcOptions](src/main/java/oracle/r2dbc/OracleR2dbcOptions.java) class.
-These connection properties may be configured as options having the same
-name as the Oracle JDBC connection property, and may have `CharSequence` value
-types.
-
-For example, the following URL configures the `oracle.net.wallet_location`
-connection property:
+If an Oracle JDBC property is not defined as an `Option`, in most cases it can
+instead be configured by a
+[connection properties file](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_CONFIG_FILE)
+or a JVM system property instead.
+[Pull requests to add missing options](https://github.com/oracle/oracle-r2dbc/pull/124)
+are also a welcome addition.
+
+When a connection property is defined in `OracleR2dbcOptions`, it may be
+configured as an R2DBC URL parameter. For example, the following URL configures
+the `oracle.net.wallet_location` connection property:
```
r2dbcs:oracle://db.host.example.com:1522/db.service.name?oracle.net.wallet_location=/path/to/wallet/
```
-The same property can also be configured programmatically:
+And, the `OracleR2dbcOptions` constants can be used in programmatic
+configuration:
```java
ConnectionFactoryOptions.builder()
.option(OracleR2dbcOptions.TLS_WALLET_LOCATION, "/path/to/wallet")
```
-The next sections list Oracle JDBC connection properties which are supported by
-Oracle R2DBC.
+All Oracle JDBC connection properties defined in `OracleR2dbcOptions` are listed
+in the next sections.
##### TLS/SSL Connection Properties
- [oracle.net.tns_admin](https://docs.oracle.com/en/database/oracle/oracle-database/23/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_TNS_ADMIN)
diff --git a/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryProviderImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryProviderImplTest.java
index 01273fc..406d8f9 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryProviderImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleConnectionFactoryProviderImplTest.java
@@ -33,7 +33,6 @@
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
-import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;
import static io.r2dbc.spi.ConnectionFactoryOptions.DATABASE;
@@ -56,7 +55,7 @@
/**
* Verifies that
* {@link OracleConnectionFactoryProviderImpl} implements behavior that
- * is specified in it's class and method level javadocs.
+ * is specified in its class and method level javadocs.
*/
public class OracleConnectionFactoryProviderImplTest {
@@ -216,7 +215,7 @@ public void testSupplierOptionNull() {
Supplier portSupplier = DatabaseConfig::port;
Supplier databaseSupplier = DatabaseConfig::serviceName;
Supplier userSupplier = DatabaseConfig::user;
- TestSupplier passwordSupplier = new TestSupplier(password());
+ TestSupplier passwordSupplier = new TestSupplier<>(password());
ConnectionFactoryOptions connectionFactoryOptions =
connectionFactoryOptions()
@@ -326,7 +325,7 @@ public void testPublisherOptionNull() {
Publisher portPublisher = Mono.fromSupplier(DatabaseConfig::port);
Publisher databasePublisher = Mono.fromSupplier(DatabaseConfig::serviceName);
Publisher userPublisher = Mono.fromSupplier(DatabaseConfig::user);
- TestSupplier passwordPublisher = new TestSupplier(password());
+ TestSupplier passwordPublisher = new TestSupplier<>(password());
ConnectionFactoryOptions connectionFactoryOptions =
connectionFactoryOptions()
diff --git a/src/test/java/oracle/r2dbc/impl/OracleResultImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleResultImplTest.java
index 66bd37f..735b3b8 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleResultImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleResultImplTest.java
@@ -640,10 +640,10 @@ public void testOracleR2dbcWarning() {
Connection connection = awaitOne(sharedConnection());
try {
- // Expect a warning for forcing a view that references a non-existent
+ // Expect a warning for invalid PL/SQL
// table
- String sql = "CREATE OR REPLACE FORCE VIEW testOracleR2dbcWarning AS" +
- " SELECT x FROM thisdoesnotexist";
+ String sql = "CREATE OR REPLACE PROCEDURE testOracleR2dbcWarning AS" +
+ " BEGIN this is not valid pl/sql; END;";
Statement warningStatement = connection.createStatement(sql);
// Collect the segments
@@ -684,7 +684,7 @@ public void testOracleR2dbcWarning() {
}
finally {
tryAwaitExecution(
- connection.createStatement("DROP VIEW testOracleR2dbcWarning"));
+ connection.createStatement("DROP PROCEDURE testOracleR2dbcWarning"));
tryAwaitNone(connection.close());
}
}
@@ -697,11 +697,10 @@ public void testOracleR2dbcWarningIgnored() {
Connection connection = awaitOne(sharedConnection());
try {
- // Expect a warning for forcing a view that references a non-existent
- // table
+ // Expect a warning for invalid PL/SQL
String sql =
- "CREATE OR REPLACE FORCE VIEW testOracleR2dbcWarningIgnored AS" +
- " SELECT x FROM thisdoesnotexist";
+ "CREATE OR REPLACE PROCEDURE testOracleR2dbcWarningIgnored AS" +
+ " BEGIN this is not valid pl/sql; END;";
Statement warningStatement = connection.createStatement(sql);
// Verify that an update count of 0 is returned.
@@ -719,7 +718,8 @@ public void testOracleR2dbcWarningIgnored() {
}
finally {
tryAwaitExecution(
- connection.createStatement("DROP VIEW testOracleR2dbcWarningIgnored"));
+ connection.createStatement(
+ "DROP PROCEDURE testOracleR2dbcWarningIgnored"));
tryAwaitNone(connection.close());
}
}
@@ -732,11 +732,10 @@ public void testOracleR2dbcWarningIgnored() {
public void testOracleR2dbcWarningNotIgnored() {
Connection connection = awaitOne(sharedConnection());
try {
- // Expect a warning for forcing a view that references a non-existent
- // table
+ // Expect a warning for invalid PL/SQL
String sql =
- "CREATE OR REPLACE FORCE VIEW testOracleR2dbcWarningIgnored AS" +
- " SELECT x FROM thisdoesnotexist";
+ "CREATE OR REPLACE PROCEDURE testOracleR2dbcWarningIgnored AS" +
+ " BEGIN this is not valid pl/sql; END;";
Statement warningStatement = connection.createStatement(sql);
AtomicInteger segmentIndex = new AtomicInteger(0);
awaitError(
@@ -754,7 +753,8 @@ public void testOracleR2dbcWarningNotIgnored() {
}
finally {
tryAwaitExecution(
- connection.createStatement("DROP VIEW testOracleR2dbcWarningIgnored"));
+ connection.createStatement(
+ "DROP PROCEDURE testOracleR2dbcWarningIgnored"));
tryAwaitNone(connection.close());
}
}
diff --git a/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java b/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java
index d0a2f1b..e88cf04 100644
--- a/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java
+++ b/src/test/java/oracle/r2dbc/impl/OracleStatementImplTest.java
@@ -2476,6 +2476,11 @@ public boolean equals(Object other) {
public String toString() {
return id + ", " + Arrays.toString(value);
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, Arrays.hashCode(value));
+ }
}
TestRow row0 = new TestRow(0L, new int[]{1, 2, 3});
@@ -2684,6 +2689,11 @@ public boolean equals(Object other) {
public String toString() {
return id + ", " + Arrays.toString(value);
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, Arrays.hashCode(value));
+ }
}
OracleR2dbcTypes.ArrayType arrayType =
@@ -3199,6 +3209,11 @@ public boolean equals(Object other) {
&& ((IdVector)other).id == id
&& Objects.equals(((IdVector)other).vector, vector);
}
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(id, vector);
+ }
}
// Round 1: Use PL/SQL to return column values
diff --git a/src/test/java/oracle/r2dbc/test/DatabaseConfig.java b/src/test/java/oracle/r2dbc/test/DatabaseConfig.java
index aa67679..56ae2cb 100644
--- a/src/test/java/oracle/r2dbc/test/DatabaseConfig.java
+++ b/src/test/java/oracle/r2dbc/test/DatabaseConfig.java
@@ -35,6 +35,7 @@
import java.sql.DriverManager;
import java.sql.SQLException;
import java.time.Duration;
+import java.util.Optional;
import java.util.Properties;
/**
@@ -178,9 +179,9 @@ public static Publisher extends Connection> sharedConnection() {
* @return The major version number of the test database.
*/
public static int databaseVersion() {
- try (var jdbcConnection = DriverManager.getConnection(String.format(
- "jdbc:oracle:thin:@%s:%s/%s", host(), port(), serviceName()),
- user(), password())) {
+ try (
+ var jdbcConnection =
+ DriverManager.getConnection(jdbcUrl(), user(), password())) {
return jdbcConnection.getMetaData().getDatabaseMajorVersion();
}
catch (SQLException sqlException) {
@@ -188,6 +189,17 @@ public static int databaseVersion() {
}
}
+ /**
+ * Returns an Oracle JDBC URL for opening connections to the test database.
+ * @return URL for the Oracle JDBC Driver. Not null.
+ */
+ public static String jdbcUrl() {
+ return String.format(
+ "jdbc:oracle:thin:@%s%s:%d/%s",
+ protocol() == null ? "" : protocol() + ":",
+ host(), port(), serviceName());
+ }
+
/**
* Returns the major version number of the Oracle JDBC Driver installed as
* a service provider for java.sql.Driver.
diff --git a/src/test/java/oracle/r2dbc/test/OracleTestKit.java b/src/test/java/oracle/r2dbc/test/OracleTestKit.java
index 90cabca..fe58473 100755
--- a/src/test/java/oracle/r2dbc/test/OracleTestKit.java
+++ b/src/test/java/oracle/r2dbc/test/OracleTestKit.java
@@ -57,6 +57,7 @@
import static io.r2dbc.spi.ConnectionFactoryOptions.USER;
import static oracle.r2dbc.test.DatabaseConfig.connectionFactoryOptions;
import static oracle.r2dbc.test.DatabaseConfig.host;
+import static oracle.r2dbc.test.DatabaseConfig.jdbcUrl;
import static oracle.r2dbc.test.DatabaseConfig.password;
import static oracle.r2dbc.test.DatabaseConfig.port;
import static oracle.r2dbc.test.DatabaseConfig.protocol;
@@ -95,11 +96,7 @@ public class OracleTestKit implements TestKit {
try {
OracleDataSource dataSource =
new oracle.jdbc.datasource.impl.OracleDataSource();
- dataSource.setURL(String.format("jdbc:oracle:thin:@%s%s:%d/%s",
- Optional.ofNullable(protocol())
- .map(protocol -> protocol + ":")
- .orElse(""),
- host(), port(), serviceName()));
+ dataSource.setURL(jdbcUrl());
dataSource.setUser(user());
dataSource.setPassword(password());
this.jdbcOperations = new JdbcTemplate(dataSource);
diff --git a/src/test/java/oracle/r2dbc/util/SharedConnectionFactory.java b/src/test/java/oracle/r2dbc/util/SharedConnectionFactory.java
index 41fdd96..1fd8ddc 100644
--- a/src/test/java/oracle/r2dbc/util/SharedConnectionFactory.java
+++ b/src/test/java/oracle/r2dbc/util/SharedConnectionFactory.java
@@ -245,7 +245,7 @@ private static RuntimeException openCursorNotAccessible() {
return new RuntimeException(
"V$OPEN_CUROSR is not accessible to the test user. " +
"Grant access as SYSDBA with: " +
- "\"GRANT SELECT ON v_$open_cursor TO "+user()+"\", " +
+ "\"GRANT SELECT ON v$open_cursor TO "+user()+"\", " +
"or disable open cursor checks with: " +
" -Doracle.r2bdc.disableCursorCloseVerification=true");
}