Skip to content

Commit 6789155

Browse files
committed
Update the hashCode logic to account for NaN equality
1 parent 74b01e5 commit 6789155

File tree

3 files changed

+37
-35
lines changed

3 files changed

+37
-35
lines changed

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Mutation.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,8 @@ public int hashCode() {
364364
* mutation equality to check for modifications before committing. We noticed that when NaNs where
365365
* used the template would always indicate a modification was present, when it turned out not to
366366
* be the case. For more information see b/206339664.
367+
*
368+
* <p>Similar change is being done while calculating `Value.hashCode()`.
367369
*/
368370
private boolean areValuesEqual(List<Value> values, List<Value> otherValues) {
369371
if (values == null && otherValues == null) {
@@ -386,8 +388,10 @@ private boolean areValuesEqual(List<Value> values, List<Value> otherValues) {
386388

387389
private boolean isNaN(Value value) {
388390
return !value.isNull()
389-
&& ((value.getType().equals(Type.float64()) && Double.isNaN(value.getFloat64()))
390-
|| (value.getType().equals(Type.float32()) && Float.isNaN(value.getFloat32())));
391+
&& ((value.getType().equals(Type.float64())
392+
&& (Double.isNaN(value.getFloat64()) || Float.isNaN((float) value.getFloat64())))
393+
|| (value.getType().equals(Type.float32())
394+
&& (Float.isNaN(value.getFloat32()) || Double.isNaN(value.getFloat32()))));
391395
}
392396

393397
static void toProto(Iterable<Mutation> mutations, List<com.google.spanner.v1.Mutation> out) {

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Value.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,9 +1375,29 @@ public final boolean equals(Object o) {
13751375

13761376
@Override
13771377
public final int hashCode() {
1378-
int result = Objects.hash(getType(), isNull);
1378+
Type typeToHash = getType();
1379+
int valueHash = isNull ? 0 : valueHash();
1380+
1381+
/**
1382+
* We are relaxing equality values here, making sure that Double.NaNs and Float.NaNs are equal
1383+
* to each other. This is because our Cloud Spanner Import / Export template in Apache Beam
1384+
* uses the mutation equality to check for modifications before committing. We noticed that
1385+
* when NaNs where used the template would always indicate a modification was present, when it
1386+
* turned out not to be the case.
1387+
*
1388+
* <p>With FLOAT32 being introduced, we want to ensure the backward compatibility of the NaN
1389+
* equality checks that existed for FLOAT64. We're promoting the type to FLOAT64 while
1390+
* calculating the type hash when the value is a NaN. We're doing a similar type promotion
1391+
* while calculating valueHash of Float32 type. Note that this is not applicable for composite
1392+
* types containing FLOAT32.
1393+
*/
1394+
if (type.getCode() == Type.Code.FLOAT32 && !isNull && Float.isNaN(getFloat32())) {
1395+
typeToHash = Type.float64();
1396+
}
1397+
1398+
int result = Objects.hash(typeToHash, isNull);
13791399
if (!isNull) {
1380-
result = 31 * result + valueHash();
1400+
result = 31 * result + valueHash;
13811401
}
13821402
return result;
13831403
}
@@ -1621,6 +1641,11 @@ boolean valueEquals(Value v) {
16211641

16221642
@Override
16231643
int valueHash() {
1644+
// For backward compatibility of NaN equality checks with Float64 NaNs.
1645+
// Refer the comment in `Value.hashCode()` for more details.
1646+
if (!isNull() && Float.isNaN(value)) {
1647+
return Double.valueOf(Double.NaN).hashCode();
1648+
}
16241649
return Float.valueOf(value).hashCode();
16251650
}
16261651
}

google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutationTest.java

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -211,40 +211,13 @@ public void equalsAndHashCode() {
211211
Mutation.delete("T1", KeySet.singleKey(Key.of("k"))), Mutation.delete("T1", Key.of("k")));
212212

213213
// Test NaNs
214-
// tester.addEqualityGroup(
215-
// // DO NOT SUBMIT
216-
// // DO NOT SUBMIT
217-
// // DO NOT SUBMIT
218-
// // DO NOT SUBMIT
219-
// // DO NOT SUBMIT
220-
// // DO NOT SUBMIT
221-
// // DO NOT SUBMIT
222-
// // DO NOT SUBMIT
223-
// // DO NOT SUBMIT
224-
// // DO NOT SUBMIT
225-
// // DO NOT SUBMIT
226-
// // DO NOT SUBMIT
227-
// // DO NOT SUBMIT
228-
// // DO NOT SUBMIT
229-
// // DO NOT SUBMIT
230-
// // DO NOT SUBMIT
231-
// // DO NOT SUBMIT
232-
// // DO NOT SUBMIT
233-
// // DO NOT SUBMIT
234-
// // DO NOT SUBMIT
235-
// // DO NOT SUBMIT
236-
// // DO NOT SUBMIT
237-
// // DO NOT SUBMIT
238-
// // DO NOT SUBMIT
239-
// // DO NOT SUBMIT
240-
// // DO NOT SUBMIT
241-
// // DO NOT SUBMIT
242-
// Mutation.newInsertBuilder("T1").set("C").to(Float.NaN).build(),
243-
// Mutation.newInsertBuilder("T1").set("C").to(Value.float32(Float.NaN)).build());
214+
// Refer the comment in `Value.hashCode()` for more details on NaN equality.
244215
tester.addEqualityGroup(
245216
Mutation.newInsertBuilder("T1").set("C").to(Double.NaN).build(),
246217
Mutation.newInsertBuilder("T1").set("C").to(Value.float64(Double.NaN)).build(),
247-
Mutation.newInsertBuilder("T1").set("C").to(Value.float64(Float.NaN)).build());
218+
Mutation.newInsertBuilder("T1").set("C").to(Float.NaN).build(),
219+
Mutation.newInsertBuilder("T1").set("C").to(Value.float64(Float.NaN)).build(),
220+
Mutation.newInsertBuilder("T1").set("C").to(Value.float32(Float.NaN)).build());
248221

249222
// Test NaN arrays
250223
tester.addEqualityGroup(

0 commit comments

Comments
 (0)