|
39 | 39 | #include <string> |
40 | 40 | #include <vector> |
41 | 41 |
|
| 42 | +<<<<<<< HEAD |
| 43 | +======= |
| 44 | +#include "base/testutil/assert.h" |
| 45 | +#include "base/testutil/sync_point.h" |
| 46 | +#include "column/chunk_factory.h" |
| 47 | +>>>>>>> ce03ba620f ([BugFix] Fix BE crash in primary key auto-increment partial update apply (#76119)) |
42 | 48 | #include "column/datum_tuple.h" |
43 | 49 | #include "fs/fs_util.h" |
44 | 50 | #include "gen_cpp/data.pb.h" |
@@ -1023,6 +1029,139 @@ TEST_F(RowsetTest, SegmentRewriterAutoIncrementTest) { |
1023 | 1029 | ASSERT_EQ(segment->num_rows(), num_rows); |
1024 | 1030 | } |
1025 | 1031 |
|
| 1032 | +// Regression test for the PK auto-increment partial-update 0-row-segment crash (write side / root cause). |
| 1033 | +// rewrite_auto_increment must NOT swallow a failed read of the partial segment: the read status used to be |
| 1034 | +// dropped (only a DCHECK guarded the row count, compiled out in release), leaving read_chunk short and letting |
| 1035 | +// append_chunk emit a corrupt segment (segment num_rows=0 while value columns=N). The read error must surface. |
| 1036 | +TEST_F(RowsetTest, SegmentRewriterAutoIncrementReadErrorTest) { |
| 1037 | + std::shared_ptr<TabletSchema> partial_tablet_schema = TabletSchemaHelper::create_tablet_schema( |
| 1038 | + {create_int_key_pb(1), create_int_key_pb(2), create_int_key_pb(3)}); |
| 1039 | + |
| 1040 | + RowsetWriterContext writer_context; |
| 1041 | + create_rowset_writer_context(12345, partial_tablet_schema, &writer_context); |
| 1042 | + writer_context.writer_type = kHorizontal; |
| 1043 | + std::unique_ptr<RowsetWriter> rowset_writer; |
| 1044 | + ASSERT_TRUE(RowsetFactory::create_rowset_writer(writer_context, &rowset_writer).ok()); |
| 1045 | + |
| 1046 | + int32_t chunk_size = 3000; |
| 1047 | + size_t num_rows = 3000; |
| 1048 | + { |
| 1049 | + std::vector<uint32_t> column_indexes{0, 1, 2}; |
| 1050 | + auto schema = ChunkHelper::convert_schema(partial_tablet_schema, column_indexes); |
| 1051 | + auto chunk = ChunkFactory::new_chunk(schema, chunk_size); |
| 1052 | + for (auto i = 0; i < num_rows / chunk_size + 1; ++i) { |
| 1053 | + chunk->reset(); |
| 1054 | + auto cols = chunk->columns(); |
| 1055 | + for (auto j = 0; j < chunk_size && i * chunk_size + j < num_rows; ++j) { |
| 1056 | + cols[0]->as_mutable_ptr()->append_datum(Datum(static_cast<int32_t>(i * chunk_size + j))); |
| 1057 | + cols[1]->as_mutable_ptr()->append_datum(Datum(static_cast<int32_t>(i * chunk_size + j + 1))); |
| 1058 | + cols[2]->as_mutable_ptr()->append_datum(Datum(static_cast<int32_t>(i * chunk_size + j + 2))); |
| 1059 | + } |
| 1060 | + std::unique_ptr<SegmentPB> seg_info = std::make_unique<SegmentPB>(); |
| 1061 | + ASSERT_OK(rowset_writer->flush_chunk(*chunk, seg_info.get())); |
| 1062 | + } |
| 1063 | + } |
| 1064 | + RowsetSharedPtr rowset = rowset_writer->build().value(); |
| 1065 | + rowset->load(); |
| 1066 | + std::string file_name = Rowset::segment_file_path(rowset->rowset_path(), rowset->rowset_id(), 0); |
| 1067 | + |
| 1068 | + std::shared_ptr<TabletSchema> tablet_schema = TabletSchemaHelper::create_tablet_schema( |
| 1069 | + {create_int_key_pb(1), create_int_key_pb(2), create_int_value_pb(3), create_int_value_pb(4)}); |
| 1070 | + std::vector<uint32_t> read_column_ids{2, 3}; |
| 1071 | + MutableColumns write_columns(read_column_ids.size()); |
| 1072 | + for (auto i = 0; i < read_column_ids.size(); ++i) { |
| 1073 | + auto tablet_column = tablet_schema->column(read_column_ids[i]); |
| 1074 | + auto column = ChunkFactory::column_from_field_type(tablet_column.type(), tablet_column.is_nullable()); |
| 1075 | + write_columns[i] = column->clone_empty(); |
| 1076 | + for (auto j = 0; j < num_rows; ++j) { |
| 1077 | + write_columns[i]->append_datum(Datum(static_cast<int32_t>(j + read_column_ids[i]))); |
| 1078 | + } |
| 1079 | + } |
| 1080 | + AutoIncrementPartialUpdateState auto_increment_partial_update_state; |
| 1081 | + auto_increment_partial_update_state.init(rowset.get(), partial_tablet_schema, 2, 0); |
| 1082 | + auto_increment_partial_update_state.write_column.reset(std::move(write_columns[0])); |
| 1083 | + write_columns.erase(write_columns.begin()); |
| 1084 | + auto dst_file_name = Rowset::segment_temp_file_path(rowset->rowset_path(), rowset->rowset_id(), 0); |
| 1085 | + std::vector<uint32_t> column_ids{3}; |
| 1086 | + |
| 1087 | + // Inject a failed read at the partial-segment get_next; rewrite_auto_increment must return the error. |
| 1088 | + SyncPoint::GetInstance()->EnableProcessing(); |
| 1089 | + SyncPoint::GetInstance()->SetCallBack("SegmentRewriter::rewrite_auto_increment:get_next", |
| 1090 | + [](void* arg) { *(Status*)arg = Status::Corruption("injected read error"); }); |
| 1091 | + auto st = SegmentRewriter::rewrite_auto_increment(file_name, dst_file_name, tablet_schema, |
| 1092 | + auto_increment_partial_update_state, column_ids, &write_columns); |
| 1093 | + SyncPoint::GetInstance()->ClearCallBack("SegmentRewriter::rewrite_auto_increment:get_next"); |
| 1094 | + SyncPoint::GetInstance()->DisableProcessing(); |
| 1095 | + ASSERT_FALSE(st.ok()) << "rewrite_auto_increment must not swallow a partial-segment read error"; |
| 1096 | + ASSERT_TRUE(st.is_corruption()) << st.to_string(); |
| 1097 | +} |
| 1098 | + |
| 1099 | +// Covers the row-count guard in rewrite_auto_increment: if get_next succeeds but read_chunk comes back with |
| 1100 | +// fewer rows than the segment (a short read), the rebuilt row would be malformed, so rewrite must reject it. |
| 1101 | +TEST_F(RowsetTest, SegmentRewriterAutoIncrementRowCountMismatchTest) { |
| 1102 | + std::shared_ptr<TabletSchema> partial_tablet_schema = TabletSchemaHelper::create_tablet_schema( |
| 1103 | + {create_int_key_pb(1), create_int_key_pb(2), create_int_key_pb(3)}); |
| 1104 | + |
| 1105 | + RowsetWriterContext writer_context; |
| 1106 | + create_rowset_writer_context(12345, partial_tablet_schema, &writer_context); |
| 1107 | + writer_context.writer_type = kHorizontal; |
| 1108 | + std::unique_ptr<RowsetWriter> rowset_writer; |
| 1109 | + ASSERT_TRUE(RowsetFactory::create_rowset_writer(writer_context, &rowset_writer).ok()); |
| 1110 | + |
| 1111 | + int32_t chunk_size = 3000; |
| 1112 | + size_t num_rows = 3000; |
| 1113 | + { |
| 1114 | + std::vector<uint32_t> column_indexes{0, 1, 2}; |
| 1115 | + auto schema = ChunkHelper::convert_schema(partial_tablet_schema, column_indexes); |
| 1116 | + auto chunk = ChunkFactory::new_chunk(schema, chunk_size); |
| 1117 | + for (auto i = 0; i < num_rows / chunk_size + 1; ++i) { |
| 1118 | + chunk->reset(); |
| 1119 | + auto cols = chunk->columns(); |
| 1120 | + for (auto j = 0; j < chunk_size && i * chunk_size + j < num_rows; ++j) { |
| 1121 | + cols[0]->as_mutable_ptr()->append_datum(Datum(static_cast<int32_t>(i * chunk_size + j))); |
| 1122 | + cols[1]->as_mutable_ptr()->append_datum(Datum(static_cast<int32_t>(i * chunk_size + j + 1))); |
| 1123 | + cols[2]->as_mutable_ptr()->append_datum(Datum(static_cast<int32_t>(i * chunk_size + j + 2))); |
| 1124 | + } |
| 1125 | + std::unique_ptr<SegmentPB> seg_info = std::make_unique<SegmentPB>(); |
| 1126 | + ASSERT_OK(rowset_writer->flush_chunk(*chunk, seg_info.get())); |
| 1127 | + } |
| 1128 | + } |
| 1129 | + RowsetSharedPtr rowset = rowset_writer->build().value(); |
| 1130 | + rowset->load(); |
| 1131 | + std::string file_name = Rowset::segment_file_path(rowset->rowset_path(), rowset->rowset_id(), 0); |
| 1132 | + |
| 1133 | + std::shared_ptr<TabletSchema> tablet_schema = TabletSchemaHelper::create_tablet_schema( |
| 1134 | + {create_int_key_pb(1), create_int_key_pb(2), create_int_value_pb(3), create_int_value_pb(4)}); |
| 1135 | + std::vector<uint32_t> read_column_ids{2, 3}; |
| 1136 | + MutableColumns write_columns(read_column_ids.size()); |
| 1137 | + for (auto i = 0; i < read_column_ids.size(); ++i) { |
| 1138 | + auto tablet_column = tablet_schema->column(read_column_ids[i]); |
| 1139 | + auto column = ChunkFactory::column_from_field_type(tablet_column.type(), tablet_column.is_nullable()); |
| 1140 | + write_columns[i] = column->clone_empty(); |
| 1141 | + for (auto j = 0; j < num_rows; ++j) { |
| 1142 | + write_columns[i]->append_datum(Datum(static_cast<int32_t>(j + read_column_ids[i]))); |
| 1143 | + } |
| 1144 | + } |
| 1145 | + AutoIncrementPartialUpdateState auto_increment_partial_update_state; |
| 1146 | + auto_increment_partial_update_state.init(rowset.get(), partial_tablet_schema, 2, 0); |
| 1147 | + auto_increment_partial_update_state.write_column.reset(std::move(write_columns[0])); |
| 1148 | + write_columns.erase(write_columns.begin()); |
| 1149 | + auto dst_file_name = Rowset::segment_temp_file_path(rowset->rowset_path(), rowset->rowset_id(), 0); |
| 1150 | + std::vector<uint32_t> column_ids{3}; |
| 1151 | + |
| 1152 | + // get_next succeeds, but the read comes back short (empty read_chunk); rewrite must reject the row-count mismatch. |
| 1153 | + SyncPoint::GetInstance()->EnableProcessing(); |
| 1154 | + SyncPoint::GetInstance()->SetCallBack("SegmentRewriter::rewrite_auto_increment:read_chunk", |
| 1155 | + [](void* arg) { ((Chunk*)arg)->reset(); }); |
| 1156 | + auto st = SegmentRewriter::rewrite_auto_increment(file_name, dst_file_name, tablet_schema, |
| 1157 | + auto_increment_partial_update_state, column_ids, &write_columns); |
| 1158 | + SyncPoint::GetInstance()->ClearCallBack("SegmentRewriter::rewrite_auto_increment:read_chunk"); |
| 1159 | + SyncPoint::GetInstance()->DisableProcessing(); |
| 1160 | + ASSERT_FALSE(st.ok()) << "rewrite_auto_increment must reject a short partial-segment read"; |
| 1161 | + ASSERT_TRUE(st.is_internal_error()) << st.to_string(); |
| 1162 | + EXPECT_NE(st.to_string().find("segment read inconsistency"), std::string::npos) << st.to_string(); |
| 1163 | +} |
| 1164 | + |
1026 | 1165 | TEST_F(RowsetTest, SegmentDeleteWriteTest) { |
1027 | 1166 | auto tablet = create_tablet(12345, 1111); |
1028 | 1167 | int64_t num_rows = 1024; |
|
0 commit comments