Skip to content

fix(java): include baseId in DataFile/DeletionFile equals and add hashCode - #9686

Merged
zhangyue19921010 merged 2 commits into
lance-format:mainfrom
Zhuoxi2000:fix-java-datafile-equals-hashcode
Oct 3, 2026
Merged

zhangyue19921010 merged 2 commits into
lance-format:mainfrom
Zhuoxi2000:fix-java-datafile-equals-hashcode

Conversation

@Zhuoxi2000

Copy link
Copy Markdown
Contributor

Fixes #9646

What

  • DataFile: equals() now compares baseId, and a matching hashCode() is added (it uses Arrays.hashCode for fields and columnIndices, which mirrors Arrays.equals in equals()).
  • DeletionFile: equals() now compares baseId, and a matching hashCode() is added.
  • DataOverlay.DataOverlayFile.hashCode() now delegates to dataFile.hashCode() instead of hashing DataFile's fields by hand.

Why

DataFile and DeletionFile overrode equals() without hashCode(), so equal instances had identity hash codes. FragmentMetadata.hashCode() hashes files and deletionFile, so equal FragmentMetadata objects also hashed differently. A HashSet/HashMap lookup with a separately built copy, such as a fragment deserialized on another JVM, missed.

Leaving baseId out of equals() let the same relative path under two different bases compare equal, even though the files resolve to different locations. The Rust DataFile and DeletionFile derive PartialEq/Eq over all fields, base_id included, 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):

cd java && ./mvnw -Dskip.build.jni=true test -Dtest=DataFileEqualityTest -Dsurefire.failIfNoSpecifiedTests=false
  • On main without the fix: Tests run: 5, Failures: 5 (hash codes differ for equal objects, baseId=1 vs baseId=2 compare equal, HashSet.contains returns false).
  • With the fix: Tests run: 5, Failures: 0, Errors: 0.

./mvnw spotless:check and checkstyle:check pass. I could not run the JNI-backed suites locally (no Rust toolchain). Those suites compare round-tripped DataFile values that come from Rust, which already includes base_id in equality, so this change should not affect them. CI will confirm.

Out of scope: several Operation classes (Append, Delete, Overwrite, Restore, ReserveFragments, SchemaOperation) also override equals() without hashCode(). 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.

…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.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions github-actions Bot added A-java Java bindings + JNI bug Something isn't working labels Oct 1, 2026
lance-gatekeeper[bot]

This comment was marked as outdated.

@lance-gatekeeper lance-gatekeeper Bot added the K-approved Latest Gatekeeper recommendation permits acceptance. label Oct 2, 2026

@zhangyue19921010 zhangyue19921010 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@zhangyue19921010 zhangyue19921010 self-assigned this Oct 2, 2026
@Zhuoxi2000

Copy link
Copy Markdown
Contributor Author

Thanks @zhangyue19921010, done in 1752093. The tests are now three:

  • testDataFileEqualsAndHashCodeIncludeBaseId: equal objects have equal hash codes with baseId null and non-null, and different bases are not equal.
  • testDeletionFileEqualsAndHashCodeIncludeBaseId: the same checks for DeletionFile.
  • testEqualFragmentMetadataWorksInHashSet: the lookup that originally broke.

All three fail without the fix and pass with it. spotless:check and checkstyle:check are clean.

@lance-gatekeeper lance-gatekeeper Bot removed the K-approved Latest Gatekeeper recommendation permits acceptance. label Oct 2, 2026

@lance-gatekeeper lance-gatekeeper Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@lance-gatekeeper lance-gatekeeper Bot added the K-approved Latest Gatekeeper recommendation permits acceptance. label Oct 2, 2026

@zhangyue19921010 zhangyue19921010 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Thanks for this PR.

@zhangyue19921010
zhangyue19921010 merged commit 7b97db7 into lance-format:main Oct 3, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-java Java bindings + JNI bug Something isn't working K-approved Latest Gatekeeper recommendation permits acceptance.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Java: DataFile/DeletionFile override equals() without hashCode() and ignore baseId

2 participants