2626import io .r2dbc .spi .R2dbcException ;
2727import io .r2dbc .spi .Row ;
2828
29+ import oracle .jdbc .OracleTypes ;
30+
2931import java .nio .ByteBuffer ;
3032import java .sql .ResultSet ;
33+ import java .sql .Timestamp ;
3134import java .sql .Types ;
35+ import java .time .LocalDateTime ;
3236
3337import static oracle .r2dbc .impl .OracleR2dbcExceptions .requireNonNull ;
3438
3741 * Implementation of the {@link Row} SPI for Oracle Database.
3842 * </p><p>
3943 * Instances of this class supply the column values of a
40- * {@link ReactiveJdbcAdapter.JdbcRow} which represents one row of data
44+ * {@link ReactiveJdbcAdapter.JdbcRow} that represents one row of data
4145 * from a JDBC {@link ResultSet}.
4246 * </p>
4347 *
@@ -57,7 +61,7 @@ final class OracleRowImpl implements Row {
5761
5862 /**
5963 * <p>
60- * Constructs a new row which supplies column values from the specified
64+ * Constructs a new row that supplies column values from the specified
6165 * {@code jdbcRow}, and uses the specified {@code rowMetadata} to determine
6266 * the default type mapping of column values.
6367 * </p>
@@ -107,7 +111,7 @@ public Object get(int index) {
107111 /**
108112 * {@inheritDoc}
109113 * <p>
110- * Implements the R2DBC SPI method by using the JDBC ResultSet which backs
114+ * Implements the R2DBC SPI method by using the JDBC ResultSet that backs
111115 * this row to convert the specified column value into the specified {@code
112116 * type}.
113117 * </p><p>
@@ -140,7 +144,7 @@ public <T> T get(int index, Class<T> type) {
140144 /**
141145 * {@inheritDoc}
142146 * <p>
143- * Implements the R2DBC SPI method by using the JDBC ResultSet which backs
147+ * Implements the R2DBC SPI method by using the JDBC ResultSet that backs
144148 * this row to convert the specified column value into the Oracle R2DBC
145149 * Driver's default Java type mapping for the column's SQL type.
146150 * </p><p>
@@ -176,7 +180,7 @@ public Object get(String name) {
176180 /**
177181 * {@inheritDoc}
178182 * <p>
179- * Implements the R2DBC SPI method by using the JDBC ResultSet which backs
183+ * Implements the R2DBC SPI method by using the JDBC ResultSet that backs
180184 * this row to convert the specified column value into the specified {@code
181185 * type}.
182186 * </p><p>
@@ -229,7 +233,7 @@ private int getColumnIndex(String name) {
229233 * Converts the value of a column at a specified {@code index} to the
230234 * specified {@code type}. This method implements conversions to target
231235 * types that are not supported by JDBC drivers. The Oracle R2DBC Driver
232- * will implement some conversions which adapt JDBC supported types into
236+ * will implement some conversions that adapt JDBC supported types into
233237 * R2DBC supported types.
234238 *
235239 * @param index 0-based index of a column
@@ -247,6 +251,8 @@ else if (type.equals(io.r2dbc.spi.Blob.class))
247251 return type .cast (getBlob (index ));
248252 else if (type .equals (io .r2dbc .spi .Clob .class ))
249253 return type .cast (getClob (index ));
254+ else if (type .equals (LocalDateTime .class ))
255+ return type .cast (getLocalDateTime (index ));
250256 else
251257 return jdbcRow .getObject (index , type );
252258 }
@@ -258,7 +264,7 @@ else if (type.equals(io.r2dbc.spi.Clob.class))
258264 * </p><p>
259265 * A JDBC driver is not required to support {@code ByteBuffer} conversions
260266 * for any SQL type, so this method is necessary to implement the
261- * conversion to {@code ByteBuffer} from a type which is supported by JDBC.
267+ * conversion to {@code ByteBuffer} from a type that is supported by JDBC.
262268 * </p><p>
263269 * This method should NOT be called when the database column type is BLOB.
264270 * The JDBC driver may require blocking network I/O in order to materialize a
@@ -280,7 +286,7 @@ private ByteBuffer getByteBuffer(int index) {
280286 * </p><p>
281287 * A JDBC driver is not required to support {@code io.r2dbc.spi.Blob}
282288 * conversions for any SQL type, so this method is necessary to implement the
283- * conversion to {@code Blob} from a type which is supported by JDBC.
289+ * conversion to {@code Blob} from a type that is supported by JDBC.
284290 * </p>
285291 * @param index 0 based column index
286292 * @return A column value as a {@code Blob}, or null if the column
@@ -302,27 +308,27 @@ private Blob getBlob(int index) {
302308 * </p><p>
303309 * A JDBC driver is not required to support {@code io.r2dbc.spi.Clob}
304310 * conversions for any SQL type, so this method is necessary to implement the
305- * conversion to {@code Clob} from a type which is supported by JDBC.
311+ * conversion to {@code Clob} from a type that is supported by JDBC.
306312 * </p>
307313 * @param index 0 based column index
308314 * @return A column value as a {@code Clob}, or null if the column
309315 * value is NULL.
310316 */
311317 private Clob getClob (int index ) {
312- Integer columnTypeCode =
313- rowMetadata .getColumnMetadata (index )
314- .getNativeTypeMetadata ()
315- .getVendorTypeNumber ();
316-
317- boolean isNChar = columnTypeCode != null &&
318- (columnTypeCode == Types .NCLOB
319- || columnTypeCode == Types .NCHAR
320- || columnTypeCode == Types .NVARCHAR
321- || columnTypeCode == Types .LONGNVARCHAR );
322318
323- java .sql .Clob jdbcClob = isNChar
324- ? jdbcRow .getObject (index , java .sql .NClob .class )
325- : jdbcRow .getObject (index , java .sql .Clob .class );
319+ // Convert to a JDBC NClob or Clob, depending on the column type
320+ final java .sql .Clob jdbcClob ;
321+ switch (getColumnTypeNumber (index )) {
322+ case Types .NCLOB :
323+ case Types .LONGNVARCHAR :
324+ case Types .NVARCHAR :
325+ case Types .NCHAR :
326+ jdbcClob = jdbcRow .getObject (index , java .sql .NClob .class );
327+ break ;
328+ default :
329+ jdbcClob = jdbcRow .getObject (index , java .sql .Clob .class );
330+ break ;
331+ }
326332
327333 return jdbcClob == null
328334 ? null
@@ -331,6 +337,35 @@ private Clob getClob(int index) {
331337 adapter .publishClobFree (jdbcClob ));
332338 }
333339
340+ /**
341+ * <p>
342+ * Converts the value of a column at the specified {@code index} to a
343+ * {@code LocalDateTime}.
344+ * </p><p>
345+ * A JDBC driver is not required to support {@code LocalDateTime} conversions
346+ * for any SQL type. The Oracle JDBC driver is known to support {@code
347+ * LocalDateTime} conversions for DATE, TIMESTAMP, TIMESTAMP WITH TIME ZONE.
348+ * </p><p>
349+ * The 21.1 Oracle JDBC Driver does not implement a correct conversion for
350+ * TIMESTAMP WITH LOCAL TIME ZONE; The driver returns a value in the database
351+ * timezone rather than the session time zone. A correct conversion is
352+ * implemented for {@code java.sql.Timestamp}, so this method is implemented
353+ * to convert that into a {@code LocalDateTime}.
354+ * </p>
355+ * @param index 0 based column index
356+ * @return A column value as a {@code Clob}, or null if the column
357+ * value is NULL.
358+ */
359+ private LocalDateTime getLocalDateTime (int index ) {
360+ if (getColumnTypeNumber (index ) == OracleTypes .TIMESTAMPLTZ ) {
361+ Timestamp timestamp = jdbcRow .getObject (index , Timestamp .class );
362+ return timestamp == null ? null : timestamp .toLocalDateTime ();
363+ }
364+ else {
365+ return jdbcRow .getObject (index , LocalDateTime .class );
366+ }
367+ }
368+
334369 /**
335370 * Checks if the specified zero-based {@code index} is a valid column index
336371 * for this row. This method is used to verify index value parameters
@@ -356,7 +391,7 @@ else if (index >= rowMetadata.getColumnNames().size()) {
356391 * as {@code type}.
357392 * </p><p>
358393 * This method handles cases where the JDBC driver may support a mapping
359- * which the Oracle R2DBC Driver does not support. For instance, the JDBC
394+ * that the Oracle R2DBC Driver does not support. For instance, the JDBC
360395 * driver may support mapping CLOB columns to String, but the Oracle R2DBC
361396 * Driver does not support this as the JDBC driver may require blocking
362397 * network I/O to convert a CLOB into a String.
@@ -367,15 +402,8 @@ else if (index >= rowMetadata.getColumnNames().size()) {
367402 * @throws R2dbcException if the type mapping is not supported
368403 */
369404 private void requireSupportedTypeMapping (int index , Class <?> type ) {
370- Integer sqlTypeCode =
371- rowMetadata .getColumnMetadata (index )
372- .getNativeTypeMetadata ()
373- .getVendorTypeNumber ();
374-
375- if (sqlTypeCode == null )
376- return ;
377405
378- switch (sqlTypeCode ) {
406+ switch (getColumnTypeNumber ( index ) ) {
379407 case Types .BLOB :
380408 if (! type .equals (Blob .class ))
381409 throw unsupportedTypeMapping ("BLOB" , index , type );
@@ -387,6 +415,23 @@ private void requireSupportedTypeMapping(int index, Class<?> type) {
387415 }
388416 }
389417
418+ /**
419+ * Returns the SQL type number of the column at a given {@code index}. The
420+ * returned number identifies either a standard type defined by
421+ * {@link Types} or a vendor specific type defined by the JDBC driver. This
422+ * method returns {@link Types#OTHER} if the JDBC driver does not provide a
423+ * type code for the column.
424+ * @param index 0 based column index
425+ * @return A SQL type number
426+ */
427+ private int getColumnTypeNumber (int index ) {
428+ Integer typeNumber = rowMetadata .getColumnMetadata (index )
429+ .getNativeTypeMetadata ()
430+ .getVendorTypeNumber ();
431+
432+ return typeNumber == null ? Types .OTHER : typeNumber ;
433+ }
434+
390435 /**
391436 * Returns an exception indicating that the Oracle R2DBC Driver does
392437 * not support mapping the database type specified as {@code sqlTypeName}
@@ -396,7 +441,7 @@ private void requireSupportedTypeMapping(int index, Class<?> type) {
396441 * @param type Java type to which mapping is not supported
397442 * @return An exception that expresses the unsupported mapping
398443 */
399- private R2dbcException unsupportedTypeMapping (
444+ private static R2dbcException unsupportedTypeMapping (
400445 String sqlTypeName , int index , Class <?> type ) {
401446 return OracleR2dbcExceptions .newNonTransientException (
402447 String .format ("Unsupported SQL to Java type mapping. " +
0 commit comments