Stevo Mitric created SPARK-60875:
------------------------------------

             Summary: Recursive CTE reads NULLs written into nested types of 
the anchor back as 0
                 Key: SPARK-60875
                 URL: https://issues.apache.org/jira/browse/SPARK-60875
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 4.4.0
            Reporter: Stevo Mitric


The self-reference of a recursive CTE (UnionLoopRef) is typed with the anchor's 
output types; only top-level nullability is widened (SPARK-52430). If the 
anchor builds an array, map or struct from non-null values, e.g. array(c) or 
map('k', c), the element, value or field type stays non-nullable. When the 
recursive step writes a NULL there, the next iteration reads it back through 
generated code that skips the null check, so the NULL silently becomes 0:

{code:sql}
CREATE OR REPLACE TEMPORARY VIEW parts AS SELECT id AS part_id, id AS cost FROM 
range(200);
WITH RECURSIVE bom(lvl, part_id, costs) AS (
  SELECT 0, part_id, array(cost) FROM parts
  UNION ALL
  SELECT lvl + 1, part_id, IF(lvl = 0, array(CAST(NULL AS BIGINT)), 
array(costs[0] + 1))
  FROM bom WHERE lvl < 2
)
SELECT lvl, count(*), count_if(costs[0] IS NULL), sum(costs[0]) FROM bom GROUP 
BY lvl ORDER BY lvl;
-- actual:   0 200 0 19900 / 1 200 200 NULL / 2 200 0 200
-- expected: 0 200 0 19900 / 1 200 200 NULL / 2 200 200 NULL
{code}

The same happens for map values (info['k'] returns 0 instead of NULL) and for 
arrays inside structs. A NULL struct element inside an array throws a 
NullPointerException instead. A column that only copies another column (b = a) 
is also affected once a's NULLs reach it.

This happens with the default configuration. Steps larger than 
spark.sql.cteRecursionAnchorRowsLimitToConvertToLocalRelation (100 rows) are 
always affected. Smaller steps are converted to a LocalRelation and are 
affected when the recursive step also reads a non-local relation (e.g. a join 
with a table), because the step then runs through generated code.

Related, same cause: when another column of the recursion needs a cast, a 
recursive step that writes a NULL into such a nested type fails analysis 
instead:

{code:sql}
WITH RECURSIVE t(n, m, arr) AS (
  SELECT 1, CAST(1 AS BIGINT), ARRAY(1)
  UNION ALL
  SELECT n + 1, n + 1, ARRAY(CAST(NULL AS INT)) FROM t WHERE n < 3
)
SELECT * FROM t;
-- [CANNOT_MERGE_INCOMPATIBLE_DATA_TYPE] Failed to merge incompatible data 
types "ARRAY<INT>" and "ARRAY<INT>"
{code}




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