2525import io .r2dbc .spi .Clob ;
2626import io .r2dbc .spi .R2dbcException ;
2727import io .r2dbc .spi .Row ;
28- import oracle .jdbc .OracleType ;
28+ import oracle .jdbc .OracleTypes ;
2929
30- import java .io .IOException ;
3130import java .nio .ByteBuffer ;
32- import java .sql .JDBCType ;
3331import java .sql .ResultSet ;
34- import java .sql .SQLType ;
32+ import java .sql .Timestamp ;
3533import java .sql .Types ;
36- import java .util . Objects ;
34+ import java .time . LocalDateTime ;
3735
3836import static oracle .r2dbc .impl .OracleR2dbcExceptions .requireNonNull ;
3937
4240 * Implementation of the {@link Row} SPI for Oracle Database.
4341 * </p><p>
4442 * Instances of this class supply the column values of a
45- * {@link ReactiveJdbcAdapter.JdbcRow} which represents one row of data
43+ * {@link ReactiveJdbcAdapter.JdbcRow} that represents one row of data
4644 * from a JDBC {@link ResultSet}.
4745 * </p>
4846 *
@@ -62,7 +60,7 @@ final class OracleRowImpl implements Row {
6260
6361 /**
6462 * <p>
65- * Constructs a new row which supplies column values from the specified
63+ * Constructs a new row that supplies column values from the specified
6664 * {@code jdbcRow}, and uses the specified {@code rowMetadata} to determine
6765 * the default type mapping of column values.
6866 * </p>
@@ -112,7 +110,7 @@ public Object get(int index) {
112110 /**
113111 * {@inheritDoc}
114112 * <p>
115- * Implements the R2DBC SPI method by using the JDBC ResultSet which backs
113+ * Implements the R2DBC SPI method by using the JDBC ResultSet that backs
116114 * this row to convert the specified column value into the specified {@code
117115 * type}.
118116 * </p><p>
@@ -145,7 +143,7 @@ public <T> T get(int index, Class<T> type) {
145143 /**
146144 * {@inheritDoc}
147145 * <p>
148- * Implements the R2DBC SPI method by using the JDBC ResultSet which backs
146+ * Implements the R2DBC SPI method by using the JDBC ResultSet that backs
149147 * this row to convert the specified column value into the Oracle R2DBC
150148 * Driver's default Java type mapping for the column's SQL type.
151149 * </p><p>
@@ -181,7 +179,7 @@ public Object get(String name) {
181179 /**
182180 * {@inheritDoc}
183181 * <p>
184- * Implements the R2DBC SPI method by using the JDBC ResultSet which backs
182+ * Implements the R2DBC SPI method by using the JDBC ResultSet that backs
185183 * this row to convert the specified column value into the specified {@code
186184 * type}.
187185 * </p><p>
@@ -234,7 +232,7 @@ private int getColumnIndex(String name) {
234232 * Converts the value of a column at a specified {@code index} to the
235233 * specified {@code type}. This method implements conversions to target
236234 * types that are not supported by JDBC drivers. The Oracle R2DBC Driver
237- * will implement some conversions which adapt JDBC supported types into
235+ * will implement some conversions that adapt JDBC supported types into
238236 * R2DBC supported types.
239237 *
240238 * @param index 0-based index of a column
@@ -252,6 +250,8 @@ else if (type.equals(io.r2dbc.spi.Blob.class))
252250 return type .cast (getBlob (index ));
253251 else if (type .equals (io .r2dbc .spi .Clob .class ))
254252 return type .cast (getClob (index ));
253+ else if (type .equals (LocalDateTime .class ))
254+ return type .cast (getLocalDateTime (index ));
255255 else
256256 return jdbcRow .getObject (index , type );
257257 }
@@ -263,7 +263,7 @@ else if (type.equals(io.r2dbc.spi.Clob.class))
263263 * </p><p>
264264 * A JDBC driver is not required to support {@code ByteBuffer} conversions
265265 * for any SQL type, so this method is necessary to implement the
266- * conversion to {@code ByteBuffer} from a type which is supported by JDBC.
266+ * conversion to {@code ByteBuffer} from a type that is supported by JDBC.
267267 * </p><p>
268268 * This method should NOT be called when the database column type is BLOB.
269269 * The JDBC driver may require blocking network I/O in order to materialize a
@@ -285,7 +285,7 @@ private ByteBuffer getByteBuffer(int index) {
285285 * </p><p>
286286 * A JDBC driver is not required to support {@code io.r2dbc.spi.Blob}
287287 * conversions for any SQL type, so this method is necessary to implement the
288- * conversion to {@code Blob} from a type which is supported by JDBC.
288+ * conversion to {@code Blob} from a type that is supported by JDBC.
289289 * </p>
290290 * @param index 0 based column index
291291 * @return A column value as a {@code Blob}, or null if the column
@@ -307,27 +307,27 @@ private Blob getBlob(int index) {
307307 * </p><p>
308308 * A JDBC driver is not required to support {@code io.r2dbc.spi.Clob}
309309 * conversions for any SQL type, so this method is necessary to implement the
310- * conversion to {@code Clob} from a type which is supported by JDBC.
310+ * conversion to {@code Clob} from a type that is supported by JDBC.
311311 * </p>
312312 * @param index 0 based column index
313313 * @return A column value as a {@code Clob}, or null if the column
314314 * value is NULL.
315315 */
316316 private Clob getClob (int index ) {
317- Integer columnTypeCode =
318- rowMetadata .getColumnMetadata (index )
319- .getNativeTypeMetadata ()
320- .getVendorTypeNumber ();
321317
322- boolean isNChar = columnTypeCode != null &&
323- (columnTypeCode == Types .NCLOB
324- || columnTypeCode == Types .NCHAR
325- || columnTypeCode == Types .NVARCHAR
326- || columnTypeCode == Types .LONGNVARCHAR );
327-
328- java .sql .Clob jdbcClob = isNChar
329- ? jdbcRow .getObject (index , java .sql .NClob .class )
330- : jdbcRow .getObject (index , java .sql .Clob .class );
318+ // Convert to a JDBC NClob or Clob, depending on the column type
319+ final java .sql .Clob jdbcClob ;
320+ switch (getColumnTypeNumber (index )) {
321+ case Types .NCLOB :
322+ case Types .LONGNVARCHAR :
323+ case Types .NVARCHAR :
324+ case Types .NCHAR :
325+ jdbcClob = jdbcRow .getObject (index , java .sql .NClob .class );
326+ break ;
327+ default :
328+ jdbcClob = jdbcRow .getObject (index , java .sql .Clob .class );
329+ break ;
330+ }
331331
332332 return jdbcClob == null
333333 ? null
@@ -336,6 +336,35 @@ private Clob getClob(int index) {
336336 adapter .publishClobFree (jdbcClob ));
337337 }
338338
339+ /**
340+ * <p>
341+ * Converts the value of a column at the specified {@code index} to a
342+ * {@code LocalDateTime}.
343+ * </p><p>
344+ * A JDBC driver is not required to support {@code LocalDateTime} conversions
345+ * for any SQL type. The Oracle JDBC driver is known to support {@code
346+ * LocalDateTime} conversions for DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE.
347+ * </p><p>
348+ * The 21.1 Oracle JDBC Driver does not implement a correct conversion for
349+ * TIMESTAMP WITH LOCAL TIME ZONE; The driver returns a value in the database
350+ * timezone rather than the session time zone. A correct conversion is
351+ * implemented for {@code java.sql.Timestamp}, so this method is implemented
352+ * to convert that into a {@code LocalDateTime}.
353+ * </p>
354+ * @param index 0 based column index
355+ * @return A column value as a {@code Clob}, or null if the column
356+ * value is NULL.
357+ */
358+ private LocalDateTime getLocalDateTime (int index ) {
359+ if (getColumnTypeNumber (index ) == OracleTypes .TIMESTAMPLTZ ) {
360+ Timestamp timestamp = jdbcRow .getObject (index , Timestamp .class );
361+ return timestamp == null ? null : timestamp .toLocalDateTime ();
362+ }
363+ else {
364+ return jdbcRow .getObject (index , LocalDateTime .class );
365+ }
366+ }
367+
339368 /**
340369 * Checks if the specified zero-based {@code index} is a valid column index
341370 * for this row. This method is used to verify index value parameters
@@ -361,7 +390,7 @@ else if (index >= rowMetadata.getColumnNames().size()) {
361390 * as {@code type}.
362391 * </p><p>
363392 * This method handles cases where the JDBC driver may support a mapping
364- * which the Oracle R2DBC Driver does not support. For instance, the JDBC
393+ * that the Oracle R2DBC Driver does not support. For instance, the JDBC
365394 * driver may support mapping CLOB columns to String, but the Oracle R2DBC
366395 * Driver does not support this as the JDBC driver may require blocking
367396 * network I/O to convert a CLOB into a String.
@@ -372,15 +401,8 @@ else if (index >= rowMetadata.getColumnNames().size()) {
372401 * @throws R2dbcException if the type mapping is not supported
373402 */
374403 private void requireSupportedTypeMapping (int index , Class <?> type ) {
375- Integer sqlTypeCode =
376- rowMetadata .getColumnMetadata (index )
377- .getNativeTypeMetadata ()
378- .getVendorTypeNumber ();
379-
380- if (sqlTypeCode == null )
381- return ;
382404
383- switch (sqlTypeCode ) {
405+ switch (getColumnTypeNumber ( index ) ) {
384406 case Types .BLOB :
385407 if (! type .equals (Blob .class ))
386408 throw unsupportedTypeMapping ("BLOB" , index , type );
@@ -392,6 +414,23 @@ private void requireSupportedTypeMapping(int index, Class<?> type) {
392414 }
393415 }
394416
417+ /**
418+ * Returns the SQL type number of the column at a given {@code index}. The
419+ * returned number identifies either a standard type defined by
420+ * {@link Types} or a vendor specific type defined by the JDBC driver. This
421+ * method returns {@link Types#OTHER} if the JDBC driver does not provide a
422+ * type code for the column.
423+ * @param index 0 based column index
424+ * @return A SQL type number
425+ */
426+ private int getColumnTypeNumber (int index ) {
427+ Integer typeNumber = rowMetadata .getColumnMetadata (index )
428+ .getNativeTypeMetadata ()
429+ .getVendorTypeNumber ();
430+
431+ return typeNumber == null ? Types .OTHER : typeNumber ;
432+ }
433+
395434 /**
396435 * Returns an exception indicating that the Oracle R2DBC Driver does
397436 * not support mapping the database type specified as {@code sqlTypeName}
@@ -401,7 +440,7 @@ private void requireSupportedTypeMapping(int index, Class<?> type) {
401440 * @param type Java type to which mapping is not supported
402441 * @return An exception that expresses the unsupported mapping
403442 */
404- private R2dbcException unsupportedTypeMapping (
443+ private static R2dbcException unsupportedTypeMapping (
405444 String sqlTypeName , int index , Class <?> type ) {
406445 return OracleR2dbcExceptions .newNonTransientException (
407446 String .format ("Unsupported SQL to Java type mapping. " +
0 commit comments