Skip to content

Commit eee7b17

Browse files
r7raul1984jijtang
authored andcommitted
fix(parquet): Fix spurious refill failure for multi-page DATA_PAGE_V2 chunks
Signed-off-by: jijtang <8199300+r7raul1984@users.noreply.github.com>
1 parent 5b248e8 commit eee7b17

2 files changed

Lines changed: 71 additions & 1 deletion

File tree

velox/dwio/parquet/reader/PageReader.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,12 @@ void PageReader::prepareDataPageV2(const PageHeader& pageHeader, int64_t row) {
404404
*pageHeader.uncompressed_page_size() - levelsSize);
405405
}
406406
if (row == kRepDefOnly) {
407-
skipBytes(bytes, inputStream_.get(), bufferStart_, bufferEnd_);
407+
// The page's compressed bytes (levels + values) were already consumed
408+
// above via readBytes(), so no further skip is needed here. An extra
409+
// skipBytes(bytes, ...) call used to double-consume the stream/buffer,
410+
// making PageReader believe the chunk was exhausted one page early and
411+
// fail with a spurious "Empty buffer returned when refilling" error
412+
// when reading the next page's header.
408413
return;
409414
}
410415

velox/dwio/parquet/tests/reader/ParquetPageReaderTest.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,3 +591,68 @@ TEST_F(ParquetPageReaderTest, refillSpansMultipleStreamChunks) {
591591
EXPECT_EQ(*second.type(), thrift::PageType::DATA_PAGE);
592592
EXPECT_EQ(*second.data_page_header()->num_values(), 11);
593593
}
594+
595+
// Regression test for a bug in prepareDataPageV2() where the row ==
596+
// kRepDefOnly branch called skipBytes() a second time on data already
597+
// consumed by readBytes() a few lines above. When a column chunk holds
598+
// multiple DATA_PAGE_V2 pages and the first page's compressed_page_size
599+
// exceeds the bytes remaining for the rest of the chunk, that extra skip
600+
// drains the underlying stream before the real end of the chunk, making
601+
// the next readPageHeader() fail as if the stream had been exhausted
602+
// early.
603+
TEST_F(ParquetPageReaderTest, repDefOnlySkipDoesNotDoubleConsumeDataPageV2) {
604+
constexpr int32_t kFirstDefineLength = 4;
605+
constexpr int32_t kFirstPageSize = 40;
606+
constexpr int32_t kFirstNumValues = 5;
607+
auto firstHeader = createDataPageV2Header(
608+
/*uncompressedSize=*/kFirstPageSize,
609+
/*compressedSize=*/kFirstPageSize,
610+
kFirstNumValues,
611+
kFirstDefineLength,
612+
/*repetitionLevelsByteLength=*/0);
613+
std::string firstHeaderBytes = serializePageHeader(firstHeader);
614+
std::string firstPageData(kFirstPageSize, '\0');
615+
616+
constexpr int32_t kSecondDefineLength = 2;
617+
constexpr int32_t kSecondPageSize = 8;
618+
constexpr int32_t kSecondNumValues = 3;
619+
auto secondHeader = createDataPageV2Header(
620+
/*uncompressedSize=*/kSecondPageSize,
621+
/*compressedSize=*/kSecondPageSize,
622+
kSecondNumValues,
623+
kSecondDefineLength,
624+
/*repetitionLevelsByteLength=*/0);
625+
std::string secondHeaderBytes = serializePageHeader(secondHeader);
626+
std::string secondPageData(kSecondPageSize, '\0');
627+
628+
// The whole point of the test is that the first page's compressed size
629+
// is larger than everything left in the chunk after it (the second
630+
// page's header + data). This is what made the old, buggy extra
631+
// skipBytes() call run past the end of the underlying stream instead of
632+
// just re-consuming already-buffered bytes.
633+
ASSERT_GT(
634+
kFirstPageSize, secondHeaderBytes.size() + secondPageData.size());
635+
636+
std::string fullData = firstHeaderBytes + firstPageData +
637+
secondHeaderBytes + secondPageData;
638+
auto inputStream = std::make_unique<SeekableArrayInputStream>(
639+
fullData.data(), fullData.size());
640+
641+
dwio::common::ColumnReaderStatistics stats;
642+
auto pageReader = std::make_unique<PageReader>(
643+
std::move(inputStream),
644+
*leafPool_,
645+
common::CompressionKind::CompressionKind_NONE,
646+
fullData.size(),
647+
stats,
648+
nullptr,
649+
/*maxRepeat=*/0,
650+
/*maxDefine=*/1);
651+
652+
// decodeRepDefs() drives preloadRepDefs(), which walks every page in the
653+
// chunk with row == kRepDefOnly. Before the fix, this threw ("Empty
654+
// buffer returned when refilling" style errors) once it tried to read
655+
// the second page's header, because the first page's redundant
656+
// skipBytes() call had already consumed those bytes (and then some).
657+
EXPECT_NO_THROW(pageReader->decodeRepDefs(kFirstNumValues + kSecondNumValues));
658+
}

0 commit comments

Comments
 (0)