sepuri sai krishna created SPARK-58821:
------------------------------------------

             Summary: sequence() raises INTERNAL_ERROR for BIGINT endpoints 
whose difference overflows Long
                 Key: SPARK-58821
                 URL: https://issues.apache.org/jira/browse/SPARK-58821
             Project: Spark
          Issue Type: Bug
          Components: SQL
    Affects Versions: 4.3.0
            Reporter: sepuri sai krishna


{{sequence(start, stop, step)}} over BIGINT raises an {{INTERNAL_ERROR}} 
(SQLSTATE XX000) for argument combinations that have a small, well-defined 
result. Reachable from plain SQL with default configuration, on both the 
interpreted and the codegen path.

{code:sql}
SELECT sequence(-9223372036854775808L, 9223372036854775807L, 
9223372036854775807L);
-- [INTERNAL_ERROR] Unreachable code reached. SQLSTATE: XX000
-- expected: [-9223372036854775808, -1, 9223372036854775806]

SELECT sequence(-9223372036854775808L, 0L, 4611686018427387904L);
-- [INTERNAL_ERROR] Unreachable code reached. SQLSTATE: XX000
-- expected: [-9223372036854775808, -4611686018427387904, 0]
{code}

The same failure occurs when the arguments are not foldable, so it is not 
confined to constant folding:

{code:sql}
CREATE OR REPLACE TEMP VIEW t AS
  SELECT CAST(-9223372036854775808 AS BIGINT) AS a,
         CAST(9223372036854775807 AS BIGINT) AS b,
         CAST(9223372036854775807 AS BIGINT) AS c;
SELECT sequence(a, b, c) FROM t;
-- [INTERNAL_ERROR] Unreachable code reached. SQLSTATE: XX000
{code}

h3. Cause

{{Sequence.sequenceLength}} computes the length in {{long}} and falls back to 
{{BigInt}} when that overflows:

{code:scala}
def sequenceLength(start: Long, stop: Long, step: Long): Int = {
  try {
    val delta = Math.subtractExact(stop, start)      // (1) overflows
    ...
    val len = if (stop == start) 1L else Math.addExact(1L, (delta / step))
    if (len > ByteArrayMethods.MAX_ROUNDED_ARRAY_LENGTH) {
      throw 
QueryExecutionErrors.createArrayWithElementsExceedLimitError(prettyName, len)
    }
    len.toInt
  } catch {
    case _: ArithmeticException =>
      val safeLen = BigInt(1) + (BigInt(stop) - BigInt(start)) / BigInt(step)   
// (2)
      if (safeLen > ByteArrayMethods.MAX_ROUNDED_ARRAY_LENGTH) {
        throw 
QueryExecutionErrors.createArrayWithElementsExceedLimitError(prettyName, 
safeLen)
      }
      throw internalError("Unreachable code reached.")                          
// (3)
    ...
{code}

The fallback assumes an overflow at (1) implies a result too large to allocate, 
so after recomputing the length exactly at (2) it only re-raises the 
array-limit error and otherwise treats (3) as dead code.

That assumption does not hold. {{Math.subtractExact(stop, start)}} overflows 
whenever {{stop - start}} exceeds the {{long}} range, which says nothing about 
the length: the length also depends on {{step}}. When the step is large, the 
exact length computed at (2) is small, no limit error is raised, and control 
reaches the {{internalError}} at (3) -- with the correct answer sitting in 
{{safeLen}}, discarded.

For the first example above, {{safeLen}} is exactly {{3}}.

h3. Scope

* BIGINT only. {{IntegralSequenceImpl}} reaches {{sequenceLength}} via 
{{getSequenceLength}}, which widens its arguments with {{toLong}}, so for 
INT/SMALLINT/TINYINT the subtraction cannot overflow. The DATE/TIMESTAMP 
callers pass microsecond values, whose range is far from the {{long}} limits.
* Both execution paths. The interpreted path calls {{Sequence.sequenceLength}} 
directly and the generated code calls the same method, so they fail identically.
* Not a recent regression. {{sequenceLength}} was introduced in its current 
shape by SPARK-43393 (commit {{afc4c49927cb}}, Nov 2023), which fixed a genuine 
correctness bug where the {{long}} length computation overflowed silently and 
{{sequence}} returned an empty array. That fix added the {{BigInt}} fallback 
and, reasonably at the time, treated the tail as unreachable -- the overflow 
cases it was written against all had huge lengths. The gap is that a large 
{{step}} makes the exact length small, so the tail is reachable after all. The 
method has not been modified since, so every release containing SPARK-43393 
(3.3.4, 3.4.2, 3.5.1, 4.0.0 and later) is affected.

h3. Suggested fix

At (3), return {{safeLen.toInt}} instead of throwing. The value has already 
been proven to be at most {{ByteArrayMethods.MAX_ROUNDED_ARRAY_LENGTH}} by the 
check immediately above, so the narrowing conversion is safe, and the 
element-filling code already handles steps of this magnitude correctly -- 
{{sequence(-4611686018427387904L, 4611686018427387903L, 
4611686018427387903L)}}, whose endpoints are close enough together to avoid the 
overflow, returns {{[-4611686018427387904, -1, 4611686018427387902]}} today.

Tests belong in {{CollectionExpressionsSuite}}, which exercises both the 
interpreted and the codegen path through {{checkEvaluation}}.




--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to