@@ -367,4 +367,41 @@ TEST_F(MergeIteratorTest, mask_merge_boundary_test) {
367367 }
368368}
369369
370+ TEST_F (MergeIteratorTest, mask_merge_exhausted_iterator) {
371+ std::vector<int32_t > v1{1 , 2 }; // Only 2 elements
372+ std::vector<int32_t > v2{10 , 11 };
373+ auto sub1 = std::make_shared<VectorChunkIterator>(_schema, COL_INT (v1));
374+ auto sub2 = std::make_shared<VectorChunkIterator>(_schema, COL_INT (v2));
375+
376+ std::vector<RowSourceMask> source_masks;
377+ // Request 3 elements from source 0, but it only has 2 elements.
378+ // This matches the exhausted iterator scenario that caused nullptr dereference.
379+ std::vector<uint16_t > expected_sources{0 , 0 , 0 , 1 , 1 };
380+ for (unsigned short expected_source : expected_sources) {
381+ source_masks.emplace_back (RowSourceMask (expected_source, false ));
382+ }
383+ RowSourceMaskBuffer mask_buffer (0 , config::storage_root_path);
384+ mask_buffer.write (source_masks);
385+ mask_buffer.flush ();
386+ mask_buffer.flip_to_read ();
387+ source_masks.clear ();
388+
389+ auto iter = new_mask_merge_iterator (std::vector<ChunkIteratorPtr>{sub1, sub2}, &mask_buffer);
390+ ASSERT_TRUE (iter->init_encoded_schema (EMPTY_GLOBAL_DICTMAPS).ok ());
391+
392+ ChunkPtr chunk = ChunkHelper::new_chunk (iter->schema (), config::vector_chunk_size);
393+ Status st;
394+ while (true ) {
395+ chunk->reset ();
396+ st = iter->get_next (chunk.get (), &source_masks);
397+ if (!st.ok ()) {
398+ break ;
399+ }
400+ }
401+ // Should return InternalError instead of crashing
402+ ASSERT_FALSE (st.ok ());
403+ ASSERT_TRUE (st.is_internal_error ());
404+ ASSERT_TRUE (st.message ().find (" child iterator is exhausted" ) != std::string::npos);
405+ }
406+
370407} // namespace starrocks
0 commit comments