cloud-fan commented on code in PR #58077:
URL: https://github.com/apache/spark/pull/58077#discussion_r3823269824


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/subquery.scala:
##########
@@ -125,7 +127,17 @@ case class InSubqueryExec(
 
   @transient private lazy val inSet = InSet(child, result.toSet)
 
-  override def nullable: Boolean = child.nullable
+  // Mirror the logical InSubquery.nullable: nullable when any output column 
is nullable
+  // (null in any column position produces UNKNOWN on a miss) or when the 
child is nullable.
+  // Respects LEGACY_IN_SUBQUERY_NULLABILITY to stay in sync with the logical 
node.
+  // See SPARK-58481.
+  override def nullable: Boolean = {
+    if (!SQLConf.get.getConf(SQLConf.LEGACY_IN_SUBQUERY_NULLABILITY)) {
+      child.nullable || plan.output.exists(_.nullable)

Review Comment:
   **Blocking:**
   
   Row-valued `IN` must remain nullable when any left-hand field is nullable. 
For multi-column inputs, `child` is the `CreateNamedStruct` from 
`PlanSubqueries`, whose top-level `nullable` is always false; with a 
non-nullable RHS, this declares the expression non-nullable even though 
`evalMultiColumn` returns UNKNOWN for a null LHS field. Generated `NOT IN` can 
then turn UNKNOWN into TRUE. Please derive LHS nullability from the struct 
fields or underlying values and add a nullable-LHS/non-nullable-RHS codegen 
regression.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/subquery.scala:
##########
@@ -165,14 +177,81 @@ case class InSubqueryExec(
     }
   }
 
+  // Three-valued IN semantics for multi-column subqueries.
+  // Result rows are InternalRow objects; InSet uses TreeSet ordering which 
treats null fields as
+  // non-equal and therefore cannot distinguish a definitively-false candidate 
(a non-null field
+  // differs) from an indeterminate one (all non-null fields match but some 
fields are null).
+  // We replicate In.eval's per-candidate logic: TRUE if any candidate matches 
exactly,
+  // UNKNOWN if no TRUE and at least one candidate is indeterminate, FALSE 
otherwise.
+  private def evalMultiColumn(inputRow: InternalRow): Any = {
+    val value = child.eval(inputRow)
+    if (value == null) return null
+    val inputStruct = value.asInstanceOf[InternalRow]
+    val output = plan.output
+    val numFields = output.length
+    // Hoist per-field orderings: invariant across all candidate rows.
+    val orderings = Array.tabulate(numFields)(i =>
+      TypeUtils.getInterpretedOrdering(output(i).dataType))
+    val fieldTypes = Array.tabulate(numFields)(i => output(i).dataType)
+    var hasUnknown = false
+    var i = 0
+    while (i < result.length) {

Review Comment:
   **Blocking:**
   
   Please avoid scanning the entire collected subquery result on every 
predicate evaluation. The generated path calls this same loop, while `InSet` 
already provides indexed struct membership; on a nested-loop join this adds 
another factor of subquery cardinality to every candidate pair. Keep the 
indexed fast path for provably non-null rows and precompute the schema, 
orderings, and any null-aware candidate index outside `evalMultiColumn`.



##########
sql/core/src/test/resources/sql-tests/inputs/subquery/in-subquery/in-joins.sql:
##########
@@ -389,6 +389,20 @@ ON s1.id = s2.id
 AND s1.id NOT IN (SELECT id FROM s3);
 
 
+-- SPARK-58481: NOT IN subquery with NULL in result and non-nullable child on 
FULL OUTER JOIN.
+-- 5 NOT IN (99, NULL) evaluates to UNKNOWN; a join condition that is not TRUE 
matches no rows,
+-- so FULL OUTER JOIN must emit null-padded rows for every row from each side 
(3+3=6 rows).

Review Comment:
   **Nit:**
   
   `s1` and `s2` contain five rows each, and the generated result contains ten 
null-padded rows.
   ```suggestion
   -- so FULL OUTER JOIN must emit null-padded rows for every row from each 
side (5+5=10 rows).
   ```



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