Skip to content

Commit 2db2fae

Browse files
Merge pull request #163 from oracle/160-lob-1gb
Set Default LOB Prefetch to 1GB
2 parents dbb4daa + ce7f5d9 commit 2db2fae

File tree

3 files changed

+140
-11
lines changed

3 files changed

+140
-11
lines changed

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

+5-9
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@
4646
import java.sql.Blob;
4747
import java.sql.Clob;
4848
import java.sql.Connection;
49-
import java.sql.Driver;
50-
import java.sql.DriverManager;
5149
import java.sql.PreparedStatement;
5250
import java.sql.ResultSet;
5351
import java.sql.SQLException;
@@ -650,15 +648,13 @@ private static void configureJdbcDefaults(OracleDataSource oracleDataSource) {
650648
setPropertyIfAbsent(oracleDataSource,
651649
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE, "25");
652650

653-
// Prefetch LOB values by default. The database's maximum supported
654-
// prefetch size, 1GB, is configured by default. This is done so that
655-
// Row.get(...) can map LOB values into ByteBuffer/String without a
656-
// blocking database call. If the entire value is prefetched, then JDBC
657-
// won't need to fetch the remainder from the database when the entire is
658-
// value requested as a ByteBuffer or String.
651+
// Prefetch LOB values by default. This allows Row.get(...) to map most LOB
652+
// values into ByteBuffer/String without a blocking database call to fetch
653+
// the remaining bytes. 1GB is configured by default, as this is close to
654+
// the maximum allowed by the Autonomous Database service.
659655
setPropertyIfAbsent(oracleDataSource,
660656
OracleConnection.CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE,
661-
"1048576");
657+
"1000000000");
662658

663659
// TODO: Disable the result set cache? This is needed to support the
664660
// SERIALIZABLE isolation level, which requires result set caching to be

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

+134-1
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,33 @@
2424
import io.r2dbc.spi.Blob;
2525
import io.r2dbc.spi.Clob;
2626
import io.r2dbc.spi.Connection;
27+
import io.r2dbc.spi.ConnectionFactories;
2728
import io.r2dbc.spi.Parameters;
2829
import io.r2dbc.spi.Row;
2930
import io.r2dbc.spi.Statement;
3031
import oracle.r2dbc.OracleR2dbcObject;
3132
import oracle.r2dbc.OracleR2dbcTypes;
3233
import oracle.r2dbc.test.DatabaseConfig;
34+
import org.junit.jupiter.api.Assumptions;
3335
import org.junit.jupiter.api.Test;
3436
import org.reactivestreams.Publisher;
3537
import reactor.core.publisher.Flux;
3638
import reactor.core.publisher.Mono;
3739

40+
import java.net.InetSocketAddress;
3841
import java.nio.ByteBuffer;
3942
import java.nio.CharBuffer;
43+
import java.nio.channels.ServerSocketChannel;
44+
import java.nio.channels.SocketChannel;
4045
import java.nio.charset.StandardCharsets;
4146
import java.util.Arrays;
4247
import java.util.List;
4348
import java.util.Map;
4449
import java.util.concurrent.atomic.AtomicInteger;
4550

51+
import static io.r2dbc.spi.ConnectionFactoryOptions.HOST;
52+
import static io.r2dbc.spi.ConnectionFactoryOptions.PORT;
53+
import static java.nio.charset.StandardCharsets.US_ASCII;
4654
import static java.util.Arrays.asList;
4755
import static oracle.r2dbc.test.DatabaseConfig.connectTimeout;
4856
import static oracle.r2dbc.test.DatabaseConfig.sharedConnection;
@@ -540,7 +548,7 @@ public Publisher<Void> discard() {
540548

541549
class TestClob implements Clob {
542550
final CharBuffer clobData =
543-
CharBuffer.wrap(new String(data, StandardCharsets.US_ASCII));
551+
CharBuffer.wrap(new String(data, US_ASCII));
544552

545553
boolean isDiscarded = false;
546554

@@ -640,4 +648,129 @@ public void testNullLob() {
640648
}
641649
}
642650

651+
/**
652+
* Verifies that the default LOB prefetch size is at least large enough to
653+
* fully prefetch 1MB of data.
654+
*/
655+
@Test
656+
public void testDefaultLobPrefetch() throws Exception {
657+
Assumptions.assumeTrue(
658+
null == DatabaseConfig.protocol(), "Test requires TCP protocol");
659+
660+
// A local server will monitor network I/O
661+
try (ServerSocketChannel localServer = ServerSocketChannel.open()) {
662+
localServer.configureBlocking(true);
663+
localServer.bind(null);
664+
665+
class TestThread extends Thread {
666+
667+
/** Count of bytes exchanged between JDBC and the database */
668+
int ioCount = 0;
669+
670+
@Override
671+
public void run() {
672+
InetSocketAddress databaseAddress =
673+
new InetSocketAddress(DatabaseConfig.host(), DatabaseConfig.port());
674+
675+
try (
676+
SocketChannel jdbcChannel = localServer.accept();
677+
SocketChannel databaseChannel =
678+
SocketChannel.open(databaseAddress)){
679+
680+
jdbcChannel.configureBlocking(false);
681+
databaseChannel.configureBlocking(false);
682+
683+
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(8192);
684+
while (true) {
685+
686+
byteBuffer.clear();
687+
if (-1 == jdbcChannel.read(byteBuffer))
688+
break;
689+
byteBuffer.flip();
690+
ioCount += byteBuffer.remaining();
691+
692+
while (byteBuffer.hasRemaining())
693+
databaseChannel.write(byteBuffer);
694+
695+
byteBuffer.clear();
696+
databaseChannel.read(byteBuffer);
697+
byteBuffer.flip();
698+
ioCount += byteBuffer.remaining();
699+
700+
while (byteBuffer.hasRemaining())
701+
jdbcChannel.write(byteBuffer);
702+
}
703+
}
704+
catch (Exception exception) {
705+
exception.printStackTrace();
706+
}
707+
}
708+
}
709+
710+
TestThread testThread = new TestThread();
711+
testThread.start();
712+
713+
714+
int lobSize = 99 + (1024 * 1024); // <-- 99 + 1MB
715+
Connection connection = awaitOne(ConnectionFactories.get(
716+
DatabaseConfig.connectionFactoryOptions()
717+
.mutate()
718+
.option(HOST, "localhost")
719+
.option(PORT,
720+
((InetSocketAddress)localServer.getLocalAddress()).getPort())
721+
.build())
722+
.create());
723+
try {
724+
awaitExecution(connection.createStatement(
725+
"CREATE TABLE testLobPrefetch ("
726+
+ " id NUMBER GENERATED ALWAYS AS IDENTITY,"
727+
+ " blobValue BLOB,"
728+
+ " clobValue CLOB,"
729+
+ " PRIMARY KEY(id))"));
730+
731+
// Insert two rows of LOBs larger than 1MB
732+
byte[] bytes = getBytes(lobSize);
733+
ByteBuffer blobValue = ByteBuffer.wrap(bytes);
734+
String clobValue = new String(bytes, US_ASCII);
735+
awaitUpdate(List.of(1,1), connection.createStatement(
736+
"INSERT INTO testLobPrefetch (blobValue, clobValue)"
737+
+ " VALUES (:blobValue, :clobValue)")
738+
.bind("blobValue", blobValue)
739+
.bind("clobValue", clobValue)
740+
.add()
741+
.bind("blobValue", blobValue)
742+
.bind("clobValue", clobValue));
743+
744+
// Query two rows of LOBs larger than 1MB
745+
awaitQuery(
746+
List.of(
747+
List.of(blobValue, clobValue),
748+
List.of(blobValue, clobValue)),
749+
row -> {
750+
try {
751+
// Expect no I/O to result from mapping a fully prefetched BLOB or
752+
// CLOB:
753+
int ioCount = testThread.ioCount;
754+
var result = List.of(row.get("blobValue"), row.get("clobValue"));
755+
assertEquals(ioCount, testThread.ioCount);
756+
return result;
757+
}
758+
catch (Exception exception) {
759+
throw new RuntimeException(exception);
760+
}
761+
},
762+
connection.createStatement(
763+
"SELECT blobValue, clobValue FROM testLobPrefetch ORDER BY id")
764+
.fetchSize(1));
765+
}
766+
finally {
767+
tryAwaitExecution(connection.createStatement(
768+
"DROP TABLE testLobPrefetch"));
769+
tryAwaitNone(connection.close());
770+
testThread.join(10_000);
771+
testThread.interrupt();
772+
}
773+
}
774+
}
775+
643776
}

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -784,7 +784,7 @@ private static Properties getJdbcDefaultProperties() throws SQLException {
784784
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE, "25");
785785
defaultProperties.setProperty(
786786
OracleConnection.CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE,
787-
"1048576");
787+
"1000000000");
788788
defaultProperties.setProperty(
789789
OracleConnection.CONNECTION_PROPERTY_THIN_NET_USE_ZERO_COPY_IO,
790790
"false");

0 commit comments

Comments
 (0)