fix(java): include baseId in DataFile/DeletionFile equals and add hashCode - #9686
zhangyue19921010 merged 2 commits into
Conversation
…hCode DataFile and DeletionFile overrode equals() without hashCode(), so equal instances got identity hash codes. FragmentMetadata.hashCode() hashes both, which made equal FragmentMetadata objects hash differently and miss in HashSet/HashMap lookups. Both equals() methods also ignored baseId, so the same relative path under two different bases compared equal. The Rust DataFile and DeletionFile derive PartialEq over all fields, including base_id. DataOverlayFile.hashCode() now delegates to DataFile.hashCode() instead of hashing DataFile fields by hand.
zhangyue19921010
left a comment
There was a problem hiding this comment.
Over all LGTM.
Non-blocking suggestion: the equal-object hash-code tests currently only cover baseId = null. Could we also add cases for both DataFile and DeletionFile with the same non-null baseId.
In addition, can we simplify the test cases and only add and merge necessary test cases?
|
Thanks @zhangyue19921010, done in 1752093. The tests are now three:
All three fail without the fix and pass with it. |
There was a problem hiding this comment.
✅ Gate recommendation: approve.
The consolidated tests now cover equal copies with null and non-null base IDs and retain the fragment HashSet regression. They pass on this head; the unchanged fix restores hash-based fragment lookups and distinguishes files across bases.
zhangyue19921010
left a comment
There was a problem hiding this comment.
+1 Thanks for this PR.
Fixes #9646
What
DataFile:equals()now comparesbaseId, and a matchinghashCode()is added (it usesArrays.hashCodeforfieldsandcolumnIndices, which mirrorsArrays.equalsinequals()).DeletionFile:equals()now comparesbaseId, and a matchinghashCode()is added.DataOverlay.DataOverlayFile.hashCode()now delegates todataFile.hashCode()instead of hashingDataFile's fields by hand.Why
DataFileandDeletionFileoverrodeequals()withouthashCode(), so equal instances had identity hash codes.FragmentMetadata.hashCode()hashesfilesanddeletionFile, so equalFragmentMetadataobjects also hashed differently. AHashSet/HashMaplookup with a separately built copy, such as a fragment deserialized on another JVM, missed.Leaving
baseIdout ofequals()let the same relative path under two different bases compare equal, even though the files resolve to different locations. The RustDataFileandDeletionFilederivePartialEq/Eqover all fields,base_idincluded, so this change brings Java in line with them.Tests
New
java/src/test/java/org/lance/fragment/DataFileEqualityTest.java(pure Java, no JNI needed):mainwithout the fix:Tests run: 5, Failures: 5(hash codes differ for equal objects,baseId=1vsbaseId=2compare equal,HashSet.containsreturns false).Tests run: 5, Failures: 0, Errors: 0../mvnw spotless:checkandcheckstyle:checkpass. I could not run the JNI-backed suites locally (no Rust toolchain). Those suites compare round-trippedDataFilevalues that come from Rust, which already includesbase_idin equality, so this change should not affect them. CI will confirm.Out of scope: several
Operationclasses (Append,Delete,Overwrite,Restore,ReserveFragments,SchemaOperation) also overrideequals()withouthashCode(). I can follow up on those separately if that is wanted.AI assistance: this change was drafted with an AI coding assistant (Claude) and verified locally with the tests above.