dongjoon-hyun commented on PR #59069:
URL: https://github.com/apache/spark/pull/59069#issuecomment-5848600085

   I reviewed the diff at cdd72c25cb2 by tracing the generated-code paths. I 
haven't run the repro queries below yet, so please double-check them.
   
   ### Correctness
   
   **1. A `With` fill call reads `INPUT_ROW`, but the split methods don't take 
it** (`CodeGenerator.scala` L1316, L1327)
   
   `wholeStageSplitArguments` follows a `CommonExpressionRef` into its 
definition but never sets `needsRow`. When `CommonExprSlots.build` turns the 
fill into a method (`worthAMethod`), the reference emits 
`computeCommonExpr_N(<INPUT_ROW>, ...)`, because `methodArgs` always passes a 
non-null `INPUT_ROW` (L344-345). That call now lands inside a split 
`caseWhen_*` method that has no `INPUT_ROW` parameter.
   
   A `With` in a non-first branch stays in the plan. So under a keyed 
`HashAggregateExec`, where `INPUT_ROW` is the local `unsafeRowBuffer`, the 
following query should fail with `Unknown variable or type 
"agg_unsafeRowAggBuffer_0"`. It throws under testing and falls back to non-WSCG 
otherwise.
   
   ```sql
   SELECT k, sum(CASE WHEN v = 0 THEN 0
                      WHEN v = 1 THEN nullif(nullif(v * 3, 1), 2)
                      WHEN v = 2 THEN v * 2 ... WHEN v = 300 THEN v * 300
                      ELSE 0 END)
   FROM t GROUP BY k
   ```
   
   - In-loop BHJ/SMJ/BNLJ join conditions, where `INPUT_ROW` is the build-row 
local, have the same problem.
   - This compiled before the PR. The second commit ("Pass INPUT_ROW only where 
read") seems to have introduced it.
   - Setting `needsRow` in the `CommonExpressionRef` arm when `INPUT_ROW != 
null` would fix it.
   
   **2. A `computeCommonExpr_N` from the discarded non-split pass can call 
removed methods** (L1197-1214, L1721-1723)
   
   Only functions added inside the `splitExpressions` call are recorded as 
discardable. `CommonExprSlots.build` adds `computeCommonExpr_N` in the same 
non-split pass, but outside that window, so it is not recorded. It survives, 
while the `caseWhen_*` methods it calls are removed, and Janino fails with `A 
method named ... is not declared`. For example, with a `ProjectExec` and 
subexpression elimination:
   
   ```scala
   val cw = largeCaseWhen("id", 300)
   val cw2 = (1 to 300).map(k => s"WHEN id = $k THEN id + $k").mkString("CASE 
", " ", " ELSE 0 END")
   spark.range(10).selectExpr(s"($cw) + 1 AS a", s"($cw) + 2 AS b",
     s"IF(id < 0, 0, nullif($cw2, 0)) AS c", s"IF(id < 0, 0, nullif($cw2, 0)) 
AS d").collect()
   ```
   
   `addNewFunctionToClass` is the only place that writes `classFunctions`. 
Recording there every function added during the non-split pass would fix this. 
It would also replace the snapshot and diff of all function names that is taken 
on every split.
   
   ### Compatibility
   
   **3.** Adding the default parameter `sources` changes the JVM signature of 
`CodegenContext.splitExpressionsWithCurrentInputs`.
   
   - The signature has not changed since the method was added in 2.3.0 
(SPARK-22701).
   - External projects call it, e.g. apache/gluten (`GenerateArrowProjection`) 
and aehrc/pathling. Their jars built against an earlier 4.x would fail with 
`NoSuchMethodError` on 4.4.0.
   - catalyst is internal, but keeping the existing 6-arg method and adding the 
new variant without default values would avoid this. Scala doesn't allow 
default arguments on more than one overload.
   
   ### Tests
   
   **4.** None of the new tests exercises the `With`/`CommonExpressionRef` 
arms, the `needsRow` path, the refusals, or the removal of discarded functions, 
so 1 and 2 are not caught. `max_by`/`min_by` update/merge expressions are 
3-branch `CASE WHEN`s that read the buffer through `INPUT_ROW`, so a keyed 
`max_by` test would cover `needsRow`.
   
   **5.** The join test only checks answers.
   
   - In all three queries the condition is generated inside the match loop, 
where the build row is in scope. The test can't detect `INPUT_ROW` misuse, and 
it can't detect splitting being disabled for joins.
   - The comment "The part of the condition on the streamed side alone is 
generated before the loop" doesn't hold for this query. 
`spark.sql.join.splitStreamedSideJoinCondition` defaults to false, and 
`ExprUtils.canEvaluateUnconditionally` does not accept `CaseWhen`.
   
   **6.** The ROLLUP test doesn't read a compacted slot, so the slot branch in 
`canPass` is never taken. `v` passes through `Expand` unchanged. Also, with 
`splitConsumeFuncByOperator` (on by default), the aggregate reads `doConsume` 
parameters.
   
   **7.** `stageCodes` duplicates the existing `genCode(ds)` but lacks its 
non-empty assertion. So `assert(!splitsCaseWhen(df))` in the last test passes 
vacuously if the plan stops being whole-stage codegen'd.
   
   ### Minor
   
   - Under WSCG, `buildCodeBlocks` runs two regexes per code string before 
`sources.nonEmpty` is checked. This hits the 22 callers that pass no `sources`, 
and it runs twice for `CaseWhen`.
   - Every split block and group method takes the union of all branch inputs. A 
wide `CASE WHEN` is therefore refused entirely, e.g. one with 85 or more 
distinct nullable `bigint` inputs, even though that is the case most likely to 
exceed 64KB.
   - The 1024-char threshold also splits small `CASE WHEN`s (2-4 branches) now. 
Could you add a benchmark for small and medium sizes, with the conf on and off?
   - `wholeStageSplitArguments` largely duplicates 
`CommonExprSlots.methodArgs`, but their rules already differ on `INPUT_ROW`, 
compacted slots, fields, `LambdaVariable` and subexpression states. Item 1 
comes from that gap.
   - The `Cast` arm of `readsSubExprState` copies `Cast.genCode`'s pass-through 
condition, so the two can drift apart.
   - The `LambdaVariable` refusal says the value is "a local declared by the 
loop", but `prepareLambdaVariable` makes it an inlined field.
   - The `CodegenFallback` arm is unreachable under WSCG.
   


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