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


##########
connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala:
##########
@@ -3732,6 +3732,23 @@ class AvroV1Suite extends AvroSuite {
       .sparkConf
       .set(SQLConf.USE_V1_SOURCE_LIST, "avro")
 
+  test("SPARK-59107: positionalFieldMatching makes an avro read 
projection-sensitive") {
+    withTempPath { dir =>
+      val path = dir.getCanonicalPath
+      spark.range(0, 5).selectExpr("id AS a", "id * 10 AS 
b").write.format("avro").save(path)
+      withTempView("t") {
+        spark.read.option("positionalFieldMatching", 
"true").format("avro").load(path)
+          .createOrReplaceTempView("t")
+        // Positional matching pairs a column with the Avro field at its 
position in the projected
+        // schema, so a scan of b alone reads the first Avro field and answers 
10, while a scan
+        // shared with sum(a) reads both fields, pairs them by coincidence and 
answers 100. The 10
+        // is wrong against the file, which is SPARK-59108; what merging must 
not do is make one
+        // subquery's value depend on what the other one projects.
+        checkAnswer(sql("SELECT (SELECT sum(a) FROM t), (SELECT sum(b) FROM 
t)"), Row(10L, 10L))

Review Comment:
   **Finding 10.** Your description says #58409 "removes that at the root, and 
this case can go with it". It takes this test with it too, and not by making it 
redundant - by failing it.
   
   I ran this exact query in a worktree at #58409's head `a3358708e60`:
   
   ```
   merged   = [10, 100]
   unmerged = [10, 100]
   ```
   
   `sum(b)` on its own is 100 there, because the fix resolves `b` against its 
position in the data schema rather than in the projection. So `Row(10L, 10L)` 
fails, and the avro arm of `hasProjectionSensitiveParser` is dead at the same 
moment - the read stops being projection-sensitive, which is exactly what those 
two numbers being equal shows.
   
   Whichever of the two lands second pays for it, so it is worth deciding now 
and saying so in the description. Two ways out:
   
   - Land #58409 first and drop the avro arm plus this test from here. That 
also drops the only reason `DataSourceUtils` imports 
`org.apache.spark.sql.avro`.
   - Keep them, land this first, and have #58409 delete both.
   
   If you would rather keep the test through the transition, the assertion that 
survives is the one the `sql/core` suite already uses - the columns each scan 
reads, which stay `Seq(Seq("a"), Seq("b"))` under the gate whatever `sum(b)` 
evaluates to. `AvroV1Suite` would need `spark.sql.adaptive.enabled` off for 
that, for the reason `FileSourceV1PlanMergingSuite:369` gives.
   



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/planmerging/PlanMerger.scala:
##########
@@ -52,8 +53,19 @@ case class MergeResult(
  * @param merged Whether this plan is the result of merging two or more plans 
(true), or
  *               is an original unmerged plan (false). Merged plans typically 
require special
  *               handling such as wrapping in CTEs.
+ * @param projectionSensitiveReads The columns the plans behind this entry 
read from each
+ *                                 projection-sensitive relation, recorded 
when the entry was
+ *                                 first cached. Every plan merged into the 
entry read those same
+ *                                 columns, which is what merging one in 
requires, so the record
+ *                                 stays true of the entry. It cannot be 
re-derived from `plan`,
+ *                                 because a merge leaves projections that the 
`ColumnPruning`
+ *                                 rerun after this rule narrows again. See
+ *                                 
`PlanMerger.collectProjectionSensitiveReads`.
  */
-case class MergedPlan(plan: LogicalPlan, merged: Boolean)
+case class MergedPlan(
+    plan: LogicalPlan,
+    merged: Boolean,
+    projectionSensitiveReads: Map[LogicalPlan, Seq[Set[String]]] = Map.empty)

Review Comment:
   **Finding 11.** Nothing uses the default. `MergedPlan(plan, false, 
projectionSensitiveReads)` at line 210 is the only construction, and the two 
other sites are `mp.copy(...)`, which carries the record over.
   
   The value it defaults to is also the permissive one: an entry built without 
a record compares equal to any plan that reads no projection-sensitive 
relation, so the merge goes through. Dropping the default makes a future 
construction site say what it means.
   
   ```scala
   case class MergedPlan(
       plan: LogicalPlan,
       merged: Boolean,
       projectionSensitiveReads: Map[LogicalPlan, Seq[Set[String]]])
   ```
   



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