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]
