Skip to content

Commit b5ed8b6

Browse files
Merge pull request #8 from oracle/timestamp_local_tz_fix
LocalDateTime Conversion for TIMESTAMP WITH LOCAL TIME ZONE
2 parents 8754193 + 8339bf9 commit b5ed8b6

File tree

3 files changed

+81
-40
lines changed

3 files changed

+81
-40
lines changed

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,10 @@ static OracleColumnMetadataImpl fromResultSetMetaData(
204204

205205
case Types.DATE:
206206
// Override JDBC's java.sql.Date type mapping to use LocalDate
207-
return newMetadata(
208-
LocalDate.class, resultSetMetaData, jdbcIndex);
207+
return newMetadata(LocalDate.class, resultSetMetaData, jdbcIndex);
209208

210-
case OracleTypes.TIMESTAMPLTZ:
211209
case Types.TIMESTAMP:
210+
case OracleTypes.TIMESTAMPLTZ:
212211
// Override JDBC's java.sql.Timestamp type mapping, and Oracle JDBC's
213212
// oracle.sql.TIMESTAMPLTZ type mapping to use LocalDateTime, and to
214213
// use an appropriate precision value. Note that the Oracle type

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

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,13 @@
2626
import io.r2dbc.spi.R2dbcException;
2727
import io.r2dbc.spi.Row;
2828

29+
import oracle.jdbc.OracleTypes;
30+
2931
import java.nio.ByteBuffer;
3032
import java.sql.ResultSet;
33+
import java.sql.Timestamp;
3134
import java.sql.Types;
35+
import java.time.LocalDateTime;
3236

3337
import static oracle.r2dbc.impl.OracleR2dbcExceptions.requireNonNull;
3438

@@ -37,7 +41,7 @@
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. " +

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@
4242
import java.time.Month;
4343
import java.time.OffsetDateTime;
4444
import java.time.Period;
45+
import java.time.ZoneId;
4546
import java.time.ZoneOffset;
4647
import java.time.temporal.ChronoUnit;
47-
import java.util.Base64;
48-
import java.util.Optional;
4948
import java.util.function.BiConsumer;
5049
import java.util.stream.Collectors;
5150
import java.util.stream.Stream;
@@ -60,8 +59,6 @@
6059
import static oracle.r2dbc.util.Awaits.awaitOne;
6160
import static oracle.r2dbc.util.Awaits.awaitUpdate;
6261
import static org.junit.jupiter.api.Assertions.assertEquals;
63-
import static org.junit.jupiter.api.Assertions.assertNull;
64-
import static org.junit.jupiter.api.Assertions.assertTrue;
6562

6663
/**
6764
* <p>
@@ -460,7 +457,7 @@ private static <T> void verifyTypeMapping(
460457
"SELECT javaValue FROM "+table+" WHERE javaValue IS NOT NULL")
461458
.execute())
462459
.flatMap(result ->
463-
result.map((row, metadata) -> row.get("javaValue"))
460+
result.map((row, metadata) -> row.get("javaValue"))
464461
)));
465462

466463
awaitOne(true,

0 commit comments

Comments
 (0)