This is an automated email from the ASF dual-hosted git repository.
zhenchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/main by this push:
new 79de3a64bf [CALCITE-7359] Incorrect result for array comparison with
ANY operator
79de3a64bf is described below
commit 79de3a64bff8c79589bf7d16ff9f32124643eb67
Author: Zhen Chen <[email protected]>
AuthorDate: Sat Jan 17 17:47:20 2026 +0800
[CALCITE-7359] Incorrect result for array comparison with ANY operator
---
.../adapter/enumerable/EnumerableCollect.java | 14 +-
.../calcite/rel/metadata/RelMdPredicates.java | 33 ++--
.../org/apache/calcite/runtime/SqlFunctions.java | 174 +++++++++++++--------
core/src/test/resources/sql/some.iq | 34 +++-
.../apache/calcite/linq4j/function/Functions.java | 88 ++++-------
5 files changed, 203 insertions(+), 140 deletions(-)
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
index d794dc6c4e..0d2a521851 100644
---
a/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
+++
b/core/src/main/java/org/apache/calcite/adapter/enumerable/EnumerableCollect.java
@@ -109,15 +109,21 @@ public static Collect create(RelNode input, RelDataType
rowType) {
RelDataType childRecordType =
result.physType.getRowType().getFieldList().get(0).getType();
if (!SqlTypeUtil.sameNamedType(collectionComponentType,
childRecordType)) {
- // In the internal representation of multisets , every element must be
a record. In case the
- // result above is a scalar type we have to wrap it around a physical
type capable of
- // representing records. For this reason the following conversion is
necessary.
+ // In the internal representation of multisets, every element must be
a record.
+ // In case the result above is a scalar type we have to wrap it around
a
+ // physical type capable of representing records.
+ // For ARRAY type with a single field, we use SCALAR format to avoid
+ // unnecessary wrapping, which allows correct comparison semantics.
// REVIEW zabetak January 7, 2019: If we can ensure that the input to
this operator
// has the correct physical type (e.g., respecting the Prefer.ARRAY
above)
// then this conversion can be removed.
+ JavaRowFormat targetFormat =
+ collectionType == SqlTypeName.ARRAY &&
child.getRowType().getFieldCount() == 1
+ ? JavaRowFormat.SCALAR
+ : JavaRowFormat.ARRAY;
conv_ =
builder.append(
- "converted", result.physType.convertTo(child_,
JavaRowFormat.ARRAY));
+ "converted", result.physType.convertTo(child_, targetFormat));
}
collectionExpr =
diff --git
a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
index 11a6705158..e04e2098ff 100644
--- a/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
+++ b/core/src/main/java/org/apache/calcite/rel/metadata/RelMdPredicates.java
@@ -40,6 +40,7 @@
import org.apache.calcite.rel.core.TableScan;
import org.apache.calcite.rel.core.Union;
import org.apache.calcite.rel.core.Values;
+import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rex.RexBuilder;
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexExecutor;
@@ -54,6 +55,7 @@
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlInternalOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.util.BitSets;
import org.apache.calcite.util.Bug;
import org.apache.calcite.util.ImmutableBitSet;
@@ -599,21 +601,26 @@ public RelOptPredicateList getPredicates(Values values,
RelMetadataQuery mq) {
eqConstant(values, rexBuilder, i, rexLiteral));
}
} else {
- RexUnknownAs rexUnknownAs = RexUnknownAs.UNKNOWN;
- RangeSet<Comparable> rangeSet = TreeRangeSet.create();
- for (RexLiteral rexLiteral : rexLiteralSet) {
- if (RexUtil.isNull(rexLiteral)) {
- rexUnknownAs = RexUnknownAs.TRUE;
- continue;
+ final RelDataType type =
values.getRowType().getFieldList().get(i).getType();
+ if (!type.isStruct()
+ && !SqlTypeUtil.isCollection(type)
+ && !SqlTypeUtil.isMap(type)) {
+ RexUnknownAs rexUnknownAs = RexUnknownAs.UNKNOWN;
+ RangeSet<Comparable> rangeSet = TreeRangeSet.create();
+ for (RexLiteral rexLiteral : rexLiteralSet) {
+ if (RexUtil.isNull(rexLiteral)) {
+ rexUnknownAs = RexUnknownAs.TRUE;
+ continue;
+ }
+ rangeSet.add(
+
Range.singleton(requireNonNull(rexLiteral.getValueAs(Comparable.class))));
}
-
rangeSet.add(Range.singleton(requireNonNull(rexLiteral.getValueAs(Comparable.class))));
+ final Sarg sarg = Sarg.of(rexUnknownAs, rangeSet);
+ predicates.add(
+ i, rexBuilder.makeCall(SqlStdOperatorTable.SEARCH,
+ rexBuilder.makeInputRef(values, i),
+ rexBuilder.makeSearchArgumentLiteral(sarg, type)));
}
- final Sarg sarg = Sarg.of(rexUnknownAs, rangeSet);
- predicates.add(
- i, rexBuilder.makeCall(SqlStdOperatorTable.SEARCH,
- rexBuilder.makeInputRef(values, i),
- rexBuilder.makeSearchArgumentLiteral(sarg,
- values.getRowType().getFieldList().get(i).getType())));
}
}
return RelOptPredicateList.of(rexBuilder, predicates);
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index 50530aef67..28b34513e1 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -2116,7 +2116,7 @@ public static boolean eq(BigDecimal b0, BigDecimal b1) {
/** SQL <code>=</code> operator applied to Object[] values (neither may be
* null). */
public static boolean eq(@Nullable Object @Nullable [] b0, @Nullable Object
@Nullable [] b1) {
- return Arrays.deepEquals(b0, b1);
+ return Functions.compareObjectArrays(b0, b1) == 0;
}
/** SQL <code>=</code> operator applied to Object values (including String;
@@ -2125,6 +2125,16 @@ public static boolean eq(Object b0, Object b1) {
return b0.equals(b1);
}
+ /** SQL <code>=</code> operator applied to List values. */
+ public static boolean eq(List<?> b0, List<?> b1) {
+ return eqNullable(b0, b1);
+ }
+
+ /** SQL <code>=</code> operator applied to Map values. */
+ public static boolean eq(Map<?, ?> b0, Map<?, ?> b1) {
+ return eqNullable(b0, b1);
+ }
+
/** SQL <code>=</code> operator applied to String values with a certain
Comparator. */
public static boolean eq(String s0, String s1, Comparator<String>
comparator) {
return comparator.compare(s0, s1) == 0;
@@ -2133,22 +2143,7 @@ public static boolean eq(String s0, String s1,
Comparator<String> comparator) {
/** SQL <code>=</code> operator applied to Object values (at least one
operand
* has ANY type; neither may be null). */
public static boolean eqAny(Object b0, Object b1) {
- if (b0.getClass().equals(b1.getClass())) {
- // The result of SqlFunctions.eq(BigDecimal, BigDecimal) makes more sense
- // than BigDecimal.equals(BigDecimal). So if both of types are
BigDecimal,
- // we just use SqlFunctions.eq(BigDecimal, BigDecimal).
- if (BigDecimal.class.isInstance(b0)) {
- return eq((BigDecimal) b0, (BigDecimal) b1);
- } else {
- return b0.equals(b1);
- }
- } else if (allAssignable(Number.class, b0, b1)) {
- return eq(toBigDecimal((Number) b0), toBigDecimal((Number) b1));
- }
- // We shouldn't rely on implementation even though overridden equals can
- // handle other types which may create worse result: for example,
- // a.equals(b) != b.equals(a)
- return false;
+ return eqNullable(b0, b1);
}
/** Returns whether two objects can both be assigned to a given class. */
@@ -2169,6 +2164,21 @@ public static boolean ne(Object b0, Object b1) {
return !eq(b0, b1);
}
+ /** SQL <code><gt;</code> operator applied to List values. */
+ public static boolean ne(List<?> b0, List<?> b1) {
+ return !eqNullable(b0, b1);
+ }
+
+ /** SQL <code><gt;</code> operator applied to Map values. */
+ public static boolean ne(Map<?, ?> b0, Map<?, ?> b1) {
+ return !eqNullable(b0, b1);
+ }
+
+ /** SQL <code><gt;</code> operator applied to Object[] values. */
+ public static boolean ne(@Nullable Object @Nullable [] b0, @Nullable Object
@Nullable [] b1) {
+ return !eqNullable(b0, b1);
+ }
+
/** SQL <code><gt;</code> operator applied to OString values with a
certain Comparator. */
public static boolean ne(String s0, String s1, Comparator<String>
comparator) {
return !eq(s0, s1, comparator);
@@ -2180,6 +2190,66 @@ public static boolean neAny(Object b0, Object b1) {
return !eqAny(b0, b1);
}
+ private static boolean eqNullable(@Nullable Object b0, @Nullable Object b1) {
+ if (b0 == b1) {
+ return true;
+ }
+ if (b0 == null || b1 == null) {
+ return false;
+ }
+ if (b0 instanceof List && b1 instanceof List) {
+ return Functions.compareLists((List<?>) b0, (List<?>) b1) == 0;
+ }
+ if (b0 instanceof Map && b1 instanceof Map) {
+ return Functions.compareMaps((Map<?, ?>) b0, (Map<?, ?>) b1) == 0;
+ }
+ if (b0 instanceof Object[] && b1 instanceof Object[]) {
+ return Functions.compareObjectArrays((Object[]) b0, (Object[]) b1) == 0;
+ }
+ if (b0.getClass().equals(b1.getClass())) {
+ if (b0 instanceof BigDecimal) {
+ return eq((BigDecimal) b0, (BigDecimal) b1);
+ }
+ return b0.equals(b1);
+ }
+ if (b0 instanceof Number && b1 instanceof Number) {
+ return eq(toBigDecimal((Number) b0), toBigDecimal((Number) b1));
+ }
+ return false;
+ }
+
+ /** Compares two nullable objects recursively if they are collections.
+ * Nulls are treated as larger than non-null values. The op is used to record
+ * the type of comparison operation for user-friendly display in error
messages. */
+ private static int compareNullable(@Nullable Object b0, @Nullable Object b1,
String op) {
+ if (b0 == b1) {
+ return 0;
+ }
+ if (b0 == null) {
+ return 1;
+ }
+ if (b1 == null) {
+ return -1;
+ }
+ if (b0 instanceof List && b1 instanceof List) {
+ return Functions.compareLists((List<?>) b0, (List<?>) b1);
+ }
+ if (b0 instanceof Map && b1 instanceof Map) {
+ return Functions.compareMaps((Map<?, ?>) b0, (Map<?, ?>) b1);
+ }
+ if (b0 instanceof Object[] && b1 instanceof Object[]) {
+ return Functions.compareObjectArrays((Object[]) b0, (Object[]) b1);
+ }
+ if (b0.getClass().equals(b1.getClass()) && b0 instanceof Comparable) {
+ //noinspection unchecked
+ return ((Comparable) b0).compareTo(b1);
+ }
+ if (b0 instanceof Number && b1 instanceof Number) {
+ return toBigDecimal((Number) b0).compareTo(toBigDecimal((Number) b1));
+ }
+ throw notComparable(op, b0, b1);
+ }
+
// <
/** SQL <code><</code> operator applied to boolean values. */
@@ -2242,28 +2312,20 @@ public static boolean lt(double b0, double b1) {
}
public static boolean lt(List<?> b0, List<?> b1) {
- return Functions.compareLists(b0, b1) < 0;
+ return compareNullable(b0, b1, "<") < 0;
}
public static boolean lt(Map<?, ?> b0, Map<?, ?> b1) {
- return Functions.compareMaps(b0, b1) < 0;
+ return compareNullable(b0, b1, "<") < 0;
}
public static boolean lt(@Nullable Object @Nullable [] b0, @Nullable Object
@Nullable [] b1) {
- return Functions.compareObjectArrays(b0, b1) < 0;
+ return compareNullable(b0, b1, "<") < 0;
}
/** SQL <code><</code> operator applied to Object values. */
public static boolean ltAny(Object b0, Object b1) {
- if (b0.getClass().equals(b1.getClass())
- && b0 instanceof Comparable) {
- //noinspection unchecked
- return ((Comparable) b0).compareTo(b1) < 0;
- } else if (allAssignable(Number.class, b0, b1)) {
- return lt(toBigDecimal((Number) b0), toBigDecimal((Number) b1));
- }
-
- throw notComparable("<", b0, b1);
+ return compareNullable(b0, b1, "<") < 0;
}
// <=
@@ -2295,26 +2357,23 @@ public static boolean le(BigDecimal b0, BigDecimal b1) {
/** SQL <code>≤</code> operator applied to List values. */
public static boolean le(List<?> b0, List<?> b1) {
- return Functions.compareLists(b0, b1) <= 0;
+ return compareNullable(b0, b1, "<=") <= 0;
+ }
+
+ /** SQL <code>≤</code> operator applied to Map values. */
+ public static boolean le(Map<?, ?> b0, Map<?, ?> b1) {
+ return compareNullable(b0, b1, "<=") <= 0;
}
/** SQL <code>≤</code> operator applied to Object[] values. */
public static boolean le(@Nullable Object @Nullable [] b0, @Nullable Object
@Nullable [] b1) {
- return Functions.compareObjectArrays(b0, b1) <= 0;
+ return compareNullable(b0, b1, "<=") <= 0;
}
/** SQL <code>≤</code> operator applied to Object values (at least one
* operand has ANY type; neither may be null). */
public static boolean leAny(Object b0, Object b1) {
- if (b0.getClass().equals(b1.getClass())
- && b0 instanceof Comparable) {
- //noinspection unchecked
- return ((Comparable) b0).compareTo(b1) <= 0;
- } else if (allAssignable(Number.class, b0, b1)) {
- return le(toBigDecimal((Number) b0), toBigDecimal((Number) b1));
- }
-
- throw notComparable("<=", b0, b1);
+ return compareNullable(b0, b1, "<=") <= 0;
}
// >
@@ -2379,29 +2438,21 @@ public static boolean gt(double b0, double b1) {
}
public static boolean gt(List<?> b0, List<?> b1) {
- return Functions.compareLists(b0, b1) > 0;
+ return compareNullable(b0, b1, ">") > 0;
}
public static boolean gt(Map<?, ?> b0, Map<?, ?> b1) {
- return Functions.compareMaps(b0, b1) > 0;
+ return compareNullable(b0, b1, ">") > 0;
}
public static boolean gt(@Nullable Object @Nullable [] b0, @Nullable Object
@Nullable [] b1) {
- return Functions.compareObjectArrays(b0, b1) > 0;
+ return compareNullable(b0, b1, ">") > 0;
}
/** SQL <code>></code> operator applied to Object values (at least one
* operand has ANY type; neither may be null). */
public static boolean gtAny(Object b0, Object b1) {
- if (b0.getClass().equals(b1.getClass())
- && b0 instanceof Comparable) {
- //noinspection unchecked
- return ((Comparable) b0).compareTo(b1) > 0;
- } else if (allAssignable(Number.class, b0, b1)) {
- return gt(toBigDecimal((Number) b0), toBigDecimal((Number) b1));
- }
-
- throw notComparable(">", b0, b1);
+ return compareNullable(b0, b1, ">") > 0;
}
// >=
@@ -2433,26 +2484,23 @@ public static boolean ge(BigDecimal b0, BigDecimal b1) {
/** SQL <code>≥</code> operator applied to List values. */
public static boolean ge(List<?> b0, List<?> b1) {
- return Functions.compareLists(b0, b1) >= 0;
+ return compareNullable(b0, b1, ">=") >= 0;
+ }
+
+ /** SQL <code>≥</code> operator applied to Map values. */
+ public static boolean ge(Map<?, ?> b0, Map<?, ?> b1) {
+ return compareNullable(b0, b1, ">=") >= 0;
}
/** SQL <code>≥</code> operator applied to Object[] values. */
public static boolean ge(@Nullable Object @Nullable [] b0, @Nullable Object
@Nullable [] b1) {
- return Functions.compareObjectArrays(b0, b1) >= 0;
+ return compareNullable(b0, b1, ">=") >= 0;
}
/** SQL <code>≥</code> operator applied to Object values (at least one
* operand has ANY type; neither may be null). */
public static boolean geAny(Object b0, Object b1) {
- if (b0.getClass().equals(b1.getClass())
- && b0 instanceof Comparable) {
- //noinspection unchecked
- return ((Comparable) b0).compareTo(b1) >= 0;
- } else if (allAssignable(Number.class, b0, b1)) {
- return ge(toBigDecimal((Number) b0), toBigDecimal((Number) b1));
- }
-
- throw notComparable(">=", b0, b1);
+ return compareNullable(b0, b1, ">=") >= 0;
}
// +
diff --git a/core/src/test/resources/sql/some.iq
b/core/src/test/resources/sql/some.iq
index 5c7f6b469f..acc61d9c92 100644
--- a/core/src/test/resources/sql/some.iq
+++ b/core/src/test/resources/sql/some.iq
@@ -986,5 +986,37 @@ EnumerableCalc(expr#0=[{inputs}], expr#1=[1], expr#2=[2],
expr#3=[3], expr#4=[AR
EnumerableValues(tuples=[[{ 0 }]])
!plan
-# End some.iq
+# [CALCITE-7359] Support various nested type comparisons in ANY/ALL
+# Test with different operators and deeper nesting
+select
+ -- Equality for nested arrays
+ array[array[1,1],array[2,2]] = any(array[array[1,1],array[2,2]],
array[array[3,3],array[4,4]]) as arr_arr_t,
+ -- Array of Row
+ array[row(1, 'a'), row(2, 'b')] = any(array[row(1, 'a'), row(2, 'b')],
array[row(3, 'c')]) as arr_row_t,
+ -- Non-equality for nested arrays
+ array[1, 2] <> any(array[1, 2], array[1, 3]) as arr_ne_t,
+ -- Inequalities for nested arrays
+ array[1, 2] < any(array[1, 1], array[1, 3]) as arr_lt_t,
+ array[1, 2] <= any(array[0, 9], array[1, 2]) as arr_le_t,
+ array[1, 2] > any(array[1, 1], array[2, 0]) as arr_gt_t,
+ array[1, 2] >= any(array[1, 2], array[1, 3]) as arr_ge_t,
+
+ -- Nested structures with NULL values
+ array[1, cast(null as integer)] = any(array[1, cast(null as integer)],
array[1, 2]) as arr_null_t,
+ array[1, cast(null as integer)] = any(array[1, 2], array[1, 3]) as
arr_null_f,
+
+ -- Deeper nesting: Array of Row of Array
+ array[row(1, array[10, 20]), row(2, array[30])] =
+ any(array[row(1, array[10, 20]), row(2, array[30])], array[row(1,
array[0])]) as deep_t,
+ array[row(1, array[10, 20]), row(2, array[30])] =
+ any(array[row(1, array[10, 21]), row(2, array[30])], array[row(1,
array[0])]) as deep_f;
++-----------+-----------+----------+----------+----------+----------+----------+------------+------------+--------+--------+
+| ARR_ARR_T | ARR_ROW_T | ARR_NE_T | ARR_LT_T | ARR_LE_T | ARR_GT_T | ARR_GE_T
| ARR_NULL_T | ARR_NULL_F | DEEP_T | DEEP_F |
++-----------+-----------+----------+----------+----------+----------+----------+------------+------------+--------+--------+
+| true | true | true | true | true | true | true
| true | false | true | false |
++-----------+-----------+----------+----------+----------+----------+----------+------------+------------+--------+--------+
+(1 row)
+!ok
+
+# End some.iq
diff --git
a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
index 6e9a186344..6c0a41297b 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
@@ -23,6 +23,7 @@
import java.io.Serializable;
import java.lang.reflect.Type;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
@@ -548,17 +549,7 @@ private static class NullsFirstComparator
if (o2 == null) {
return 1;
}
- if (o1 instanceof Comparable && o2 instanceof Comparable) {
- //noinspection unchecked
- return ((Comparable) o1).compareTo(o2);
- } else if (o1 instanceof List && o2 instanceof List) {
- return compareLists((List<?>) o1, (List<?>) o2);
- } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
- return compareObjectArrays((Object[]) o1, (Object[]) o2);
- } else {
- throw new IllegalArgumentException("Item types do not match: "
- + o1.getClass() + " vs " + o2.getClass());
- }
+ return compareListItems(o1, o2);
}
}
@@ -566,26 +557,7 @@ private static class NullsFirstComparator
private static class NullsLastComparator
implements Comparator<Object>, Serializable {
@Override public int compare(@Nullable Object o1, @Nullable Object o2) {
- if (o1 == o2) {
- return 0;
- }
- if (o1 == null) {
- return 1;
- }
- if (o2 == null) {
- return -1;
- }
- if (o1 instanceof Comparable && o2 instanceof Comparable) {
- //noinspection unchecked
- return ((Comparable) o1).compareTo(o2);
- } else if (o1 instanceof List && o2 instanceof List) {
- return compareLists((List<?>) o1, (List<?>) o2);
- } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
- return compareObjectArrays((Object[]) o1, (Object[]) o2);
- } else {
- throw new IllegalArgumentException("Item types do not match: "
- + o1.getClass() + " vs " + o2.getClass());
- }
+ return compareListItems(o1, o2);
}
}
@@ -602,17 +574,7 @@ private static class NullsFirstReverseComparator
if (o2 == null) {
return 1;
}
- if (o1 instanceof Comparable && o2 instanceof Comparable) {
- //noinspection unchecked
- return -((Comparable) o1).compareTo(o2);
- } else if (o1 instanceof List && o2 instanceof List) {
- return -compareLists((List<?>) o1, (List<?>) o2);
- } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
- return -compareObjectArrays((Object[]) o1, (Object[]) o2);
- } else {
- throw new IllegalArgumentException("Item types do not match: "
- + o1.getClass() + " vs " + o2.getClass());
- }
+ return -compareListItems(o1, o2);
}
}
@@ -667,6 +629,13 @@ public static int compareMaps(Map<?, ?> b0, Map<?, ?> b1) {
return 0;
}
+ private static BigDecimal toBigDecimal(Number number) {
+ return number instanceof BigDecimal ? (BigDecimal) number
+ : number instanceof BigInteger ? new BigDecimal((BigInteger) number)
+ : number instanceof Long ? new BigDecimal(number.longValue())
+ : new BigDecimal(number.doubleValue());
+ }
+
private static int compareListItems(@Nullable Object item0, @Nullable Object
item1) {
if (item0 == item1) {
return 0;
@@ -684,13 +653,24 @@ private static int compareListItems(@Nullable Object
item0, @Nullable Object ite
return compareMaps((Map) item0, (Map) item1);
} else if (item0 instanceof Object[] && item1 instanceof Object[]) {
return compareObjectArrays((Object[]) item0, (Object[]) item1);
- } else if (item0.getClass().equals(item1.getClass()) && item0 instanceof
Comparable<?>) {
- final Comparable b0Comparable = (Comparable) item0;
- final Comparable b1Comparable = (Comparable) item1;
- return b0Comparable.compareTo(b1Comparable);
+ } else if (item0 instanceof Number && item1 instanceof Number) {
+ final BigDecimal d0 = toBigDecimal((Number) item0);
+ final BigDecimal d1 = toBigDecimal((Number) item1);
+ return d0.compareTo(d1);
+ } else if (item0.getClass().equals(item1.getClass())) {
+ if (item0 instanceof Comparable<?>) {
+ final Comparable b0Comparable = (Comparable) item0;
+ final Comparable b1Comparable = (Comparable) item1;
+ //noinspection unchecked
+ return b0Comparable.compareTo(b1Comparable);
+ }
+ return Objects.equals(item0, item1)
+ ? 0
+ : Integer.compare(System.identityHashCode(item0),
System.identityHashCode(item1));
} else {
- throw new IllegalArgumentException("Item types do not match: "
- + item0.getClass() + " vs " + item1.getClass());
+ // comparison between objects with different types are possible,
+ // and they always return false
+ return item0.getClass().getName().compareTo(item1.getClass().getName());
}
}
@@ -721,17 +701,7 @@ private static class NullsLastReverseComparator
if (o2 == null) {
return -1;
}
- if (o1 instanceof Comparable && o2 instanceof Comparable) {
- //noinspection unchecked
- return -((Comparable) o1).compareTo(o2);
- } else if (o1 instanceof List && o2 instanceof List) {
- return -compareLists((List<?>) o1, (List<?>) o2);
- } else if (o1 instanceof Object[] && o2 instanceof Object[]) {
- return -compareObjectArrays((Object[]) o1, (Object[]) o2);
- } else {
- throw new IllegalArgumentException("Item types do not match: "
- + o1.getClass() + " vs " + o2.getClass());
- }
+ return -compareListItems(o1, o2);
}
}