Commit 0b913a8
committed
[SPARK-46367][SQL] Support narrowing projection of
### What changes were proposed in this pull request?
When a `KeyedPartitioning` passes through a `PartitioningPreservingUnaryExecNode` (e.g. `ProjectExec`), the previous implementation projected the partitioning as a whole expression via `multiTransformDown`. If any expression position could not be mapped to an output attribute, the entire `KeyedPartitioning` was silently dropped, resulting in `UnknownPartitioning`.
This PR replaces that approach with a per-position projection algorithm implemented in two new private helpers (`projectKeyedPartitionings` and `projectOtherPartitionings`), with the main `outputPartitioning` reduced to a simple split, project, and combine:
1. For each expression position (0..N-1), collect the unique expressions at that position across all input `KeyedPartitioning`s (using `ExpressionSet` to deduplicate semantically equal expressions), then project each through the output aliases via `projectExpression`.
2. Positions with at least one projected alternative are *projectable*; they define the maximum achievable granularity. Positions that cannot be expressed in the output are dropped (narrowing).
3. The shared `partitionKeys` are projected to the subset of projectable positions via `KeyedPartitioning.projectKeys`.
4. The final `KeyedPartitioning`s are the cross-product of per-position alternatives, computed lazily via `MultiTransform.generateCartesianProduct`, deduplicated, and bounded by a single outer `take(aliasCandidateLimit)`.
All resulting `KeyedPartitioning`s at the same granularity share the same `partitionKeys` object, preserving the invariant required by `GroupPartitionsExec`.
A new `isNarrowed: Boolean` flag is added to `KeyedPartitioning` and set to `true` when the projection drops one or more key positions. The flag is sticky: a subsequent `PartitioningPreservingUnaryExecNode` that passes all positions through does not reset it. When `isNarrowed=true` and `isGrouped=false`, `GroupPartitionsExec` would merge original partitions that held distinct keys, carrying the same data-skew risk as `allowKeysSubsetOfPartitionKeys`. `groupedSatisfies` therefore gates such narrowed partitionings behind that config. When `isGrouped=true` after narrowing, the projected keys are still distinct so no merging happens and no config is required.
The config `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys.enabled` is renamed to `spark.sql.sources.v2.bucketing.allowKeysSubsetOfPartitionKeys.enabled` to reflect that it now covers both joins and aggregates. The old key is retained as an alternative for backwards compatibility.
Note: this PR does not handle the case where a partition key column is pruned from the scan output by column pruning. `V2ScanPartitioningAndOrdering` drops the `KeyedPartitioning` entirely when any partition key expression references a column absent from the scan's output set, so there is nothing for `PartitioningPreservingUnaryExecNode` to project. Addressing that case requires changes at the logical planning level and is left for a follow-up.
### Why are the changes needed?
Without this fix, a `ProjectExec` that drops any column of a multi-column partition key causes the entire `KeyedPartitioning` to be lost. This breaks storage-partitioned join optimisations (SPJ) that rely on the partitioning surviving projection (e.g. a subquery that renames or projects away a partition key column).
### Does this PR introduce _any_ user-facing change?
Yes. SPJ is now preserved through `ProjectExec` nodes:
- Alias projections (e.g. `SELECT id AS pk FROM t`) no longer break SPJ.
- Narrowing projections (e.g. `SELECT id FROM t` where `t` is partitioned by `(id, name)`) enable SPJ when the projected keys remain distinct, or when `spark.sql.sources.v2.bucketing.allowKeysSubsetOfPartitionKeys.enabled` is set and the keys become non-unique.
The config key `spark.sql.sources.v2.bucketing.allowJoinKeysSubsetOfPartitionKeys.enabled` is renamed to `spark.sql.sources.v2.bucketing.allowKeysSubsetOfPartitionKeys.enabled`; the old key continues to work via `.withAlternative`.
### How was this patch tested?
Unit tests added/updated in `ProjectedOrderingAndPartitioningSuite`:
- Full-granularity alias substitution
- 2->1 and 3->2 narrowing with and without aliases
- `PartitioningCollection` with mixed projectability
- `isNarrowed=true, isGrouped=false`: `groupedSatisfies` blocked without config, allowed with `allowKeysSubsetOfPartitionKeys`
- `isNarrowed=true, isGrouped=true`: `satisfies` succeeds without config
- `isNarrowed` stickiness: a second `PartitioningPreservingUnaryExecNode` hop preserves the flag
End-to-end tests added in `KeyGroupedPartitioningSuite`:
- Alias in subquery does not break SPJ
- Narrowing projection with duplicate projected keys requires `allowKeysSubsetOfPartitionKeys`
- Narrowing projection with distinct projected keys triggers SPJ without config
- Aggregate with GROUP BY on a subset of partition keys: shuffle by default, `GroupPartitionsExec` with `allowKeysSubsetOfPartitionKeys`
### Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude Sonnet 4.6KeyedPartitioning in PartitioningPreservingUnaryExecNode
1 parent 68456a6 commit 0b913a8
9 files changed
Lines changed: 567 additions & 83 deletions
File tree
- sql
- catalyst/src/main/scala/org/apache/spark/sql
- catalyst/plans/physical
- internal
- core/src
- main/scala/org/apache/spark/sql/execution
- exchange
- test/scala/org/apache/spark/sql
- connector
- execution
- exchange
Lines changed: 34 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
353 | 353 | | |
354 | 354 | | |
355 | 355 | | |
356 | | - | |
357 | | - | |
358 | | - | |
359 | | - | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
360 | 364 | | |
361 | | - | |
362 | | - | |
363 | | - | |
364 | | - | |
365 | | - | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
366 | 368 | | |
367 | 369 | | |
368 | 370 | | |
| |||
417 | 419 | | |
418 | 420 | | |
419 | 421 | | |
420 | | - | |
421 | | - | |
422 | | - | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
423 | 425 | | |
424 | 426 | | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
425 | 433 | | |
426 | 434 | | |
427 | 435 | | |
428 | 436 | | |
429 | | - | |
| 437 | + | |
| 438 | + | |
430 | 439 | | |
431 | 440 | | |
432 | 441 | | |
| |||
480 | 489 | | |
481 | 490 | | |
482 | 491 | | |
483 | | - | |
| 492 | + | |
484 | 493 | | |
485 | | - | |
486 | | - | |
| 494 | + | |
| 495 | + | |
487 | 496 | | |
488 | 497 | | |
489 | 498 | | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
490 | 505 | | |
491 | 506 | | |
492 | 507 | | |
| |||
502 | 517 | | |
503 | 518 | | |
504 | 519 | | |
505 | | - | |
506 | | - | |
507 | | - | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
508 | 523 | | |
509 | 524 | | |
510 | 525 | | |
| |||
Lines changed: 10 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2142 | 2142 | | |
2143 | 2143 | | |
2144 | 2144 | | |
2145 | | - | |
2146 | | - | |
2147 | | - | |
2148 | | - | |
2149 | | - | |
| 2145 | + | |
| 2146 | + | |
| 2147 | + | |
| 2148 | + | |
| 2149 | + | |
| 2150 | + | |
| 2151 | + | |
2150 | 2152 | | |
2151 | 2153 | | |
2152 | 2154 | | |
2153 | 2155 | | |
| 2156 | + | |
2154 | 2157 | | |
2155 | 2158 | | |
2156 | 2159 | | |
| |||
7926 | 7929 | | |
7927 | 7930 | | |
7928 | 7931 | | |
7929 | | - | |
7930 | | - | |
| 7932 | + | |
| 7933 | + | |
7931 | 7934 | | |
7932 | 7935 | | |
7933 | 7936 | | |
| |||
Lines changed: 100 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
21 | | - | |
| 21 | + | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| |||
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | | - | |
33 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
34 | 58 | | |
35 | 59 | | |
36 | 60 | | |
| |||
41 | 65 | | |
42 | 66 | | |
43 | 67 | | |
44 | | - | |
45 | 68 | | |
46 | | - | |
47 | | - | |
| 69 | + | |
| 70 | + | |
48 | 71 | | |
49 | | - | |
| 72 | + | |
50 | 73 | | |
51 | | - | |
| 74 | + | |
52 | 75 | | |
53 | 76 | | |
54 | 77 | | |
55 | 78 | | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
61 | 147 | | |
62 | 148 | | |
63 | 149 | | |
| |||
Lines changed: 3 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
408 | 408 | | |
409 | 409 | | |
410 | 410 | | |
411 | | - | |
| 411 | + | |
412 | 412 | | |
413 | 413 | | |
414 | 414 | | |
415 | 415 | | |
416 | | - | |
| 416 | + | |
417 | 417 | | |
418 | 418 | | |
419 | 419 | | |
| |||
512 | 512 | | |
513 | 513 | | |
514 | 514 | | |
515 | | - | |
| 515 | + | |
516 | 516 | | |
517 | 517 | | |
518 | 518 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
407 | 407 | | |
408 | 408 | | |
409 | 409 | | |
410 | | - | |
| 410 | + | |
411 | 411 | | |
412 | 412 | | |
413 | 413 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
0 commit comments