NestDream opened a new issue, #3628:
URL: https://github.com/apache/fluss/issues/3628

   ### Search before asking
   
   - [x] I searched in the [issues](https://github.com/apache/fluss/issues) and 
found nothing similar.
   
   
   ### Fluss version
   
   0.9.0 (latest release)
   
   ### Please describe the bug 🐞
   
   ## Summary
   
   `Schema.PrimaryKey.equals()` compares only the primary-key column names, but 
`hashCode()` folds in `super.hashCode()`:
   
   ```java
   // fluss-common/src/main/java/org/apache/fluss/metadata/Schema.java
   public boolean equals(Object o) {
       ...
       return Objects.equals(columnNames, that.columnNames);   // value-based
   }
   
   public int hashCode() {
       return Objects.hash(super.hashCode(), columnNames);      // includes 
Object.hashCode()
   }
   ```
   
   `PrimaryKey` extends only `Object`, so `super.hashCode()` is the 
per-instance `Object.hashCode()` value. Two `PrimaryKey` instances with the 
same columns are therefore `equals()` but return different hash codes, breaking 
the general contract of `Object.hashCode()`:
   
   > "If two objects are equal according to the `equals(Object)` method, then 
calling the
   > `hashCode` method on each of the two objects must produce the same integer 
result."
   > — [`java.lang.Object.hashCode()`, Java SE 11 
API](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Object.html#hashCode())
   
   `Schema.hashCode()` includes the primary key, so the violation propagates to 
`Schema`, and to the public `TableDescriptor`, whose `equals`/`hashCode` 
include the schema. `Schema` is `@PublicEvolving` and `PrimaryKey` is 
`@PublicStable`, so any caller that puts a schema or table descriptor into a 
`HashSet`/`HashMap` is affected. This is easy to hit after a schema round-trips 
through its JSON form (`Schema#toJsonBytes` / `fromJsonBytes`, which is how 
Fluss persists and reloads a schema, e.g. via `SchemaZNode`): the reloaded 
schema is `equals()` to the in-memory one but is not found in a hash-based 
collection.
   
   ## How to reproduce
   
   ```java
   Schema schema = Schema.newBuilder()
           .column("id", DataTypes.INT()).column("name", DataTypes.STRING())
           .primaryKey("id").build();
   
   Schema reloaded = Schema.fromJsonBytes(schema.toJsonBytes());
   
   schema.equals(reloaded);                    // true
   schema.hashCode() == reloaded.hashCode();   // false  <- contract violation
   
   Set<Schema> set = new HashSet<>();
   set.add(schema);
   set.contains(reloaded);                     // false  <- equal schema not 
found
   ```
   
   Standalone runnable reproduction against the released `0.9.1-incubating` 
artifact (no cluster, no Fluss build needed): 
https://github.com/NestDream/fluss-pk-hashcode-demo. Run `./run-before.sh`; the 
core check is 
[SchemaHashCodeDemo.java#L30-L37](https://github.com/NestDream/fluss-pk-hashcode-demo/blob/main/src/main/java/io/github/nestdream/flussdemo/SchemaHashCodeDemo.java#L30-L37),
 with captured before/after logs under 
[`evidence/`](https://github.com/NestDream/fluss-pk-hashcode-demo/tree/main/evidence).
   
   ## Root Cause
   
   The sibling types in the same file, `Schema.hashCode()` and 
`Column.hashCode()`, hash only their value fields with no identity term; 
`PrimaryKey` is the only one that folds in `super.hashCode()`, which points to 
a slip rather than a design choice. (`constraintName` is intentionally excluded 
from `equals()`: it is a derived, non-persisted name that is regenerated on 
deserialize by `SchemaJsonSerde`. So the correct alignment is to hash the same 
field `equals()` compares.)
   
   
   ### Solution
   
   Compute `hashCode()` over the same field `equals()` compares:
   
   ```java
   public int hashCode() {
       return Objects.hash(columnNames);
   }
   ```
   
   I have the one-line fix ready with a unit test (JSON round-trip) and an 
end-to-end ITCase (create table via the Admin API, read the schema back, use it 
as a hash key), both failing before the fix and passing after.
   
   
   ### Are you willing to submit a PR?
   
   - [x] I'm willing to submit a PR!


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to