[ 
https://issues.apache.org/jira/browse/SPARK-58780?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated SPARK-58780:
-----------------------------------
    Labels: pull-request-available  (was: )

> Transpilation with multiple lambdas can hide a cartesian join
> -------------------------------------------------------------
>
>                 Key: SPARK-58780
>                 URL: https://issues.apache.org/jira/browse/SPARK-58780
>             Project: Spark
>          Issue Type: Sub-task
>          Components: PySpark
>    Affects Versions: 4.3.0
>            Reporter: Holden Karau
>            Priority: Major
>              Labels: pull-request-available
>
> When a Python UDF is the **sole join condition** and it gets transpiled to 
> native
> Catalyst expressions, the `spark.sql.crossJoin.enabled=false` guard stops 
> firing --
> but the query still executes as an `O(n*m)` `CartesianProduct`. A user who
> deliberately disabled cross joins to catch accidental quadratic scans silently
> loses that protection the moment they enable transpilation. The failure mode
> changes from a loud analysis error to a silent quadratic scan.
> ## Reproduction
> ```python
> from pyspark.sql import Row, SparkSession
> from pyspark.sql.functions import udf
> from pyspark.sql.types import BooleanType
> spark = (SparkSession.builder.master("local[2]")
>          .config("spark.sql.ansi.enabled", "true").getOrCreate())
> spark.conf.set("spark.sql.crossJoin.enabled", "false")
> left = spark.createDataFrame([Row(a=1), Row(a=2)])
> right = spark.createDataFrame([Row(b=1), Row(b=2)])
> eq = udf(lambda a, b: a == b, BooleanType())
> df = left.join(right, eq("a", "b"))   # UDF is the ONLY join condition
> df.collect()
> ```
> | `transpilePyUDFs` | Result |
> |---|---|
> | `false` | raises `AnalysisException: Detected implicit cartesian product 
> for INNER join between logical plans` |
> | `true`  | **no error**, returns `[Row(a=1, b=1), Row(a=2, b=2)]`, 
> `executedPlan` contains `CartesianProduct`, no `EvalPython` |
> ## Why it happens
> `CheckCartesianProducts.isCartesianProduct`
> (`sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/optimizer/Optimizer.scala:2652`)
> asks only whether *some conjunct references both sides*:
> ```scala
> case _ => !conditions.map(_.references).exists(refs =>
>   refs.exists(join.left.outputSet.contains) && 
> refs.exists(join.right.outputSet.contains))
> ```
> * **Interpreted:** the Python UDF cannot be evaluated as a join condition, so 
> it is
>   pulled out as `Filter` above a join with no condition. No conjunct 
> references both
>   sides, `isCartesianProduct` is true, and the guard raises.
> * **Transpiled:** the UDF becomes a native `CASE WHEN (isnull(a) AND 
> isnull(b)) THEN true ...`
>   that *does* reference both sides, so `isCartesianProduct` returns false and 
> the guard
>   is skipped. But no equi-join key is extractable from a `CASE WHEN`, so the 
> planner
>   still chooses `CartesianProduct`.
> So the predicate the guard uses ("does a conjunct span both sides?") is a 
> proxy for
> "is there a usable join key?", and lowering breaks the correspondence between 
> them.



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

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

Reply via email to