From 47f82a6da0807b53328adfcf91b4903b6fbbc2bc Mon Sep 17 00:00:00 2001 From: Sun Dapeng Date: Fri, 21 Aug 2026 09:43:25 +0800 Subject: [PATCH] [spark] Cover the zero a truncation reports surviving a later ANALYZE A truncation reports an exact zero for the partitions it emptied. ANALYZE measures a partition and writes what it found. Nothing pinned what the second does to the first, and the failure would be silent in the direction that matters: a zero degraded back to unknown reads as "nobody measured this", so a planner that guards on the value being known starts skipping the partition instead of knowing it is empty. The case truncates a measured partition and then runs both forms of ANALYZE over it, asserting the zero is still a zero. It needs `TRUNCATE TABLE` and `ANALYZE TABLE` together, which is why it could not go in with either of them. --- .../CatalogManagedPartitionAnalyzeTest.scala | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/CatalogManagedPartitionAnalyzeTest.scala b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/CatalogManagedPartitionAnalyzeTest.scala index 89d57134f89c..b8d6b9a32077 100644 --- a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/CatalogManagedPartitionAnalyzeTest.scala +++ b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/CatalogManagedPartitionAnalyzeTest.scala @@ -42,6 +42,33 @@ import scala.collection.JavaConverters._ */ class CatalogManagedPartitionAnalyzeTest extends PaimonSparkTestWithRestCatalogBase { + test("ANALYZE after a TRUNCATE keeps the exact zero the truncation reported") { + val tableName = "analyze_after_truncate" + withTable(tableName) { + createTable(tableName) + writeCsvPartition(tableName, "20260101", "00", 1) + repair(tableName) + sql(s"ANALYZE TABLE ${qualified(tableName)} COMPUTE STATISTICS NOSCAN").collect() + assert(statisticsOf(tableName, "20260101", "00").fileCount() == 1L) + + sql(s"TRUNCATE TABLE ${qualified(tableName)} PARTITION (dt = '20260101', hour = '00')") + val truncated = statisticsOf(tableName, "20260101", "00") + assert(truncated.fileCount() == 0L, truncated.toString) + assert(truncated.recordCount() == 0L, truncated.toString) + + // Measuring an empty partition must not turn "nothing is here" back into "nobody measured". + sql(s"ANALYZE TABLE ${qualified(tableName)} COMPUTE STATISTICS NOSCAN").collect() + val listed = statisticsOf(tableName, "20260101", "00") + assert(listed.fileCount() == 0L, listed.toString) + assert(listed.recordCount() == 0L, listed.toString) + + sql(s"ANALYZE TABLE ${qualified(tableName)} COMPUTE STATISTICS").collect() + val scanned = statisticsOf(tableName, "20260101", "00") + assert(scanned.fileCount() == 0L, scanned.toString) + assert(scanned.recordCount() == 0L, scanned.toString) + } + } + test("ANALYZE with a full partition spec measures only that partition") { val tableName = "analyze_full_spec" withTable(tableName) {