You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hudi.apache.org by da...@apache.org on 2026/10/12 02:59:03 UTC
(hudi) branch release-1.1.2 updated: test(spark): validate cleaned files in partition stats restore test
This is an automated email from the ASF dual-hosted git repository.
danny0405 pushed a commit to branch release-1.1.2
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/release-1.1.2 by this push:
new 9d17c9f78e2c test(spark): validate cleaned files in partition stats restore test
9d17c9f78e2c is described below
commit 9d17c9f78e2c6ca070476a673d4700bc048c8bed
Author: danny0405 <yu...@gmail.com>
AuthorDate: Mon Oct 12 10:58:55 2026 +0800
test(spark): validate cleaned files in partition stats restore test
Savepoints can retain first-commit file slices in merge-on-read tables. Check column stats against successful deletions from completed cleans instead of assuming every first-commit file was removed. Require nonempty deletions and column stats so the check cannot pass vacuously.
---
.../hudi/functional/TestPartitionStatsIndex.scala | 27 ++++++++++++----------
1 file changed, 15 insertions(+), 12 deletions(-)
diff --git a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/hudi/functional/TestPartitionStatsIndex.scala b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/hudi/functional/TestPartitionStatsIndex.scala
index 802c625ceebf..b024fe94aa38 100644
--- a/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/hudi/functional/TestPartitionStatsIndex.scala
+++ b/hudi-spark-datasource/hudi-spark/src/test/scala/org/apache/hudi/functional/TestPartitionStatsIndex.scala
@@ -383,9 +383,9 @@ class TestPartitionStatsIndex extends PartitionStatsIndexTestBase {
* 1. Enable column_stats, partition_stats and record_index (files/RLI already enabled by default).
* 2. Do two inserts and validate index initialization.
* 3. Do a savepoint on the second commits.
- * 4. Add three more commits to trigger clean, which cleans the files from the first commit.
+ * 4. Add three more commits to trigger clean and record the files actually deleted.
* 5. Restore, and validate that partition_stats is deleted, but column_stats partition exists.
- * 6. Validate that column_stats does not contain records with file names from first commit.
+ * 6. Validate that column_stats does not contain records for the deleted files.
*/
@Test
def testPartitionStatsWithRestore(): Unit = {
@@ -400,7 +400,6 @@ class TestPartitionStatsIndex extends PartitionStatsIndexTestBase {
hudiOpts,
operation = DataSourceWriteOptions.INSERT_OPERATION_OPT_VAL,
saveMode = SaveMode.Overwrite)
- val firstCompletedInstant = metaClient.getActiveTimeline.getCommitsTimeline.filterCompletedInstants().lastInstant()
// Second ingest.
doWriteAndValidateDataAndPartitionStats(
hudiOpts,
@@ -436,13 +435,16 @@ class TestPartitionStatsIndex extends PartitionStatsIndexTestBase {
saveMode = SaveMode.Append)
}
metaClient = HoodieTableMetaClient.reload(metaClient)
- // Clean commit should be triggered.
- val cleanInstantOpt = metaClient.getActiveTimeline.getCleanerTimeline.lastInstant()
- assertTrue(cleanInstantOpt.isPresent)
- val cleanMetadataBytes = metaClient.getActiveTimeline.getInstantDetails(cleanInstantOpt.get)
- val cleanMetadata = deserializeAvroMetadataLegacy(cleanMetadataBytes.get(), classOf[HoodieCleanMetadata])
- // This clean operation deletes 6 files created by the first commit.
- assertTrue(cleanMetadata.getTotalFilesDeleted > 0)
+ // A savepoint can retain first-commit file slices in MOR tables. Check the files
+ // actually deleted by all completed cleans instead of assuming their commit time.
+ val cleanTimeline = metaClient.getActiveTimeline.getCleanerTimeline.filterCompletedInstants()
+ assertFalse(cleanTimeline.empty())
+ val deletedFileNames = cleanTimeline.getInstants.asScala.flatMap { instant =>
+ val cleanMetadata = deserializeAvroMetadataLegacy(
+ cleanTimeline.getInstantDetails(instant).get(), classOf[HoodieCleanMetadata])
+ cleanMetadata.getPartitionMetadata.values().asScala.flatMap(_.getSuccessDeleteFiles.asScala)
+ }.toSet
+ assertTrue(deletedFileNames.nonEmpty, "The test must clean files before restoring")
// Restore to savepoint
writeClient.restoreToSavepoint(savepointTimestamp)
// Verify restore completed
@@ -486,9 +488,10 @@ class TestPartitionStatsIndex extends PartitionStatsIndexTestBase {
val columnStats = columnStatsIndex
.loadColumnStatsIndexRecords(targetColumnsToIndex, shouldReadInMemory = true)
.collectAsList()
- // All files from first commit have been removed.
+ assertFalse(columnStats.isEmpty, "Column stats must be present after restoring and writing")
for (stats <- columnStats.asScala) {
- assertFalse(stats.getFileName.contains(firstCompletedInstant.get().requestedTime()))
+ assertFalse(deletedFileNames.contains(stats.getFileName),
+ s"Column stats must not retain cleaned file ${stats.getFileName}")
}
}