From 118ce275b3a4044726f3b0043d8dbb151942283b Mon Sep 17 00:00:00 2001 From: yangshangqing95 Date: Thu, 30 Jul 2026 17:26:08 -0400 Subject: [PATCH] Core: Preserve manifest content pruning when ignoring residuals --- .../org/apache/iceberg/BaseEntriesTable.java | 7 ++-- .../iceberg/TestEntriesMetadataTable.java | 35 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/apache/iceberg/BaseEntriesTable.java b/core/src/main/java/org/apache/iceberg/BaseEntriesTable.java index ae77bff7036d..05cce1e02434 100644 --- a/core/src/main/java/org/apache/iceberg/BaseEntriesTable.java +++ b/core/src/main/java/org/apache/iceberg/BaseEntriesTable.java @@ -71,7 +71,6 @@ static CloseableIterable planFiles( Expression rowFilter = context.rowFilter(); boolean caseSensitive = context.caseSensitive(); boolean ignoreResiduals = context.ignoreResiduals(); - Expression filter = ignoreResiduals ? Expressions.alwaysTrue() : rowFilter; LoadingCache evalCache = Caffeine.newBuilder() @@ -82,7 +81,7 @@ static CloseableIterable planFiles( return ManifestEvaluator.forRowFilter(rowFilter, transformedSpec, caseSensitive); }); ManifestContentEvaluator manifestContentEvaluator = - new ManifestContentEvaluator(filter, tableSchema.asStruct(), caseSensitive); + new ManifestContentEvaluator(rowFilter, tableSchema.asStruct(), caseSensitive); CloseableIterable filteredManifests = CloseableIterable.filter( @@ -91,9 +90,11 @@ static CloseableIterable planFiles( evalCache.get(manifest.partitionSpecId()).eval(manifest) && manifestContentEvaluator.eval(manifest)); + Expression residual = ignoreResiduals ? Expressions.alwaysTrue() : rowFilter; + return CloseableIterable.transform( filteredManifests, - manifest -> new ManifestReadTask(table, manifest, projectedSchema, filter)); + manifest -> new ManifestReadTask(table, manifest, projectedSchema, residual)); } /** diff --git a/core/src/test/java/org/apache/iceberg/TestEntriesMetadataTable.java b/core/src/test/java/org/apache/iceberg/TestEntriesMetadataTable.java index 25e138f96249..f80f29256d89 100644 --- a/core/src/test/java/org/apache/iceberg/TestEntriesMetadataTable.java +++ b/core/src/test/java/org/apache/iceberg/TestEntriesMetadataTable.java @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assumptions.assumeThat; import java.util.List; +import org.apache.iceberg.expressions.Expressions; import org.apache.iceberg.relocated.com.google.common.collect.ImmutableList; import org.apache.iceberg.relocated.com.google.common.collect.Iterables; import org.apache.iceberg.types.TypeUtil; @@ -152,4 +153,38 @@ public void testEntriesTableWithDeleteManifests() { .as("Should contain 1 delete file record") .isEqualTo(1); } + + @TestTemplate + public void testIgnoreResidualsPreservesManifestContentPruning() { + assumeThat(formatVersion).as("Only V2 Tables Support Deletes").isGreaterThanOrEqualTo(2); + + table.newAppend().appendFile(FILE_A).appendFile(FILE_B).commit(); + table.newRowDelta().addDeletes(fileADeletes()).commit(); + + String dataManifestPath = table.currentSnapshot().dataManifests(table.io()).get(0).path(); + String deleteManifestPath = table.currentSnapshot().deleteManifests(table.io()).get(0).path(); + + List entriesTables = + ImmutableList.of(new ManifestEntriesTable(table), new AllEntriesTable(table)); + + for (Table entriesTable : entriesTables) { + TableScan scan = + entriesTable + .newScan() + .filter(Expressions.equal("data_file.content", FileContent.POSITION_DELETES.id())) + .ignoreResiduals(); + + List tasks = ImmutableList.copyOf(scan.planFiles()); + + assertThat(tasks) + .as("Should retain manifest content pruning for %s", entriesTable.name()) + .extracting(task -> task.file().location()) + .contains(deleteManifestPath) + .doesNotContain(dataManifestPath); + + assertThat(tasks) + .as("Should ignore residuals for %s", entriesTable.name()) + .allSatisfy(task -> assertThat(task.residual()).isEqualTo(Expressions.alwaysTrue())); + } + } }