|
24 | 24 | import io.r2dbc.spi.Blob;
|
25 | 25 | import io.r2dbc.spi.Clob;
|
26 | 26 | import io.r2dbc.spi.Connection;
|
| 27 | +import io.r2dbc.spi.ConnectionFactories; |
27 | 28 | import io.r2dbc.spi.Parameters;
|
28 | 29 | import io.r2dbc.spi.Row;
|
29 | 30 | import io.r2dbc.spi.Statement;
|
30 | 31 | import oracle.r2dbc.OracleR2dbcObject;
|
31 | 32 | import oracle.r2dbc.OracleR2dbcTypes;
|
32 | 33 | import oracle.r2dbc.test.DatabaseConfig;
|
| 34 | +import org.junit.jupiter.api.Assumptions; |
33 | 35 | import org.junit.jupiter.api.Test;
|
34 | 36 | import org.reactivestreams.Publisher;
|
35 | 37 | import reactor.core.publisher.Flux;
|
36 | 38 | import reactor.core.publisher.Mono;
|
37 | 39 |
|
| 40 | +import java.net.InetSocketAddress; |
38 | 41 | import java.nio.ByteBuffer;
|
39 | 42 | import java.nio.CharBuffer;
|
| 43 | +import java.nio.channels.ServerSocketChannel; |
| 44 | +import java.nio.channels.SocketChannel; |
40 | 45 | import java.nio.charset.StandardCharsets;
|
41 | 46 | import java.util.Arrays;
|
42 | 47 | import java.util.List;
|
43 | 48 | import java.util.Map;
|
44 | 49 | import java.util.concurrent.atomic.AtomicInteger;
|
45 | 50 |
|
| 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; |
46 | 54 | import static java.util.Arrays.asList;
|
47 | 55 | import static oracle.r2dbc.test.DatabaseConfig.connectTimeout;
|
48 | 56 | import static oracle.r2dbc.test.DatabaseConfig.sharedConnection;
|
@@ -540,7 +548,7 @@ public Publisher<Void> discard() {
|
540 | 548 |
|
541 | 549 | class TestClob implements Clob {
|
542 | 550 | final CharBuffer clobData =
|
543 |
| - CharBuffer.wrap(new String(data, StandardCharsets.US_ASCII)); |
| 551 | + CharBuffer.wrap(new String(data, US_ASCII)); |
544 | 552 |
|
545 | 553 | boolean isDiscarded = false;
|
546 | 554 |
|
@@ -640,4 +648,129 @@ public void testNullLob() {
|
640 | 648 | }
|
641 | 649 | }
|
642 | 650 |
|
| 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 | + |
643 | 776 | }
|
0 commit comments