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


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/PushDownUtils.scala:
##########
@@ -434,6 +434,9 @@ object PushDownUtils extends Logging {
       runtimeFilters: Seq[Expression],
       partitionFields: Seq[PartitionPredicateField]): 
Seq[PartitionPredicateImpl] = {
     val catalystExprs = runtimeFilters.flatMap {
+      case DynamicPruningExpression(in: InSubqueryExec) if 
in.isResultUnavailable =>
+        None
+      case DynamicPruningExpression(Literal.TrueLiteral) => None

Review Comment:
   **Finding 5.** This line changes behaviour on the pre-existing, default-on 
iterative V2 pushdown path (SPARK-55596), independently of the new feature. 
(The `isResultUnavailable` case above it is essential — without it 
`translateRuntimeFilterV2` would hit `values().getOrElse { throw internalError 
}`. And the `case TrueLiteral => None` you added in `translateRuntimeFilterV2` 
is behaviour-neutral, since base's `case other` already returned `None` after a 
warning. This one is different: it actually changes what gets pushed.)
   
   `DynamicPruningExpression(Literal.TrueLiteral)` is what 
`PlanDynamicPruningFilters`/`PlanAdaptiveDynamicPruningFilters` emit whenever 
`onlyInBroadcast` holds and no broadcast can be reused — the default with 
`spark.sql.optimizer.dynamicPartitionPruning.reuseBroadcastOnly=true`. On base 
it survives all the way into a pushed predicate:
   
   - `pushRuntimeFilters` keeps it as a candidate: `translateRuntimeFilterV2` 
returns `None` for it, and `TrueLiteral.references` is empty so 
`f.references.subsetOf(filterAttrs)` is trivially true;
   - `createRuntimePartitionPredicates` maps it to `Some(TrueLiteral)` through 
`case DynamicPruningExpression(e)`;
   - in `createPartitionPredicates`, `getPartitionFiltersAndDataFilters` first 
puts it in `dataFilters` (it requires `references.nonEmpty`), but then 
`extractPredicatesWithinOutputSet` pulls it straight back as an 
`extraPartitionFilter` — `predicates.scala:266-271` returns `Some(other)` when 
`other.references.subsetOf(outputSet)`, which an empty reference set always 
satisfies;
   - `isPushablePartitionFilter` accepts it (deterministic, no subquery, no 
`PythonUDF`), and `PartitionPredicateImpl.apply` returns `Some` because it only 
rejects *unmatched* references (`PartitionPredicateImpl.scala:105-117`).
   
   So today a degenerate DPP filter pushes a `PartitionPredicate` wrapping 
`true` down to the connector and sets `partPredicatesPushed = true`, which 
makes `replanWithRuntimeFilters` re-run `scan.toBatch.planInputPartitions()` 
for no gain. Dropping it is a real improvement — but it is a silent change to 
released, default-on behaviour, with no test and nothing in the description: 
the same shape as the direct-reuse tightening @dongjoon-hyun asked you to 
separate out.
   
   `DataSourceV2EnhancedRuntimePartitionFilterSuite` already has everything 
needed to pin it — a DPP query with `reuseBroadcastOnly=true` and no reusable 
broadcast, asserting `assertPushedPartitionPredicates(df, expectedCount = 0)`. 
Otherwise, please call it out in the description, or split it out (arguably 
under its own ticket, since it is a SPARK-55596 bug rather than part of 
SPARK-58265).
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/ProjectedBroadcastValueSubqueryExec.scala:
##########
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.execution
+
+import java.util.concurrent.{Future => JFuture}
+
+import scala.concurrent.duration.Duration
+
+import org.apache.spark.SparkException
+import org.apache.spark.rdd.RDD
+import org.apache.spark.sql.catalyst.InternalRow
+import org.apache.spark.sql.catalyst.expressions.{Attribute, 
AttributeReference, Cast, Expression, NamedExpression, UnsafeRow}
+import org.apache.spark.sql.catalyst.plans.QueryPlan
+import org.apache.spark.sql.errors.QueryExecutionErrors
+import org.apache.spark.sql.execution.BroadcastValueResult.{Available, 
Unavailable}
+import org.apache.spark.sql.execution.joins.HashedRelation
+import org.apache.spark.sql.execution.metric.SQLMetrics
+import org.apache.spark.util.ThreadUtils
+
+/** Collects pruning values from the full rows of an already required hash 
broadcast. */
+case class ProjectedBroadcastValueSubqueryExec(

Review Comment:
   **Finding 4.** This new `BaseSubqueryExec` subtype needs to be registered in 
`SQLLastAttemptAccumulator`, which matches the subtype set exhaustively and 
deliberately bails out on anything it doesn't know 
(`SQLLastAttemptAccumulator.scala:365-384`, untouched by this PR):
   
   ```scala
   case sl: BaseSubqueryExec => sl match {
     case s: SubqueryExec => scopeIds(s.child)
     case _: SubqueryBroadcastExec =>
       // Used by DPP filter only, not part of main flow of query execution.
       Nil
     case _: SubqueryAdaptiveBroadcastExec =>
       // Used by DPP filter only.
       Nil
     case r: ReusedSubqueryExec => recurse(r.child)
     case p =>
       // Bail out if future unknown implementation is encountered.
       bailOutReason = Some(s"Unsupported BaseSubqueryExec: 
${p.getClass.getName}")
       Nil
   }
   ```
   
   `extractStageRDDScopes` ends with `p.subqueries.flatMap(recurse)`, and 
`AdaptiveSparkPlanHelper.flatMap` applies the function to the root node first 
(`foreach(p)` calls `f(p)` before descending), so the 
`ProjectedBroadcastValueSubqueryExec` itself falls into `case p` and sets 
`bailOutReason`. Once that is set, `extractStageRDDScopes` returns `Left(...)` 
and `lastAttemptValueForQueryExecution` — and `lastAttemptValueForDataset` 
through it — returns `None` for the **whole query**, with an "Unable to extract 
RDD scopes from query execution plan" warning. 
`unexpectedLastAttemptMetricOperation` is called with `invalidate = false` and 
no exception, so it only logs: it fails quietly and no existing test notices — 
`SQLLastAttemptMetricPlanShapesSuite` covers `lastAttemptValueForDataset` per 
plan shape but has no DPP shape.
   
   Both DPP siblings are already listed there, so the existing 
`SubqueryBroadcastExec`/`SubqueryAdaptiveBroadcastExec` paths keep working and 
only the new node breaks it. Fix is the same shape as its neighbours:
   
   ```scala
           case _: ProjectedBroadcastValueSubqueryExec =>
             // Used by DPP filter only.
             Nil
   ```
   



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