From 6b2147ec88268362bfd8fc14742621809c14b895 Mon Sep 17 00:00:00 2001 From: meiyi Date: Wed, 29 Jul 2026 15:44:08 +0800 Subject: [PATCH] [fix](be) Validate WAL replay column types ### What problem does this PR solve? Issue Number: None Related PR: None Problem Summary: WAL replay reused a deserialized source column with the target plan's type metadata without validating that the physical and logical types matched. A schema change could therefore expose an INT column as STRING and trigger a fatal cast during projection. Validate the deserialized WAL column, its compatibility with the replay target, and exact logical type equality before inserting it into the destination block. Type mismatches now return a retryable error with WAL and column context instead of reaching the fatal cast. ### Release note None ### Check List (For Author) - Test: Not run (compilation and tests explicitly excluded by request) - Behavior changed: Yes (invalid WAL replay type mappings return an error instead of aborting the BE) - Does this need documentation: No --- be/src/load/group_commit/wal/wal_reader.cpp | 43 +++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/be/src/load/group_commit/wal/wal_reader.cpp b/be/src/load/group_commit/wal/wal_reader.cpp index 1f77b54b9f1688..6449faef46072a 100644 --- a/be/src/load/group_commit/wal/wal_reader.cpp +++ b/be/src/load/group_commit/wal/wal_reader.cpp @@ -99,13 +99,42 @@ Status WalReader::_do_get_next_block(Block* block, size_t* read_rows, bool* eof) return Status::InternalError("read wal {} fail, pos {}, columns size {}", _wal_path, pos, src_block.columns()); } - ColumnPtr column_ptr = src_block.get_by_position(pos).column; - if (!column_ptr && slot_desc->is_nullable()) { - column_ptr = make_nullable(column_ptr); + const auto& source_column = src_block.get_by_position(pos); + const auto& target_column = output_block_columns[index]; + auto source_status = source_column.check_type_and_column_match(); + if (!source_status.ok()) { + return Status::InternalError( + "Invalid WAL column while replaying WAL {}: slot={} (unique_id={}), " + "source_position={}, error={}", + _wal_path, slot_desc->col_name(), slot_desc->col_unique_id(), pos, + source_status.to_string()); } - dst_block.insert(index, ColumnWithTypeAndName(std::move(column_ptr), - output_block_columns[index].type, - output_block_columns[index].name)); + + ColumnWithTypeAndName replay_column(source_column.column, target_column.type, + target_column.name); + auto replay_status = replay_column.check_type_and_column_match(); + if (!replay_status.ok()) { + return Status::InternalError( + "WAL replay column type mismatch: wal={}, slot={} (unique_id={}), " + "source_position={}, source_name={}, source_type={}, source_column={}, " + "target_name={}, target_type={}, error={}", + _wal_path, slot_desc->col_name(), slot_desc->col_unique_id(), pos, + source_column.name, source_column.type->get_name(), + source_column.column->get_name(), target_column.name, + target_column.type == nullptr ? "null" : target_column.type->get_name(), + replay_status.to_string()); + } + if (!source_column.type->equals(*target_column.type)) { + return Status::InternalError( + "WAL replay logical type mismatch: wal={}, slot={} (unique_id={}), " + "source_position={}, source_name={}, source_type={}, target_name={}, " + "target_type={}", + _wal_path, slot_desc->col_name(), slot_desc->col_unique_id(), pos, + source_column.name, source_column.type->get_name(), target_column.name, + target_column.type->get_name()); + } + dst_block.insert(index, ColumnWithTypeAndName(source_column.column, target_column.type, + target_column.name)); index++; } block->swap(dst_block); @@ -142,4 +171,4 @@ Status WalReader::_get_columns_impl(std::unordered_map return Status::OK(); } -} // namespace doris \ No newline at end of file +} // namespace doris