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;