This is an automated email from the ASF dual-hosted git repository.

seawinde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/master by this push:
     new df6a3242797 [fix](binlog) Support hidden key columns in row binlog 
(#65076)
df6a3242797 is described below

commit df6a32427974ac04821253da30e062a0021ffd0f
Author: seawinde <[email protected]>
AuthorDate: Wed Jul 15 16:47:00 2026 +0800

    [fix](binlog) Support hidden key columns in row binlog (#65076)
    
    ### What problem does this PR solve?
    
    Issue Number: #65418
    
    Related PR: #63110
    
    Problem Summary:
    
    Row binlog schema and the BE row-binlog writer previously treated normal
    row-binlog columns as visible source columns only. This misses hidden
    key columns, while hidden non-key internal columns such as
    sequence/delete/version/skip-bitmap columns should still be excluded
    from row binlog.
    
    Root cause: In `OlapTable.generateTableRowBinlogSchema()` and
    `SchemaChangeHandler.addColumnRowBinlog()`, FE generated and maintained
    row-binlog schema from visible columns only. In `RowBinlogSegmentWriter`
    / `RowBinlogSourceDataWriter`, BE also used visible-column counts to map
    source columns to row-binlog normal columns.
    
    This PR makes row-binlog normal columns follow a simple source-schema
    prefix contract: visible columns plus hidden key columns are written,
    and trailing hidden non-key columns are skipped. FE schema generation
    and add-column schema-change sync now use this contract. BE row-binlog
    writing uses the same normal-column count for full writes, partial
    update filtering, key column materialization, and BEFORE value columns.
    
    | File | Change Description |
    |------|-------------------|
    | `OlapTable.java` | Generate row-binlog schema from full base schema,
    include hidden key columns, skip hidden non-key columns, and reject
    visible/key columns after hidden non-key columns. |
    | `SchemaChangeHandler.java` | Keep hidden key columns when syncing ADD
    COLUMN changes to row-binlog schema, while skipping hidden non-key
    columns. |
    | `row_binlog_segment_writer.*` | Use row-binlog normal column count
    instead of visible column count, filter partial-update source cids to
    normal columns, and collect all key columns in the normal prefix. |
    | FE/BE/regression tests | Cover hidden key schema generation/writing
    and hidden non-key exclusion. |
---
 .../storage/segment/row_binlog_segment_writer.cpp  | 144 ++++++++++++-------
 be/src/storage/segment/row_binlog_segment_writer.h |  12 +-
 be/test/olap/rowset/group_rowset_writer_test.cpp   | 127 ++++++++++++++++-
 .../segment/row_binlog_segment_writer_test.cpp     | 157 +++++++++++++++++++++
 .../apache/doris/alter/SchemaChangeHandler.java    |   6 +-
 .../java/org/apache/doris/catalog/OlapTable.java   |   5 +-
 .../doris/alter/SchemaChangeHandlerTest.java       |  41 ++++++
 .../catalog/OlapTableRowBinlogSchemaTest.java      |  54 ++++++-
 .../row_binlog_p0/test_row_binlog_basic.groovy     |  10 +-
 9 files changed, 490 insertions(+), 66 deletions(-)

diff --git a/be/src/storage/segment/row_binlog_segment_writer.cpp 
b/be/src/storage/segment/row_binlog_segment_writer.cpp
index c1102b318bb..6a5737a613f 100644
--- a/be/src/storage/segment/row_binlog_segment_writer.cpp
+++ b/be/src/storage/segment/row_binlog_segment_writer.cpp
@@ -17,6 +17,9 @@
 
 #include "storage/segment/row_binlog_segment_writer.h"
 
+#include <algorithm>
+#include <iterator>
+
 #include "cloud/config.h"
 #include "common/cast_set.h"
 #include "core/block/column_with_type_and_name.h"
@@ -76,7 +79,7 @@ Status RowBinlogSegmentWriter::init() {
     _binlog_op_col_id = static_cast<uint32_t>(op_col_id);
     _normal_col_start_id = tso_col_id == 0 ? BINLOG_COLNUM : 0;
 
-    uint32_t normal_col_num = 
cast_set<uint32_t>(source_schema->num_visible_columns());
+    uint32_t normal_col_num = 
cast_set<uint32_t>(_source_data_writer->normal_column_count());
     _before_col_start_id = _normal_col_start_id + normal_col_num;
 
     if (!_write_before && _tablet_schema->num_columns() > normal_col_num + 
BINLOG_COLNUM) {
@@ -117,6 +120,7 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
     if (UNLIKELY(source_schema == nullptr)) {
         return Status::InternalError("binlog<row> writer missing 
source_tablet_schema");
     }
+    const size_t normal_column_count = 
_source_data_writer->normal_column_count();
 
     bool is_partial_update = _binlog_opts.source.partial_update_info &&
                              
_binlog_opts.source.partial_update_info->is_partial_update() &&
@@ -125,6 +129,7 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
     std::vector<uint32_t> partial_cids =
             is_partial_update ? 
_binlog_opts.source.partial_update_info->update_cids
                               : std::vector<uint32_t>();
+    std::vector<uint32_t> row_binlog_partial_cids = partial_cids;
     if (is_partial_update) {
         if (block->columns() <= source_schema->num_key_columns() ||
             block->columns() >= source_schema->num_columns()) {
@@ -135,11 +140,12 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
                     _tablet_schema->num_columns()));
         }
 
-        // binlog don't need invisible column
-        auto erase_invisible_col = std::remove_if(
-                partial_cids.begin(), partial_cids.end(),
-                [&](uint32_t cid) { return cid >= 
source_schema->num_visible_columns(); });
-        partial_cids.erase(erase_invisible_col, partial_cids.end());
+        // Partial update lists source cids. Row-binlog writes visible columns 
and hidden key
+        // columns, so hidden non-key source columns are skipped.
+        auto erase_non_normal_col = std::remove_if(
+                row_binlog_partial_cids.begin(), row_binlog_partial_cids.end(),
+                [&](uint32_t cid) { return 
!_source_data_writer->is_normal_column(cid); });
+        row_binlog_partial_cids.erase(erase_non_normal_col, 
row_binlog_partial_cids.end());
     }
 
     // get delete_sign_column from source block if has
@@ -181,9 +187,9 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
                                                                 row_pos, 
num_rows));
     }
 
-    size_t max_normal_col_id = _normal_col_start_id + 
source_schema->num_visible_columns();
-    RETURN_IF_ERROR(_source_data_writer->fill_normal_columns(_column_writers, 
_normal_col_start_id,
-                                                             
max_normal_col_id, partial_cids));
+    size_t max_normal_col_id = _normal_col_start_id + normal_column_count;
+    RETURN_IF_ERROR(_source_data_writer->fill_normal_columns(
+            _column_writers, _normal_col_start_id, max_normal_col_id, 
row_binlog_partial_cids));
 
     // We read historical rows only when we really need them:
     // 1. partial update: build the full AFTER row.
@@ -203,9 +209,14 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
 
     if (is_partial_update) {
         std::vector<uint32_t> row_binlog_missing_column_ids;
-        _source_data_writer->filter_source_ids(
-                _binlog_opts.source.partial_update_info->missing_cids,
-                row_binlog_missing_column_ids);
+        row_binlog_missing_column_ids.reserve(
+                _binlog_opts.source.partial_update_info->missing_cids.size());
+        // Missing cids are source cids too. Only normal columns have AFTER 
writers.
+        for (uint32_t cid : 
_binlog_opts.source.partial_update_info->missing_cids) {
+            if (_source_data_writer->is_normal_column(cid)) {
+                row_binlog_missing_column_ids.emplace_back(cid);
+            }
+        }
 
         // build AFTER block (fill missing columns in full_block)
         
RETURN_IF_ERROR(_historical_data_writer->build_after_block(&full_block, 
row_pos, num_rows));
@@ -215,7 +226,8 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
         
RETURN_IF_ERROR(after_convertor->set_source_content_with_specifid_columns(
                 &full_block, row_pos, num_rows, 
row_binlog_missing_column_ids));
         for (auto cid : row_binlog_missing_column_ids) {
-            auto converted_cid = _normal_col_start_id + cid;
+            auto converted_cid =
+                    _normal_col_start_id + 
_source_data_writer->normal_column_ordinal(cid);
             auto converted_result = after_convertor->convert_column_data(cid);
             if (!converted_result.first.ok()) {
                 return converted_result.first;
@@ -230,9 +242,9 @@ Status RowBinlogSegmentWriter::append_block(const Block* 
block, size_t row_pos,
     DCHECK(!_tablet_schema->has_sequence_col());
     // _converted_key_columns must be resized before fill binlog columns
     _converted_key_columns.resize(_tablet_schema->num_key_columns());
-    for (size_t i = _normal_col_start_id; i < 
_tablet_schema->num_key_columns(); i++) {
-        _converted_key_columns[i] = _source_data_writer->get_converted_column(
-                cast_set<uint32_t>(i - _normal_col_start_id));
+    const auto& source_key_columns = _source_data_writer->source_key_columns();
+    for (size_t i = 0; i < source_key_columns.size(); ++i) {
+        _converted_key_columns[_normal_col_start_id + i] = 
source_key_columns[i];
     }
 
     std::vector<int64_t> no_operators = std::vector<int64_t> {};
@@ -374,7 +386,14 @@ Status RowBinlogSegmentWriter::_fill_before_columns(size_t 
num_rows) {
     if (UNLIKELY(source_schema == nullptr)) {
         return Status::InternalError("row binlog writer missing 
source_tablet_schema");
     }
-    size_t value_column_num = source_schema->num_visible_value_columns();
+    std::vector<uint32_t> value_cids;
+    for (uint32_t cid = 0; cid < source_schema->num_columns(); ++cid) {
+        const auto& column = source_schema->column(cid);
+        if (column.visible() && !column.is_key()) {
+            value_cids.emplace_back(cid);
+        }
+    }
+    size_t value_column_num = value_cids.size();
     if (value_column_num == 0) {
         // No BEFORE columns in row binlog schema.
         return Status::OK();
@@ -401,13 +420,6 @@ Status RowBinlogSegmentWriter::_fill_before_columns(size_t 
num_rows) {
     } else {
         DCHECK(_historical_data_writer != nullptr);
 
-        std::vector<uint32_t> value_cids;
-        uint32_t value_start = 
cast_set<uint32_t>(source_schema->num_key_columns());
-        uint32_t value_end = 
cast_set<uint32_t>(source_schema->num_visible_columns());
-        for (uint32_t cid = value_start; cid < value_end; ++cid) {
-            value_cids.emplace_back(cid);
-        }
-
         DCHECK_EQ(before_cids.size(), value_cids.size());
         
RETURN_IF_ERROR(_historical_data_writer->build_before_block(&before_block, 
value_cids, 0,
                                                                     num_rows));
@@ -429,18 +441,35 @@ Status 
RowBinlogSegmentWriter::_fill_before_columns(size_t num_rows) {
     return Status::OK();
 }
 
+bool RowBinlogSourceDataWriter::is_normal_column(uint32_t source_cid) const {
+    return std::find(_normal_column_ids.begin(), _normal_column_ids.end(), 
source_cid) !=
+           _normal_column_ids.end();
+}
+
+size_t RowBinlogSourceDataWriter::normal_column_ordinal(uint32_t source_cid) 
const {
+    auto it = std::find(_normal_column_ids.begin(), _normal_column_ids.end(), 
source_cid);
+    DCHECK(it != _normal_column_ids.end()) << source_cid;
+    return cast_set<size_t>(std::distance(_normal_column_ids.begin(), it));
+}
+
 Status RowBinlogSourceDataWriter::init() {
     _olap_data_convertor = std::make_unique<OlapBlockDataConvertor>();
-    // _normal_column_ids: the columns which we need to write into binlog from 
source block
     if (UNLIKELY(_opt.source.tablet_schema == nullptr)) {
         return Status::InternalError("row binlog writer missing 
source_tablet_schema");
     }
-    for (uint32_t i = 0; i < _opt.source.tablet_schema->num_visible_columns(); 
i++) {
-        _normal_column_ids.emplace_back(i);
+
+    const auto& source_schema = _opt.source.tablet_schema;
+    _normal_column_ids.clear();
+    for (uint32_t cid = 0; cid < source_schema->num_columns(); ++cid) {
+        const auto& column = source_schema->column(cid);
+        if (!column.visible() && !column.is_key()) {
+            continue;
+        }
+        _normal_column_ids.emplace_back(cid);
     }
-    _olap_data_convertor->reserve(_opt.source.tablet_schema->num_columns());
-    for (size_t cid = 0; cid < _opt.source.tablet_schema->num_columns(); 
cid++) {
-        
_olap_data_convertor->add_column_data_convertor(_opt.source.tablet_schema->column(cid));
+    _olap_data_convertor->reserve(source_schema->num_columns());
+    for (size_t cid = 0; cid < source_schema->num_columns(); cid++) {
+        
_olap_data_convertor->add_column_data_convertor(source_schema->column(cid));
     }
     return Status::OK();
 }
@@ -448,31 +477,43 @@ Status RowBinlogSourceDataWriter::init() {
 Status RowBinlogSourceDataWriter::prepare_by_source_block(
         const Block* block, size_t row_pos, size_t num_rows,
         std::vector<uint32_t>& partial_source_cids, Block* full_block) {
-    _converted_columns.resize(_normal_column_ids.size());
+    TabletSchemaSPtr tablet_schema = _opt.source.tablet_schema;
+    // OlapBlockDataConvertor and historical lookup are indexed by source cid, 
so keep
+    // converted columns sparse over the source schema instead of row-binlog 
normal order.
+    _converted_columns.assign(tablet_schema->num_columns(), nullptr);
 
     // LOG(INFO) << block->dump_data(0, num_rows);
 
     // convert column data from engine format to storage layer format
     size_t col_pos_in_block = 0;
-    TabletSchemaSPtr tablet_schema = _opt.source.tablet_schema;
-    const auto& including_cids =
-            partial_source_cids.empty() ? _normal_column_ids : 
partial_source_cids;
-    for (auto& cid : including_cids) {
-        const ColumnWithTypeAndName& col = 
block->get_by_position(col_pos_in_block++);
+    const bool is_partial_update = !partial_source_cids.empty();
+    const size_t column_count =
+            is_partial_update ? partial_source_cids.size() : 
_normal_column_ids.size();
+    for (size_t ordinal = 0; ordinal < column_count; ++ordinal) {
+        const uint32_t source_cid =
+                is_partial_update ? partial_source_cids[ordinal] : 
_normal_column_ids[ordinal];
+        DCHECK_LT(source_cid, tablet_schema->num_columns());
+        const ColumnWithTypeAndName& col =
+                block->get_by_position(is_partial_update ? col_pos_in_block++ 
: source_cid);
 
         
RETURN_IF_ERROR(_olap_data_convertor->set_source_content_with_specifid_column(
-                col, row_pos, num_rows, cid));
+                col, row_pos, num_rows, source_cid));
         // olap data convertor alway start from id = 0
-        auto converted_result = _olap_data_convertor->convert_column_data(cid);
+        auto converted_result = 
_olap_data_convertor->convert_column_data(source_cid);
         if (!converted_result.first.ok()) {
             return converted_result.first;
         }
-        _converted_columns[cid] = converted_result.second;
+        _converted_columns[source_cid] = converted_result.second;
 
-        if (cid < tablet_schema->num_key_columns()) {
-            _key_columns.push_back(converted_result.second);
+        full_block->replace_by_position(source_cid, col.column);
+    }
+    for (uint32_t cid : _normal_column_ids) {
+        if (!tablet_schema->column(cid).is_key()) {
+            continue;
         }
-        full_block->replace_by_position(cid, col.column);
+        DCHECK_LT(cid, _converted_columns.size());
+        DCHECK(_converted_columns[cid] != nullptr);
+        _key_columns.push_back(_converted_columns[cid]);
     }
     _num_rows = num_rows;
 
@@ -497,15 +538,20 @@ Status RowBinlogSourceDataWriter::fill_normal_columns(
         std::vector<uint32_t>& partial_source_cids) {
     DCHECK_EQ(end - start, _normal_column_ids.size());
 
-    const auto& including_cids =
-            partial_source_cids.empty() ? _normal_column_ids : 
partial_source_cids;
-    for (size_t cid : including_cids) {
-        DCHECK(column_writers[start + cid]->get_column()->type() ==
+    const bool is_partial_update = !partial_source_cids.empty();
+    const size_t column_count =
+            is_partial_update ? partial_source_cids.size() : 
_normal_column_ids.size();
+    for (size_t ordinal = 0; ordinal < column_count; ++ordinal) {
+        uint32_t cid =
+                is_partial_update ? partial_source_cids[ordinal] : 
_normal_column_ids[ordinal];
+        size_t normal_ordinal = is_partial_update ? normal_column_ordinal(cid) 
: ordinal;
+        DCHECK(column_writers[start + normal_ordinal]->get_column()->type() ==
                _opt.source.tablet_schema->columns()[cid]->type())
                 << cid;
-        RETURN_IF_ERROR(column_writers[start + 
cid]->append(_converted_columns[cid]->get_nullmap(),
-                                                            
_converted_columns[cid]->get_data(),
-                                                            _num_rows));
+        DCHECK(_converted_columns[cid] != nullptr);
+        RETURN_IF_ERROR(column_writers[start + normal_ordinal]->append(
+                _converted_columns[cid]->get_nullmap(), 
_converted_columns[cid]->get_data(),
+                _num_rows));
     }
 
     return Status::OK();
diff --git a/be/src/storage/segment/row_binlog_segment_writer.h 
b/be/src/storage/segment/row_binlog_segment_writer.h
index 3438bff6b7b..69023d8a3a7 100644
--- a/be/src/storage/segment/row_binlog_segment_writer.h
+++ b/be/src/storage/segment/row_binlog_segment_writer.h
@@ -44,8 +44,6 @@ public:
 
     void clear();
 
-    IOlapColumnDataAccessor* get_converted_column(uint32_t cid) { return 
_converted_columns[cid]; }
-
     bool need_before() const { return _opt.write_before; }
 
     const std::vector<IOlapColumnDataAccessor*>& source_key_columns() const { 
return _key_columns; }
@@ -53,11 +51,11 @@ public:
 
     std::unique_ptr<OlapBlockDataConvertor>& olap_data_convertor() { return 
_olap_data_convertor; }
 
-    void filter_source_ids(std::vector<uint32_t>& full_cids, 
std::vector<uint32_t>& res_cids) {
-        res_cids.reserve(full_cids.size());
-        std::set_intersection(_normal_column_ids.begin(), 
_normal_column_ids.end(),
-                              full_cids.begin(), full_cids.end(), 
std::back_inserter(res_cids));
-    }
+    size_t normal_column_count() const { return _normal_column_ids.size(); }
+
+    bool is_normal_column(uint32_t source_cid) const;
+
+    size_t normal_column_ordinal(uint32_t source_cid) const;
 
 private:
     const SegmentWriteBinlogOptions& _opt;
diff --git a/be/test/olap/rowset/group_rowset_writer_test.cpp 
b/be/test/olap/rowset/group_rowset_writer_test.cpp
index 4ebd2db4ca3..159a77a6480 100644
--- a/be/test/olap/rowset/group_rowset_writer_test.cpp
+++ b/be/test/olap/rowset/group_rowset_writer_test.cpp
@@ -23,6 +23,7 @@
 
 #include <chrono>
 #include <memory>
+#include <set>
 #include <string>
 #include <thread>
 #include <vector>
@@ -36,6 +37,9 @@
 #include "runtime/runtime_profile.h"
 #include "storage/binlog.h"
 #include "storage/olap_define.h"
+#include "storage/partial_update_info.h"
+#include "storage/rowset/rowset_reader.h"
+#include "storage/rowset/rowset_reader_context.h"
 #include "storage/storage_engine.h"
 #include "storage/tablet/tablet.h"
 #include "storage/tablet/tablet_manager.h"
@@ -72,10 +76,24 @@ protected:
         ExecEnv::GetInstance()->set_storage_engine(std::move(engine));
 
         _request = testutil::create_tablet_request(
-                10010, 270068390, 10001, 1, TKeysType::UNIQUE_KEYS,
-                {{"k1", TPrimitiveType::INT, true}, {"v1", 
TPrimitiveType::INT, false}});
+                10010, 270068390, 10001, 2, TKeysType::UNIQUE_KEYS,
+                {{"k1", TPrimitiveType::INT, true},
+                 {"__DORIS_TEST_HIDDEN_KEY__", TPrimitiveType::BIGINT, true},
+                 {"__DORIS_TEST_HIDDEN_VALUE__", TPrimitiveType::INT, false},
+                 {"v1", TPrimitiveType::INT, false},
+                 {"v2", TPrimitiveType::BIGINT, false},
+                 {std::string(DELETE_SIGN), TPrimitiveType::TINYINT, false}});
+        _request.tablet_schema.columns[1].__set_visible(false);
+        _request.tablet_schema.columns[2].__set_visible(false);
+        _request.tablet_schema.columns[5].__set_visible(false);
+        _request.tablet_schema.columns[2].__set_is_allow_null(true);
+        _request.tablet_schema.columns[4].__set_is_allow_null(true);
         _request.__set_enable_unique_key_merge_on_write(true);
         testutil::enable_row_binlog(&_request);
+        
_request.row_binlog_schema.columns.erase(_request.row_binlog_schema.columns.begin()
 + 5);
+        
_request.row_binlog_schema.columns.erase(_request.row_binlog_schema.columns.begin()
 + 2);
+        _request.row_binlog_schema.columns[5].__set_is_allow_null(true);
+        _request.row_binlog_schema.columns[6].__set_is_allow_null(true);
         auto profile = 
std::make_unique<RuntimeProfile>("GroupRowsetWriterTest");
         ASSERT_TRUE(engine_ptr->create_tablet(_request, profile.get()).ok());
         _tablet = engine_ptr->tablet_manager()->get_tablet(_request.tablet_id);
@@ -101,13 +119,31 @@ protected:
             auto& columns = columns_guard.mutable_columns();
             for (int i = 0; i < num_rows; ++i) {
                 
columns[0]->insert(Field::create_field<PrimitiveType::TYPE_INT>(start_key + i));
-                columns[1]->insert(
+                
columns[1]->insert(Field::create_field<PrimitiveType::TYPE_BIGINT>(
+                        static_cast<int64_t>(1000 + start_key + i)));
+                
columns[2]->insert(Field::create_field<PrimitiveType::TYPE_INT>(
+                        static_cast<int32_t>(10000 + start_key + i)));
+                columns[3]->insert(
                         
Field::create_field<PrimitiveType::TYPE_INT>((start_key + i) * 10));
+                
columns[4]->insert(Field::create_field<PrimitiveType::TYPE_BIGINT>(
+                        static_cast<int64_t>((start_key + i) * 100)));
+                
columns[5]->insert(Field::create_field<PrimitiveType::TYPE_TINYINT>(0));
             }
         }
         return block;
     }
 
+    Block create_partial_update_block() const {
+        Block block = _tablet->tablet_schema()->create_block_by_cids({0, 1, 2, 
3});
+        auto columns_guard = block.mutate_columns_scoped();
+        auto& columns = columns_guard.mutable_columns();
+        columns[0]->insert(Field::create_field<PrimitiveType::TYPE_INT>(1));
+        
columns[1]->insert(Field::create_field<PrimitiveType::TYPE_BIGINT>(1001));
+        columns[2]->insert(Field::create_field<PrimitiveType::TYPE_INT>(200));
+        columns[3]->insert(Field::create_field<PrimitiveType::TYPE_INT>(20));
+        return block;
+    }
+
     Status create_group_rowset_writer(std::unique_ptr<GroupRowsetWriter>* 
group_writer,
                                       RowsetId* data_rowset_id, size_t 
num_rows) {
         RowsetWriterContext data_context;
@@ -225,4 +261,89 @@ TEST_F(GroupRowsetWriterTest, success) {
     EXPECT_TRUE(file_exists(second_segment_path));
 }
 
+TEST_F(GroupRowsetWriterTest, partialUpdateSkipsHiddenNonKeyColumns) {
+    ASSERT_EQ(1, 
_tablet->row_binlog_tablet_schema()->field_index("__DORIS_TEST_HIDDEN_KEY__"));
+    ASSERT_EQ(-1, 
_tablet->row_binlog_tablet_schema()->field_index("__DORIS_TEST_HIDDEN_VALUE__"));
+    ASSERT_EQ(-1, 
_tablet->row_binlog_tablet_schema()->field_index(DELETE_SIGN));
+
+    auto partial_update_info = std::make_shared<PartialUpdateInfo>();
+    ASSERT_TRUE(
+            partial_update_info
+                    ->init(_tablet->tablet_id(), 1, *_tablet->tablet_schema(),
+                           UniqueKeyUpdateModePB::UPDATE_FIXED_COLUMNS,
+                           PartialUpdateNewRowPolicyPB::APPEND,
+                           {"k1", "__DORIS_TEST_HIDDEN_KEY__", 
"__DORIS_TEST_HIDDEN_VALUE__", "v1"},
+                           false, 0, 0, "", "")
+                    .ok());
+    EXPECT_EQ((std::vector<uint32_t> {0, 1, 2, 3}), 
partial_update_info->update_cids);
+    EXPECT_EQ((std::vector<uint32_t> {4, 5}), 
partial_update_info->missing_cids);
+
+    RowsetWriterContext row_binlog_context;
+    row_binlog_context.tablet = _tablet;
+    row_binlog_context.tablet_schema = _tablet->row_binlog_tablet_schema();
+    row_binlog_context.rowset_state = PREPARED;
+    row_binlog_context.segments_overlap = NONOVERLAPPING;
+    row_binlog_context.max_rows_per_segment = 1024;
+    row_binlog_context.write_type = DataWriteType::TYPE_DIRECT;
+    row_binlog_context.partial_update_info = partial_update_info;
+    row_binlog_context.mow_context =
+            std::make_shared<MowContext>(1, 1, 
std::make_shared<RowsetIdUnorderedSet>(),
+                                         std::vector<RowsetSharedPtr> {}, 
nullptr);
+    row_binlog_context.write_binlog_opt().enable = true;
+    auto& binlog_options = 
row_binlog_context.write_binlog_opt().write_binlog_config();
+    binlog_options.source.tablet_schema = _tablet->tablet_schema();
+    binlog_options.source.partial_update_info = partial_update_info;
+    binlog_options.source.mow_context = row_binlog_context.mow_context;
+    binlog_options.source.source_write_type = DataWriteType::TYPE_DIRECT;
+
+    auto lsn_buffer = AutoIncIDBuffer::create_shared(1, 1, 
kBinlogLsnAutoIncId);
+    lsn_buffer->append_range_for_test(1000, 1);
+    std::shared_ptr<std::vector<int64_t>> lsn_ids;
+    ASSERT_TRUE(allocate_binlog_lsn(lsn_buffer, 1, &lsn_ids).ok());
+    binlog_options.insert_seg_lsn(0, lsn_ids);
+
+    auto row_binlog_writer_res = 
_tablet->create_rowset_writer(row_binlog_context, false);
+    ASSERT_TRUE(row_binlog_writer_res.has_value());
+    auto row_binlog_writer = std::move(row_binlog_writer_res.value());
+
+    Block block = create_partial_update_block();
+    ASSERT_TRUE(row_binlog_writer->flush_single_block(&block).ok());
+
+    RowsetSharedPtr row_binlog_rowset;
+    ASSERT_TRUE(row_binlog_writer->build(row_binlog_rowset).ok());
+    ASSERT_EQ(1, row_binlog_rowset->num_segments());
+
+    const auto& row_binlog_schema = _tablet->row_binlog_tablet_schema();
+    ASSERT_EQ(7, row_binlog_schema->num_columns());
+    std::vector<uint32_t> return_columns {0, 1, 2, 3, 4, 5, 6};
+    RowsetReaderContext reader_context;
+    reader_context.tablet_schema = row_binlog_schema;
+    reader_context.need_ordered_result = false;
+    reader_context.return_columns = &return_columns;
+
+    RowsetReaderSharedPtr rowset_reader;
+    ASSERT_TRUE(row_binlog_rowset->create_reader(&rowset_reader).ok());
+    ASSERT_TRUE(rowset_reader->init(&reader_context).ok());
+
+    Block output_block = row_binlog_schema->create_block();
+    auto status = rowset_reader->next_batch(&output_block);
+    ASSERT_TRUE(status.ok()) << status;
+    ASSERT_EQ(1, output_block.rows());
+    ASSERT_EQ(return_columns.size(), output_block.columns());
+
+    EXPECT_EQ(1, (*output_block.get_by_position(0).column)[0].get<TYPE_INT>());
+    EXPECT_EQ(1001, 
(*output_block.get_by_position(1).column)[0].get<TYPE_BIGINT>());
+    EXPECT_EQ(20, 
(*output_block.get_by_position(2).column)[0].get<TYPE_INT>());
+    EXPECT_TRUE(output_block.get_by_position(3).column->is_null_at(0));
+    EXPECT_EQ(static_cast<int128_t>(1000),
+              
(*output_block.get_by_position(4).column)[0].get<TYPE_LARGEINT>());
+    EXPECT_FALSE(output_block.get_by_position(5).column->is_null_at(0));
+    EXPECT_EQ(ROW_BINLOG_APPEND, 
(*output_block.get_by_position(5).column)[0].get<TYPE_BIGINT>());
+    EXPECT_TRUE(output_block.get_by_position(6).column->is_null_at(0));
+
+    Block eof_block = row_binlog_schema->create_block();
+    status = rowset_reader->next_batch(&eof_block);
+    EXPECT_TRUE(status.is<ErrorCode::END_OF_FILE>()) << status;
+}
+
 } // namespace doris
diff --git a/be/test/storage/segment/row_binlog_segment_writer_test.cpp 
b/be/test/storage/segment/row_binlog_segment_writer_test.cpp
new file mode 100644
index 00000000000..0c0c4b9972f
--- /dev/null
+++ b/be/test/storage/segment/row_binlog_segment_writer_test.cpp
@@ -0,0 +1,157 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "storage/segment/row_binlog_segment_writer.h"
+
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "core/field.h"
+#include "core/value/decimalv2_value.h"
+#include "storage/binlog.h"
+#include "storage/iterator/olap_data_convertor.h"
+#include "storage/tablet/tablet_schema.h"
+
+namespace doris::segment_v2 {
+
+namespace {
+
+TabletColumn create_column(int32_t unique_id, const std::string& name, 
FieldType type, bool is_key,
+                           bool visible) {
+    TabletColumn column(FieldAggregationMethod::OLAP_FIELD_AGGREGATION_NONE, 
type, !is_key);
+    column.set_unique_id(unique_id);
+    column.set_name(name);
+    column.set_is_key(is_key);
+    column.set_length(type == FieldType::OLAP_FIELD_TYPE_LARGEINT ? 16 : 4);
+    column.set_index_length(column.length());
+    column._visible = visible;
+    return column;
+}
+
+TabletSchemaSPtr create_source_schema() {
+    auto schema = std::make_shared<TabletSchema>();
+    schema->append_column(create_column(0, "k1", 
FieldType::OLAP_FIELD_TYPE_INT, true, true));
+    schema->append_column(create_column(1, "__DORIS_TEST_HIDDEN_KEY__",
+                                        FieldType::OLAP_FIELD_TYPE_LARGEINT, 
true, false));
+    schema->append_column(create_column(2, "v1", 
FieldType::OLAP_FIELD_TYPE_INT, false, true));
+    schema->append_column(create_column(3, "__DORIS_TEST_HIDDEN_VALUE__",
+                                        FieldType::OLAP_FIELD_TYPE_INT, false, 
false));
+    schema->_keys_type = UNIQUE_KEYS;
+    return schema;
+}
+
+Block create_source_block(const TabletSchemaSPtr& schema) {
+    Block block = schema->create_block();
+    auto columns_guard = block.mutate_columns_scoped();
+    auto& columns = columns_guard.mutable_columns();
+    for (int i = 0; i < 2; ++i) {
+        columns[0]->insert(Field::create_field<TYPE_INT>(10 + i));
+        
columns[1]->insert(Field::create_field<TYPE_LARGEINT>(static_cast<int128_t>(1000
 + i)));
+        columns[2]->insert(Field::create_field<TYPE_INT>(100 + i));
+        columns[3]->insert(Field::create_field<TYPE_INT>(10000 + i));
+    }
+    return block;
+}
+
+} // namespace
+
+TEST(RowBinlogSegmentWriterTest, collectHiddenKeyInSourceDataWriter) {
+    auto source_schema = create_source_schema();
+    SegmentWriteBinlogOptions options;
+    options.source.tablet_schema = source_schema;
+
+    RowBinlogSourceDataWriter writer(options);
+    ASSERT_TRUE(writer.init().ok());
+    EXPECT_EQ(3, writer.normal_column_count());
+
+    Block block = create_source_block(source_schema);
+    Block full_block = source_schema->create_block();
+    std::vector<uint32_t> partial_source_cids;
+    ASSERT_TRUE(
+            writer.prepare_by_source_block(&block, 0, 2, partial_source_cids, 
&full_block).ok());
+
+    const auto& key_columns = writer.source_key_columns();
+    ASSERT_EQ(2, key_columns.size());
+
+    ASSERT_NE(nullptr, key_columns[0]->get_data_at(1));
+    EXPECT_EQ(11, *reinterpret_cast<const 
int32_t*>(key_columns[0]->get_data_at(1)));
+
+    ASSERT_NE(nullptr, key_columns[1]->get_data_at(1));
+    EXPECT_EQ(static_cast<int128_t>(1001),
+              *reinterpret_cast<const 
int128_t*>(key_columns[1]->get_data_at(1)));
+}
+
+TEST(RowBinlogSegmentWriterTest, skipHiddenNonKeyBeforeVisibleColumn) {
+    auto source_schema = std::make_shared<TabletSchema>();
+    source_schema->append_column(
+            create_column(0, "k1", FieldType::OLAP_FIELD_TYPE_INT, true, 
true));
+    source_schema->append_column(create_column(1, 
"__DORIS_TEST_HIDDEN_VALUE__",
+                                               FieldType::OLAP_FIELD_TYPE_INT, 
false, false));
+    source_schema->append_column(
+            create_column(2, "v1", FieldType::OLAP_FIELD_TYPE_INT, false, 
true));
+    source_schema->_keys_type = UNIQUE_KEYS;
+
+    SegmentWriteBinlogOptions options;
+    options.source.tablet_schema = source_schema;
+
+    RowBinlogSourceDataWriter writer(options);
+    ASSERT_TRUE(writer.init().ok());
+    EXPECT_EQ(2, writer.normal_column_count());
+    EXPECT_TRUE(writer.is_normal_column(0));
+    EXPECT_FALSE(writer.is_normal_column(1));
+    EXPECT_TRUE(writer.is_normal_column(2));
+    EXPECT_EQ(0, writer.normal_column_ordinal(0));
+    EXPECT_EQ(1, writer.normal_column_ordinal(2));
+}
+
+TEST(RowBinlogSegmentWriterTest, skipMultipleHiddenNonKeyColumns) {
+    auto source_schema = std::make_shared<TabletSchema>();
+    source_schema->append_column(
+            create_column(0, "k1", FieldType::OLAP_FIELD_TYPE_INT, true, 
true));
+    source_schema->append_column(create_column(1, 
"__DORIS_TEST_HIDDEN_VALUE_1__",
+                                               FieldType::OLAP_FIELD_TYPE_INT, 
false, false));
+    source_schema->append_column(
+            create_column(2, "v1", FieldType::OLAP_FIELD_TYPE_INT, false, 
true));
+    source_schema->append_column(create_column(3, 
"__DORIS_TEST_HIDDEN_VALUE_2__",
+                                               FieldType::OLAP_FIELD_TYPE_INT, 
false, false));
+    source_schema->append_column(
+            create_column(4, "v2", FieldType::OLAP_FIELD_TYPE_INT, false, 
true));
+    source_schema->append_column(create_column(5, 
"__DORIS_TEST_HIDDEN_VALUE_3__",
+                                               FieldType::OLAP_FIELD_TYPE_INT, 
false, false));
+    source_schema->_keys_type = UNIQUE_KEYS;
+
+    SegmentWriteBinlogOptions options;
+    options.source.tablet_schema = source_schema;
+
+    RowBinlogSourceDataWriter writer(options);
+    ASSERT_TRUE(writer.init().ok());
+    EXPECT_EQ(3, writer.normal_column_count());
+    EXPECT_TRUE(writer.is_normal_column(0));
+    EXPECT_FALSE(writer.is_normal_column(1));
+    EXPECT_TRUE(writer.is_normal_column(2));
+    EXPECT_FALSE(writer.is_normal_column(3));
+    EXPECT_TRUE(writer.is_normal_column(4));
+    EXPECT_FALSE(writer.is_normal_column(5));
+    EXPECT_EQ(0, writer.normal_column_ordinal(0));
+    EXPECT_EQ(1, writer.normal_column_ordinal(2));
+    EXPECT_EQ(2, writer.normal_column_ordinal(4));
+}
+
+} // namespace doris::segment_v2
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java 
b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java
index f818fcecd12..233cead1bdc 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeHandler.java
@@ -322,9 +322,9 @@ public class SchemaChangeHandler extends AlterHandler {
     private void addColumnRowBinlog(List<Column> rowBinlogSchema, Column 
newColumn, ColumnPosition columnPos,
                                     Set<String> newColNameSet, boolean 
needHistoricalValue,
                                     IntSupplier columnUniqueIdSupplier) throws 
DdlException {
-        if (!newColumn.isVisible()) {
-            // row binlog schema is generated from visible columns only, so 
schema change must not
-            // sync hidden system columns such as 
sequence/delete/version/skip-bitmap columns.
+        if (!newColumn.isVisible() && !newColumn.isKey()) {
+            // Row-binlog writes visible columns plus hidden key columns. Skip 
hidden non-key
+            // system columns such as sequence/delete/version/skip-bitmap 
columns.
             return;
         }
 
diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
index 0174f379c4c..bcd9e76e7c4 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
@@ -2433,7 +2433,10 @@ public class OlapTable extends Table implements 
MTMVRelatedTableIf, GsonPostProc
         boolean needHistoricalValue = 
getBinlogConfig().getNeedHistoricalValue();
         List<Column> beforeColumns = new ArrayList<>();
 
-        for (Column column : getBaseSchema(false)) {
+        for (Column column : getBaseSchema(true)) {
+            if (!column.isVisible() && !column.isKey()) {
+                continue;
+            }
             Preconditions.checkState(!column.getType().isVariantType(),
                     "binlog<Row> does not support VARIANT column: " + 
column.getName());
             Preconditions.checkState(!column.isAutoInc(),
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java
index 040b43aaf0a..8294a566fda 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/alter/SchemaChangeHandlerTest.java
@@ -24,6 +24,7 @@ import org.apache.doris.catalog.Index;
 import org.apache.doris.catalog.KeysType;
 import org.apache.doris.catalog.MaterializedIndexMeta;
 import org.apache.doris.catalog.OlapTable;
+import org.apache.doris.catalog.PrimitiveType;
 import org.apache.doris.catalog.Table;
 import org.apache.doris.catalog.info.ColumnPosition;
 import org.apache.doris.catalog.info.IndexType;
@@ -37,6 +38,7 @@ import org.apache.doris.qe.ConnectContext;
 import org.apache.doris.qe.StmtExecutor;
 import org.apache.doris.utframe.TestWithFeService;
 
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import org.apache.logging.log4j.LogManager;
@@ -46,8 +48,11 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntSupplier;
 import java.util.stream.Collectors;
 
 public class SchemaChangeHandlerTest extends TestWithFeService {
@@ -888,6 +893,42 @@ public class SchemaChangeHandlerTest extends 
TestWithFeService {
 
     }
 
+    @Test
+    public void testRowBinlogSchemaChangeKeepsHiddenKeyColumn() throws 
Exception {
+        SchemaChangeHandler schemaChangeHandler = new SchemaChangeHandler();
+        List<Column> rowBinlogSchema = Lists.newArrayList();
+        Column key = new Column("k1", PrimitiveType.INT);
+        key.setIsKey(true);
+        rowBinlogSchema.add(Column.generateRowBinlogKeyColumn(key));
+        rowBinlogSchema.add(new Column(Column.BINLOG_LSN_COL, 
PrimitiveType.LARGEINT));
+        rowBinlogSchema.add(new Column(Column.BINLOG_OPERATION_COL, 
PrimitiveType.BIGINT));
+        rowBinlogSchema.add(new Column(Column.BINLOG_TIMESTAMP_COL, 
PrimitiveType.BIGINT));
+
+        Column hiddenKey = new Column("__DORIS_TEST_HIDDEN_KEY__", 
PrimitiveType.BIGINT);
+        hiddenKey.setIsKey(true);
+        hiddenKey.setIsVisible(false);
+        AtomicInteger uniqueId = new AtomicInteger(10);
+        IntSupplier uniqueIdSupplier = uniqueId::getAndIncrement;
+        Method addColumnRowBinlog = 
SchemaChangeHandler.class.getDeclaredMethod(
+                "addColumnRowBinlog", List.class, Column.class, 
ColumnPosition.class,
+                java.util.Set.class, boolean.class, IntSupplier.class);
+        addColumnRowBinlog.setAccessible(true);
+        addColumnRowBinlog.invoke(schemaChangeHandler, rowBinlogSchema, 
hiddenKey, null,
+                Sets.newHashSet(hiddenKey.getName()), false, uniqueIdSupplier);
+
+        List<String> columnNames = 
rowBinlogSchema.stream().map(Column::getName).collect(Collectors.toList());
+        Assertions.assertEquals(1, 
columnNames.indexOf("__DORIS_TEST_HIDDEN_KEY__"));
+        Assertions.assertEquals(2, columnNames.indexOf(Column.BINLOG_LSN_COL));
+
+        Column hiddenValue = new Column("__DORIS_TEST_HIDDEN_VALUE__", 
PrimitiveType.INT);
+        hiddenValue.setIsKey(false);
+        hiddenValue.setIsVisible(false);
+        addColumnRowBinlog.invoke(schemaChangeHandler, rowBinlogSchema, 
hiddenValue, null,
+                Sets.newHashSet(hiddenValue.getName()), false, 
uniqueIdSupplier);
+        columnNames = 
rowBinlogSchema.stream().map(Column::getName).collect(Collectors.toList());
+        
Assertions.assertFalse(columnNames.contains("__DORIS_TEST_HIDDEN_VALUE__"));
+    }
+
     @Test
     public void testAggAddOrDropInvertedIndex() throws Exception {
         LOG.info("dbName: {}", Env.getCurrentInternalCatalog().getDbNames());
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableRowBinlogSchemaTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableRowBinlogSchemaTest.java
index 42a3bc8e078..b1fe029106a 100755
--- 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableRowBinlogSchemaTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableRowBinlogSchemaTest.java
@@ -30,12 +30,15 @@ import java.util.stream.Collectors;
 public class OlapTableRowBinlogSchemaTest {
 
     private static OlapTable newTestTable(BinlogConfig binlogConfig) {
-        long baseIndexId = 1L;
         Column key = new Column("k1", PrimitiveType.INT);
         key.setIsKey(true);
         Column value = new Column("v1", Type.INT, false, null, false, "7", "");
         value.setIsKey(false);
-        List<Column> baseSchema = Lists.newArrayList(key, value);
+        return newTestTable(binlogConfig, Lists.newArrayList(key, value));
+    }
+
+    private static OlapTable newTestTable(BinlogConfig binlogConfig, 
List<Column> baseSchema) {
+        long baseIndexId = 1L;
 
         // Construct a minimal olap table for row binlog schema generation.
         OlapTable table = new OlapTable(1L, "tbl", baseSchema, 
KeysType.PRIMARY_KEYS, null, null);
@@ -113,4 +116,51 @@ public class OlapTableRowBinlogSchemaTest {
         Assertions.assertFalse(table.needRowBinlog());
         Assertions.assertTrue(table.getBaseIndexMeta().getRowBinlogIndexId() 
<= 0);
     }
+
+    @Test
+    public void testRowBinlogSchemaIncludesHiddenKeyColumns() {
+        Column key = new Column("k1", PrimitiveType.INT);
+        key.setIsKey(true);
+        Column hiddenKey = new Column("__DORIS_TEST_HIDDEN_KEY__", 
PrimitiveType.BIGINT);
+        hiddenKey.setIsKey(true);
+        hiddenKey.setIsVisible(false);
+        Column value = new Column("v1", PrimitiveType.INT);
+        value.setIsKey(false);
+        Column hiddenValue = new Column("__DORIS_TEST_HIDDEN_VALUE__", 
PrimitiveType.INT);
+        hiddenValue.setIsKey(false);
+        hiddenValue.setIsVisible(false);
+
+        OlapTable table = 
newTestTable(BinlogTestUtils.newTestRowBinlogConfig(true, true),
+                Lists.newArrayList(key, hiddenKey, value, hiddenValue));
+
+        List<String> columnNames = 
table.getRowBinlogMeta().getSchema(true).stream().map(Column::getName)
+                .collect(Collectors.toList());
+        Assertions.assertEquals("k1", columnNames.get(0));
+        Assertions.assertEquals("__DORIS_TEST_HIDDEN_KEY__", 
columnNames.get(1));
+        Assertions.assertEquals("v1", columnNames.get(2));
+        
Assertions.assertFalse(columnNames.contains("__DORIS_TEST_HIDDEN_VALUE__"));
+        
Assertions.assertTrue(columnNames.contains(Column.generateBeforeColName("v1")));
+        
Assertions.assertFalse(columnNames.contains(Column.generateBeforeColName("__DORIS_TEST_HIDDEN_KEY__")));
+        Assertions.assertEquals(4, columnNames.indexOf(Column.BINLOG_LSN_COL));
+    }
+
+    @Test
+    public void 
testRowBinlogSchemaSkipsHiddenNonKeyColumnBeforeVisibleColumn() {
+        Column key = new Column("k1", PrimitiveType.INT);
+        key.setIsKey(true);
+        Column hiddenValue = new Column("__DORIS_TEST_HIDDEN_VALUE__", 
PrimitiveType.INT);
+        hiddenValue.setIsKey(false);
+        hiddenValue.setIsVisible(false);
+        Column value = new Column("v1", PrimitiveType.INT);
+        value.setIsKey(false);
+
+        OlapTable table = 
newTestTable(BinlogTestUtils.newTestRowBinlogConfig(true, false),
+                Lists.newArrayList(key, hiddenValue, value));
+        List<String> columnNames = 
table.getRowBinlogMeta().getSchema(true).stream().map(Column::getName)
+                .collect(Collectors.toList());
+        Assertions.assertEquals("k1", columnNames.get(0));
+        Assertions.assertEquals("v1", columnNames.get(1));
+        
Assertions.assertFalse(columnNames.contains("__DORIS_TEST_HIDDEN_VALUE__"));
+        Assertions.assertEquals(2, columnNames.indexOf(Column.BINLOG_LSN_COL));
+    }
 }
diff --git a/regression-test/suites/row_binlog_p0/test_row_binlog_basic.groovy 
b/regression-test/suites/row_binlog_p0/test_row_binlog_basic.groovy
index c1e40c33ba8..d810420346b 100644
--- a/regression-test/suites/row_binlog_p0/test_row_binlog_basic.groovy
+++ b/regression-test/suites/row_binlog_p0/test_row_binlog_basic.groovy
@@ -293,6 +293,14 @@ suite("test_row_binlog_basic", "nonConcurrent") {
         FROM binlog("table" = "test_mow_seq_with_binlog")
         ORDER BY __DORIS_BINLOG_TSO__, __DORIS_BINLOG_LSN__
     """
+
+    test {
+        sql """
+            SELECT __DORIS_SEQUENCE_COL__
+            FROM binlog("table" = "test_mow_seq_with_binlog")
+        """
+        exception "Unknown column"
+    }
     
     sql "SET skip_delete_bitmap = false"
-}
\ No newline at end of file
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to