Skip to content

[SPARK-40608][SQL] Push necessary predicates from partially translatable V2 filters - #58948

Open
xiaoh1024 wants to merge 3 commits into
apache:masterfrom
xiaoh1024:fix/spark-40608-partial-v2-filters
Open

xiaoh1024 wants to merge 3 commits into
apache:masterfrom
xiaoh1024:fix/spark-40608-partial-v2-filters

Conversation

@xiaoh1024

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Extract necessary predicates when a V2 scan filter cannot be fully translated, for both SupportsPushDownFilters and SupportsPushDownV2Filters. Retain the original expression for post-scan filtering.

For example, id = 1 OR (id = 2 AND split(name, ',')[0] = 'fred') can push id = 1 OR id = 2 while Spark evaluates the original condition.

Why are the changes needed?

An unsupported expression in one branch currently prevents the entire OR predicate from being pushed down, even when both branches imply a translatable condition. Extracting that condition lets the data source filter rows earlier.

Does this PR introduce any user-facing change?

Yes. Eligible V2 scans push an additional filter; query results are unchanged.

How was this patch tested?

Added regression tests covering both filter APIs, residual filtering, NULL values, unsupported OR branches, NOT, non-deterministic expressions, and fully translatable filters. Added an H2 JDBC test checking the pushed predicate and query results.

Was this patch authored or co-authored using generative AI tooling?

Yes.

@xiaoh1024
xiaoh1024 marked this pull request as ready for review September 21, 2026 18:00
szehon-ho
szehon-ho previously approved these changes Sep 23, 2026

@szehon-ho szehon-ho left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM. The three inline suggestions are optional style cleanups for clearer Scala. They should be applied together because they change the helper callback type.

@szehon-ho
szehon-ho dismissed their stale review September 23, 2026 04:32

Withdrawing my approval: on another reading, we found that retaining both an extracted predicate and its original residual can underestimate cardinality. Sorry I missed this in the initial review.

@szehon-ho szehon-ho left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry for missing this in my initial review. I found a statistics issue on another reading, so I have withdrawn my approval for now. I had the same finding when trying to do the 'inferred filter' DSV2 API: https://github.com/apache/spark/pull/58145/changes Details below

Comment on lines +130 to +134
extractPushablePredicate(
filterExpr,
DataSourceV2Strategy.translateFilterV2)
.flatMap(translateFilter)
.foreach(translatedFilters += _)

@szehon-ho szehon-ho Sep 23, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could we avoid retaining the synthesized predicate as an additional post-scan filter when the original expression is already retained? This applies to both filter APIs.

For example, let P = id = 1 OR (id = 2 AND U) and let the extracted predicate be Q = id = 1 OR id = 2, where U cannot be translated. If the source rejects Q and returns it for reevaluation, residual reconstruction produces Q AND P. Since P already implies Q, that extra condition is redundant.

This can affect statistics: FilterEstimation multiplies the selectivities of the two sides of AND, while estimating OR does not update column statistics. With 10 non-null distinct ID values, no histogram, and U unsupported by the estimator, the estimated selectivity is 19% for P but 3.61% for Q AND P. Cost-based join reordering runs after scan pushdown and before the later boolean cleanup, so the redundant filter can affect join planning.

Could we track the predicates synthesized by partial extraction and omit their returned residuals when the original predicate is still enforced?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching this — fixed now.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants