Search before asking
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():
// 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
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
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, with captured before/after logs under 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:
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?
Search before asking
Fluss version
0.9.0 (latest release)
Please describe the bug 🐞
Summary
Schema.PrimaryKey.equals()compares only the primary-key column names, buthashCode()folds insuper.hashCode():PrimaryKeyextends onlyObject, sosuper.hashCode()is the per-instanceObject.hashCode()value. TwoPrimaryKeyinstances with the same columns are thereforeequals()but return different hash codes, breaking the general contract ofObject.hashCode():Schema.hashCode()includes the primary key, so the violation propagates toSchema, and to the publicTableDescriptor, whoseequals/hashCodeinclude the schema.Schemais@PublicEvolvingandPrimaryKeyis@PublicStable, so any caller that puts a schema or table descriptor into aHashSet/HashMapis 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. viaSchemaZNode): the reloaded schema isequals()to the in-memory one but is not found in a hash-based collection.How to reproduce
Standalone runnable reproduction against the released
0.9.1-incubatingartifact (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, with captured before/after logs underevidence/.Root Cause
The sibling types in the same file,
Schema.hashCode()andColumn.hashCode(), hash only their value fields with no identity term;PrimaryKeyis the only one that folds insuper.hashCode(), which points to a slip rather than a design choice. (constraintNameis intentionally excluded fromequals(): it is a derived, non-persisted name that is regenerated on deserialize bySchemaJsonSerde. So the correct alignment is to hash the same fieldequals()compares.)Solution
Compute
hashCode()over the same fieldequals()compares: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?