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