This is an automated email from the ASF dual-hosted git repository.
RussellSpitzer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/main by this push:
new e5f8ab19c8 Spark 3.5: Use snapshot schema for CBO column statistics
(#18459)
e5f8ab19c8 is described below
commit e5f8ab19c82fcf24be430cc6c0cecf9a23f5afe4
Author: Rajarshi Sarkar <[email protected]>
AuthorDate: Mon Oct 26 22:17:50 2026 +0530
Spark 3.5: Use snapshot schema for CBO column statistics (#18459)
---
.../org/apache/iceberg/spark/source/SparkScan.java | 4 +-
.../apache/iceberg/spark/source/TestSparkScan.java | 52 ++++++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git
a/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/SparkScan.java
b/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/SparkScan.java
index 6cf12f6e76..820461ff87 100644
---
a/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/SparkScan.java
+++
b/spark/v3.5/spark/src/main/java/org/apache/iceberg/spark/source/SparkScan.java
@@ -214,12 +214,14 @@ abstract class SparkScan implements Scan,
SupportsReportStatistics {
Map<Integer, List<BlobMetadata>> groupedByField =
metadataList.stream()
+ .filter(metadata -> !metadata.fields().isEmpty())
.collect(
Collectors.groupingBy(
metadata -> metadata.fields().get(0),
Collectors.toList()));
+ Schema snapshotSchema = SnapshotUtil.schemaFor(table,
snapshot.snapshotId());
for (Map.Entry<Integer, List<BlobMetadata>> entry :
groupedByField.entrySet()) {
- String colName = table.schema().findColumnName(entry.getKey());
+ String colName = snapshotSchema.findColumnName(entry.getKey());
NamedReference ref = FieldReference.column(colName);
Long ndv = null;
diff --git
a/spark/v3.5/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkScan.java
b/spark/v3.5/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkScan.java
index 2eb61407b0..251ad8f23a 100644
---
a/spark/v3.5/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkScan.java
+++
b/spark/v3.5/spark/src/test/java/org/apache/iceberg/spark/source/TestSparkScan.java
@@ -28,6 +28,7 @@ import static
org.apache.iceberg.spark.SystemFunctionPushDownHelper.timestampStr
import static org.apache.spark.sql.functions.date_add;
import static org.apache.spark.sql.functions.expr;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
import java.util.List;
import java.util.Map;
@@ -468,6 +469,57 @@ public class TestSparkScan extends TestBaseWithCatalog {
withSQLConf(reportColStatsEnabled, () -> checkColStatisticsReported(scan,
4L, expectedTwoNDVs));
}
+ @TestTemplate
+ public void testStatisticsMappedWithSnapshotSchema() throws
NoSuchTableException {
+ sql("CREATE TABLE %s (id int, data string) USING iceberg", tableName);
+ sql("INSERT INTO %s VALUES (1, 'a')", tableName);
+
+ Table table = validationCatalog.loadTable(tableIdent);
+ long snapshotId = table.currentSnapshot().snapshotId();
+
+ GenericStatisticsFile statisticsFile =
+ new GenericStatisticsFile(
+ snapshotId,
+ "/test/statistics/file.puffin",
+ 100,
+ 42,
+ ImmutableList.of(
+ new GenericBlobMetadata(
+ APACHE_DATASKETCHES_THETA_V1,
+ snapshotId,
+ 1,
+ ImmutableList.of(1),
+ ImmutableMap.of("ndv", "4")),
+ // Blob for field ID 2 ('data') which is in the snapshot
schema but later dropped
+ // from table
+ new GenericBlobMetadata(
+ APACHE_DATASKETCHES_THETA_V1,
+ snapshotId,
+ 1,
+ ImmutableList.of(2),
+ ImmutableMap.of("ndv", "2")),
+ // Blob with empty fields (e.g. table-level or non-field blob)
+ new GenericBlobMetadata(
+ DUMMY_BLOB_TYPE, snapshotId, 1, ImmutableList.of(),
ImmutableMap.of())));
+
+ table.updateStatistics().setStatistics(statisticsFile).commit();
+
+ // Drop column 'data' (field ID 2) to simulate real schema evolution
+ table.updateSchema().deleteColumn("data").commit();
+
+ Table updatedTable = validationCatalog.loadTable(tableIdent);
+ SparkScanBuilder scanBuilder =
+ new SparkScanBuilder(spark, updatedTable,
CaseInsensitiveStringMap.empty());
+ SparkScan scan = (SparkScan) scanBuilder.build();
+
+ Map<String, String> reportColStatsEnabled =
+ ImmutableMap.of(SQLConf.CBO_ENABLED().key(), "true");
+
+ withSQLConf(
+ reportColStatsEnabled,
+ () ->
assertThatCode(scan::estimateStatistics).doesNotThrowAnyException());
+ }
+
@TestTemplate
public void testUnpartitionedYears() throws Exception {
createUnpartitionedTable(spark, tableName);