-
Notifications
You must be signed in to change notification settings - Fork 114
Fix ResolvedKeySpacePath.equals and hashCode #3591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f153d1e
f6bdc40
ffc000c
a84876b
c35d1d4
a070102
a64373e
0720af5
f2691b4
89174b8
98d4b03
6db80e8
e4fbffd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -276,27 +276,21 @@ public boolean equals(Object obj) { | |
} | ||
KeySpacePath that = (KeySpacePath) obj; | ||
|
||
// Check that the KeySpaceDirectories of the two paths are "equal enough". | ||
// Even this is probably overkill since the isCompatible check in KeySpaceDirectory | ||
// will keep us from doing anything too bad. We could move this check into KeySpaceDirectory | ||
// but comparing two directories by value would necessitate traversing the entire directory | ||
// tree, so instead we will use a narrower definition of equality here. | ||
boolean directoriesEqual = Objects.equals(this.getDirectory().getKeyType(), that.getDirectory().getKeyType()) && | ||
Objects.equals(this.getDirectory().getName(), that.getDirectory().getName()) && | ||
Objects.equals(this.getDirectory().getValue(), that.getDirectory().getValue()); | ||
// Directories use reference equality, because the expected usage is that they go into a | ||
// singleton KeySpace. | ||
boolean directoriesEqual = this.getDirectory().equals(that.getDirectory()); | ||
|
||
// the values might be byte[] | ||
return directoriesEqual && | ||
Objects.equals(this.getValue(), that.getValue()) && | ||
Objects.equals(this.getParent(), that.getParent()); | ||
KeySpaceDirectory.areEqual(this.getValue(), that.getValue()) && | ||
Objects.equals(this.getParent(), that.getParent()); | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Objects.hash( | ||
getDirectory().getKeyType(), | ||
getDirectory().getName(), | ||
getDirectory().getValue(), | ||
getValue(), | ||
getDirectory(), | ||
KeySpaceDirectory.valueHashCode(getValue()), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be the same as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Only if the directory is a constant. If the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case there may be a discrepancy where
Whereas
So it looks as if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, yes, that is probably true. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I spent a bit of time thinking about this, and I think, actually, the correct answer is to leave |
||
parent); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/* | ||
* PathValueTest.java | ||
* | ||
* This source file is part of the FoundationDB open source project | ||
* | ||
* Copyright 2015-2025 Apple Inc. and the FoundationDB project authors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.apple.foundationdb.record.provider.foundationdb.keyspace; | ||
|
||
import org.junit.jupiter.api.Test; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.Arguments; | ||
import org.junit.jupiter.params.provider.MethodSource; | ||
|
||
import java.util.stream.Stream; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertEquals; | ||
import static org.junit.jupiter.api.Assertions.assertNotEquals; | ||
|
||
/** | ||
* Tests for {@link PathValue}. | ||
*/ | ||
class PathValueTest { | ||
|
||
/** | ||
* Test data for PathValue equality tests. | ||
*/ | ||
static Stream<Arguments> equalPathValuePairs() { | ||
return Stream.of( | ||
Arguments.of("null values", null, null, null, null), | ||
Arguments.of("same string values", "test", null, "test", null), | ||
Arguments.of("same long values", 42L, null, 42L, null), | ||
Arguments.of("same boolean values", true, null, true, null), | ||
Arguments.of("same byte[] values", new byte[] {1, 2, 3}, null, new byte[] {1, 2, 3}, null), | ||
Arguments.of("same metadata", "test", new byte[]{1, 2, 3}, "test", new byte[]{1, 2, 3}) | ||
); | ||
} | ||
|
||
/** | ||
* Test data for PathValue inequality tests. | ||
*/ | ||
static Stream<Arguments> unequalPathValuePairs() { | ||
return Stream.of( | ||
Arguments.of("different string values", "test1", null, "test2", null), | ||
Arguments.of("different long values", 42L, null, 100L, null), | ||
Arguments.of("different boolean values", true, null, false, null), | ||
Arguments.of("different types", "string", null, 42L, null), | ||
Arguments.of("different metadata", "test", new byte[]{1, 2, 3}, "test", new byte[]{4, 5, 6}), | ||
Arguments.of("one null metadata", "test", new byte[]{1, 2, 3}, "test", null), | ||
Arguments.of("one null value", null, null, "test", null), | ||
Arguments.of("different value with same metadata", "test1", new byte[]{1, 2, 3}, "test2", new byte[]{1, 2, 3}) | ||
ohadzeliger marked this conversation as resolved.
Show resolved
Hide resolved
|
||
); | ||
} | ||
|
||
@ParameterizedTest(name = "{0}") | ||
@MethodSource("equalPathValuePairs") | ||
void testEqualsAndHashCodeForEqualValues(String description, Object resolvedValue1, byte[] metadata1, | ||
Object resolvedValue2, byte[] metadata2) { | ||
PathValue value1 = new PathValue(resolvedValue1, metadata1); | ||
PathValue value2 = new PathValue(resolvedValue2, metadata2); | ||
|
||
assertEquals(value1, value2, "PathValues should be equal: " + description); | ||
assertEquals(value1.hashCode(), value2.hashCode(), "Equal PathValues should have equal hash codes: " + description); | ||
} | ||
|
||
@ParameterizedTest(name = "{0}") | ||
@MethodSource("unequalPathValuePairs") | ||
void testNotEqualsForUnequalValues(String description, Object resolvedValue1, byte[] metadata1, | ||
Object resolvedValue2, byte[] metadata2) { | ||
PathValue value1 = new PathValue(resolvedValue1, metadata1); | ||
PathValue value2 = new PathValue(resolvedValue2, metadata2); | ||
|
||
assertNotEquals(value1, value2, "PathValues should not be equal: " + description); | ||
} | ||
|
||
@Test | ||
void testTrivialEquality() { | ||
PathValue value1 = new PathValue("Foo", null); | ||
|
||
assertEquals(value1, value1, "Cover reference equality shortcut"); | ||
assertNotEquals("Foo", value1, "Check it doesn't fail with non-PathValue"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a defensive coding we can add
equals
andhashCode
toAnyValue
, though all reasonable implementations should use the static constant...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but
AnyValue
is a private-nested class, soKeySpaceDirectory
is the only way to construct one, so you really shouldn't be accessing it other than via the constant.