Skip to content

[CALCITE-7554] NlsString.compareTo inconsistent with equals/hashCode - #5141

Open
lanky228 wants to merge 1 commit into
apache:mainfrom
lanky228:calcite-7554
Open

[CALCITE-7554] NlsString.compareTo inconsistent with equals/hashCode#5141
lanky228 wants to merge 1 commit into
apache:mainfrom
lanky228:calcite-7554

Conversation

@lanky228

@lanky228 lanky228 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

What

NlsString.compareTo() only compared the decoded string value, ignoring charsetName, collation, and bytesValue. This made compareTo inconsistent with equals()/hashCode(), which consider all metadata fields.

Why It Matters

This violates the Java contract: when equals() == true, compareTo() == 0 must also hold. The bug caused TreeSet and TreeMap to silently collapse distinct NlsString values that shared the same decoded string but differed in charset or collation.

Example: ("hello", "LATIN1", null) and ("hello", "UTF-8", null) are not equal per equals(), but the old compareTo() returned 0 — causing a TreeSet to keep only one of them.

Changes

NlsString.java (+32 -2):

  • After comparing decoded string values (preserving the original collator branch), break ties by comparing:
    1. charsetNameComparator.nullsFirst(String::compareTo) for null safety
    2. collationSqlCollation does not implement Comparable, so toString() is used as proxy key
    3. bytesValueByteString implements Comparable<ByteString>, using Comparator.naturalOrder()
  • All comparisons use Comparator.nullsFirst() for null-safe handling (fields are @Nullable)
  • No new helper methods — standard library Comparator utilities only

UtilTest.java (+53):

  • testNlsStringCompareToConsistency(): 5 cases verifying compareTo/equals consistency:
    • Same text, different charset → not equal, compareTo != 0
    • Same text, different collation → not equal, compareTo != 0
    • Identical values → equal, compareTo == 0
    • Both null charset → equal, compareTo == 0
    • One null, one non-null charset → not equal, compareTo != 0
  • testNlsStringTreeSetRetainsDistinctValues(): 4 values with same string "hello" but different charset — TreeSet must retain all 4 (before fix: collapsed to 1)

Verification

  • ./gradlew :core:compileJava — BUILD SUCCESSFUL
  • ./gradlew :core:checkstyleMain — passed
  • ./gradlew :core:test --tests "org.apache.calcite.util.UtilTest" — 7 tests passed

Note

This is a behavior change: TreeSet<NlsString> ordering results may differ from before. This is the intended fix — the old behavior was a bug.

Jira

CALCITE-7554

return cmp;
}

// Charset: ("hello","LATIN1") vs ("hello","UTF-8") -> not equal

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.

Please no such comments about past bugs. They will make no sense to future readers.

compareTo() only compared the decoded string, ignoring charset,
collation, and byte representation. This broke the compareTo/equals
contract: two NlsStrings with same text but different charset would
compare as equal, causing TreeSet to silently collapse them.

Fix: after string comparison, compare charsetName, collation
(via toString()), and bytesValue. Uses Comparator.nullsFirst
for null-safe comparison.
@sonarqubecloud

sonarqubecloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

@lanky228

lanky228 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks, removed the historical comments. Kept the last line because the byte comparison step is non-obvious and needs explanation.

@mihaibudiu

Copy link
Copy Markdown
Contributor

In general please make changes as new commits, so the reviewers can see what's new.

@mihaibudiu mihaibudiu added the LGTM-will-merge-soon Overall PR looks OK. Only minor things left. label Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

LGTM-will-merge-soon Overall PR looks OK. Only minor things left.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants