Skip to content

Spark 4.2: Push down opaque partition predicates - #17956

Open
szehon-ho wants to merge 1 commit into
apache:mainfrom
szehon-ho:codex/pr-14984-patch
Open

Spark 4.2: Push down opaque partition predicates#17956
szehon-ho wants to merge 1 commit into
apache:mainfrom
szehon-ho:codex/pr-14984-patch

Conversation

@szehon-ho

@szehon-hoszehon-ho commented Sep 4, 2026

Copy link
Copy Markdown
Member

Description

This adds support for Spark 4.2's new opaque PartitionPredicate API on top of #14984.

Iceberg now:

  • accumulates data and partition predicates across iterative pushdown calls
  • evaluates opaque Spark partition predicates against planned task partition keys
  • prunes tasks that do not match evaluable predicates
  • matches partition fields by field ID and transform for each scanned spec
  • keeps tasks when a predicate cannot be evaluated against an evolved spec

All partition predicates are currently returned to Spark for post-scan evaluation. This provides a correctness backstop for partition evolution. A future optimization can return only predicates that cannot be evaluated against every scanned spec.

The tests cover direct predicate evaluation, iterative pushdown, partition evolution, unsupported V2 filters, UDFs, non-first and multiple partition fields, and nested identity partition fields.

Testing

  • TestFilteredScan: 85 tests, 9 skipped, 0 failures
  • TestFilterPushDown: 38 tests, 0 failures
  • focused endsWith partition predicate and partition-evolution tests pass
  • Spark 4.2 Java formatting check passes

AI Disclosure

  • Model: GPT-5
  • Platform/Tool: Codex
  • Human Oversight: partially reviewed
  • Prompt Summary: Add Spark 4.2 PartitionPredicate support to Iceberg scan planning, including partition evolution handling and integration tests.

@szehon-hoszehon-ho changed the title Spark: Push down opaque partition predicates in 4.2Spark 4.2: Push down opaque partition predicatesSep 4, 2026
Use Spark 4.2 PartitionPredicate to filter scan tasks by partition values while retaining predicates for Spark post-scan evaluation when specs cannot evaluate them.
Generated-by: Codex (GPT-5)

this.filters = expressions;
this.pushedPredicates = pushablePredicates.toArray(new Predicate[0]);
this.filters.addAll(expressions);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accumulating is correct here, SupportsPushDownV2Filters requires it once supportsIterativePushdown() returns true. But it changes what TestFilteredScan.testUnpartitionedIDFilters exercises: that test builds the SparkScanBuilder once at line 204, outside its loop, so iteration i now plans id = 0 AND id = 1 AND … AND id = i instead of just id = i.

It stays green because Iceberg checks each conjunct separately against the file bounds [0, 9] and never sees that the conjunction is unsatisfiable, and the row-level assertion below it goes through a separate read(...) that doesn't touch the builder. So no coverage is lost outright, but the hasSize(1) assertion is no longer checking the filter it appears to push, and it now quietly depends on Iceberg not detecting contradictions.

Could you move the builder construction inside the loop? That matches testUnpartitionedCaseInsensitiveIDFilters just below it:

 for (int i = 0; i < 10; i += 1) {
SparkScanBuilder builder =
new SparkScanBuilder(spark, TABLES.load(options.get("path")), options);
pushFilters(builder, EqualTo.apply("id", i));

try (CloseableIterable<? extends ScanTask> taskIterable = scan.planFiles()) {
List<T> plannedTasks = Lists.newArrayList();
Map<Integer, PartitionPredicateEvaluator> evaluatorsBySpecId = Maps.newHashMap();
int numPlannedTasks = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: numPlannedTasks counts tasks before pruning, but plannedTasks holds the ones that survived. Maybe rename it to numScannedTasks or numCandidateTasks?

LOG.debug(
"Planned {} task group(s) with {} grouping key type and {} unique grouping key(s) for table {}",
"Planned {} task group(s) with {} grouping key type and {} unique grouping key(s) for"
+ " table {}",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary change? There are a few other places.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@szehon-ho@huaxingao