ericyuan915 opened a new issue, #19208:
URL: https://github.com/apache/hudi/issues/19208

   ### Bug Description
   
   **What happened:**
   Reading a nested ROW column from a base Parquet file whose row count exceeds 
the vectorized read batch size (RecordIterators.DEFAULT_BATCH_SIZE = 2048) 
throws ArrayIndexOutOfBoundsException from NestedColumnReader#readRow. This is 
a plain base-file read. It reproduces for any base Parquet file with a nested 
ROW column that spans more than one read batch.
   
   **What you expected:**
   The nested ROW column reads back correctly across the batch boundary — rows 
past 2048 returned, all-null child rows collapsed to NULL as intended — with no 
exception, identical to reading the same data when it fits in a single batch.
   
   **Steps to reproduce:**
   1. Write more than 2048 rows (e.g. 2048 + 100) containing a nested ROW 
column (e.g. row<f0 int, f1 string>) into a single base Parquet file, so one 
read split crosses the 2048-row batch boundary.
   2. Read the table selecting the nested ROW column. 
   3. ParquetColumnarRowSplitReader#nextBatch crashes with 
ArrayIndexOutOfBoundsException the moment it processes the batch that crosses 
row 2048.
   
   (A row<row<decimal>> shape reproduces the same crash via a slightly 
different path — see the DECIMAL note in root cause.)
   
   **Root cause:**
   The FLINK-35702 Dremel nested reader carries a one-record lookahead. On a 
full, non-final batch, NestedPrimitiveColumnReader#readAndNewVector reads one 
value past the batch in its do { … } while (readValue() && repetitionLevel != 
0) loop, and #getLevelDelegation returns the accumulated definition/repetition 
level arrays including that trailing level (then re-seeds the lists with it for 
the next batch). NestedPositionUtil#calculateRowOffsets returns positionsCount 
= fieldDefinitionLevels.length, so positionsCount = batchSize + 1 = 2049, while 
the materialized child column vectors are sized to their value count = 2048.    
                                                                                
                                                                                
                              
                                                                                
                                                                                
                                                    
   This lookahead is harmless in Flink itself, and harmless on Hudi's 
primitive/collection paths, because ParquetColumnarRowSplitReader#nextBatch 
caps the emitted batch at num = min(batchSize, remaining) — the phantom 2049th 
position is never surfaced downstream.                                          
                                                                                
                                
                                                                                
                                                                                
                                                    
   The crash is in a Hudi-specific divergence from Flink: 
NestedColumnReader#readRow has an extra "collapse a row whose children are all 
null into a null row" loop, so that a SQL row(null, null) round-trips to NULL 
(preserving the deleted legacy RowColumnReader semantics; the in-code comment 
notes this diverges from Flink 2.1, which would surface Row(null, null)). This 
loop does
   `int rowCount = rowPosition.getPositionsCount();   // 2049                   
                                                                                
                                                   
     for (int j = 0; j < rowCount; j++) {                                       
                                                                                
                                                    
       if (heapRowVector.isNullAt(j)) continue;                                 
                                                                                
                                                    
       for (WritableColumnVector child : finalChildrenVectors) {                
                                                                                
                                                    
         if (!child.isNullAt(j)) { ... }               // child len == 2048 → 
j==2048 overruns                                                                
                                                      
       }                                                                        
                                                                                
                                                    
     } `
   
   At j = 2048 it calls child.isNullAt(2048) on a length-2048 
AbstractHeapVector → Index 2048 out of bounds for length 2048. Flink's own 
NestedColumnReader has no such loop, so upstream Flink is unaffected — this is 
an upstream-Hudi defect introduced with the nested-reader port.                 
                                                                                
                                       
                                                                                
                                                                                
                                                    
   DECIMAL subtlety: DECIMAL leaves are materialized as a ParquetDecimalVector 
wrapping a Heap*Vector, which is not itself an AbstractHeapVector. A nested row 
whose short children are all decimals (e.g. row<row<decimal>>) hits the same 
overrun and must be unwrapped when computing the safe bound.
   
   ### Environment
   
   **Hudi version:** master (1.3.0-SNAPSHOT). Present since the FLINK-35702 
Dremel nested-reader port — #18636 (2026-05-07), #18700 (2026-05-08), #18701 
(2026-05-09) — and extended to hudi-flink2.0 by the backport #18959 
(2026-06-11). Not present in 1.2.0 or earlier.
   **Query engine:** (Spark/Flink/Trino etc) Flink. Affects every hudi-flink 
module that carries the Dremel NestedColumnReader: hudi-flink1.18.x, 
hudi-flink1.19.x, hudi-flink1.20.x, hudi-flink2.0.x, hudi-flink2.1.x. 
(hudi-flink1.17.x does not have this reader and is unaffected.)
   **Relevant configs:** None — default vectorized COW read path. No config 
gates it; the batch size is the hardcoded RecordIterators.DEFAULT_BATCH_SIZE = 
2048. Triggered whenever a single read split over a base file with a nested ROW 
column crosses 2048 rows. 
   
   
   ### Logs and Stack Trace
   
   java.lang.ArrayIndexOutOfBoundsException: Index 2048 out of bounds for 
length 2048                                                                     
                                                        
       at 
org.apache.flink.table.data.columnar.vector.heap.AbstractHeapVector.isNullAt(AbstractHeapVector.java:94)
                                                                                
                  
       at 
org.apache.hudi.table.format.cow.vector.reader.NestedColumnReader.readRow(NestedColumnReader.java:162)
                                                                                
                    
       ...                                                                      
                                                                                
                                                    
       at 
org.apache.hudi.table.format.cow.vector.reader.ParquetColumnarRowSplitReader.nextBatch(ParquetColumnarRowSplitReader.java:352)


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

Reply via email to