peter-toth commented on code in PR #59789:
URL: https://github.com/apache/spark/pull/59789#discussion_r4378624128


##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -11064,6 +11064,69 @@ class KeyGroupedPartitioningSuite
         s"the aggregate's side is grouped over the join:\n$plan")
     }
   }
+
+  test("SPARK-60260: SPJ keeps right results when v2 bucketing turns off after 
planning") {
+    withSQLConf(
+        SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1",
+        SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_ENABLED.key -> "true",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_REUSE_BROADCAST_ONLY.key -> "false",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_FALLBACK_FILTER_RATIO.key -> "10") {
+      withTable(s"testcat.ns.$items", s"testcat.ns.$purchases") {
+        // Seven keys, one split each on both sides, so the join needs no 
GroupPartitionsExec.
+        createTable(items, itemsColumns, Array(identity("id")))
+        sql(s"INSERT INTO testcat.ns.$items VALUES " +
+            "(1, 'a', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(2, 'b', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(3, 'c', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(4, 'd', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(5, 'e', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(6, 'f', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(7, 'g', 10.0, cast('2020-01-01' as timestamp))")
+
+        createTable(purchases, purchasesColumns, Array(identity("item_id")))
+        sql(s"INSERT INTO testcat.ns.$purchases VALUES " +
+            "(1, 1.0, cast('2020-01-01' as timestamp)), " +
+            "(2, 2.0, cast('2020-01-01' as timestamp)), " +
+            "(3, 3.0, cast('2020-01-01' as timestamp)), " +
+            "(4, 4.0, cast('2020-01-01' as timestamp)), " +
+            "(5, 5.0, cast('2020-01-01' as timestamp)), " +
+            "(6, 6.0, cast('2020-01-01' as timestamp)), " +
+            "(7, 7.0, cast('2020-01-01' as timestamp))")
+
+        // Plans the join with v2 bucketing on, asserts it is the shape the 
bug needs -- a
+        // storage-partitioned join (sort-merge join, no shuffle, no 
GroupPartitionsExec) with
+        // dynamic partition pruning on the keyed scan -- then executes it 
with bucketing turned off
+        // and checks the rows. Asserting the plan keeps the test from passing 
without actually
+        // exercising the keyed-scan rebuild path.
+        def checkWithBucketingTurnedOff(filter: String, expected: Seq[Row]): 
Unit = {
+          val df = sql(
+            s"SELECT i.id, i.name, p.price FROM testcat.ns.$items i " +
+            s"JOIN testcat.ns.$purchases p ON i.id = p.item_id WHERE $filter")
+          val planned = stripAQEPlan(df.queryExecution.executedPlan)
+          assert(collect(planned) { case j: SortMergeJoinExec => j }.nonEmpty,
+            s"expected a sort-merge join:\n$planned")
+          assert(collectShuffles(planned).isEmpty,
+            s"expected a storage-partitioned join with no shuffle:\n$planned")
+          assert(collectGroupPartitions(planned).isEmpty,
+            s"expected one split per key, no GroupPartitionsExec:\n$planned")
+          assert(collectScans(planned).exists(_.runtimeFilters.nonEmpty),
+            s"expected dynamic partition pruning on the keyed scan:\n$planned")
+          withSQLConf(SQLConf.V2_BUCKETING_ENABLED.key -> "false") {
+            checkAnswer(df, expected)
+          }
+        }
+
+        // All keys survive the filter: on master this returns 2 rows instead 
of 7.
+        checkWithBucketingTurnedOff("p.price > 0.0",
+          Seq(Row(1L, "a", 1.0f), Row(2L, "b", 2.0f), Row(3L, "c", 3.0f), 
Row(4L, "d", 4.0f),
+            Row(5L, "e", 5.0f), Row(6L, "f", 6.0f), Row(7L, "g", 7.0f)))
+        // A filter that prunes splits: on master this fails with "Can't zip 
RDDs with unequal
+        // numbers of partitions"; only item ids 6 and 7 survive.
+        checkWithBucketingTurnedOff("p.price > 5.0", Seq(Row(6L, "f", 6.0f), 
Row(7L, "g", 7.0f)))

Review Comment:
   **Finding 2.** The fix covers two more shapes, and the test pins neither. 
Both ran like `checkWithBucketingTurnedOff`: planned with bucketing on, then 
collected with it off, AQE on.
   
   | Shape | master | here |
   |---|---|---|
   | `items` holds two rows per id, so a `GroupPartitionsExec` sits above the 
pruned scan | `[INTERNAL_ERROR] GroupPartitionsExec's child no longer reports 
the partitioning it was planned over`, for both filters | right (14 rows, 4 
rows) |
   | `purchases` is not partitioned and 
`spark.sql.sources.v2.bucketing.shuffle.enabled` is on, so it is shuffled onto 
the keyed `items` scan | 2 rows instead of 7, and `Can't zip RDDs with unequal 
numbers of partitions: List(2, 7)` for `p.price > 5.0` | right |
   
   Turning the helper's `collectGroupPartitions(planned).isEmpty` and 
`collectShuffles(planned).isEmpty` into expected counts lets one helper run all 
three table setups. The first one is worth pinning in particular, since it 
shows the fix turns the `checkChildStillMatches` internal error into right rows.
   



##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -11064,6 +11064,69 @@ class KeyGroupedPartitioningSuite
         s"the aggregate's side is grouped over the join:\n$plan")
     }
   }
+
+  test("SPARK-60260: SPJ keeps right results when v2 bucketing turns off after 
planning") {

Review Comment:
   **Finding 3.** The suite keeps its runtime-filtering tests under SPJ in 
`KeyGroupedPartitioningRuntimeFilterTests` 
(`KeyGroupedPartitioningSuite.scala:129`), next to "SPARK-45652: SPJ should 
handle empty partition after dynamic filtering". That trait runs each test 
twice:
   - in `KeyGroupedPartitioningRuntimeFilterSuite`, against the default catalog;
   - in `KeyGroupedPartitioningCatalystRuntimeFilterSuite`, against 
`InMemoryCatalystRuntimeFilterCatalog`, whose scans take Catalyst runtime 
filters.
   
   Here the test runs only against the default catalog. I ran its shape in both 
suites. Against the Catalyst-filter catalog, master also returns 2 rows instead 
of 7 and fails with `Can't zip RDDs`, through the 
`SupportsRuntimeCatalystFiltering` path of `PushDownUtils.pushRuntimeFilters`, 
and both are right here. The helpers the test uses are on 
`KeyGroupedPartitioningSuiteBase`, so it should move as is.
   



##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -11064,6 +11064,69 @@ class KeyGroupedPartitioningSuite
         s"the aggregate's side is grouped over the join:\n$plan")
     }
   }
+
+  test("SPARK-60260: SPJ keeps right results when v2 bucketing turns off after 
planning") {
+    withSQLConf(
+        SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1",
+        SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_ENABLED.key -> "true",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_REUSE_BROADCAST_ONLY.key -> "false",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_FALLBACK_FILTER_RATIO.key -> "10") {
+      withTable(s"testcat.ns.$items", s"testcat.ns.$purchases") {
+        // Seven keys, one split each on both sides, so the join needs no 
GroupPartitionsExec.
+        createTable(items, itemsColumns, Array(identity("id")))
+        sql(s"INSERT INTO testcat.ns.$items VALUES " +
+            "(1, 'a', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(2, 'b', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(3, 'c', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(4, 'd', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(5, 'e', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(6, 'f', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(7, 'g', 10.0, cast('2020-01-01' as timestamp))")
+
+        createTable(purchases, purchasesColumns, Array(identity("item_id")))
+        sql(s"INSERT INTO testcat.ns.$purchases VALUES " +
+            "(1, 1.0, cast('2020-01-01' as timestamp)), " +
+            "(2, 2.0, cast('2020-01-01' as timestamp)), " +
+            "(3, 3.0, cast('2020-01-01' as timestamp)), " +
+            "(4, 4.0, cast('2020-01-01' as timestamp)), " +
+            "(5, 5.0, cast('2020-01-01' as timestamp)), " +
+            "(6, 6.0, cast('2020-01-01' as timestamp)), " +
+            "(7, 7.0, cast('2020-01-01' as timestamp))")
+
+        // Plans the join with v2 bucketing on, asserts it is the shape the 
bug needs -- a
+        // storage-partitioned join (sort-merge join, no shuffle, no 
GroupPartitionsExec) with
+        // dynamic partition pruning on the keyed scan -- then executes it 
with bucketing turned off
+        // and checks the rows. Asserting the plan keeps the test from passing 
without actually
+        // exercising the keyed-scan rebuild path.
+        def checkWithBucketingTurnedOff(filter: String, expected: Seq[Row]): 
Unit = {
+          val df = sql(
+            s"SELECT i.id, i.name, p.price FROM testcat.ns.$items i " +
+            s"JOIN testcat.ns.$purchases p ON i.id = p.item_id WHERE $filter")
+          val planned = stripAQEPlan(df.queryExecution.executedPlan)
+          assert(collect(planned) { case j: SortMergeJoinExec => j }.nonEmpty,
+            s"expected a sort-merge join:\n$planned")
+          assert(collectShuffles(planned).isEmpty,
+            s"expected a storage-partitioned join with no shuffle:\n$planned")
+          assert(collectGroupPartitions(planned).isEmpty,
+            s"expected one split per key, no GroupPartitionsExec:\n$planned")
+          assert(collectScans(planned).exists(_.runtimeFilters.nonEmpty),
+            s"expected dynamic partition pruning on the keyed scan:\n$planned")
+          withSQLConf(SQLConf.V2_BUCKETING_ENABLED.key -> "false") {
+            checkAnswer(df, expected)
+          }

Review Comment:
   **Finding 4.** Two departures from the suite's other flip-after-planning 
tests, "SPARK-59279: a planned k-way merge survives a later config change" and 
"SPARK-60044: a planned k-way merge keeps its ordering after a later config 
change":
   
   - `checkAnswer(df, expected)` uses the default `checkToRDD = true`. That 
first runs `df.materializedRdd.count()`, a new `QueryExecution` planned with 
bucketing off, and only then collects the committed plan. The siblings pass 
`checkToRDD = false`, so only the committed plan runs.
   - The plan assertions look only at the initial AQE plan, so nothing checks 
the node this PR changes, the scan `PlanAdaptiveDynamicPruningFilters` 
rebuilds. Like the siblings, look at the executed plan again once the query has 
run.
   
   ```suggestion
             withSQLConf(SQLConf.V2_BUCKETING_ENABLED.key -> "false") {
               // Without `checkToRDD`, so only the committed plan runs, not a 
newly planned one.
               QueryTest.checkAnswer(df, expected, checkToRDD = false)
               // Under AQE the executed plan only reaches the rebuilt scans 
once the query has run.
               val executed = stripAQEPlan(df.queryExecution.executedPlan)
               assert(collectScans(executed).forall(
                 _.outputPartitioning.isInstanceOf[KeyedPartitioning]),
                 s"expected the rebuilt scans to keep their keyed 
layout:\n$executed")
             }
   ```
   
   With it the test passes here. On master the scan with the DPP filter reports 
`UnknownPartitioning` after the run, so the assertion pins the rebuilt scan 
itself, not only its rows. For the shuffled-onto shape in 2, only the keyed 
scan reports `KeyedPartitioning`, so the check there would look at that scan 
alone.
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/BatchScanExec.scala:
##########
@@ -117,9 +122,14 @@ case class BatchScanExec(
         runtimeFilters.filterNot(_ == 
DynamicPruningExpression(Literal.TrueLiteral)),
         output),
       keyGroupedPartitioning = prunedKeyGroupedPartitioning.map(
-        _.map(QueryPlan.normalizeExpressions(_, output))))
+        _.map(QueryPlan.normalizeExpressions(_, output))))(v2BucketingEnabled)
   }
 
+  // `v2BucketingEnabled` is not in the case class product, so carry it 
through the TreeNode
+  // transforms that rebuild this node (e.g. the runtime-filter rewrite at 
stage creation), which
+  // would otherwise drop it back to the live-config default.
+  override def otherCopyArgs: Seq[AnyRef] = Boolean.box(v2BucketingEnabled) :: 
Nil

Review Comment:
   **Finding 7.** Two things in these lines:
   - Without `otherCopyArgs`, a rebuild does not fall back to the live config. 
`makeCopy` finds no constructor that fits, and the query fails with "Failed to 
copy node ... wrong number of arguments: 6 expected: 7". I checked by removing 
it.
   - `TreeNode` declares `otherCopyArgs` as `protected`, and so do the other 
nodes with a second parameter list, such as `WholeStageCodegenExec` and 
`SubqueryAdaptiveBroadcastExec`.
   
   Both are moot with 6.
   
   ```suggestion
     // `v2BucketingEnabled` is not in the case class product, so pass it to 
the TreeNode transforms
     // that rebuild this node (e.g. the runtime-filter rewrite at stage 
creation). `makeCopy` fails
     // without it, since the constructor takes one more argument than the 
product holds.
     override protected def otherCopyArgs: Seq[AnyRef] = 
Boolean.box(v2BucketingEnabled) :: Nil
   ```
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2ScanExecBase.scala:
##########
@@ -53,6 +53,16 @@ trait DataSourceV2ScanExecBase
    * `SupportsReportOrdering` */
   def ordering: Option[Seq[SortOrder]]
 
+  /**
+   * Whether storage-partitioned join is on for this scan, captured when it 
was planned. A scan
+   * rebuilt or re-executed after planning (for instance one that
+   * `PlanAdaptiveDynamicPruningFilters` recreates at stage creation) keeps 
the decision it was
+   * planned with, rather than re-reading 
`spark.sql.sources.v2.bucketing.enabled`, which can
+   * change between planning and execution and would drop the keyed layout the 
join was planned
+   * with (SPARK-60260). A scan that freezes it overrides this; others use the 
live config.
+   */

Review Comment:
   **Finding 8.** "A scan rebuilt or re-executed after planning ... keeps the 
decision it was planned with" holds for a copy made by a tree transform, 
through `makeCopy` and `otherCopyArgs`. A scan that AQE re-plans is different. 
`AdaptiveSparkPlanExec.reOptimize` calls `planner.plan`, which builds a fresh 
`BatchScanExec(...)()` with the config as it is then. That is fine, since 
`EnsureRequirements` then runs over the new plan, but the scaladoc should say 
which rebuilds it covers. Moot with 6.
   
   ```suggestion
     /**
      * Whether storage-partitioned join is on for this scan, captured when it 
was planned. A copy
      * of a planned scan made by a tree transform (for instance the one
      * `PlanAdaptiveDynamicPruningFilters` makes at stage creation) keeps the 
decision it was
      * planned with, rather than re-reading 
`spark.sql.sources.v2.bucketing.enabled`, which can
      * change between planning and execution and would drop the keyed layout 
the join was planned
      * with (SPARK-60260). A scan that AQE re-plans is a new scan and takes 
the config then. A scan
      * that freezes it overrides this; others use the live config.
      */
   ```
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/BatchScanExec.scala:
##########
@@ -42,7 +43,11 @@ case class BatchScanExec(
     runtimeFilters: Seq[Expression],
     ordering: Option[Seq[SortOrder]] = None,
     @transient table: Table,
-    keyGroupedPartitioning: Option[Seq[Expression]] = None
+    keyGroupedPartitioning: Option[Seq[Expression]] = None)(
+    // A second parameter list so adding this field keeps `BatchScanExec`'s 
extractor arity, leaving
+    // the many `case BatchScanExec(...)` matches untouched. Captured when the 
scan is planned and
+    // carried through rebuilds (see `v2BucketingEnabled` and `otherCopyArgs`).
+    override val v2BucketingEnabled: Boolean = SQLConf.get.v2BucketingEnabled

Review Comment:
   **Finding 6.** The PR adds a second parameter list, an `otherCopyArgs` 
override, a `doCanonicalize` change and a trait hook to carry one decision the 
node already has a field for. `DataSourceV2Strategy` can make the decision 
where it builds the scan, and `keyGroupedPartitioning` can carry it:
   
   ```scala
         // Decide storage-partitioned join here, once, so a rebuild of the 
planned node does not
         // read the config again (SPARK-60260).
         val keyGroupedPartitioning = relation.keyGroupedPartitioning
           .filter(_ => session.sessionState.conf.v2BucketingEnabled)
         val batchExec = BatchScanExec(relation.output, relation.scan, 
runtimeFilters,
           relation.ordering, relation.relation.table, keyGroupedPartitioning)
   ```
   
   And `DataSourceV2ScanExecBase.reportedKeyedPartitioning` drops the config 
term:
   
   ```scala
         case Some(exprs) if KeyedPartitioning.supportsExpressions(exprs) &&
   ```
   
   What it gains:
   - `BatchScanExec`'s constructor, `apply` and `copy` stay as they are. With 
the second list, out-of-tree code that builds the node stops compiling 
("missing argument list"), and code compiled against the 6-argument signatures 
fails to link. MiMa does not see it, since `org.apache.spark.sql.execution` is 
excluded. That matters most for the backports.
   - `keyGroupedPartitioning` is a product field, so `makeCopy`, `copy`, 
`doCanonicalize`, `equals` and `hashCode` already carry it, and the `()` 
changes in the three test suites go away.
   - Nothing samples `SQLConf.get` in a default argument behind 
`BatchScanExec(...)()`.
   
   With these two changes on master, the new test, both shapes in 2 against 
both runtime-filter catalogs, and `KeyGroupedPartitioningSuite`, 
`SparkPlanSuite`, `EnsureRequirementsSuite` and `GroupPartitionsExecSuite` pass.
   
   The costs:
   - `keyGroupedPartitioning = None` then means both "the source reports no 
keys" and "SPJ is off for this scan". Its scaladoc in 
`DataSourceV2ScanExecBase` would need a line for that.
   - An out-of-tree `DataSourceV2ScanExecBase` that sets 
`keyGroupedPartitioning` itself is no longer gated by the config. In the tree, 
only `BatchScanExec` gets one.
   
   If you keep the second parameter list, please at least drop the `= 
SQLConf.get.v2BucketingEnabled` default, and pass the value at 
`DataSourceV2Strategy.scala:204`, the only production call site. Then a future 
rebuild through the constructor instead of `makeCopy` cannot silently go back 
to the live config, which is this bug.
   



##########
sql/core/src/test/scala/org/apache/spark/sql/connector/KeyGroupedPartitioningSuite.scala:
##########
@@ -11064,6 +11064,69 @@ class KeyGroupedPartitioningSuite
         s"the aggregate's side is grouped over the join:\n$plan")
     }
   }
+
+  test("SPARK-60260: SPJ keeps right results when v2 bucketing turns off after 
planning") {
+    withSQLConf(
+        SQLConf.AUTO_BROADCASTJOIN_THRESHOLD.key -> "-1",
+        SQLConf.ADAPTIVE_EXECUTION_ENABLED.key -> "true",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_ENABLED.key -> "true",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_REUSE_BROADCAST_ONLY.key -> "false",
+        SQLConf.DYNAMIC_PARTITION_PRUNING_FALLBACK_FILTER_RATIO.key -> "10") {
+      withTable(s"testcat.ns.$items", s"testcat.ns.$purchases") {
+        // Seven keys, one split each on both sides, so the join needs no 
GroupPartitionsExec.
+        createTable(items, itemsColumns, Array(identity("id")))
+        sql(s"INSERT INTO testcat.ns.$items VALUES " +
+            "(1, 'a', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(2, 'b', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(3, 'c', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(4, 'd', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(5, 'e', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(6, 'f', 10.0, cast('2020-01-01' as timestamp)), " +
+            "(7, 'g', 10.0, cast('2020-01-01' as timestamp))")
+
+        createTable(purchases, purchasesColumns, Array(identity("item_id")))
+        sql(s"INSERT INTO testcat.ns.$purchases VALUES " +
+            "(1, 1.0, cast('2020-01-01' as timestamp)), " +
+            "(2, 2.0, cast('2020-01-01' as timestamp)), " +
+            "(3, 3.0, cast('2020-01-01' as timestamp)), " +
+            "(4, 4.0, cast('2020-01-01' as timestamp)), " +
+            "(5, 5.0, cast('2020-01-01' as timestamp)), " +
+            "(6, 6.0, cast('2020-01-01' as timestamp)), " +
+            "(7, 7.0, cast('2020-01-01' as timestamp))")
+
+        // Plans the join with v2 bucketing on, asserts it is the shape the 
bug needs -- a
+        // storage-partitioned join (sort-merge join, no shuffle, no 
GroupPartitionsExec) with
+        // dynamic partition pruning on the keyed scan -- then executes it 
with bucketing turned off
+        // and checks the rows. Asserting the plan keeps the test from passing 
without actually
+        // exercising the keyed-scan rebuild path.
+        def checkWithBucketingTurnedOff(filter: String, expected: Seq[Row]): 
Unit = {
+          val df = sql(
+            s"SELECT i.id, i.name, p.price FROM testcat.ns.$items i " +
+            s"JOIN testcat.ns.$purchases p ON i.id = p.item_id WHERE $filter")
+          val planned = stripAQEPlan(df.queryExecution.executedPlan)
+          assert(collect(planned) { case j: SortMergeJoinExec => j }.nonEmpty,
+            s"expected a sort-merge join:\n$planned")
+          assert(collectShuffles(planned).isEmpty,
+            s"expected a storage-partitioned join with no shuffle:\n$planned")
+          assert(collectGroupPartitions(planned).isEmpty,
+            s"expected one split per key, no GroupPartitionsExec:\n$planned")
+          assert(collectScans(planned).exists(_.runtimeFilters.nonEmpty),
+            s"expected dynamic partition pruning on the keyed scan:\n$planned")
+          withSQLConf(SQLConf.V2_BUCKETING_ENABLED.key -> "false") {
+            checkAnswer(df, expected)
+          }
+        }
+
+        // All keys survive the filter: on master this returns 2 rows instead 
of 7.
+        checkWithBucketingTurnedOff("p.price > 0.0",
+          Seq(Row(1L, "a", 1.0f), Row(2L, "b", 2.0f), Row(3L, "c", 3.0f), 
Row(4L, "d", 4.0f),
+            Row(5L, "e", 5.0f), Row(6L, "f", 6.0f), Row(7L, "g", 7.0f)))
+        // A filter that prunes splits: on master this fails with "Can't zip 
RDDs with unequal
+        // numbers of partitions"; only item ids 6 and 7 survive.

Review Comment:
   **Finding 9.** "on master" stops being true once this merges, while "without 
the fix" stays true.
   
   ```suggestion
           // All keys survive the filter: without the fix this returns 2 rows 
instead of 7.
           checkWithBucketingTurnedOff("p.price > 0.0",
             Seq(Row(1L, "a", 1.0f), Row(2L, "b", 2.0f), Row(3L, "c", 3.0f), 
Row(4L, "d", 4.0f),
               Row(5L, "e", 5.0f), Row(6L, "f", 6.0f), Row(7L, "g", 7.0f)))
           // A filter that prunes splits: without the fix this fails with 
"Can't zip RDDs with
           // unequal numbers of partitions"; only item ids 6 and 7 survive.
   ```
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to