This is an automated email from the ASF dual-hosted git repository.

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 8b51ac5bc6 [codegen] Fix floating point record equaliser semantics 
(#8534)
8b51ac5bc6 is described below

commit 8b51ac5bc60c533e8ee7ecdfa4b92d5925b60269
Author: Eunbin Son <[email protected]>
AuthorDate: Mon Jul 13 12:20:06 2026 +0900

    [codegen] Fix floating point record equaliser semantics (#8534)
---
 .../paimon/codegen/EqualiserCodeGenerator.scala    |  20 ++-
 .../apache/paimon/codegen/ScalarOperatorGens.scala | 171 +++++++++++++++++----
 .../paimon/codegen/EqualiserCodeGeneratorTest.java | 156 +++++++++++++++++++
 3 files changed, 310 insertions(+), 37 deletions(-)

diff --git 
a/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
 
b/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
index 314734adb9..0585bf4a05 100644
--- 
a/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
+++ 
b/paimon-codegen/src/main/scala/org/apache/paimon/codegen/EqualiserCodeGenerator.scala
@@ -21,10 +21,12 @@ package org.apache.paimon.codegen
 import org.apache.paimon.codegen.GenerateUtils._
 import org.apache.paimon.codegen.ScalarOperatorGens.{generateEquals, 
generateRowEqualiser}
 import org.apache.paimon.types.{BooleanType, DataType}
-import org.apache.paimon.types.DataTypeChecks.isCompositeType
+import org.apache.paimon.types.DataTypeChecks.{getNestedTypes, isCompositeType}
 import org.apache.paimon.types.DataTypeRoot._
 import org.apache.paimon.utils.TypeUtils.isPrimitive
 
+import scala.collection.JavaConverters._
+
 class EqualiserCodeGenerator(fieldTypes: Array[DataType], fields: Array[Int]) {
 
   private val RECORD_EQUALISER = className[RecordEqualiser]
@@ -41,6 +43,7 @@ class EqualiserCodeGenerator(fieldTypes: Array[DataType], 
fields: Array[Int]) {
     val className = newName(name)
 
     val containsIgnoreFields = fieldTypes.length > fields.length
+    val supportsBinaryRowEquals = !fields.exists(idx => 
containsFloatingPoint(fieldTypes(idx)))
     val equalsMethodCodes = for (idx <- fields) yield 
generateEqualsMethod(ctx, idx)
     val equalsMethodCalls = for (idx <- fields) yield {
       val methodName = getEqualsMethodName(idx)
@@ -58,7 +61,7 @@ class EqualiserCodeGenerator(fieldTypes: Array[DataType], 
fields: Array[Int]) {
 
           @Override
           public boolean equals($ROW_DATA $LEFT_INPUT, $ROW_DATA $RIGHT_INPUT) 
{
-            if ($LEFT_INPUT instanceof $BINARY_ROW && $RIGHT_INPUT instanceof 
$BINARY_ROW && !$containsIgnoreFields) {
+            if ($LEFT_INPUT instanceof $BINARY_ROW && $RIGHT_INPUT instanceof 
$BINARY_ROW && !$containsIgnoreFields && $supportsBinaryRowEquals) {
               return $LEFT_INPUT.equals($RIGHT_INPUT);
             }
 
@@ -132,7 +135,11 @@ class EqualiserCodeGenerator(fieldTypes: Array[DataType], 
fields: Array[Int]) {
       rightFieldTerm: String,
       leftNullTerm: String,
       rightNullTerm: String) = {
-    if (isInternalPrimitive(fieldType)) {
+    if (fieldType.getTypeRoot == FLOAT) {
+      ("", s"java.lang.Float.compare($leftFieldTerm, $rightFieldTerm) == 0")
+    } else if (fieldType.getTypeRoot == DOUBLE) {
+      ("", s"java.lang.Double.compare($leftFieldTerm, $rightFieldTerm) == 0")
+    } else if (isInternalPrimitive(fieldType)) {
       ("", s"$leftFieldTerm == $rightFieldTerm")
     } else if (isCompositeType(fieldType)) {
       val equaliserTerm = generateRowEqualiser(ctx, fieldType)
@@ -153,4 +160,11 @@ class EqualiserCodeGenerator(fieldTypes: Array[DataType], 
fields: Array[Int]) {
 
     case _ => false
   }
+
+  private def containsFloatingPoint(t: DataType): Boolean = t.getTypeRoot 
match {
+    case FLOAT | DOUBLE => true
+    case ARRAY | MAP | MULTISET | ROW | VECTOR =>
+      getNestedTypes(t).asScala.exists(containsFloatingPoint)
+    case _ => false
+  }
 }
diff --git 
a/paimon-codegen/src/main/scala/org/apache/paimon/codegen/ScalarOperatorGens.scala
 
b/paimon-codegen/src/main/scala/org/apache/paimon/codegen/ScalarOperatorGens.scala
index 46217b1c4c..fb8a044049 100644
--- 
a/paimon-codegen/src/main/scala/org/apache/paimon/codegen/ScalarOperatorGens.scala
+++ 
b/paimon-codegen/src/main/scala/org/apache/paimon/codegen/ScalarOperatorGens.scala
@@ -21,7 +21,8 @@ package org.apache.paimon.codegen
 import org.apache.paimon.codegen.GenerateUtils._
 import org.apache.paimon.data.serializer.InternalMapSerializer
 import org.apache.paimon.types._
-import org.apache.paimon.types.DataTypeChecks.{getFieldTypes, isCompositeType}
+import org.apache.paimon.types.DataTypeChecks.{getFieldTypes, getNestedTypes, 
isCompositeType}
+import org.apache.paimon.types.DataTypeRoot.{DOUBLE, FLOAT}
 import org.apache.paimon.utils.InternalRowUtils
 import org.apache.paimon.utils.TypeCheckUtils._
 import org.apache.paimon.utils.TypeUtils.isInteroperable
@@ -50,7 +51,13 @@ object ScalarOperatorGens {
           s" is not supported now")
     }
     val canEqual = isInteroperable(left.resultType, right.resultType)
-    if (isCharacterString(left.resultType) && 
isCharacterString(right.resultType)) {
+    if (left.resultType.getTypeRoot == FLOAT && right.resultType.getTypeRoot 
== FLOAT) {
+      generateOperatorIfNotNull(ctx, resultType, left, right)(
+        (leftTerm, rightTerm) => s"java.lang.Float.compare($leftTerm, 
$rightTerm) == 0")
+    } else if (left.resultType.getTypeRoot == DOUBLE && 
right.resultType.getTypeRoot == DOUBLE) {
+      generateOperatorIfNotNull(ctx, resultType, left, right)(
+        (leftTerm, rightTerm) => s"java.lang.Double.compare($leftTerm, 
$rightTerm) == 0")
+    } else if (isCharacterString(left.resultType) && 
isCharacterString(right.resultType)) {
       generateOperatorIfNotNull(ctx, resultType, left, right)(
         (leftTerm, rightTerm) => s"$leftTerm.equals($rightTerm)")
     }
@@ -232,10 +239,11 @@ object ScalarOperatorGens {
           new BooleanType(elementType.isNullable))
 
         // TODO: With BinaryVector available, we can use it here.
+        val supportsBinaryArrayEquals = !containsFloatingPoint(elementType)
         val stmt =
           s"""
              |boolean $resultTerm;
-             |if ($leftTerm instanceof $BINARY_ARRAY && $rightTerm instanceof 
$BINARY_ARRAY) {
+             |if ($leftTerm instanceof $BINARY_ARRAY && $rightTerm instanceof 
$BINARY_ARRAY && $supportsBinaryArrayEquals) {
              |  $resultTerm = $leftTerm.equals($rightTerm);
              |} else {
              |  if ($leftTerm.size() == $rightTerm.size()) {
@@ -290,12 +298,19 @@ object ScalarOperatorGens {
 
         val leftMapTerm = newName("leftMap")
         val leftKeyTerm = newName("leftKey")
+        val leftKeyNullTerm = newName("leftKeyIsNull")
+        val leftKeyExpr =
+          GeneratedExpression(leftKeyTerm, leftKeyNullTerm, "", keyType)
         val leftValueTerm = newName("leftValue")
         val leftValueNullTerm = newName("leftValueIsNull")
         val leftValueExpr =
           GeneratedExpression(leftValueTerm, leftValueNullTerm, "", valueType)
 
         val rightMapTerm = newName("rightMap")
+        val rightKeyTerm = newName("rightKey")
+        val rightKeyNullTerm = newName("rightKeyIsNull")
+        val rightKeyExpr =
+          GeneratedExpression(rightKeyTerm, rightKeyNullTerm, "", keyType)
         val rightValueTerm = newName("rightValue")
         val rightValueNullTerm = newName("rightValueIsNull")
         val rightValueExpr =
@@ -305,6 +320,8 @@ object ScalarOperatorGens {
         val entryCls = classOf[java.util.Map.Entry[AnyRef, 
AnyRef]].getCanonicalName
         val valueEqualsExpr =
           generateEquals(ctx, leftValueExpr, rightValueExpr, new 
BooleanType(valueType.isNullable))
+        val keyEqualsExpr =
+          generateEquals(ctx, leftKeyExpr, rightKeyExpr, new 
BooleanType(keyType.isNullable))
 
         val internalTypeCls = classOf[DataType].getCanonicalName
         val keyTypeTerm = ctx.addReusableObject(keyType, "keyType", 
internalTypeCls)
@@ -312,37 +329,115 @@ object ScalarOperatorGens {
         val mapDataUtil = className[InternalMapSerializer]
 
         val stmt =
-          s"""
-             |boolean $resultTerm;
-             |if ($leftTerm.size() == $rightTerm.size()) {
-             |  $resultTerm = true;
-             |  $mapCls $leftMapTerm = $mapDataUtil
-             |      .convertToJavaMap($leftTerm, $keyTypeTerm, $valueTypeTerm);
-             |  $mapCls $rightMapTerm = $mapDataUtil
-             |      .convertToJavaMap($rightTerm, $keyTypeTerm, 
$valueTypeTerm);
-             |
-             |  for ($entryCls $entryTerm : $leftMapTerm.entrySet()) {
-             |    $keyCls $leftKeyTerm = ($keyCls) $entryTerm.getKey();
-             |    if ($rightMapTerm.containsKey($leftKeyTerm)) {
-             |      $valueCls $leftValueTerm = ($valueCls) 
$entryTerm.getValue();
-             |      $valueCls $rightValueTerm = ($valueCls) 
$rightMapTerm.get($leftKeyTerm);
-             |      boolean $leftValueNullTerm = ($leftValueTerm == null);
-             |      boolean $rightValueNullTerm = ($rightValueTerm == null);
-             |
-             |      ${valueEqualsExpr.code}
-             |      if (!${valueEqualsExpr.resultTerm}) {
-             |        $resultTerm = false;
-             |        break;
-             |      }
-             |    } else {
-             |      $resultTerm = false;
-             |      break;
-             |    }
-             |  }
-             |} else {
-             |  $resultTerm = false;
-             |}
-             """.stripMargin
+          if (containsFloatingPoint(keyType)) {
+            val leftKeyArrayTerm = newName("leftKeyArray")
+            val rightKeyArrayTerm = newName("rightKeyArray")
+            val leftValueArrayTerm = newName("leftValueArray")
+            val rightValueArrayTerm = newName("rightValueArray")
+            val matchedTerm = newName("matched")
+            val leftIndexTerm = newName("leftIndex")
+            val rightIndexTerm = newName("rightIndex")
+            val foundTerm = newName("found")
+
+            s"""
+               |boolean $resultTerm;
+               |if ($leftTerm.size() == $rightTerm.size()) {
+               |  $resultTerm = true;
+               |  $ARRAY_DATA $leftKeyArrayTerm = $leftTerm.keyArray();
+               |  $ARRAY_DATA $rightKeyArrayTerm = $rightTerm.keyArray();
+               |  $ARRAY_DATA $leftValueArrayTerm = $leftTerm.valueArray();
+               |  $ARRAY_DATA $rightValueArrayTerm = $rightTerm.valueArray();
+               |  boolean[] $matchedTerm = new boolean[$rightTerm.size()];
+               |
+               |  for (int $leftIndexTerm = 0; $leftIndexTerm < 
$leftTerm.size(); $leftIndexTerm++) {
+               |    $keyCls $leftKeyTerm = null;
+               |    boolean $leftKeyNullTerm = 
$leftKeyArrayTerm.isNullAt($leftIndexTerm);
+               |    if (!$leftKeyNullTerm) {
+               |      $leftKeyTerm =
+               |        ${rowFieldReadAccess(leftIndexTerm, leftKeyArrayTerm, 
keyType)};
+               |    }
+               |    boolean $foundTerm = false;
+               |
+               |    for (int $rightIndexTerm = 0; $rightIndexTerm < 
$rightTerm.size(); $rightIndexTerm++) {
+               |      if ($matchedTerm[$rightIndexTerm]) {
+               |        continue;
+               |      }
+               |
+               |      $keyCls $rightKeyTerm = null;
+               |      boolean $rightKeyNullTerm = 
$rightKeyArrayTerm.isNullAt($rightIndexTerm);
+               |      if (!$rightKeyNullTerm) {
+               |        $rightKeyTerm =
+               |          ${rowFieldReadAccess(rightIndexTerm, 
rightKeyArrayTerm, keyType)};
+               |      }
+               |
+               |      ${keyEqualsExpr.code}
+               |      if (($leftKeyNullTerm && $rightKeyNullTerm) ||
+               |          (!$leftKeyNullTerm && !$rightKeyNullTerm && 
${keyEqualsExpr.resultTerm})) {
+               |        $valueCls $leftValueTerm = null;
+               |        boolean $leftValueNullTerm = 
$leftValueArrayTerm.isNullAt($leftIndexTerm);
+               |        if (!$leftValueNullTerm) {
+               |          $leftValueTerm =
+               |            ${rowFieldReadAccess(leftIndexTerm, 
leftValueArrayTerm, valueType)};
+               |        }
+               |
+               |        $valueCls $rightValueTerm = null;
+               |        boolean $rightValueNullTerm = 
$rightValueArrayTerm.isNullAt($rightIndexTerm);
+               |        if (!$rightValueNullTerm) {
+               |          $rightValueTerm =
+               |            ${rowFieldReadAccess(rightIndexTerm, 
rightValueArrayTerm, valueType)};
+               |        }
+               |
+               |        ${valueEqualsExpr.code}
+               |        if (${valueEqualsExpr.resultTerm}) {
+               |          $matchedTerm[$rightIndexTerm] = true;
+               |          $foundTerm = true;
+               |        }
+               |        break;
+               |      }
+               |    }
+               |
+               |    if (!$foundTerm) {
+               |      $resultTerm = false;
+               |      break;
+               |    }
+               |  }
+               |} else {
+               |  $resultTerm = false;
+               |}
+               """.stripMargin
+          } else {
+            s"""
+               |boolean $resultTerm;
+               |if ($leftTerm.size() == $rightTerm.size()) {
+               |  $resultTerm = true;
+               |  $mapCls $leftMapTerm = $mapDataUtil
+               |      .convertToJavaMap($leftTerm, $keyTypeTerm, 
$valueTypeTerm);
+               |  $mapCls $rightMapTerm = $mapDataUtil
+               |      .convertToJavaMap($rightTerm, $keyTypeTerm, 
$valueTypeTerm);
+               |
+               |  for ($entryCls $entryTerm : $leftMapTerm.entrySet()) {
+               |    $keyCls $leftKeyTerm = ($keyCls) $entryTerm.getKey();
+               |    if ($rightMapTerm.containsKey($leftKeyTerm)) {
+               |      $valueCls $leftValueTerm = ($valueCls) 
$entryTerm.getValue();
+               |      $valueCls $rightValueTerm = ($valueCls) 
$rightMapTerm.get($leftKeyTerm);
+               |      boolean $leftValueNullTerm = ($leftValueTerm == null);
+               |      boolean $rightValueNullTerm = ($rightValueTerm == null);
+               |
+               |      ${valueEqualsExpr.code}
+               |      if (!${valueEqualsExpr.resultTerm}) {
+               |        $resultTerm = false;
+               |        break;
+               |      }
+               |    } else {
+               |      $resultTerm = false;
+               |      break;
+               |    }
+               |  }
+               |} else {
+               |  $resultTerm = false;
+               |}
+               """.stripMargin
+          }
         (stmt, resultTerm)
     }
 
@@ -361,5 +456,13 @@ object ScalarOperatorGens {
     }
   }
 
+  private def containsFloatingPoint(t: DataType): Boolean = t.getTypeRoot 
match {
+    case FLOAT | DOUBLE => true
+    case DataTypeRoot.ARRAY | DataTypeRoot.MAP | DataTypeRoot.MULTISET | 
DataTypeRoot.ROW |
+        DataTypeRoot.VECTOR =>
+      getNestedTypes(t).asScala.exists(containsFloatingPoint)
+    case _ => false
+  }
+
   // 
----------------------------------------------------------------------------------------------
 }
diff --git 
a/paimon-codegen/src/test/java/org/apache/paimon/codegen/EqualiserCodeGeneratorTest.java
 
b/paimon-codegen/src/test/java/org/apache/paimon/codegen/EqualiserCodeGeneratorTest.java
index 7e977291e8..6bbb4f7651 100644
--- 
a/paimon-codegen/src/test/java/org/apache/paimon/codegen/EqualiserCodeGeneratorTest.java
+++ 
b/paimon-codegen/src/test/java/org/apache/paimon/codegen/EqualiserCodeGeneratorTest.java
@@ -18,18 +18,21 @@
 
 package org.apache.paimon.codegen;
 
+import org.apache.paimon.data.BinaryArray;
 import org.apache.paimon.data.BinaryRow;
 import org.apache.paimon.data.BinaryRowWriter;
 import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.BinaryWriter;
 import org.apache.paimon.data.BlobData;
 import org.apache.paimon.data.Decimal;
+import org.apache.paimon.data.GenericArray;
 import org.apache.paimon.data.GenericMap;
 import org.apache.paimon.data.GenericRow;
 import org.apache.paimon.data.Timestamp;
 import org.apache.paimon.data.serializer.InternalArraySerializer;
 import org.apache.paimon.data.serializer.InternalMapSerializer;
 import org.apache.paimon.data.serializer.InternalRowSerializer;
+import org.apache.paimon.data.serializer.InternalSerializers;
 import org.apache.paimon.data.serializer.InternalVectorSerializer;
 import org.apache.paimon.data.serializer.Serializer;
 import org.apache.paimon.data.variant.GenericVariant;
@@ -41,6 +44,7 @@ import org.apache.paimon.types.VarCharType;
 import org.apache.paimon.utils.Pair;
 
 import org.junit.jupiter.api.RepeatedTest;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.EnumSource;
 
@@ -256,6 +260,158 @@ public class EqualiserCodeGeneratorTest {
         assertThat(result).isEqualTo(expected);
     }
 
+    @Test
+    public void testFloatingPointEqualiserMatchesJdkComparison() {
+        RecordEqualiser doubleEqualiser =
+                new EqualiserCodeGenerator(new DataType[] {DataTypes.DOUBLE()})
+                        .generateRecordEqualiser("doubleFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        double[] doubles = {
+            Double.NEGATIVE_INFINITY, -1.0d, -0.0d, 0.0d, 1.0d, 
Double.POSITIVE_INFINITY, Double.NaN
+        };
+        for (double a : doubles) {
+            for (double b : doubles) {
+                assertFloatingPointEqualiser(
+                        doubleEqualiser, DataTypes.DOUBLE(), a, b, 
Double.compare(a, b) == 0);
+            }
+        }
+        assertFloatingPointEqualiser(
+                doubleEqualiser,
+                DataTypes.DOUBLE(),
+                Double.longBitsToDouble(0x7ff8000000000001L),
+                Double.NaN,
+                true);
+
+        RecordEqualiser floatEqualiser =
+                new EqualiserCodeGenerator(new DataType[] {DataTypes.FLOAT()})
+                        .generateRecordEqualiser("floatFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        float[] floats = {
+            Float.NEGATIVE_INFINITY, -1.0f, -0.0f, 0.0f, 1.0f, 
Float.POSITIVE_INFINITY, Float.NaN
+        };
+        for (float a : floats) {
+            for (float b : floats) {
+                assertFloatingPointEqualiser(
+                        floatEqualiser, DataTypes.FLOAT(), a, b, 
Float.compare(a, b) == 0);
+            }
+        }
+        assertFloatingPointEqualiser(
+                floatEqualiser,
+                DataTypes.FLOAT(),
+                Float.intBitsToFloat(0x7fc00001),
+                Float.NaN,
+                true);
+
+        RecordEqualiser doubleArrayEqualiser =
+                new EqualiserCodeGenerator(new DataType[] 
{DataTypes.ARRAY(DataTypes.DOUBLE())})
+                        .generateRecordEqualiser("doubleArrayFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        assertFloatingPointEqualiser(
+                doubleArrayEqualiser,
+                DataTypes.ARRAY(DataTypes.DOUBLE()),
+                BinaryArray.fromPrimitiveArray(
+                        new double[] 
{Double.longBitsToDouble(0x7ff8000000000001L)}),
+                BinaryArray.fromPrimitiveArray(new double[] {Double.NaN}),
+                true);
+
+        RecordEqualiser floatArrayEqualiser =
+                new EqualiserCodeGenerator(new DataType[] 
{DataTypes.ARRAY(DataTypes.FLOAT())})
+                        .generateRecordEqualiser("floatArrayFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        assertFloatingPointEqualiser(
+                floatArrayEqualiser,
+                DataTypes.ARRAY(DataTypes.FLOAT()),
+                BinaryArray.fromPrimitiveArray(new float[] 
{Float.intBitsToFloat(0x7fc00001)}),
+                BinaryArray.fromPrimitiveArray(new float[] {Float.NaN}),
+                true);
+
+        DataType doubleArrayType = DataTypes.ARRAY(DataTypes.DOUBLE());
+        RecordEqualiser nestedDoubleArrayEqualiser =
+                new EqualiserCodeGenerator(new DataType[] 
{DataTypes.ARRAY(doubleArrayType)})
+                        
.generateRecordEqualiser("nestedDoubleArrayFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        assertFloatingPointEqualiser(
+                nestedDoubleArrayEqualiser,
+                DataTypes.ARRAY(doubleArrayType),
+                new GenericArray(
+                        new Object[] {
+                            BinaryArray.fromPrimitiveArray(
+                                    new double[] 
{Double.longBitsToDouble(0x7ff8000000000001L)})
+                        }),
+                new GenericArray(
+                        new Object[] {BinaryArray.fromPrimitiveArray(new 
double[] {Double.NaN})}),
+                true);
+
+        DataType doubleRowType = DataTypes.ROW(DataTypes.DOUBLE());
+        RecordEqualiser doubleRowArrayEqualiser =
+                new EqualiserCodeGenerator(new DataType[] 
{DataTypes.ARRAY(doubleRowType)})
+                        .generateRecordEqualiser("doubleRowArrayFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        assertFloatingPointEqualiser(
+                doubleRowArrayEqualiser,
+                DataTypes.ARRAY(doubleRowType),
+                new GenericArray(
+                        new Object[] 
{GenericRow.of(Double.longBitsToDouble(0x7ff8000000000001L))}),
+                new GenericArray(new Object[] {GenericRow.of(Double.NaN)}),
+                true);
+
+        DataType doubleRowMapType = DataTypes.MAP(doubleRowType, 
DataTypes.INT());
+        RecordEqualiser doubleRowMapEqualiser =
+                new EqualiserCodeGenerator(new DataType[] {doubleRowMapType})
+                        .generateRecordEqualiser("doubleRowMapFieldEquals")
+                        
.newInstance(Thread.currentThread().getContextClassLoader());
+        Map<Object, Object> leftMap = new HashMap<>();
+        
leftMap.put(GenericRow.of(Double.longBitsToDouble(0x7ff8000000000001L)), 1);
+        Map<Object, Object> rightMap = new HashMap<>();
+        rightMap.put(GenericRow.of(Double.NaN), 1);
+        assertFloatingPointEqualiser(
+                doubleRowMapEqualiser,
+                doubleRowMapType,
+                new GenericMap(leftMap),
+                new GenericMap(rightMap),
+                true);
+
+        Map<Object, Object> leftNullKeyMap = new HashMap<>();
+        leftNullKeyMap.put(null, 1);
+        Map<Object, Object> rightNullKeyMap = new HashMap<>();
+        rightNullKeyMap.put(null, 1);
+        assertFloatingPointEqualiser(
+                doubleRowMapEqualiser,
+                doubleRowMapType,
+                new GenericMap(leftNullKeyMap),
+                new GenericMap(rightNullKeyMap),
+                true);
+    }
+
+    private static void assertFloatingPointEqualiser(
+            RecordEqualiser equaliser,
+            DataType dataType,
+            Object left,
+            Object right,
+            boolean equal) {
+        Function<Object, BinaryRow> toBinaryRow =
+                value -> {
+                    BinaryRow row = new BinaryRow(1);
+                    BinaryRowWriter writer = new BinaryRowWriter(row);
+                    Serializer<?> serializer = 
InternalSerializers.create(dataType);
+                    BinaryWriter.write(writer, 0, value, dataType, serializer);
+                    writer.complete();
+                    return row;
+                };
+        assertThat(equaliser.equals(GenericRow.of(left), GenericRow.of(right)))
+                .as("equals(generic %s, generic %s)", left, right)
+                .isEqualTo(equal);
+        assertThat(equaliser.equals(toBinaryRow.apply(left), 
GenericRow.of(right)))
+                .as("equals(binary %s, generic %s)", left, right)
+                .isEqualTo(equal);
+        assertThat(equaliser.equals(GenericRow.of(left), 
toBinaryRow.apply(right)))
+                .as("equals(generic %s, binary %s)", left, right)
+                .isEqualTo(equal);
+        assertThat(equaliser.equals(toBinaryRow.apply(left), 
toBinaryRow.apply(right)))
+                .as("equals(binary %s, binary %s)", left, right)
+                .isEqualTo(equal);
+    }
+
     @RepeatedTest(100)
     public void testManyFields() {
         int size = 499;

Reply via email to