Skip to content

Commit 42bf658

Browse files
Merge pull request #36 from oracle/32_lob_prefetch
32 lob prefetch
2 parents 1a6429d + 9433fe8 commit 42bf658

File tree

10 files changed

+139
-375
lines changed

10 files changed

+139
-375
lines changed

README.md

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ Options. For Options having any of the following names, a CharSequence value may
173173
- [oracle.net.ssl_context_protocol](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_SSL_CONTEXT_PROTOCOL)
174174
- [oracle.jdbc.fanEnabled](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_FAN_ENABLED)
175175
- [oracle.jdbc.implicitStatementCacheSize](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE)
176+
- [oracle.jdbc.defaultLobPrefetchSize](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE)
176177
- Oracle Net Descriptors of the form ```(DESCRIPTION=...)``` may be specified as an io.r2dbc.spi.Option having the name `oracleNetDescriptor`.
177178
- If `oracleNetDescriptor` is specified, then it is invalid to specify any other options that might conflict with information in the descriptor, such as: `HOST`, `PORT`, `DATABASE`, and `SSL`.
178179
- The `oracleNetDescriptor` option may appear in the query section of an R2DBC URL: `r2dbc:oracle://?oracleNetDescriptor=(DESCRIPTION=...)`
@@ -269,15 +270,6 @@ values for a non-empty set of column names.
269270
```Result``` for each returned cursor.
270271

271272
### Type Mappings
272-
- Blob and Clob objects are the default mapping implemented by Row.get(...) for
273-
BLOB and CLOB columns. ByteBuffer and String mappings are not supported for BLOB
274-
and CLOB.
275-
- Oracle Database allows BLOBs and CLOBs to store terabytes of data; This
276-
amount would exceed the capacity of a ByteBuffer or String.
277-
- Blob and Clob objects stream data over a series of ByteBuffers or Strings.
278-
- Requiring content to be streamed over multiple buffers is necessary for Oracle
279-
R2DBC to avoid a potentially memory exhausting implementation in which BLOBs and
280-
CLOBs must be fully materialized as a return value for Row.get(...).
281273
- javax.json.JsonObject and oracle.sql.json.OracleJsonObject are supported as
282274
Java type mappings for JSON column values.
283275
- java.time.Duration is supported as a Java type mapping for INTERVAL DAY TO SECOND
@@ -288,6 +280,27 @@ column values.
288280
The Oracle Database type named "DATE" stores the same information as a LocalDateTime:
289281
year, month, day, hour, minute, and second.
290282

283+
### BLOB, CLOB, and NCLOB
284+
When a SQL query returns a LOB value, a
285+
portion of that value is prefetched from the database and the remaining portion
286+
must be fetched with additional database calls. The number of prefetched
287+
bytes is configured by an ```Option``` named [oracle.jdbc.defaultLobPrefetchSize](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html?is-external=true#CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE)
288+
. The default value of this ```Option``` is 1 GB.
289+
290+
The ```Row.get(...)``` method allows LOB values to be mapped into materialized
291+
types like ```ByteBuffer``` and ```String```. If the prefetch size is large
292+
enough to have fetched the entire LOB value, then ```Row.get(...)``` can
293+
return a ```ByteBuffer/String``` without any additional database calls.
294+
Otherwise, if the LOB value is larger than the prefetch size, then
295+
```Row.get(...)``` must execute a **blocking database call** to fetch the
296+
remainder of that value.
297+
298+
For systems in which LOB values are too large to be prefetched, a smaller
299+
prefetch size can be configured, and LOB values may be mapped into ```Blob```
300+
or ```Clob``` objects rather than ```ByteBuffer``` or ```String```. ```Blob```
301+
and ```Clob``` objects allow the LOB value to be streamed using non-blocking
302+
database calls.
303+
291304
# Secure Programming Guidelines
292305
The following security guidelines should be followed when programming with the Oracle R2DBC Driver.
293306
### Defend Against SQL Injection Attacks

src/main/java/oracle/r2dbc/OracleR2dbcTypes.java

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,6 @@ private OracleR2dbcTypes() {}
4949
public static final Type BINARY_FLOAT =
5050
new TypeImpl(Float.class, "BINARY_FLOAT");
5151

52-
/**
53-
* A Binary Large Object (BLOB) as implemented by Oracle Database. The default
54-
* Java type mapping is {@link io.r2dbc.spi.Blob} rather than
55-
* {@link java.nio.ByteBuffer}, which is the mapping of the standard
56-
* {@link io.r2dbc.spi.R2dbcType#BLOB}.
57-
*/
58-
public static final Type BLOB =
59-
new TypeImpl(io.r2dbc.spi.Blob.class, "BLOB");
60-
61-
/**
62-
* A Character Large Object (BLOB) as implemented by Oracle Database. The
63-
* default Java type mapping is {@link io.r2dbc.spi.Clob} rather than
64-
* {@link String}, which is the mapping of the standard
65-
* {@link io.r2dbc.spi.R2dbcType#CLOB}.
66-
*/
67-
public static final Type CLOB =
68-
new TypeImpl(io.r2dbc.spi.Clob.class, "CLOB");
69-
7052
/**
7153
* Stores a period of time in days, hours, minutes, and seconds.
7254
*/
@@ -94,15 +76,6 @@ private OracleR2dbcTypes() {}
9476
public static final Type LONG_RAW =
9577
new TypeImpl(ByteBuffer.class, "LONG RAW");
9678

97-
/**
98-
* A National Character Large Object (NCLOB) as implemented by Oracle
99-
* Database. The default Java type mapping is {@link io.r2dbc.spi.Clob}
100-
* rather than {@link String}, which is the mapping of the standard
101-
* {@link io.r2dbc.spi.R2dbcType#NCLOB}.
102-
*/
103-
public static final Type NCLOB =
104-
new TypeImpl(io.r2dbc.spi.Clob.class, "NCLOB");
105-
10679
/**
10780
* Base 64 string representing the unique address of a row in its table.
10881
*/

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

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,14 @@ final class OracleReactiveJdbcAdapter implements ReactiveJdbcAdapter {
192192

193193
// Support statement cache configuration
194194
Option.valueOf(
195-
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE)
195+
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE),
196+
197+
// Support LOB prefetch size configuration. A large size is configured
198+
// by default to support cases where memory is available to store entire
199+
// LOB values. A non-default size may be configured when LOB values are
200+
// too large to be prefetched and must be streamed from Blob/Clob objects.
201+
Option.valueOf(
202+
OracleConnection.CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE)
196203

197204
);
198205

@@ -599,30 +606,52 @@ private static void configureJdbcDefaults(OracleDataSource oracleDataSource) {
599606
runOrHandleSQLException(() ->
600607
oracleDataSource.setConnectionProperty(enableJdbcSpecCompliance, "true"));
601608

602-
// Have the Oracle JDBC Driver cache PreparedStatements by default.
603-
runOrHandleSQLException(() -> {
604-
// Don't override a value set by user code
605-
String userValue = oracleDataSource.getConnectionProperty(
606-
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE);
607-
608-
if (userValue == null) {
609-
// The default value of the OPEN_CURSORS parameter in the 21c
610-
// and 19c databases is 50:
611-
// https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/OPEN_CURSORS.html#GUID-FAFD1247-06E5-4E64-917F-AEBD4703CF40
612-
// Assuming this default, then a default cache size of 25 will keep
613-
// each session at or below 50% of it's cursor capacity, which seems
614-
// reasonable.
615-
oracleDataSource.setConnectionProperty(
616-
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE,
617-
"25");
618-
}
619-
});
609+
// Cache PreparedStatements by default. The default value of the
610+
// OPEN_CURSORS parameter in the 21c and 19c databases is 50:
611+
// https://docs.oracle.com/en/database/oracle/oracle-database/21/refrn/OPEN_CURSORS.html#GUID-FAFD1247-06E5-4E64-917F-AEBD4703CF40
612+
// Assuming this default, then a default cache size of 25 will keep
613+
// each session at or below 50% of it's cursor capacity, which seems
614+
// reasonable.
615+
setPropertyIfAbsent(oracleDataSource,
616+
OracleConnection.CONNECTION_PROPERTY_IMPLICIT_STATEMENT_CACHE_SIZE, "25");
617+
618+
// Prefetch LOB values by default. The database's maximum supported
619+
// prefetch size, 1GB, is configured by default. This is done so that
620+
// Row.get(...) can map LOB values into ByteBuffer/String without a
621+
// blocking database call. If the entire value is prefetched, then JDBC
622+
// won't need to fetch the remainder from the database when the entire is
623+
// value requested as a ByteBuffer or String.
624+
setPropertyIfAbsent(oracleDataSource,
625+
OracleConnection.CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE,
626+
"1048576");
620627

621628
// TODO: Disable the result set cache? This is needed to support the
622629
// SERIALIZABLE isolation level, which requires result set caching to be
623630
// disabled.
624631
}
625632

633+
/**
634+
* Sets a JDBC connection {@code property} to a provided {@code value} if an
635+
* {@code oracleDataSource} has not already been configured with a
636+
* {@code value} for that {@code property}. This method is used to set
637+
* default values for properties that may otherwise be configured with user
638+
* defined values.
639+
* @param oracleDataSource DataSource to configure. Not null.
640+
* @param property Name of property to set. Not null.
641+
* @param value Value of {@code property} to set. Not null.
642+
*/
643+
private static void setPropertyIfAbsent(
644+
OracleDataSource oracleDataSource, String property, String value) {
645+
646+
runOrHandleSQLException(() -> {
647+
String userValue = oracleDataSource.getConnectionProperty(property);
648+
649+
// Don't override a value set by user code
650+
if (userValue == null)
651+
oracleDataSource.setConnectionProperty(property, value);
652+
});
653+
}
654+
626655
/**
627656
* {@inheritDoc}
628657
* <p>

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

Lines changed: 0 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import java.sql.Timestamp;
3434
import java.sql.Types;
3535
import java.time.LocalDateTime;
36-
import java.time.OffsetDateTime;
3736

3837
import static oracle.r2dbc.impl.OracleR2dbcExceptions.requireNonNull;
3938

@@ -85,20 +84,8 @@ final class OracleRowImpl implements Row {
8584
* Implements the R2DBC SPI method by using the {@code JdbcRow} that backs
8685
* this row to convert the specified column value into the Oracle R2DBC
8786
* Driver's default Java type mapping for the column's SQL type.
88-
* </p><p>
89-
* This implementation does not support mapping the {@code BLOB} SQL type to
90-
* {@code ByteBuffer}, nor does it support mapping the {@code CLOB} SQL
91-
* type to {@code String}. This implementation will map {@code BLOB} to
92-
* {@link Blob} and map {@code CLOB} to {@link Clob}.
9387
* </p>
9488
*
95-
* @implNote Mapping {@code BLOB/CLOB} to {@code ByteBuffer/String} is not
96-
* supported because Oracle Database allows LOBs to store terabytes of
97-
* data. If the Oracle R2DBC Driver were to fully materialize a LOB
98-
* prior to emitting this row, the amount of memory necessary to do so
99-
* might exceed the capacity of {@code ByteBuffer/String}, and could even
100-
* exceed the amount of memory available to the Java Virtual Machine.
101-
*
10289
* @throws IllegalArgumentException If the {@code index} is less than 0,
10390
* or greater than the maximum column index.
10491
*/
@@ -115,20 +102,7 @@ public Object get(int index) {
115102
* Implements the R2DBC SPI method by using the JDBC ResultSet that backs
116103
* this row to convert the specified column value into the specified {@code
117104
* type}.
118-
* </p><p>
119-
* This implementation does not support mapping the {@code BLOB} SQL type to
120-
* {@code ByteBuffer}, nor does it support mapping the {@code CLOB} SQL
121-
* type to {@code String}. This implementation only supports mapping
122-
* {@code BLOB} to {@link Blob} and {@code CLOB} to {@link Clob}.
123105
* </p>
124-
*
125-
* @implNote Mapping {@code BLOB/CLOB} to {@code ByteBuffer/String} is not
126-
* supported because Oracle Database allows LOBs to store terabytes of data.
127-
* If the Oracle R2DBC Driver were to fully materialize a LOB
128-
* prior to emitting this row, the amount of memory necessary to do so
129-
* might exceed the capacity of {@code ByteBuffer/String}, and could even
130-
* exceed the amount of memory available to the Java Virtual Machine.
131-
*
132106
* @throws IllegalArgumentException {@inheritDoc}
133107
* @throws IllegalArgumentException If the {@code index} is less than 0,
134108
* or greater than the maximum column index.
@@ -152,20 +126,7 @@ public <T> T get(int index, Class<T> type) {
152126
* This method uses a case-insensitive column name match. If more than one
153127
* column has a matching name, this method returns the value of the
154128
* matching column with the lowest index.
155-
* </p><p>
156-
* This implementation does not support mapping the {@code BLOB} SQL type to
157-
* {@code ByteBuffer}, nor does it support mapping the {@code CLOB} SQL
158-
* type to {@code String}. This implementation will map {@code BLOB} to
159-
* {@link Blob} and map {@code CLOB} to {@link Clob}.
160129
* </p>
161-
*
162-
* @implNote Mapping {@code BLOB/CLOB} to {@code ByteBuffer/String} is not
163-
* supported because Oracle Database allows LOBs to store terabytes of data.
164-
* If the Oracle R2DBC Driver were to fully materialize a LOB
165-
* prior to emitting this row, the amount of memory necessary to do so
166-
* might exceed the capacity of {@code ByteBuffer/String}, and could even
167-
* exceed the amount of memory available to the Java Virtual Machine.
168-
*
169130
* @throws IllegalArgumentException {@inheritDoc}
170131
* @throws IllegalArgumentException If there is no column with a matching
171132
* {@code name}.
@@ -188,20 +149,7 @@ public Object get(String name) {
188149
* This method uses a case-insensitive column name match. If more than one
189150
* column has a matching name, this method returns the value of the
190151
* matching column with the lowest index.
191-
* </p><p>
192-
* This implementation does not support mapping the {@code BLOB} SQL type to
193-
* {@code ByteBuffer}, nor does it support mapping the {@code CLOB} SQL
194-
* type to {@code String}. This implementation only supports mapping
195-
* {@code BLOB} to {@link Blob} and {@code CLOB} to {@link Clob}.
196152
* </p>
197-
*
198-
* @implNote Mapping {@code BLOB/CLOB} to {@code ByteBuffer/String} is not
199-
* supported because Oracle Database allows LOBs to store terabytes of data.
200-
* If the Oracle R2DBC Driver were to fully materialize a LOB
201-
* prior to emitting this row, the amount of memory necessary to do so
202-
* might exceed the capacity of {@code ByteBuffer/String}, and could even
203-
* exceed the amount of memory available to the Java Virtual Machine.
204-
*
205153
* @throws IllegalArgumentException {@inheritDoc}
206154
* @throws IllegalArgumentException If conversion to the specified
207155
* {@code type} is not supported.
@@ -252,9 +200,6 @@ private int getColumnIndex(String name) {
252200
* @throws R2dbcException If the conversion is not supported.
253201
*/
254202
private <T> T convertColumnValue(int index, Class<T> type) {
255-
requireSupportedTypeMapping(index, type);
256-
257-
//TODO Should support type.isAssignableFrom(...) here?
258203
if (type.equals(ByteBuffer.class))
259204
return type.cast(getByteBuffer(index));
260205
else if (type.equals(io.r2dbc.spi.Blob.class))
@@ -275,10 +220,6 @@ else if (type.equals(LocalDateTime.class))
275220
* A JDBC driver is not required to support {@code ByteBuffer} conversions
276221
* for any SQL type, so this method is necessary to implement the
277222
* conversion to {@code ByteBuffer} from a type that is supported by JDBC.
278-
* </p><p>
279-
* This method should NOT be called when the database column type is BLOB.
280-
* The JDBC driver may require blocking network I/O in order to materialize a
281-
* BLOB as a ByteBuffer.
282223
* </p>
283224
* @param index 0 based column index
284225
* @return A column value as a {@code ByteBuffer}, or null if the column
@@ -394,37 +335,6 @@ else if (index >= rowMetadata.getColumnNames().size()) {
394335
}
395336
}
396337

397-
/**
398-
* <p>
399-
* Checks if the Oracle R2DBC Driver supports mapping the database type
400-
* of the column at the specified {@code index} to the Java type specified
401-
* as {@code type}.
402-
* </p><p>
403-
* This method handles cases where the JDBC driver may support a mapping
404-
* that the Oracle R2DBC Driver does not support. For instance, the JDBC
405-
* driver may support mapping CLOB columns to String, but the Oracle R2DBC
406-
* Driver does not support this as the JDBC driver may require blocking
407-
* network I/O to convert a CLOB into a String.
408-
* </p>
409-
*
410-
* @param index 0-based column index
411-
* @param type Class of the type that the column value is converted to
412-
* @throws R2dbcException if the type mapping is not supported
413-
*/
414-
private void requireSupportedTypeMapping(int index, Class<?> type) {
415-
416-
switch (getColumnTypeNumber(index)) {
417-
case Types.BLOB:
418-
if (! type.equals(Blob.class))
419-
throw unsupportedTypeMapping("BLOB", index, type);
420-
break;
421-
case Types.CLOB:
422-
if (! type.equals(Clob.class))
423-
throw unsupportedTypeMapping("CLOB", index, type);
424-
break;
425-
}
426-
}
427-
428338
/**
429339
* Returns the SQL type number of the column at a given {@code index}. The
430340
* returned number identifies either a standard type defined by
@@ -442,22 +352,4 @@ private int getColumnTypeNumber(int index) {
442352
return typeNumber == null ? Types.OTHER : typeNumber;
443353
}
444354

445-
/**
446-
* Returns an exception indicating that the Oracle R2DBC Driver does
447-
* not support mapping the database type specified as {@code sqlTypeName}
448-
* to the Java type specified as {@code type}.
449-
* @param sqlTypeName Name of a SQL type, like "BLOB" or "NUMBER"
450-
* @param index Column index having a SQL type named {@code sqlTypeName}
451-
* @param type Java type to which mapping is not supported
452-
* @return An exception that expresses the unsupported mapping
453-
*/
454-
private static R2dbcException unsupportedTypeMapping(
455-
String sqlTypeName, int index, Class<?> type) {
456-
return OracleR2dbcExceptions.newNonTransientException(
457-
String.format("Unsupported SQL to Java type mapping. " +
458-
"SQL Type: %s, Column Index: %d, Java Type: %s",
459-
sqlTypeName, index, type.getName()),
460-
null);
461-
}
462-
463355
}

0 commit comments

Comments
 (0)