LuciferYang commented on code in PR #55924:
URL: https://github.com/apache/spark/pull/55924#discussion_r3921319232


##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetDeltaByteArrayEncodingSuite.scala:
##########
@@ -100,6 +214,117 @@ class ParquetDeltaByteArrayEncodingSuite extends 
ParquetCompatibilityTest with S
       geoType)
   }
 
+  testGeo("geo interleaves skipBinary with readGeoData (null/skipped rows)") { 
geoType =>
+    // A geometry column with null or skipped rows alternates skipBinary (used 
for the
+    // skipped rows) with readGeometry/readGeography on the same reader. Both 
paths now
+    // share the reusable prevBuf, so the shared prefix carried across a 
skipped value must
+    // still be honored by the following read. The values also exceed 
prevBuf's initial
+    // 64-byte capacity, exercising the grow-and-preserve branch under 
interleaving.
+    assertGeoReadWriteWithSkip(writer, reader, sharedPrefixPolygons(6), 
geoType)

Review Comment:
   The geo skip-interleave case cannot tell a bug from working code. All six 
polygons are 221 bytes and every consecutive `prefixLength` is 197, so 
`prevBuf` grows to 221 once and never again: `readGeoData`'s `System.arraycopy` 
only ever runs with `prefixLength` 0, and the grow-and-preserve branch named at 
:222 is never taken. The common prefix of v0 and v2 is also 197 bytes, so if 
`skipBinary` only advanced `in` and stopped writing into `prevBuf`, v2 would 
still decode correctly and the test would still pass.
   
   Values of differing lengths, where consecutive values share a longer prefix 
than values two apart, would fix it; `longPrefixValues` on the non-geo side 
already has that shape. One thing to keep in mind: `corrupt first geo value 
with a non-zero prefix fails fast` at :266 relies on a 202-byte shared prefix, 
so it must not drop to 0 when the values are reshaped.



##########
sql/core/src/main/java/org/apache/spark/sql/execution/datasources/parquet/VectorizedDeltaByteArrayReader.java:
##########
@@ -79,20 +84,31 @@ private void readValues(int total, WritableColumnVector c, 
int rowId) {
       // value of the page should have an empty prefix, it may not
       // because of PARQUET-246.
       int prefixLength = prefixLengthVector.getInt(currentRow);
-      ByteBuffer suffix = suffixReader.getBytes(currentRow);
-      byte[] suffixArray = suffix.array();
-      int suffixLength = suffix.limit() - suffix.position();
+      int suffixLength = suffixReader.getSuffixLength(currentRow);
       int length = prefixLength + suffixLength;
 
-      // We have to do this to materialize the output
+      // The prefix is shared with the previously decoded value, so it can be 
at most as long
+      // as that value. A larger prefixLength means the file is corrupt (e.g. 
PARQUET-246 on the
+      // first value of the first page). prevBuf is pre-zeroed and reused, so 
without this guard
+      // such input would silently assemble stale/zero prefix bytes instead of 
failing.
+      checkPrefixLength(prefixLength);
+
+      // Grow prevBuf if needed, preserving the prefix bytes already in place.
+      if (length > prevBuf.length) {
+        byte[] newBuf = new byte[Math.max(length, prevBuf.length * 2)];

Review Comment:
   The suffix length from `getSuffixLength` reaches `new byte[Math.max(length, 
prevBuf.length * 2)]` without ever being checked against the bytes actually 
left in the page, so the allocation now happens before the read. On master the 
same input went through `in.slice(length)`, and `SingleBufferInputStream.slice` 
compares `remaining()` and throws `EOFException` with no allocation at all, 
surfacing as a plain `ParquetDecodingException`. A corrupt page that used to 
give a clear decoding error now drives an allocation of up to 2GB, i.e. an 
`OutOfMemoryError`, which being an `Error` is not covered by 
`ignoreCorruptFiles` and takes the other tasks in that executor down with it.
   
   `getSuffixLength` is the natural home for the length check: `in` is private 
to `VectorizedDeltaLengthByteArrayReader` so the grow branch cannot reach it, 
and all three loops already call `getSuffixLength` before growing. At that 
point `in` sits exactly at this row's suffix, `available()` is exact, and 
anything larger is a corrupt page. A negative `suffixLength` belongs there too; 
a negative `prefixLength` belongs in `checkPrefixLength`, which today only 
guards the upper bound.



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