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

mrhhsg 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 4571c06e825 [fix](be) Reject unsafe ordered links for hidden columns 
(#69395)
4571c06e825 is described below

commit 4571c06e825076dad3d70fbedb9e479a86a3c6e8
Author: Jerry Hu <[email protected]>
AuthorDate: Mon Oct 26 09:41:36 2026 +0800

    [fix](be) Reject unsafe ordered links for hidden columns (#69395)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: None
    
    Problem Summary: A non-empty single-version rowset can store physical
    placeholders for publish-time hidden VERSION and TSO columns. Its reader
    synthesizes the published values from rowset metadata, but ordered
    file-link compaction can combine such rowsets into a multi-version
    output whose reader uses the physical placeholders. This can return
    incorrect hidden-column values. The same risk applies to the changelog
    quick-merge link path.
    
    Reject a link only when the rowsets actually selected for linking
    include such a singleton, so the existing physical merge materializes
    the values. A UNIQUE KEY merge-on-read table with
    `light_schema_change=false` can retain the VERSION column while
    `version_col_idx` is unset; look up VERSION by name to cover that
    supported case. Safe materialized range rowsets and tidy prefixes remain
    linkable.
    
    Scope: This PR changes ordered-link admission, not row-store JSONB
    generation, point/Row-ID reads, or historical rowsets already containing
    incorrect physical values. It does not add a rowset metadata certificate
    or change the protobuf/storage format.
    
    ### Release note
    
    Prevent ordered file-link compaction from exposing publish-time hidden
    VERSION/TSO placeholders.
    
    ### Check List (For Author)
    
    - Test:
    - ASAN BE+FE build: `BUILD_TYPE=ASAN ./build.sh --be --fe -j32` passed,
    including FE Checkstyle.
    - BE unit tests: `BUILD_TYPE=ASAN ./run-be-ut.sh --run
    --filter='OrderedDataCompactionTest.*' -j32` passed (7/7).
    - Worktree-isolated SQL regression:
    `test_ordered_compaction_hidden_version_no_light_schema_change` and
    `test_ordered_compaction_hidden_version_column` passed (1/1 each) using
    the local regression runner on private FE/BE ports. The latter suite's
    COMMIT_TSO/time-travel branch is feature-flag dependent and was not
    independently confirmed in this run.
    - `build-support/check-build-hygiene.sh`,
    `build-support/check-format.sh`, and `git diff --check` passed.
    - Changed-file `clang-tidy` was attempted but did not complete cleanly:
    the local toolchain analysis cannot find `stddef.h` from
    `/usr/include/wchar.h`. No clang-tidy pass is claimed.
    - Behavior changed: Yes. Unsafe ordered links fall back to ordinary
    physical merge; safe range rowsets and selected tidy prefixes can still
    use file linking.
    - Does this need documentation: No.
---
 be/src/storage/compaction/compaction.cpp           |  24 +++
 .../compaction/ordered_data_compaction_test.cpp    | 203 ++++++++++++++++++++-
 ...st_ordered_compaction_hidden_version_column.out |  22 +++
 ...ction_hidden_version_no_light_schema_change.out |  12 ++
 ...ordered_compaction_hidden_version_column.groovy | 174 ++++++++++++++++++
 ...on_hidden_version_no_light_schema_change.groovy | 104 +++++++++++
 6 files changed, 536 insertions(+), 3 deletions(-)

diff --git a/be/src/storage/compaction/compaction.cpp 
b/be/src/storage/compaction/compaction.cpp
index c7e9f3beb33..51c59e545f1 100644
--- a/be/src/storage/compaction/compaction.cpp
+++ b/be/src/storage/compaction/compaction.cpp
@@ -753,6 +753,24 @@ bool CompactionMixin::handle_ordered_data_compaction() {
         }
     }
 
+    // A non-empty singleton stores placeholders for publish-time VERSION and 
TSO columns.
+    // Linking [2-2] and [3-3] into [2-3] would expose those placeholders 
rather than the
+    // synthesized values. Check only the rowsets actually linked: the tidy 
scan below can
+    // drop an unsafe tail [10-10] while retaining a safe range prefix [2-9].
+    auto links_unmaterialized_hidden_column = [this] {
+        return _input_rowsets.front()->start_version() != 
_input_rowsets.back()->end_version() &&
+               std::ranges::any_of(_input_rowsets, [](const RowsetSharedPtr& 
rowset) {
+                   const auto& schema = rowset->tablet_schema();
+                   return rowset->num_rows() > 0 &&
+                          rowset->start_version() == rowset->end_version() &&
+                          // A non-light-schema-change tablet can retain 
VERSION by name while
+                          // version_col_idx is -1; linking its singleton 
would expose stored 0.
+                          (schema->field_index(VERSION_COL) >= 0 ||
+                           schema->commit_tso_col_idx() >= 0 ||
+                           schema->changelog_tso_col_idx() >= 0);
+               });
+    };
+
     if (is_changelog_compaction) {
         DCHECK(!_input_rowsets.empty()) << "tablet=" << _tablet->tablet_id();
         auto compaction_level = 
_input_rowsets.front()->rowset_meta()->compaction_level();
@@ -763,6 +781,9 @@ bool CompactionMixin::handle_ordered_data_compaction() {
         if (!can_quick_merge_changelog) {
             return false;
         }
+        if (links_unmaterialized_hidden_column()) {
+            return false;
+        }
         _trigger_quick_merge_by_changelog = true;
 
         // Changelog quick merge at LMax is a special meta/link compaction 
path selected by
@@ -791,6 +812,9 @@ bool CompactionMixin::handle_ordered_data_compaction() {
                 }
             }
         }
+        if (links_unmaterialized_hidden_column()) {
+            return false;
+        }
     }
 
     // most rowset of current compaction is nonoverlapping
diff --git a/be/test/storage/compaction/ordered_data_compaction_test.cpp 
b/be/test/storage/compaction/ordered_data_compaction_test.cpp
index 39d7911244c..b91cd6be66a 100644
--- a/be/test/storage/compaction/ordered_data_compaction_test.cpp
+++ b/be/test/storage/compaction/ordered_data_compaction_test.cpp
@@ -130,14 +130,17 @@ protected:
     int32_t _saved_ordered_data_compaction_min_segment_size = 0;
     int32_t _saved_segments_key_bounds_truncation_threshold = 0;
 
-    TabletSchemaSPtr create_schema(KeysType keys_type = DUP_KEYS) {
+    TabletSchemaSPtr create_schema(KeysType keys_type = DUP_KEYS, bool 
with_version_column = false,
+                                   bool with_commit_tso_column = false,
+                                   bool unset_version_col_idx = false) {
         TabletSchemaSPtr tablet_schema = std::make_shared<TabletSchema>();
         TabletSchemaPB tablet_schema_pb;
         tablet_schema_pb.set_keys_type(keys_type);
         tablet_schema_pb.set_num_short_key_columns(1);
         tablet_schema_pb.set_num_rows_per_row_block(1024);
         tablet_schema_pb.set_compress_kind(COMPRESS_NONE);
-        tablet_schema_pb.set_next_column_unique_id(4);
+        tablet_schema_pb.set_next_column_unique_id(4 + with_version_column +
+                                                   with_commit_tso_column);
 
         ColumnPB* column_1 = tablet_schema_pb.add_column();
         column_1->set_unique_id(1);
@@ -174,6 +177,38 @@ protected:
             column_3->set_is_bf_column(false);
         }
 
+        if (with_version_column) {
+            ColumnPB* version_column = tablet_schema_pb.add_column();
+            version_column->set_unique_id(4);
+            version_column->set_name(VERSION_COL);
+            version_column->set_type("BIGINT");
+            version_column->set_is_key(false);
+            version_column->set_is_nullable(false);
+            version_column->set_visible(false);
+            version_column->set_default_value("0");
+            version_column->set_length(8);
+            version_column->set_index_length(8);
+            version_column->set_aggregation("REPLACE");
+            if (!unset_version_col_idx) {
+                tablet_schema_pb.set_version_col_idx(3);
+            }
+        }
+
+        if (with_commit_tso_column) {
+            
tablet_schema_pb.set_commit_tso_col_idx(tablet_schema_pb.column_size());
+            ColumnPB* tso_column = tablet_schema_pb.add_column();
+            tso_column->set_unique_id(tablet_schema_pb.column_size());
+            tso_column->set_name(COMMIT_TSO_COL);
+            tso_column->set_type("BIGINT");
+            tso_column->set_is_key(false);
+            tso_column->set_is_nullable(false);
+            tso_column->set_visible(false);
+            tso_column->set_default_value("0");
+            tso_column->set_length(8);
+            tso_column->set_index_length(8);
+            tso_column->set_aggregation("NONE");
+        }
+
         tablet_schema->init_from_pb(tablet_schema_pb);
         return tablet_schema;
     }
@@ -301,7 +336,8 @@ protected:
     RowsetSharedPtr create_rowset(
             TabletSchemaSPtr tablet_schema, TabletSharedPtr tablet,
             const SegmentsOverlapPB& overlap,
-            std::vector<std::vector<std::tuple<int64_t, int64_t>>> 
rowset_data) {
+            std::vector<std::vector<std::tuple<int64_t, int64_t>>> rowset_data,
+            Version version = Version(-1, -1), int64_t stored_version = 0) {
         RowsetWriterContext writer_context;
         if (overlap == NONOVERLAPPING) {
             for (auto i = 1; i < rowset_data.size(); i++) {
@@ -314,6 +350,9 @@ protected:
         }
         create_rowset_writer_context(tablet_schema, tablet->tablet_path(), 
overlap, UINT32_MAX,
                                      &writer_context);
+        if (version.first >= 0) {
+            writer_context.version = version;
+        }
 
         auto res = RowsetFactory::create_rowset_writer(*engine_ref, 
writer_context, true);
         EXPECT_TRUE(res.has_value()) << res.error();
@@ -333,6 +372,15 @@ protected:
                     uint8_t num = 0;
                     columns[2]->insert_data((const char*)&num, sizeof(num));
                 }
+                if (auto version_idx = 
tablet_schema->field_index(VERSION_COL); version_idx >= 0) {
+                    columns[version_idx]->insert_data((const 
char*)&stored_version,
+                                                      sizeof(stored_version));
+                }
+                if (tablet_schema->commit_tso_col_idx() >= 0) {
+                    int64_t tso_placeholder = 0;
+                    columns[tablet_schema->commit_tso_col_idx()]->insert_data(
+                            (const char*)&tso_placeholder, 
sizeof(tso_placeholder));
+                }
                 num_rows++;
             }
             block.set_columns(std::move(columns));
@@ -349,6 +397,32 @@ protected:
         return rowset;
     }
 
+    std::vector<int64_t> read_hidden_values(Rowset* rowset, const 
TabletSchemaSPtr& tablet_schema,
+                                            ColumnId hidden_column_id) {
+        auto read_schema = 
std::make_shared<ReadSchema>(project_columns_by_ordinal(
+                tablet_schema->columns(), std::vector<ColumnId> {0, 
hidden_column_id}));
+        EXPECT_TRUE(read_schema->init_from_tablet_schema(*tablet_schema, 
false, false).ok());
+        RowsetReaderContext reader_context;
+        reader_context.read_schema = read_schema;
+        RowsetReaderSharedPtr reader;
+        create_and_init_rowset_reader(rowset, reader_context, &reader);
+        std::vector<int64_t> values;
+        Status status = Status::OK();
+        do {
+            Block block = read_schema->create_read_block();
+            status = reader->next_batch(&block);
+            for (size_t i = 0; i < block.rows(); ++i) {
+                values.push_back(block.get_by_position(1).column->get_int(i));
+            }
+        } while (status.ok());
+        EXPECT_EQ(status, Status::Error<END_OF_FILE>(""));
+        return values;
+    }
+
+    std::vector<int64_t> read_versions(Rowset* rowset, const TabletSchemaSPtr& 
tablet_schema) {
+        return read_hidden_values(rowset, tablet_schema, 
tablet_schema->field_index(VERSION_COL));
+    }
+
     void init_rs_meta(RowsetMetaSharedPtr& pb1, int64_t start, int64_t end) {
         std::string json_rowset_meta = R"({
             "rowset_id": 540085,
@@ -559,6 +633,129 @@ TEST_F(OrderedDataCompactionTest, test_01) {
     }
 }
 
+TEST_F(OrderedDataCompactionTest, 
test_version_col_singleton_rowset_not_linked) {
+    auto tablet_schema = create_schema(UNIQUE_KEYS, true);
+    auto tablet = create_tablet(*tablet_schema, false, 10000, false);
+    
ASSERT_TRUE(io::global_local_filesystem()->create_directory(tablet->tablet_path()).ok());
+
+    std::vector<std::vector<std::vector<std::tuple<int64_t, int64_t>>>> 
input_data;
+    generate_input_data(2, 1, 2, input_data);
+    std::vector<RowsetSharedPtr> input_rowsets;
+    for (const auto& rowset_data : input_data) {
+        auto rowset = create_rowset(tablet_schema, tablet, NONOVERLAPPING, 
rowset_data);
+        ASSERT_EQ(rowset->start_version(), rowset->end_version());
+        ASSERT_EQ(rowset->num_rows(), 2);
+        EXPECT_EQ(read_versions(rowset.get(), tablet_schema),
+                  (std::vector<int64_t> {rowset->end_version(), 
rowset->end_version()}));
+        input_rowsets.push_back(std::move(rowset));
+    }
+
+    CumulativeCompaction compaction(*engine_ref, tablet);
+    compaction._input_rowsets = std::move(input_rowsets);
+    ASSERT_FALSE(compaction.handle_ordered_data_compaction());
+}
+
+TEST_F(OrderedDataCompactionTest, 
test_version_col_with_unset_index_singleton_not_linked) {
+    // A schema created without light schema change has the VERSION column but 
no index in PB.
+    auto tablet_schema = create_schema(UNIQUE_KEYS, true, false, true);
+    ASSERT_EQ(tablet_schema->version_col_idx(), -1);
+    ASSERT_EQ(tablet_schema->field_index(VERSION_COL), 3);
+    auto tablet = create_tablet(*tablet_schema, false, 10000, false);
+    
ASSERT_TRUE(io::global_local_filesystem()->create_directory(tablet->tablet_path()).ok());
+
+    std::vector<std::vector<std::vector<std::tuple<int64_t, int64_t>>>> 
input_data;
+    generate_input_data(2, 1, 2, input_data);
+    std::vector<RowsetSharedPtr> input_rowsets;
+    for (const auto& rowset_data : input_data) {
+        auto rowset = create_rowset(tablet_schema, tablet, NONOVERLAPPING, 
rowset_data);
+        ASSERT_EQ(rowset->start_version(), rowset->end_version());
+        ASSERT_EQ(rowset->num_rows(), 2);
+        EXPECT_EQ(read_versions(rowset.get(), tablet_schema),
+                  (std::vector<int64_t> {rowset->end_version(), 
rowset->end_version()}));
+        input_rowsets.push_back(std::move(rowset));
+    }
+
+    CumulativeCompaction compaction(*engine_ref, tablet);
+    compaction._input_rowsets = std::move(input_rowsets);
+    ASSERT_FALSE(compaction.handle_ordered_data_compaction());
+}
+
+TEST_F(OrderedDataCompactionTest, 
test_version_col_materialized_range_rowsets_can_link) {
+    auto tablet_schema = create_schema(UNIQUE_KEYS, true);
+    auto tablet = create_tablet(*tablet_schema, false, 10000, false);
+    
ASSERT_TRUE(io::global_local_filesystem()->create_directory(tablet->tablet_path()).ok());
+
+    std::vector<std::vector<std::vector<std::tuple<int64_t, int64_t>>>> 
input_data;
+    generate_input_data(2, 1, 2, input_data);
+    auto first = create_rowset(tablet_schema, tablet, NONOVERLAPPING, 
input_data[0],
+                               Version(1000, 1001), 1001);
+    auto second = create_rowset(tablet_schema, tablet, NONOVERLAPPING, 
input_data[1],
+                                Version(1002, 1003), 1003);
+    EXPECT_EQ(read_versions(first.get(), tablet_schema), (std::vector<int64_t> 
{1001, 1001}));
+    EXPECT_EQ(read_versions(second.get(), tablet_schema), 
(std::vector<int64_t> {1003, 1003}));
+
+    CumulativeCompaction compaction(*engine_ref, tablet);
+    compaction._input_rowsets = {first, second};
+    ASSERT_TRUE(compaction.handle_ordered_data_compaction());
+    EXPECT_EQ(compaction._output_rowset->start_version(), 1000);
+    EXPECT_EQ(compaction._output_rowset->end_version(), 1003);
+    EXPECT_EQ(read_versions(compaction._output_rowset.get(), tablet_schema),
+              (std::vector<int64_t> {1001, 1001, 1003, 1003}));
+}
+
+TEST_F(OrderedDataCompactionTest, 
test_commit_tso_singleton_rowsets_not_linked) {
+    auto tablet_schema = create_schema(DUP_KEYS, false, true);
+    auto tablet = create_tablet(*tablet_schema, false, 10000, false);
+    
ASSERT_TRUE(io::global_local_filesystem()->create_directory(tablet->tablet_path()).ok());
+
+    std::vector<std::vector<std::vector<std::tuple<int64_t, int64_t>>>> 
input_data;
+    generate_input_data(2, 1, 2, input_data);
+    std::vector<RowsetSharedPtr> input_rowsets;
+    for (size_t i = 0; i < input_data.size(); ++i) {
+        auto rowset = create_rowset(tablet_schema, tablet, NONOVERLAPPING, 
input_data[i]);
+        rowset->rowset_meta()->set_commit_tso(100 + i);
+        EXPECT_EQ(read_hidden_values(rowset.get(), tablet_schema,
+                                     tablet_schema->commit_tso_col_idx()),
+                  (std::vector<int64_t> {100 + static_cast<int64_t>(i),
+                                         100 + static_cast<int64_t>(i)}));
+        input_rowsets.push_back(std::move(rowset));
+    }
+
+    CumulativeCompaction compaction(*engine_ref, tablet);
+    compaction._input_rowsets = std::move(input_rowsets);
+    ASSERT_FALSE(compaction.handle_ordered_data_compaction());
+}
+
+TEST_F(OrderedDataCompactionTest, test_unsafe_tail_does_not_block_safe_prefix) 
{
+    auto tablet_schema = create_schema(UNIQUE_KEYS, true);
+    auto tablet = create_tablet(*tablet_schema, false, 10000, false);
+    
ASSERT_TRUE(io::global_local_filesystem()->create_directory(tablet->tablet_path()).ok());
+
+    std::vector<std::vector<std::vector<std::tuple<int64_t, int64_t>>>> 
input_data;
+    generate_input_data(5, 1, 2, input_data);
+    std::vector<RowsetSharedPtr> input_rowsets;
+    for (size_t i = 0; i < input_data.size() - 1; ++i) {
+        input_rowsets.push_back(create_rowset(tablet_schema, tablet, 
NONOVERLAPPING, input_data[i],
+                                              Version(1000 + 2 * i, 1001 + 2 * 
i), 1001 + 2 * i));
+    }
+    // Mark a two-segment singleton tail untidy; it must not veto the tidy 
range prefix.
+    auto tail_data = std::vector<std::vector<std::tuple<int64_t, int64_t>>> {
+            {input_data.back()[0][0]}, {input_data.back()[0][0]}};
+    auto tail = create_rowset(tablet_schema, tablet, OVERLAPPING, tail_data, 
Version(1008, 1008));
+    ASSERT_EQ(tail->num_segments(), 2);
+    ASSERT_TRUE(tail->is_segments_overlapping());
+    input_rowsets.push_back(tail);
+
+    CumulativeCompaction compaction(*engine_ref, tablet);
+    compaction._input_rowsets = std::move(input_rowsets);
+    ASSERT_TRUE(compaction.handle_ordered_data_compaction());
+    EXPECT_EQ(compaction._input_rowsets.size(), 4);
+    EXPECT_EQ(compaction._output_rowset->start_version(), 1000);
+    EXPECT_EQ(compaction._output_rowset->end_version(), 1007);
+    EXPECT_EQ(read_versions(compaction._output_rowset.get(), tablet_schema),
+              (std::vector<int64_t> {1001, 1001, 1003, 1003, 1005, 1005, 1007, 
1007}));
+}
+
 TEST_F(OrderedDataCompactionTest, test_index_disk_size) {
     auto num_input_rowset = 3;
     auto num_segments = 2;
diff --git 
a/regression-test/data/fault_injection_p0/test_ordered_compaction_hidden_version_column.out
 
b/regression-test/data/fault_injection_p0/test_ordered_compaction_hidden_version_column.out
new file mode 100644
index 00000000000..cd4ecbbf743
--- /dev/null
+++ 
b/regression-test/data/fault_injection_p0/test_ordered_compaction_hidden_version_column.out
@@ -0,0 +1,22 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !versions --
+10     true
+11     true
+20     true
+21     true
+
+-- !min_max --
+true   true
+
+-- !version_filters --
+2      2
+
+-- !commit_tso --
+10     true
+11     true
+20     true
+21     true
+
+-- !time_travel --
+10     10
+11     11
diff --git 
a/regression-test/data/fault_injection_p0/test_ordered_compaction_hidden_version_no_light_schema_change.out
 
b/regression-test/data/fault_injection_p0/test_ordered_compaction_hidden_version_no_light_schema_change.out
new file mode 100644
index 00000000000..db1ee8131fa
--- /dev/null
+++ 
b/regression-test/data/fault_injection_p0/test_ordered_compaction_hidden_version_no_light_schema_change.out
@@ -0,0 +1,12 @@
+-- This file is automatically generated. You should know what you did if you 
want to edit this
+-- !versions --
+10     true
+11     true
+20     true
+21     true
+
+-- !min_max --
+true   true
+
+-- !version_filters --
+2      2
diff --git 
a/regression-test/suites/fault_injection_p0/test_ordered_compaction_hidden_version_column.groovy
 
b/regression-test/suites/fault_injection_p0/test_ordered_compaction_hidden_version_column.groovy
new file mode 100644
index 00000000000..ced7e4b2240
--- /dev/null
+++ 
b/regression-test/suites/fault_injection_p0/test_ordered_compaction_hidden_version_column.groovy
@@ -0,0 +1,174 @@
+// 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.
+
+suite("test_ordered_compaction_hidden_version_column", "nonConcurrent") {
+    if (isCloudMode()) {
+        return
+    }
+
+    setBeConfigTemporary([
+            enable_ordered_data_compaction: true,
+            ordered_data_compaction_min_segment_size: 1,
+            segments_key_bounds_truncation_threshold: -1
+    ]) {
+        sql "DROP TABLE IF EXISTS 
test_ordered_compaction_hidden_version_column"
+        sql """
+            CREATE TABLE test_ordered_compaction_hidden_version_column (
+                k INT NOT NULL,
+                v INT NOT NULL
+            ) UNIQUE KEY(k)
+            DISTRIBUTED BY HASH(k) BUCKETS 1
+            PROPERTIES (
+                "replication_num" = "1",
+                "enable_unique_key_merge_on_write" = "false",
+                "disable_auto_compaction" = "true")
+        """
+
+        sql "INSERT INTO test_ordered_compaction_hidden_version_column VALUES 
(10, 10), (11, 11)"
+        sql "INSERT INTO test_ordered_compaction_hidden_version_column VALUES 
(20, 20), (21, 21)"
+        sql "SET show_hidden_columns = true"
+        def firstVersions = sql """SELECT __DORIS_VERSION_COL__
+            FROM test_ordered_compaction_hidden_version_column WHERE k < 20 
ORDER BY k"""
+        def secondVersions = sql """SELECT __DORIS_VERSION_COL__
+            FROM test_ordered_compaction_hidden_version_column WHERE k >= 20 
ORDER BY k"""
+        assert firstVersions.size() == 2 && secondVersions.size() == 2
+        long v1 = firstVersions[0][0].toString().toLong()
+        long v2 = secondVersions[0][0].toString().toLong()
+        assert firstVersions.every { it[0].toString().toLong() == v1 }
+        assert secondVersions.every { it[0].toString().toLong() == v2 }
+        assert v1 > 0 && v2 == v1 + 1
+
+        def tabletInfo = sql_return_maparray(
+                "SHOW TABLETS FROM 
test_ordered_compaction_hidden_version_column").get(0)
+        def tabletId = tabletInfo.TabletId
+        def (inputCode, inputBody, inputError) = curl("GET", 
tabletInfo.MetaUrl)
+        assert inputCode == 0 : inputError
+        def inputRowsets = parseJson(inputBody.trim()).rs_metas
+        for (def version : [v1, v2]) {
+            assert inputRowsets.count {
+                it.start_version.toString().toLong() == version &&
+                        it.end_version.toString().toLong() == version &&
+                        it.num_rows.toString().toLong() == 2 &&
+                        it.num_segments.toString().toLong() == 1
+            } == 1
+        }
+        try {
+            GetDebugPoint().enableDebugPointForAllBEs(
+                    
"SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets",
+                    [tablet_id: "${tabletId}", start_version: "${v1}", 
end_version: "${v2}"])
+            
trigger_and_wait_compaction("test_ordered_compaction_hidden_version_column", 
"cumulative")
+        } finally {
+            GetDebugPoint().disableDebugPointForAllBEs(
+                    
"SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets")
+        }
+
+        def metaUrl = sql_return_maparray(
+                "SHOW TABLETS FROM 
test_ordered_compaction_hidden_version_column").get(0).MetaUrl
+        def (code, body, error) = curl("GET", metaUrl)
+        assert code == 0 : error
+        def rowsets = parseJson(body.trim()).rs_metas
+        assert rowsets.count {
+            it.start_version.toString().toLong() == v1 &&
+                    it.end_version.toString().toLong() == v2 &&
+                    it.num_rows.toString().toLong() == 4
+        } == 1
+
+        order_qt_versions """SELECT k,
+            (__DORIS_VERSION_COL__ = CASE WHEN k < 20 THEN ${v1} ELSE ${v2} 
END) AS correct_version
+            FROM test_ordered_compaction_hidden_version_column ORDER BY k"""
+        qt_min_max """SELECT MIN(__DORIS_VERSION_COL__) = ${v1},
+            MAX(__DORIS_VERSION_COL__) = ${v2}
+            FROM test_ordered_compaction_hidden_version_column"""
+        qt_version_filters """SELECT
+            (SELECT COUNT(*) FROM test_ordered_compaction_hidden_version_column
+                WHERE __DORIS_VERSION_COL__ = ${v1}),
+            (SELECT COUNT(*) FROM test_ordered_compaction_hidden_version_column
+                WHERE __DORIS_VERSION_COL__ = ${v2})"""
+
+        def featureEnabled = { String name ->
+            def row = sql("SHOW FRONTEND CONFIG LIKE '%${name}'").find {
+                it[0] == name || it[0] == "experimental_${name}"
+            }
+            return row != null && row[1] == "true"
+        }
+        if (featureEnabled("enable_feature_changelog") && 
featureEnabled("enable_feature_binlog")) {
+            sql "DROP TABLE IF EXISTS 
test_ordered_compaction_hidden_tso_column"
+            sql """
+                CREATE TABLE test_ordered_compaction_hidden_tso_column (
+                    k INT NOT NULL,
+                    v INT NOT NULL
+                ) DUPLICATE KEY(k)
+                DISTRIBUTED BY HASH(k) BUCKETS 1
+                PROPERTIES (
+                    "replication_num" = "1",
+                    "changelog.enable" = "true",
+                    "disable_auto_compaction" = "true")
+            """
+            sql "INSERT INTO test_ordered_compaction_hidden_tso_column VALUES 
(10, 10), (11, 11)"
+            sql "INSERT INTO test_ordered_compaction_hidden_tso_column VALUES 
(20, 20), (21, 21)"
+            def firstTsos = sql """SELECT __DORIS_COMMIT_TSO_COL__
+                FROM test_ordered_compaction_hidden_tso_column WHERE k < 20 
ORDER BY k"""
+            def secondTsos = sql """SELECT __DORIS_COMMIT_TSO_COL__
+                FROM test_ordered_compaction_hidden_tso_column WHERE k >= 20 
ORDER BY k"""
+            assert firstTsos.size() == 2 && secondTsos.size() == 2
+            long t1 = firstTsos[0][0].toString().toLong()
+            long t2 = secondTsos[0][0].toString().toLong()
+            assert t1 > 0 && t2 > t1
+            assert firstTsos.every { it[0].toString().toLong() == t1 }
+            assert secondTsos.every { it[0].toString().toLong() == t2 }
+
+            def tsoTabletInfo = sql_return_maparray(
+                    "SHOW TABLETS FROM 
test_ordered_compaction_hidden_tso_column").get(0)
+            def (tsoInputCode, tsoInputBody, tsoInputError) = curl("GET", 
tsoTabletInfo.MetaUrl)
+            assert tsoInputCode == 0 : tsoInputError
+            def tsoInputRowsets = 
parseJson(tsoInputBody.trim()).rs_metas.findAll {
+                it.start_version.toString().toLong() == 
it.end_version.toString().toLong() &&
+                        it.num_rows.toString().toLong() == 2 &&
+                        it.num_segments.toString().toLong() == 1
+            }.sort { it.start_version.toString().toLong() }
+            assert tsoInputRowsets.size() == 2
+            long firstVersion = 
tsoInputRowsets[0].start_version.toString().toLong()
+            long secondVersion = 
tsoInputRowsets[1].end_version.toString().toLong()
+            assert secondVersion == firstVersion + 1
+            try {
+                GetDebugPoint().enableDebugPointForAllBEs(
+                        
"SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets",
+                        [tablet_id: "${tsoTabletInfo.TabletId}", 
start_version: "${firstVersion}",
+                         end_version: "${secondVersion}"])
+                
trigger_and_wait_compaction("test_ordered_compaction_hidden_tso_column", 
"cumulative")
+            } finally {
+                GetDebugPoint().disableDebugPointForAllBEs(
+                        
"SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets")
+            }
+
+            def tsoMetaUrl = sql_return_maparray(
+                    "SHOW TABLETS FROM 
test_ordered_compaction_hidden_tso_column").get(0).MetaUrl
+            def (tsoCode, tsoBody, tsoError) = curl("GET", tsoMetaUrl)
+            assert tsoCode == 0 : tsoError
+            assert parseJson(tsoBody.trim()).rs_metas.count {
+                it.start_version.toString().toLong() == firstVersion &&
+                        it.end_version.toString().toLong() == secondVersion &&
+                        it.num_rows.toString().toLong() == 4
+            } == 1
+            order_qt_commit_tso """SELECT k,
+                (__DORIS_COMMIT_TSO_COL__ = CASE WHEN k < 20 THEN ${t1} ELSE 
${t2} END) AS correct_tso
+                FROM test_ordered_compaction_hidden_tso_column ORDER BY k"""
+            order_qt_time_travel """SELECT k, v FROM 
test_ordered_compaction_hidden_tso_column
+                FOR VERSION AS OF ${t1} ORDER BY k"""
+        }
+    }
+}
diff --git 
a/regression-test/suites/fault_injection_p0/test_ordered_compaction_hidden_version_no_light_schema_change.groovy
 
b/regression-test/suites/fault_injection_p0/test_ordered_compaction_hidden_version_no_light_schema_change.groovy
new file mode 100644
index 00000000000..5b245cd885c
--- /dev/null
+++ 
b/regression-test/suites/fault_injection_p0/test_ordered_compaction_hidden_version_no_light_schema_change.groovy
@@ -0,0 +1,104 @@
+// 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.
+
+suite("test_ordered_compaction_hidden_version_no_light_schema_change", 
"nonConcurrent") {
+    if (isCloudMode()) {
+        return
+    }
+
+    setBeConfigTemporary([
+            enable_ordered_data_compaction: true,
+            ordered_data_compaction_min_segment_size: 1,
+            segments_key_bounds_truncation_threshold: -1
+    ]) {
+        sql "DROP TABLE IF EXISTS 
test_ordered_compaction_hidden_version_no_light_schema_change"
+        sql """
+            CREATE TABLE 
test_ordered_compaction_hidden_version_no_light_schema_change (
+                k INT NOT NULL,
+                v INT NOT NULL
+            ) UNIQUE KEY(k)
+            DISTRIBUTED BY HASH(k) BUCKETS 1
+            PROPERTIES (
+                "replication_num" = "1",
+                "enable_unique_key_merge_on_write" = "false",
+                "light_schema_change" = "false",
+                "disable_auto_compaction" = "true")
+        """
+
+        sql "INSERT INTO 
test_ordered_compaction_hidden_version_no_light_schema_change VALUES (10, 10), 
(11, 11)"
+        sql "INSERT INTO 
test_ordered_compaction_hidden_version_no_light_schema_change VALUES (20, 20), 
(21, 21)"
+        sql "SET show_hidden_columns = true"
+        def firstVersions = sql """SELECT __DORIS_VERSION_COL__
+            FROM test_ordered_compaction_hidden_version_no_light_schema_change 
WHERE k < 20 ORDER BY k"""
+        def secondVersions = sql """SELECT __DORIS_VERSION_COL__
+            FROM test_ordered_compaction_hidden_version_no_light_schema_change 
WHERE k >= 20 ORDER BY k"""
+        assert firstVersions.size() == 2 && secondVersions.size() == 2
+        long v1 = firstVersions[0][0].toString().toLong()
+        long v2 = secondVersions[0][0].toString().toLong()
+        assert firstVersions.every { it[0].toString().toLong() == v1 }
+        assert secondVersions.every { it[0].toString().toLong() == v2 }
+        assert v1 > 0 && v2 == v1 + 1
+
+        def tabletInfo = sql_return_maparray(
+                "SHOW TABLETS FROM 
test_ordered_compaction_hidden_version_no_light_schema_change").get(0)
+        def tabletId = tabletInfo.TabletId
+        def (inputCode, inputBody, inputError) = curl("GET", 
tabletInfo.MetaUrl)
+        assert inputCode == 0 : inputError
+        def inputRowsets = parseJson(inputBody.trim()).rs_metas
+        for (def version : [v1, v2]) {
+            assert inputRowsets.count {
+                it.start_version.toString().toLong() == version &&
+                        it.end_version.toString().toLong() == version &&
+                        it.num_rows.toString().toLong() == 2 &&
+                        it.num_segments.toString().toLong() == 1
+            } == 1
+        }
+        try {
+            GetDebugPoint().enableDebugPointForAllBEs(
+                    
"SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets",
+                    [tablet_id: "${tabletId}", start_version: "${v1}", 
end_version: "${v2}"])
+            
trigger_and_wait_compaction("test_ordered_compaction_hidden_version_no_light_schema_change",
 "cumulative")
+        } finally {
+            GetDebugPoint().disableDebugPointForAllBEs(
+                    
"SizeBasedCumulativeCompactionPolicy::pick_input_rowsets.set_input_rowsets")
+        }
+
+        def metaUrl = sql_return_maparray(
+                "SHOW TABLETS FROM 
test_ordered_compaction_hidden_version_no_light_schema_change").get(0).MetaUrl
+        def (code, body, error) = curl("GET", metaUrl)
+        assert code == 0 : error
+        def rowsets = parseJson(body.trim()).rs_metas
+        assert rowsets.count {
+            it.start_version.toString().toLong() == v1 &&
+                    it.end_version.toString().toLong() == v2 &&
+                    it.num_rows.toString().toLong() == 4
+        } == 1
+
+        order_qt_versions """SELECT k,
+            (__DORIS_VERSION_COL__ = CASE WHEN k < 20 THEN ${v1} ELSE ${v2} 
END) AS correct_version
+            FROM test_ordered_compaction_hidden_version_no_light_schema_change 
ORDER BY k"""
+        qt_min_max """SELECT MIN(__DORIS_VERSION_COL__) = ${v1},
+            MAX(__DORIS_VERSION_COL__) = ${v2}
+            FROM 
test_ordered_compaction_hidden_version_no_light_schema_change"""
+        qt_version_filters """SELECT
+            (SELECT COUNT(*) FROM 
test_ordered_compaction_hidden_version_no_light_schema_change
+                WHERE __DORIS_VERSION_COL__ = ${v1}),
+            (SELECT COUNT(*) FROM 
test_ordered_compaction_hidden_version_no_light_schema_change
+                WHERE __DORIS_VERSION_COL__ = ${v2})"""
+
+    }
+}


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

Reply via email to