Skip to content

[SPARK-59250][SQL] Translate an empty runtime IN filter to AlwaysFalse in DSv2 runtime filtering - #58524

Closed
pan3793 wants to merge 3 commits into
apache:masterfrom
pan3793:dpp-empty-in
Closed

pan3793 wants to merge 3 commits into
apache:masterfrom
pan3793:dpp-empty-in

Conversation

@pan3793

@pan3793 pan3793 commented Sep 4, 2026

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

In DataSourceV2Strategy.translateRuntimeFilterV2, match an InSubqueryExec whose subquery result is empty before the pushable-column check, and translate it to AlwaysFalse.

expr IN () is false for every row regardless of expr, so the translation is valid even when expr itself is untranslatable, e.g. a join key wrapped in a cast inserted by type coercion (cast(col as bigint) IN dynamicpruning#x).

The test connector scans InMemoryTableWithV2Filter and InMemoryBaseTable.InMemoryBatchScan are updated to honor AlwaysFalse by pruning all partitions.

Why are the changes needed?

When the build side of a DPP join produces no rows, the runtime filter degenerates to key IN (). Today this is either emitted as a zero-value IN predicate that connectors cannot interpret meaningfully (Spark's own InMemoryTableWithV2Filter ignores it: its filter() requires children().length > 1), or, when the key is wrapped in a cast, dropped entirely with "Can't translate ... unsupported expression". Either way the scan reads every partition to produce zero output rows.

Translating to AlwaysFalse prunes everything and makes the connector contract explicit: connectors receive a well-defined predicate instead of a degenerate IN.

Does this PR introduce any user-facing change?

No correctness change. DSv2 scans receiving an empty runtime IN filter can now prune all partitions instead of reading everything.

How was this patch tested?

New tests in DynamicPartitionPruningV2Suite covering a bare pruning key and a cast-wrapped pruning key; both assert filteredPartitions is empty. The tests run through V1 Filter, V2 Predicate, and Catalyst runtime filtering. Previously all 25 partitions were read.

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

Generated-by: Claude Fable 5

@pan3793

pan3793 commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

cc @szehon-ho

@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.

Optimization makes sense to me

spark.conf.set("spark.sql.catalog.testcat", classOf[InMemoryTableWithV2FilterCatalog].getName)
}

import testImplicits._

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.

nit: can we put it on top or more natural place ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved the import and helper into the shared DynamicPartitionPruningV2Suite.

scans.head
}

test("SPARK-59250: DPP prunes all DSv2 partitions when the runtime IN filter is empty",

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 also cover the SupportsRuntimeFiltering (V1 Filter) path? This PR updates InMemoryBaseTable.InMemoryBatchScan to handle AlwaysFalse, but these tests run only with InMemoryTableWithV2FilterCatalog, so they exercise InMemoryV2FilterBatchScan and leave the V1 compatibility path untested. It may be possible to share the tests with DynamicPartitionPruningV2Suite so both implementations run them.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Moved the tests into DynamicPartitionPruningV2Suite, so they now run through V1 Filter, V2 Predicate, and Catalyst runtime filtering.

@uros-b

uros-b commented Sep 5, 2026

Copy link
Copy Markdown
Member

Thank you @pan3793 and @szehon-ho!

@pan3793 pan3793 closed this in f3e6e17 Sep 7, 2026
pan3793 added a commit that referenced this pull request Sep 7, 2026
…e in DSv2 runtime filtering

### What changes were proposed in this pull request?

In `DataSourceV2Strategy.translateRuntimeFilterV2`, match an `InSubqueryExec` whose subquery result is empty before the pushable-column check, and translate it to `AlwaysFalse`.

`expr IN ()` is false for every row regardless of `expr`, so the translation is valid even when `expr` itself is untranslatable, e.g. a join key wrapped in a cast inserted by type coercion (`cast(col as bigint) IN dynamicpruning#x`).

The test connector scans `InMemoryTableWithV2Filter` and `InMemoryBaseTable.InMemoryBatchScan` are updated to honor `AlwaysFalse` by pruning all partitions.

### Why are the changes needed?

When the build side of a DPP join produces no rows, the runtime filter degenerates to `key IN ()`. Today this is either emitted as a zero-value `IN` predicate that connectors cannot interpret meaningfully (Spark's own `InMemoryTableWithV2Filter` ignores it: its `filter()` requires `children().length > 1`), or, when the key is wrapped in a cast, dropped entirely with "Can't translate ... unsupported expression". Either way the scan reads every partition to produce zero output rows.

Translating to `AlwaysFalse` prunes everything and makes the connector contract explicit: connectors receive a well-defined predicate instead of a degenerate IN.

### Does this PR introduce _any_ user-facing change?

No correctness change. DSv2 scans receiving an empty runtime IN filter can now prune all partitions instead of reading everything.

### How was this patch tested?

New tests in `DynamicPartitionPruningV2Suite` covering a bare pruning key and a cast-wrapped pruning key; both assert `filteredPartitions` is empty. The tests run through V1 `Filter`, V2 `Predicate`, and Catalyst runtime filtering. Previously all 25 partitions were read.

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

Generated-by: Claude Fable 5

Closes #58524 from pan3793/dpp-empty-in.

Authored-by: Cheng Pan <pan3793@gmail.com>
Signed-off-by: Cheng Pan <chengpan@apache.org>
(cherry picked from commit f3e6e17)
Signed-off-by: Cheng Pan <chengpan@apache.org>
@pan3793

pan3793 commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

Merge Summary:

Posted by merge_spark_pr.py

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.

3 participants