weiqingy commented on code in PR #2379:
URL: https://github.com/apache/auron/pull/2379#discussion_r3522037320


##########
thirdparty/auron-iceberg/src/main/scala/org/apache/spark/sql/auron/iceberg/IcebergScanSupport.scala:
##########
@@ -382,6 +416,40 @@ object IcebergScanSupport extends Logging {
     }
   }
 
+  private def runtimeFilteredPartitions(exec: BatchScanExec): 
Option[Seq[InputPartition]] = {
+    if (exec.runtimeFilters.isEmpty) {
+      return None
+    }
+
+    try {
+      MethodUtils.invokeMethod(exec, true, "prepare")

Review Comment:
   `prepare()` is a public `SparkPlan` method and `exec` is a typed 
`BatchScanExec`, so `exec.prepare()` compiles directly — only 
`waitForSubqueries` (protected) and `filteredPartitions` genuinely need 
reflective access. Calling `exec.prepare()` here would drop one reflection call 
and leave a single reflection helper (`invokeDeclaredMethod`) for the two 
methods that actually need it. Minor.



##########
thirdparty/auron-iceberg/src/main/scala/org/apache/spark/sql/auron/iceberg/IcebergScanSupport.scala:
##########
@@ -82,35 +85,54 @@ object IcebergScanSupport extends Logging {
     }
   }
 
-  def plan(exec: BatchScanExec): Option[IcebergScanPlan] = {
-    exec.getTagValue(scanPlanTag) match {
+  def plan(exec: BatchScanExec, useRuntimeFilters: Boolean = false): 
Option[IcebergScanPlan] = {
+    val tag =
+      if (useRuntimeFilters && exec.runtimeFilters.nonEmpty) {
+        runtimeFilteredScanPlanTag
+      } else {
+        scanPlanTag
+      }
+    exec.getTagValue(tag) match {
       case Some(cached) => cached
       case None =>
-        val planned = planUncached(exec)
-        exec.setTagValue(scanPlanTag, planned)
+        val planned = planUncached(exec, useRuntimeFilters)
+        exec.setTagValue(tag, planned)
         planned
     }
   }
 
-  private def planUncached(exec: BatchScanExec): Option[IcebergScanPlan] = {
+  def withRuntimeFilters(
+      exec: BatchScanExec,
+      runtimeFilters: Seq[SparkExpression]): BatchScanExec = {
+    if (exec.runtimeFilters == runtimeFilters) {

Review Comment:
   This guard `exec.runtimeFilters == runtimeFilters` looks like it's always 
true, so the `Shims.get.copyBatchScanExecWithRuntimeFilters(...)` else-branch 
is never taken. `NativeIcebergTableScanExec` is only ever constructed at 
`IcebergConvertProvider.scala:59` as `NativeIcebergTableScanExec(e, plan, 
e.runtimeFilters)`, so its `runtimeFilters` field is always the same object as 
`basedScan.runtimeFilters` (same `e`). Both call sites of `withRuntimeFilters` 
— `NativeIcebergTableScanExec.scala:68` and `:250` — pass 
`withRuntimeFilters(basedScan, runtimeFilters)` with `runtimeFilters eq 
basedScan.runtimeFilters`, and the node (a `LeafExecNode`) is never rebuilt 
with different filters anywhere in the tree.
   
   Two things follow from that. First, all five `@sparkver` overloads of the 
new shim in `ShimsImpl.scala` (plus the abstract method in `Shims.scala`) are 
never invoked at runtime, so the new integration tests can't exercise them — a 
wrong version-specific `copy(...)` argument list would surface only as a 
compile error on that profile, never as a test failure. Second, 
`doCanonicalize` at `NativeIcebergTableScanExec.scala:249-250` reduces to the 
previous `basedScan.canonicalized` (the wrapper returns `basedScan` unchanged), 
so the new comment there — "first make sure it sees the top-level runtime 
filters" — describes a transformation that doesn't currently happen.
   
   Is this intentional groundwork for a future path that builds the node with 
filters *different* from `basedScan` (in which case a comment saying so, plus a 
test that takes the copy branch, would make the shim's ~40 version-specific 
lines defensible), or could `withRuntimeFilters` and the shim be dropped in 
favor of using `basedScan` directly? Since the field is always 
`basedScan.runtimeFilters`, I'm curious which direction you had in mind.



##########
spark-extension-shims-spark/src/main/scala/org/apache/spark/sql/auron/ShimsImpl.scala:
##########
@@ -301,6 +302,45 @@ class ShimsImpl extends Shims with Logging {
       child: SparkPlan): NativeGenerateBase =
     NativeGenerateExec(generator, requiredChildOutput, outer, generatorOutput, 
child)
 
+  @sparkver("3.0 / 3.1")
+  override def copyBatchScanExecWithRuntimeFilters(
+      exec: BatchScanExec,
+      runtimeFilters: Seq[Expression]): BatchScanExec =
+    exec.copy(exec.output, exec.scan)
+
+  @sparkver("3.2")
+  override def copyBatchScanExecWithRuntimeFilters(
+      exec: BatchScanExec,
+      runtimeFilters: Seq[Expression]): BatchScanExec =
+    exec.copy(exec.output, exec.scan, runtimeFilters)
+
+  @sparkver("3.3")
+  override def copyBatchScanExecWithRuntimeFilters(
+      exec: BatchScanExec,
+      runtimeFilters: Seq[Expression]): BatchScanExec =
+    exec.copy(exec.output, exec.scan, runtimeFilters, 
exec.keyGroupedPartitioning)
+
+  @sparkver("3.4")
+  override def copyBatchScanExecWithRuntimeFilters(
+      exec: BatchScanExec,
+      runtimeFilters: Seq[Expression]): BatchScanExec =
+    exec.copy(
+      exec.output,
+      exec.scan,
+      runtimeFilters,
+      exec.keyGroupedPartitioning,
+      exec.ordering,
+      exec.table,
+      exec.commonPartitionValues,
+      exec.applyPartialClustering,
+      exec.replicatePartitions)
+
+  @sparkver("3.5 / 4.0 / 4.1")

Review Comment:
   This groups 4.1 with 3.5/4.0 on the assumption that Spark 4.1's 
`BatchScanExec` constructor is still `(output, scan, runtimeFilters, ordering, 
table, spjParams)`. The shims module compiles for 4.1 even though iceberg 
doesn't build there, so if 4.1 changed that constructor the 4.1 profile would 
fail to compile rather than fail a test. Was the 4.1 branch actually built 
against a 4.1 profile, or is this optimistic grouping ahead of 4.1 GA? If it 
hasn't been compiled against 4.1 yet, would it be safer to split 4.1 into its 
own branch (or drop it from the group) until the signature is confirmed?



##########
thirdparty/auron-iceberg/src/main/scala/org/apache/spark/sql/execution/auron/plan/NativeIcebergTableScanExec.scala:
##########
@@ -60,6 +63,15 @@ case class NativeIcebergTableScanExec(basedScan: 
BatchScanExec, plan: IcebergSca
   override val output = basedScan.output
   override val outputPartitioning = basedScan.outputPartitioning
 
+  private lazy val plan: IcebergScanPlan = {
+    if (runtimeFilters.nonEmpty) {
+      val filteredScan = IcebergScanSupport.withRuntimeFilters(basedScan, 
runtimeFilters)
+      IcebergScanSupport.plan(filteredScan, useRuntimeFilters = 
true).getOrElse(staticPlan)

Review Comment:
   When runtime-filtered planning returns `None`, this falls back to 
`staticPlan` — the unfiltered plan over all partitions — with no log line. 
Correctness is safe here (the enclosing join re-applies the predicate, and 
prune-to-empty returns `Some(Seq.empty)` rather than `None`, so it doesn't hit 
this branch), so this is really an observability question: someone debugging 
"did DPP actually apply?" gets no signal that the query quietly scanned 
everything. Would a `logWarning` on the `getOrElse(staticPlan)` branch help — 
something noting that runtime-filtered planning was unavailable and all 
partitions are being scanned?



##########
thirdparty/auron-iceberg/src/main/scala/org/apache/spark/sql/auron/iceberg/IcebergScanSupport.scala:
##########
@@ -382,6 +416,40 @@ object IcebergScanSupport extends Logging {
     }
   }
 
+  private def runtimeFilteredPartitions(exec: BatchScanExec): 
Option[Seq[InputPartition]] = {
+    if (exec.runtimeFilters.isEmpty) {
+      return None
+    }
+
+    try {
+      MethodUtils.invokeMethod(exec, true, "prepare")
+      MethodUtils.invokeMethod(exec, true, "waitForSubqueries")
+      invokeDeclaredMethod(exec, "filteredPartitions") match {
+        case Some(seq: scala.collection.Seq[_]) =>
+          Some(flattenPartitions(seq))
+        case _ =>
+          None
+      }
+    } catch {
+      case NonFatal(t) =>

Review Comment:
   This `NonFatal` catch wraps `prepare` / `waitForSubqueries` / 
`filteredPartitions` and returns `None`, which flows up to `inputPartitions` 
and full-scans all partitions. That also swallows a genuine DPP subquery or 
broadcast execution failure and turns it into a silent full-table scan, where 
vanilla Spark's `BatchScanExec` would have surfaced the failure. Partitioning 
errors here isn't wrong for correctness (the join re-filters), but it changes 
error semantics — a real failure in `waitForSubqueries` gets masked. Should a 
`waitForSubqueries` failure be swallowed and fall through to a full scan at 
all, or would you rather let it propagate so a real subquery/broadcast failure 
stays visible? (It does log a warning, so this is about whether full-scanning 
past a genuine failure is the right default, not about missing logs.)



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

Reply via email to