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>&lt;gt;</code> operator applied to List values. */
+  public static boolean ne(List<?> b0, List<?> b1) {
+    return !eqNullable(b0, b1);
+  }
+
+  /** SQL <code>&lt;gt;</code> operator applied to Map values. */
+  public static boolean ne(Map<?, ?> b0, Map<?, ?> b1) {
+    return !eqNullable(b0, b1);
+  }
+
+  /** SQL <code>&lt;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>&lt;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>&lt;</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>&lt;</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>&le;</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>&le;</code> operator applied to Map values. */
+  public static boolean le(Map<?, ?> b0, Map<?, ?> b1) {
+    return compareNullable(b0, b1, "<=") <= 0;
   }
 
   /** SQL <code>&le;</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>&le;</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>&gt;</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>&ge;</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>&ge;</code> operator applied to Map values. */
+  public static boolean ge(Map<?, ?> b0, Map<?, ?> b1) {
+    return compareNullable(b0, b1, ">=") >= 0;
   }
 
   /** SQL <code>&ge;</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>&ge;</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);
     }
   }
 

Reply via email to