Skip to content

[spark] Let MSCK see the null partition of a value-only format table - #9521

Open
sundapeng wants to merge 1 commit into
apache:masterfrom
sundapeng:fix/msck-default-partition-in-value-only-layout
Open

[spark] Let MSCK see the null partition of a value-only format table#9521
sundapeng wants to merge 1 commit into
apache:masterfrom
sundapeng:fix/msck-default-partition-in-value-only-layout

Conversation

@sundapeng

Copy link
Copy Markdown
Member

Purpose

Fix MSCK REPAIR TABLE for catalog-managed Format Tables that use value-only partition paths.

The default partition directory (normally __DEFAULT_PARTITION__) starts with _. Repair did not pass the configured default partition name to filesystem discovery, so that directory was treated as hidden. ADD PARTITIONS could not discover it, while SYNC or DROP PARTITIONS could unregister a live partition and make its rows invisible even though the data files remained.

Pass the table's default partition name to the existing discovery overload. Keep raw path discovery unchanged so values such as month=01 are not cast and rewritten.

Tests

  • mvn -pl paimon-spark/paimon-spark-common -DskipITs -Dtest=FormatTablePartitionRepairTest -DfailIfNoTests=false test
    • JUnit: 22 passed
    • ScalaTest executed by the module lifecycle: 36 passed
  • mvn -pl paimon-spark/paimon-spark-common spotless:check

The new regression cases cover ADD, SYNC, a custom default partition name, a default partition on a non-leaf value-only level, and the unaffected key-value layout.

`__DEFAULT_PARTITION__` is `partition.default-name`, the value Paimon
substitutes when a dynamic partition column is null or empty. It is the same
value whatever the path layout. What the layout decides is the directory name:
with `key=value` the null partition lives in `dt=__DEFAULT_PARTITION__`, whose
first character is `d`; with a value-only layout the directory name is the value
itself, so it begins with `_` and the generic hidden-directory rule swallows it.

`PartitionPathUtils.isHiddenFile` already knows this and spares the directory
whose name equals the table's default partition name, but only when it is told
what that name is. `FormatTablePartitionRepair` called the five-argument
`searchPartSpecAndPaths`, which passes `defaultPartValue` as null, so the rescue
never applied and MSCK never saw the directory. Two ways to lose data followed:

- `MSCK REPAIR TABLE ... ADD PARTITIONS` never registered the null partition;
- `MSCK REPAIR TABLE ... SYNC PARTITIONS` read it as "registered but the
  directory is gone" and unregistered a partition that still holds data. Later
  queries then skip it without a word.

`listStatusRecursively` applies the same rule while descending, so a null value
on a non-leaf level hid the whole subtree rather than one directory.

The scan path already gets this right: `FileSystemSplitEnumerator` passes
`table.defaultPartName()` to the eight-argument overload. Repair now does the
same. `partitionFilter` and `partitionType` stay null on purpose — repair diffs
raw directory names, and handing it a partition type would reintroduce the cast
that rewrites `month=01` to `1` and no longer round-trips to the real directory.

The registered spec keeps the literal `__DEFAULT_PARTITION__`, which is what
`FormatTableCommit` writes through the same `extractPartitionSpecFromPathOnlyValue`
helper, so both sides of the diff agree and a second SYNC stays a no-op.

Tests cover the missed registration, the wrongful unregistration, a subtree
under a null non-leaf level, a table that overrides `partition.default-name`
(hardcoding the literal passes every other case), and a key=value layout whose
value starts with `_`, which is unaffected because the underscore sits on the
value rather than on the first character of the directory name.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant