Skip to content

Commit 84341e7

Browse files
Test OBJECT types
1 parent 292e8a2 commit 84341e7

15 files changed

+1479
-259
lines changed

src/main/java/oracle/r2dbc/OracleR2dbcObject.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
public interface OracleR2dbcObject extends io.r2dbc.spi.Readable {
44

55
OracleR2dbcObjectMetadata getMetadata();
6+
67
}

src/main/java/oracle/r2dbc/OracleR2dbcObjectMetadata.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,19 @@
55
import java.util.List;
66
import java.util.NoSuchElementException;
77

8+
/**
9+
* Represents the metadata for attributes of an OBJECT. Metadata for attributes
10+
* can either be retrieved by index or by name. Attribute indexes are
11+
* {@code 0}-based. Retrieval by attribute name is case-insensitive.
12+
*/
813
public interface OracleR2dbcObjectMetadata {
914

15+
/**
16+
* Returns the type of the OBJECT which metadata is provided for.
17+
* @return The type of the OBJECT. Not null.
18+
*/
19+
OracleR2dbcTypes.ObjectType getObjectType();
20+
1021
/**
1122
* Returns the {@link ReadableMetadata} for one attribute.
1223
*

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

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,6 @@ private OracleR2dbcTypes() {}
103103
public static final Type REF_CURSOR =
104104
new TypeImpl(Result.class, "SYS_REFCURSOR");
105105

106-
/**
107-
* A user defined OBJECT type.
108-
*/
109-
public static final Type OBJECT =
110-
new TypeImpl(OracleR2dbcObject.class, "OBJECT");
111-
112106
/**
113107
* <p>
114108
* Creates an {@link ArrayType} representing a user defined {@code ARRAY}
@@ -219,10 +213,9 @@ private static final class ArrayTypeImpl
219213
extends TypeImpl implements ArrayType {
220214

221215
/**
222-
* Constructs an ARRAY type with the given {@code name}. The constructed
223-
* {@code ArrayType} as a default Java type mapping of
224-
* {@code Object[].class}. This is consistent with the standard
225-
* {@link R2dbcType#COLLECTION} type.
216+
* Constructs an ARRAY type with the given {@code name}. {@code Object[]} is
217+
* the default mapping of the constructed type. This is consistent with the
218+
* standard {@link R2dbcType#COLLECTION} type.
226219
* @param name User defined name of the type. Not null.
227220
*/
228221
ArrayTypeImpl(String name) {
@@ -235,15 +228,12 @@ private static final class ObjectTypeImpl
235228
extends TypeImpl implements ObjectType {
236229

237230
/**
238-
* Constructs an ARRAY type with the given {@code name}. The constructed
239-
* {@code ObjectType} as a default Java type mapping of
240-
* {@code Object[].class}. This is consistent with the standard
241-
* {@link R2dbcType#COLLECTION} type.
231+
* Constructs an OBJECT type with the given {@code name}.
232+
* {@code OracleR2dbcObject} is the default mapping of the constructed type.
242233
* @param name User defined name of the type. Not null.
243234
*/
244235
ObjectTypeImpl(String name) {
245-
// TODO: Consider defining Readable.class as the default type mapping.
246-
super(Object.class, name);
236+
super(OracleR2dbcObject.class, name);
247237
}
248238
}
249239

@@ -311,6 +301,19 @@ public String getName() {
311301
public String toString() {
312302
return getName();
313303
}
304+
305+
@Override
306+
public boolean equals(Object other) {
307+
if (! (other instanceof Type))
308+
return false;
309+
310+
return sqlName.equals(((Type)other).getName());
311+
}
312+
313+
@Override
314+
public int hashCode() {
315+
return sqlName.hashCode();
316+
}
314317
}
315318

316319
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ static <T> T requireNonNull(T obj, String message) {
8181
return obj;
8282
}
8383

84+
8485
/**
8586
* Checks if a {@code jdbcConnection} is open, and throws an exception if the
8687
* check fails.

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

Lines changed: 171 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,12 @@
4444
import oracle.r2dbc.impl.ReadablesMetadata.RowMetadataImpl;
4545
import oracle.sql.INTERVALDS;
4646
import oracle.sql.INTERVALYM;
47+
import oracle.sql.TIMESTAMPLTZ;
48+
import oracle.sql.TIMESTAMPTZ;
4749

4850
import java.math.BigDecimal;
4951
import java.nio.ByteBuffer;
5052
import java.sql.Array;
51-
import java.sql.Connection;
52-
import java.sql.JDBCType;
5353
import java.sql.ResultSet;
5454
import java.sql.SQLException;
5555
import java.sql.Struct;
@@ -63,8 +63,12 @@
6363
import java.time.OffsetTime;
6464
import java.time.Period;
6565
import java.time.temporal.ChronoUnit;
66+
import java.util.Arrays;
67+
import java.util.List;
6668
import java.util.Map;
6769
import java.util.NoSuchElementException;
70+
import java.util.Objects;
71+
import java.util.TreeMap;
6872
import java.util.concurrent.TimeUnit;
6973
import java.util.function.Function;
7074
import java.util.function.IntFunction;
@@ -277,18 +281,16 @@ else if (Object.class.equals(type)) {
277281
: convert(index, defaultType);
278282
}
279283
else {
280-
ReadableMetadata metadata = readablesMetadata.get(index);
281-
if (type.isArray() && R2dbcType.COLLECTION.equals(metadata.getType())) {
282-
// Note that a byte[] would be a valid mapping for a RAW column, so this
283-
// branch is only taken if the target type is an array, and the column
284-
// type is a SQL ARRAY (ie: COLLECTION).
285-
value = getJavaArray(index, type.getComponentType());
284+
Type sqlType = readablesMetadata.get(index).getType();
285+
286+
if (sqlType instanceof OracleR2dbcTypes.ArrayType) {
287+
value = getOracleArray(index, type);
286288
}
287-
else if (type.isAssignableFrom(OracleR2dbcObject.class)
288-
&& JDBCType.STRUCT.equals(metadata.getNativeTypeMetadata())) {
289-
value = getOracleR2dbcObject(index);
289+
else if (sqlType instanceof OracleR2dbcTypes.ObjectType) {
290+
value = getOracleObject(index, type);
290291
}
291292
else {
293+
// Fallback to a built-in conversion supported by Oracle JDBC
292294
value = jdbcReadable.getObject(index, type);
293295
}
294296
}
@@ -402,6 +404,22 @@ private LocalDateTime getLocalDateTime(int index) {
402404
*/
403405
}
404406

407+
/**
408+
* Returns an ARRAY at the given {@code index} as a specified {@code type}.
409+
* This method supports conversions to Java arrays of the default Java type
410+
* mapping for the ARRAY's element type. This conversion is the standard type
411+
* mapping for a COLLECTION, as defined by the R2DBC Specification.
412+
*/
413+
private <T> T getOracleArray(int index, Class<T> type) {
414+
if (type.isArray()) {
415+
return type.cast(getJavaArray(index, type.getComponentType()));
416+
}
417+
else {
418+
// Fallback to a built-in conversion supported by Oracle JDBC
419+
return jdbcReadable.getObject(index, type);
420+
}
421+
}
422+
405423
/**
406424
* <p>
407425
* Converts the value of a column at the specified {@code index} to a Java
@@ -623,7 +641,38 @@ private <T> T[] convertArray(Object[] array, Class<T> desiredType) {
623641
(Function<Object, T>)getMappingFunction(elementType, desiredType));
624642
}
625643

626-
private OracleR2dbcObject getOracleR2dbcObject(int index) {
644+
/**
645+
* Converts the OBJECT column at a given {@code index} to the specified
646+
* {@code type}. For symmetry with the object types supported by Statement
647+
* bind methods, this method supports conversions to
648+
* {@code OracleR2dbcObject}, {@code Object[]}, or
649+
* {@code Map<String, Object>}.
650+
*/
651+
private <T> T getOracleObject(int index, Class<T> type) {
652+
653+
if (type.isAssignableFrom(OracleR2dbcObject.class)) {
654+
// Support conversion to OracleR2dbcObject by default
655+
return type.cast(getOracleR2dbcObject(index));
656+
}
657+
else if (type.isAssignableFrom(Object[].class)) {
658+
// Support conversion to Object[] for symmetry with bind types
659+
return type.cast(toArray(getOracleR2dbcObject(index)));
660+
}
661+
else if (type.isAssignableFrom(Map.class)) {
662+
// Support conversion to Map<String, Object> for symmetry with bind
663+
// types
664+
return type.cast(toMap(getOracleR2dbcObject(index)));
665+
}
666+
else {
667+
// Fallback to a built-in conversion of Oracle JDBC
668+
return jdbcReadable.getObject(index, type);
669+
}
670+
}
671+
672+
/**
673+
* Returns the OBJECT at a given {@code index} as an {@code OracleR2dbcObject}
674+
*/
675+
private OracleR2dbcObjectImpl getOracleR2dbcObject(int index) {
627676

628677
OracleStruct oracleStruct =
629678
jdbcReadable.getObject(index, OracleStruct.class);
@@ -639,6 +688,42 @@ private OracleR2dbcObject getOracleR2dbcObject(int index) {
639688
adapter);
640689
}
641690

691+
/**
692+
* Returns of an array with the default mapping of each value in a
693+
* {@code readable}
694+
*/
695+
private static Object[] toArray(OracleReadableImpl readable) {
696+
if (readable == null)
697+
return null;
698+
699+
Object[] array =
700+
new Object[readable.readablesMetadata.getList().size()];
701+
702+
for (int i = 0; i < array.length; i++)
703+
array[i] = readable.get(i);
704+
705+
return array;
706+
}
707+
708+
/**
709+
* Returns a map containing the default mapping of each value in an
710+
* {@code object}, keyed to the value's name.
711+
*/
712+
private static Map<String, Object> toMap(OracleReadableImpl readable) {
713+
if (readable == null)
714+
return null;
715+
716+
List<? extends ReadableMetadata> metadataList =
717+
readable.readablesMetadata.getList();
718+
719+
Map<String, Object> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
720+
721+
for (int i = 0; i < metadataList.size(); i++)
722+
map.put(metadataList.get(i).getName(), readable.get(i));
723+
724+
return map;
725+
}
726+
642727
private <T,U> Function<T,U> getMappingFunction(
643728
Class<T> fromType, Class<U> toType) {
644729

@@ -712,6 +797,37 @@ else if (toType.isAssignableFrom(LocalTime.class)) {
712797
offsetDateTime.toLocalTime();
713798
}
714799
}
800+
else if (TIMESTAMPTZ.class.isAssignableFrom(fromType)) {
801+
if (toType.isAssignableFrom(OffsetDateTime.class)) {
802+
mappingFunction = (TIMESTAMPTZ timestampWithTimeZone) ->
803+
fromJdbc(() -> timestampWithTimeZone.toOffsetDateTime());
804+
}
805+
else if (toType.isAssignableFrom(OffsetTime.class)) {
806+
mappingFunction = (TIMESTAMPTZ timestampWithTimeZone) ->
807+
fromJdbc(() -> timestampWithTimeZone.toOffsetTime());
808+
}
809+
else if (toType.isAssignableFrom(LocalDateTime.class)) {
810+
mappingFunction = (TIMESTAMPTZ timestampWithTimeZone) ->
811+
fromJdbc(() -> timestampWithTimeZone.toLocalDateTime());
812+
}
813+
}
814+
else if (TIMESTAMPLTZ.class.isAssignableFrom(fromType)) {
815+
if (toType.isAssignableFrom(OffsetDateTime.class)) {
816+
mappingFunction = (TIMESTAMPLTZ timestampWithLocalTimeZone) ->
817+
fromJdbc(() ->
818+
timestampWithLocalTimeZone.offsetDateTimeValue(jdbcConnection));
819+
}
820+
else if (toType.isAssignableFrom(OffsetTime.class)) {
821+
mappingFunction = (TIMESTAMPLTZ timestampWithLocalTimeZone) ->
822+
fromJdbc(() ->
823+
timestampWithLocalTimeZone.offsetTimeValue(jdbcConnection));
824+
}
825+
else if (toType.isAssignableFrom(LocalDateTime.class)) {
826+
mappingFunction = (TIMESTAMPLTZ timestampWithLocalTimeZone) ->
827+
fromJdbc(() ->
828+
timestampWithLocalTimeZone.localDateTimeValue(jdbcConnection));
829+
}
830+
}
715831
else if (INTERVALYM.class.isAssignableFrom(fromType)
716832
&& toType.isAssignableFrom(Period.class)) {
717833
mappingFunction = (INTERVALYM intervalym) -> {
@@ -937,11 +1053,18 @@ public OutParametersMetadata getMetadata() {
9371053
}
9381054
}
9391055

940-
private static final class OracleR2dbcObjectImpl
1056+
private final class OracleR2dbcObjectImpl
9411057
extends OracleReadableImpl implements OracleR2dbcObject {
9421058

9431059
private final OracleR2dbcObjectMetadata metadata;
9441060

1061+
/**
1062+
* JDBC readable that backs this object. It is retained with this field to
1063+
* implement equals and hashcode without having to convert between the
1064+
* default JDBC object mappings and those of R2DBC.
1065+
*/
1066+
private final StructJdbcReadable structJdbcReadable;
1067+
9451068
/**
9461069
* <p>
9471070
* Constructs a new set of out parameters that supplies values of a
@@ -950,22 +1073,53 @@ private static final class OracleR2dbcObjectImpl
9501073
* </p>
9511074
* @param jdbcConnection JDBC connection that created the
9521075
* {@code jdbcReadable}. Not null.
953-
* @param jdbcReadable Readable values from a JDBC Driver. Not null.
1076+
* @param structJdbcReadable Readable values from a JDBC Driver. Not null.
9541077
* @param metadata Metadata of each value. Not null.
9551078
* @param adapter Adapts JDBC calls into reactive streams. Not null.
9561079
*/
9571080
private OracleR2dbcObjectImpl(
958-
java.sql.Connection jdbcConnection, DependentCounter dependentCounter,
959-
JdbcReadable jdbcReadable, OracleR2dbcObjectMetadataImpl metadata,
1081+
java.sql.Connection jdbcConnection,
1082+
DependentCounter dependentCounter,
1083+
StructJdbcReadable structJdbcReadable,
1084+
OracleR2dbcObjectMetadataImpl metadata,
9601085
ReactiveJdbcAdapter adapter) {
961-
super(jdbcConnection, dependentCounter, jdbcReadable, metadata, adapter);
1086+
super(
1087+
jdbcConnection, dependentCounter, structJdbcReadable, metadata, adapter);
9621088
this.metadata = metadata;
1089+
this.structJdbcReadable = structJdbcReadable;
9631090
}
9641091

9651092
@Override
9661093
public OracleR2dbcObjectMetadata getMetadata() {
9671094
return metadata;
9681095
}
1096+
1097+
@Override
1098+
public boolean equals(Object other) {
1099+
if (!(other instanceof OracleR2dbcObjectImpl))
1100+
return super.equals(other);
1101+
1102+
OracleR2dbcObjectImpl otherObject = (OracleR2dbcObjectImpl) other;
1103+
if (! readablesMetadata.equals(otherObject.metadata))
1104+
return false;
1105+
1106+
return Arrays.deepEquals(
1107+
structJdbcReadable.attributes,
1108+
otherObject.structJdbcReadable.attributes);
1109+
}
1110+
1111+
@Override
1112+
public int hashCode() {
1113+
return Objects.hash(readablesMetadata, structJdbcReadable);
1114+
}
1115+
1116+
@Override
1117+
public String toString() {
1118+
return format(
1119+
"%s = %s",
1120+
metadata.getObjectType().getName(),
1121+
toMap(this));
1122+
}
9691123
}
9701124

9711125
private final class StructJdbcReadable implements JdbcReadable {

0 commit comments

Comments
 (0)