[core] Reject merge-schema type widening for primary-key and partition columns - #10077
zhang-arvin wants to merge 2 commits into
Conversation
|
The Evidence — the same jobs fail on a clean
This PR only touches This comment was generated by an AI agent (Hermes Agent). |
JingsongLi
left a comment
There was a problem hiding this comment.
Reviewed 17406ca9e0ff1d4d857670a5c74887557a9aec9e. Requirement fit: SUPPORTED; this closes a real correctness gap in path-based schema merging, and the guard belongs before schema persistence. Keep this PR open.
The core checks pass locally on JDK 8: all 17 SchemaMergingUtilsTest cases plus three independent reviewer tests covering rejected key/partition widening without schema persistence, case-insensitive matching, column addition with widening disabled, and the reported INT/BIGINT bucket difference. The two rejection tests fail against the pre-fix helper and pass with this change.
However, the current Spark3 CI has a directly related failure in Paimon Sink: enable schema evolution, so the PR needs the integration-test update described inline. This failure is separate from the reported core-suite failures. I inspected the current CI trace; I did not rerun the Spark suite locally.
| assertNotUpdatingKeyColumn( | ||
| currentTableSchema, currentType, newRowType, "update", "primary key"); |
There was a problem hiding this comment.
[P2] Update the Spark evolution tests for the new key-type contract
This correctly rejects a scenario that the existing Spark success tests still require: paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/PaimonSinkTest.scala:242 creates primary key a INT, feeds MemoryStream[(Long, Date, Int)] with type widening enabled, and expects the key to become BIGINT. The Spark 3.4 copy at paimon-spark/paimon-spark-3.4/src/test/scala/org/apache/paimon/spark/PaimonSinkTest.scala:239 does the same. Current Spark3 CI fails that test with Cannot update primary key type from INT NOT NULL to BIGINT NOT NULL: [a], with the stack pointing to this new guard. Please update both successful evolution tests to retain the key type while evolving non-key columns, and add a separate streaming rejection case checking that schema metadata and existing rows remain unchanged. The guard should remain; the integration tests need to reflect the intended invariant in this PR.
| new HashMap<>(), | ||
| ""); | ||
|
|
||
| DataField bWidened = new DataField(-1, "b", new VarCharType(100)); |
There was a problem hiding this comment.
This test is named testRejectTypeWideningOnPartitionColumn , but it changes VARCHAR(MAX) to VARCHAR(100) , which is a narrowing conversion, and it only succeeds as a merge candidate because allowExplicitCast is enabled below. Could we instead use INT -> BIGINT for the partition column and call mergeSchemas with allowExplicitCast=false ? That would directly verify that the normal type-widening path rejects partition-column type changes, independently of explicit-cast behavior.
There was a problem hiding this comment.
The corresponding corrected test setup would look like:
DataField b = new DataField(1, "b", new IntType());
// ...
DataField bWidened = new DataField(-1, "b", new BigIntType());
RowType t = new RowType(Lists.newArrayList(a, bWidened));
assertThatThrownBy(() -> SchemaMergingUtils.mergeSchemas(current, t, true, false, true))
.isInstanceOf(UnsupportedOperationException.class)
.hasMessageContaining("Cannot update partition column type");…n columns The automatic schema merge path (SchemaMergingUtils#mergeSchemas) merged every field type and kept the existing primaryKeys/partitionKeys name lists without validating the protected columns, while the explicit schema change path rejects type changes on those columns (SchemaManagerUtils#assertNotUpdatingPartitionKeys / #assertNotUpdatingPrimaryKeys). Widening a key column changes its value encoding, so the same logical key hashes into a different bucket (INT(-100000) -> bucket 3 vs BIGINT(-100000) -> bucket 13), silently creating a second bucket for one key and bypassing intra-bucket deduplication. Reuse the explicit path's guard in the automatic merge path: reject the merge before the merged schema is committed when a primary-key or partition column changes type. Type widening for non-key columns is unaffected.
17406ca to
ec9f605
Compare
…n columns Automatic schema merging widened every field type while preserving the existing key-name lists, so an INT primary key could be committed as BIGINT with the primary key list unchanged. The physical hash of a key changes with its type, so the same logical key lands in a different bucket and escapes per-bucket deduplication. Reject type changes to primary-key and partition columns in the automatic merge path, matching the invariant the explicit schema-change path already enforces. Non-key widening keeps working. Update PaimonSinkTest's schema-evolution case to keep the primary key at INT: that test covers the evolution that is allowed (widening a non-key column and adding a column), and its previous primary-key widening is now correctly rejected. Covered by the new rejection tests in SchemaMergingUtilsTest.
234cd73 to
e3ae10a
Compare
What is the purpose of the change
Fixes #9993. The automatic schema merge path bypasses the invariant that the explicit schema change path enforces: key columns must not change type.
Brief change log
SchemaMergingUtils#mergeSchemas(TableSchema, RowType, ...)merges every incoming field type and then re-attaches the existingprimaryKeys()/partitionKeys()name lists, without ever validating that those columns kept their type.SchemaManagerUtilsalready rejects this for explicit changes viaassertNotUpdatingPartitionKeys/assertNotUpdatingPrimaryKeys, butFileSystemSchemaManager#mergeSchema(i.e. every write-time automatic schema merge) calledmergeSchemasdirectly and skipped those guards.assertNotUpdatingKeyColumn, invoked right before the mergedTableSchemais constructed, which rejects the merge with anUnsupportedOperationExceptionwhen a primary-key or partition column's type changed.Why it matters
Widening a key column changes its value encoding, so the same logical key hashes into a different bucket. Concretely, for a bucketed primary-key table,
INT(-100000)hashes to bucket 3 whileBIGINT(-100000)hashes to bucket 13 — the same logical key ends up in two different buckets, so intra-bucket deduplication no longer applies and duplicate rows accumulate.Tests
testRejectTypeWideningOnPrimaryKeyColumn— covers the reportedINT -> BIGINTprimary-key scenario, asserting the merge is rejected.testRejectTypeWideningOnPartitionColumn— same for a partition column.testAllowTypeWideningOnNonKeyColumn— guards against over-rejection: non-key widening still merges.All 17 tests in
SchemaMergingUtilsTestpass.