markj-db opened a new pull request, #59190:
URL: https://github.com/apache/spark/pull/59190

   ### What changes were proposed in this pull request?
   
   `org.apache.spark.sql.execution.ExternalAppendOnlyUnsafeRowArray` counted 
the buffered rows of a
   window partition in a 32-bit `Int` (`numRows`), exposed via `def length: 
Int`. When a single
   window partition exceeds `Int.MaxValue` (2,147,483,647) rows, the counter 
overflows and wraps
   negative, which silently corrupts the window-frame logic that reads it.
   
   This PR:
   
   - Widens the row counter and its accessor to `Long` (`numRows`/`length` in
     `ExternalAppendOnlyUnsafeRowArray`), and widens the offset-frame cursor
     `OffsetWindowFunctionFrameBase.inputIndex` to `Long`. This makes 
`lag`/`lead`
     (`FrameLessOffsetWindowFunctionFrame`) and the unbounded-offset `nth_value`
     (`UnboundedOffsetWindowFunctionFrame`) produce **correct** results over 
partitions larger than
     `Int.MaxValue` rows. These frames ignore the driver row index and track 
their cursor as a
     `Long`.
   - Adds `WindowFunctionFrame.supportsLargePartition` (defaults to `false`; 
overridden to `true` on
     the two offset frames above). The remaining frames still track the per-row 
index and window
     bounds in 32-bit `Int` -- including the driver's `rowIndex` in 
`WindowEvaluatorFactory` and
     `ArrowWindowPythonEvaluatorFactory`, and the bounds in the 
sliding/ranking/segment-tree frames
     -- so they cannot handle partitions larger than `Int.MaxValue` rows.
   - Adds a fail-fast guard 
(`WindowEvaluatorFactoryBase.checkPartitionSizeLimit`, called by both
     window drivers after buffering a partition) that throws a new user-facing 
error
     `WINDOW_FUNCTION_PARTITION_SIZE_EXCEEDS_LIMIT` when a partition exceeds 
`Int.MaxValue` rows and
     any active frame does not support it, instead of silently producing wrong 
results (or throwing
     an `ArrayIndexOutOfBoundsException`). The 32-bit narrowing sites in those 
frames now use
     `Math.toIntExact` as a defensive backstop that fails loudly rather than 
truncating silently.
   
   Full `Long` propagation for the non-offset frames (frame-bound API, 
`AggregateProcessor`, and the
   core `UnsafeExternalSorter.getInsertionOrderIterator(int startIndex)`) is a 
larger change and is
   intentionally left as a follow-up; this PR converts their silent corruption 
into a clear error.
   
   This is the read-path counterpart of the write/insertion-path overflow fixed 
by
   [SPARK-51757](https://issues.apache.org/jira/browse/SPARK-51757), which did 
not widen
   `numRows`/`length`.
   
   ### Why are the changes needed?
   
   Window queries over a single partition larger than `Int.MaxValue` rows can 
return silently
   incorrect results. The clearest case, confirmed by reproduction, is 
`lag`/`lead` returning `NULL`
   for every row:
   
   ```sql
   SELECT count(*) AS n_rows, max(prev) AS max_prev, max(id) AS max_id
   FROM (
     SELECT id, lag(id, 1) OVER (PARTITION BY CAST(0 AS INT) ORDER BY id) AS 
prev
     FROM range(2200000000)
   );
   ```
   
   ```
   n_rows   = 2200000000     (all rows funnel to ONE partition via PARTITION BY 
a constant)
   max_prev = NULL           <-- BUG. Correct value is 2199999998
   max_id   = 2199999999     (sanity: the range itself is intact)
   ```
   
   `lag(id, 1)` over ids `0..2199999999` must yield `NULL, 0, 1, ..., 
2199999998`, so `max(prev)`
   must be `2199999998`; it returned `NULL` for every row. For `2^31 < N < 
2^32` the wrapped count is
   negative and routes to the "offset outside partition -> default" branch; an
   `ArrayIndexOutOfBoundsException` is the sibling symptom at larger `N`. A 
query should either
   produce a correct result or fail with a clear error, never silently return 
wrong data.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes.
   
   - `lag`/`lead` and unbounded-offset `nth_value` over a partition larger than 
`Int.MaxValue` rows
     now return the correct result. Previously they returned `NULL` (or threw
     `ArrayIndexOutOfBoundsException`).
   - Other window frames (e.g. sliding, running aggregates, ranking) over such 
a partition now fail
     with the new error condition 
`WINDOW_FUNCTION_PARTITION_SIZE_EXCEEDS_LIMIT` (SQLSTATE `54000`)
     instead of silently returning wrong results. Partitions up to and 
including `Int.MaxValue` rows
     are unaffected.
   
   ### How was this patch tested?
   
   - New `WindowFunctionFrameSuite`: covers the `checkPartitionSizeLimit` guard 
at the `Int.MaxValue`
     boundary (allowed) and above it (rejected for unsafe frames, allowed when 
all frames support
     large partitions, rejected when any frame does not), the thrown error 
condition, and that
     `FrameLessOffsetWindowFunctionFrame` declares large-partition support 
while the base defaults to
     `false`.
   - New test in `ExternalAppendOnlyUnsafeRowArraySuite` pinning `length` as a 
`Long`.
   - Regression: `DataFrameWindowFunctionsSuite` and `WindowSegmentTreeSuite` 
pass unchanged.
   
   Materializing a 2^31+ row partition in a unit test is infeasible (the manual 
reproduction above
   took ~30 minutes over 2.2B rows on a dedicated core), so the overflow 
boundary is covered by the
   targeted unit tests above rather than an end-to-end run.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Isaac
   
   This pull request and its description were written by Isaac.
   


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