Josh Rosen created SPARK-58431:
----------------------------------
Summary: SimplifyExtractValueOps folds an out-of-bounds array
index to NULL under ANSI, suppressing INVALID_ARRAY_INDEX and corrupting the
result's nullability
Key: SPARK-58431
URL: https://issues.apache.org/jira/browse/SPARK-58431
Project: Spark
Issue Type: Bug
Components: SQL
Affects Versions: 4.0.0
Reporter: Josh Rosen
This is a report of a correctness bug reachable under default configuration on
Spark 4.x (ANSI mode is now enabled by default). A query that must raise
{{INVALID_ARRAY_INDEX}} instead returns a fabricated value of {{{}0{}}},
silently.
{{SimplifyExtractValueOps}} rewrites {{array(...)[i]}} when the index is a
literal ({{{}ComplexTypes.scala{}}}):
{code:java}
case ga @ GetArrayItem(CreateArray(elems, _), IntegerLiteral(idx), _) =>
if (idx >= 0 && idx < elems.size) {
elems(idx)
} else {
// out of bounds, mimic the runtime behavior and return null
Literal(null, ga.dataType)
} {code}
The comment states the intent: mimic the runtime behavior. That was accurate
when the rule was written (SPARK-18601, 2.2.0), but SPARK-33386 (3.1.1) changed
the runtime behavior: under ANSI, an out-of-bounds array access _raises_ rather
than returning null. The rule was never updated, and it does not consult
{{GetArrayItem.failOnError}} (which defaults to
{{{}SQLConf.get.ansiEnabled{}}}).
This causes two problems:
* The error is suppressed.
* Under ANSI {{GetArrayItem}} declares itself *non-nullable* (it throws
instead of returning null), so substituting a null literal places a NULL in a
slot the schema marks non-nullable. Depending on the downstream consumer, this
can return either NULL (incorrect under ANSI) or as the type's zero value (also
incorrect).
*Repro* (default configuration on 4.x; add {{SET spark.sql.ansi.enabled=true}}
on older versions):
{code:java}
SET spark.sql.ansi.enabled=true;
CREATE TABLE arr_src(c INT) USING parquet;
INSERT INTO arr_src VALUES (1);
SELECT array(c, 2, 3)[5] AS v FROM arr_src;
-- prints: NULL (expected under ANSI: INVALID_ARRAY_INDEX error)
SELECT count(*) FROM arr_src WHERE array(c, 2, 3)[5] IS NULL;
-- prints: 0 (the value that just printed NULL matches no IS NULL
filter)
SELECT array(c, 2, 3)[5] IS NULL AS n FROM arr_src;
-- prints: false
SELECT sum(array(c,2,3)[5]) AS s, count(array(c,2,3)[5]) AS n FROM arr_src;
-- prints: NULL, 1 (count treats it as one NON-null value; sum of it is
null) {code}
No consistent value can produce those four outputs together. {*}Which symptom
the corrupted slot surfaces as is read-path-dependent{*}: the spark-sql CLI's
row rendering honors the physical null bit and prints {{{}NULL{}}}, while
{{Dataset.collect()}} (Scala/Java/Python APIs) trusts the declared {{{}nullable
= false{}}}, elides the null check, and materializes a fabricated {{0.}}
Affected versions: the fold dates from 2.2.0, but it only became incorrect when
SPARK-33386 (3.1.1) made ANSI out-of-bounds access raise. So 3.1.1+ with ANSI
enabled, and {*}4.x by default{*}.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]