github-actions[bot] commented on code in PR #45192:
URL: https://github.com/apache/doris/pull/45192#discussion_r1875496036


##########
be/test/olap/rowset/segment_v2/inverted_index/compaction/index_compaction_test.cpp:
##########
@@ -15,14 +15,9 @@
 // specific language governing permissions and limitations
 // under the License.
 
-#include <gtest/gtest.h>
+#include <gmock/gmock.h>

Review Comment:
   warning: 'gmock/gmock.h' file not found [clang-diagnostic-error]
   ```cpp
   #include <gmock/gmock.h>
            ^
   ```
   



##########
be/test/olap/rowset/segment_v2/inverted_index/compaction/util/index_compaction_utils.cpp:
##########
@@ -0,0 +1,621 @@
+// 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 <gtest/gtest.h>

Review Comment:
   warning: 'gtest/gtest.h' file not found [clang-diagnostic-error]
   ```cpp
   #include <gtest/gtest.h>
            ^
   ```
   



##########
be/test/olap/rowset/segment_v2/inverted_index/compaction/index_compaction_test.cpp:
##########
@@ -144,300 +95,640 @@
     std::unique_ptr<DataDir> _data_dir = nullptr;
     TabletSharedPtr _tablet = nullptr;
     std::string _absolute_dir;
-    std::string _curreent_dir;
+    std::string _current_dir;
 };
 
-std::vector<DataRow> read_data(const std::string file_name) {
-    std::ifstream file(file_name);
-    EXPECT_TRUE(file.is_open());
-
-    std::string line;
-    std::vector<DataRow> data;
-
-    while (std::getline(file, line)) {
-        std::stringstream ss(line);
-        std::string item;
-        DataRow row;
-        EXPECT_TRUE(std::getline(ss, item, ','));
-        row.key = std::stoi(item);
-        EXPECT_TRUE(std::getline(ss, item, ','));
-        row.word = item;
-        EXPECT_TRUE(std::getline(ss, item, ','));
-        row.url = item;
-        EXPECT_TRUE(std::getline(ss, item, ','));
-        row.num = std::stoi(item);
-        data.emplace_back(std::move(row));
-    }
+TEST_F(IndexCompactionTest, tes_write_index_normally) {
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 2);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(1));
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    auto custom_check_normal = [](const BaseCompaction& compaction,
+                                  const RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 0);
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    RowsetSharedPtr output_rowset_normal;
+    st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, _tablet, 
false,
+                                             output_rowset_normal, 
custom_check_normal);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+    const auto& seg_path_normal = output_rowset_normal->segment_path(0);
+    EXPECT_TRUE(seg_path_normal.has_value()) << seg_path_normal.error();
+    auto inverted_index_file_reader_normal = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_normal, seg_path_normal.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+    // check index file terms
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+    auto dir_normal_compaction = 
inverted_index_file_reader_normal->_open(10001, "");
+    EXPECT_TRUE(dir_normal_compaction.has_value()) << 
dir_normal_compaction.error();
+    std::ostringstream oss;
+    IndexCompactionUtils::check_terms_stats(dir_idx_compaction->get(), oss);
+    std::string output = oss.str();
+    EXPECT_EQ(output, expected_output);
+    oss.str("");
+    oss.clear();
+    IndexCompactionUtils::check_terms_stats(dir_normal_compaction->get(), oss);
+    output = oss.str();
+    EXPECT_EQ(output, expected_output);
+
+    st = 
IndexCompactionUtils::check_idx_file_correctness(dir_idx_compaction->get(),
+                                                          
dir_normal_compaction->get());
+    EXPECT_TRUE(st.ok()) << st.to_string();
 
-    file.close();
-    return data;
+    // check meta and file
+    std::map<int, QueryData> query_map = {
+            {0, {{"99", "66", "56", "87", "85", "96", "20000"}, {21, 25, 22, 
18, 14, 18, 0}}},
+            {3, {{"99", "66", "56", "87", "85", "96", "10000"}, {12, 20, 25, 
23, 16, 24, 0}}},
+            {1, {{"good", "maybe", "great", "null"}, {197, 191, 194, 0}}},
+            {2, {{"musicstream.com", "http", "https", "null"}, {191, 799, 
1201, 0}}}};
+    IndexCompactionUtils::check_meta_and_file(output_rowset_index, 
_tablet_schema, query_map);
+    IndexCompactionUtils::check_meta_and_file(output_rowset_normal, 
_tablet_schema, query_map);
 }
 
-bool query_bkd(const TabletIndex* index,
-               std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
-               const std::vector<int>& query_data, const std::vector<int>& 
query_result) {
-    const auto& idx_reader = BkdIndexReader::create_shared(index, 
inverted_index_file_reader);
-    const auto& index_searcher_builder = 
std::make_unique<BKDIndexSearcherBuilder>();
-    auto dir = inverted_index_file_reader->open(index);
-    EXPECT_TRUE(dir.has_value());
-    auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
-    EXPECT_TRUE(searcher_result.has_value());
-    auto bkd_searcher = 
std::get_if<BKDIndexSearcherPtr>(&searcher_result.value());
-    EXPECT_TRUE(bkd_searcher != nullptr);
-    idx_reader->_type_info = 
get_scalar_type_info((FieldType)(*bkd_searcher)->type);
-    EXPECT_TRUE(idx_reader->_type_info != nullptr);
-    idx_reader->_value_key_coder = 
get_key_coder(idx_reader->_type_info->type());
-
-    for (int i = 0; i < query_data.size(); i++) {
-        vectorized::Field param_value = Int32(query_data[i]);
-        std::unique_ptr<segment_v2::InvertedIndexQueryParamFactory> 
query_param = nullptr;
-        
EXPECT_TRUE(segment_v2::InvertedIndexQueryParamFactory::create_query_value(
-                            PrimitiveType::TYPE_INT, &param_value, query_param)
-                            .ok());
-        auto result = std::make_shared<roaring::Roaring>();
-        EXPECT_TRUE(idx_reader
-                            ->invoke_bkd_query(query_param->get_value(),
-                                               
InvertedIndexQueryType::EQUAL_QUERY, *bkd_searcher,
-                                               result)
-                            .ok());
-        EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
-    }
-    return true;
+TEST_F(IndexCompactionTest, test_col_unique_ids_empty) {
+    // clear column unique id in tablet index 10001 and rebuild tablet_schema
+    TabletSchemaPB schema_pb;
+    _tablet_schema->to_schema_pb(&schema_pb);
+    auto* index_pb = schema_pb.mutable_index(1);
+    index_pb->clear_col_unique_id();
+    _tablet_schema->init_from_pb(schema_pb);
+
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 3); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    // index 10001 cannot be found in idx file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(!dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+    EXPECT_THAT(dir_idx_compaction.error().to_string(),
+                testing::HasSubstr("No index with id 10001 found"));
 }
 
-bool query_string(const TabletIndex* index,
-                  std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
-                  const std::string& column_name, const 
std::vector<std::string>& query_data,
-                  const std::vector<int>& query_result) {
-    const auto& idx_reader =
-            StringTypeInvertedIndexReader::create_shared(index, 
inverted_index_file_reader);
-    const auto& index_searcher_builder = 
std::make_unique<FulltextIndexSearcherBuilder>();
-    auto dir = inverted_index_file_reader->open(index);
-    EXPECT_TRUE(dir.has_value());
-    auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
-    EXPECT_TRUE(searcher_result.has_value());
-    auto string_searcher = 
std::get_if<FulltextIndexSearcherPtr>(&searcher_result.value());
-    EXPECT_TRUE(string_searcher != nullptr);
-    std::wstring column_name_ws = StringUtil::string_to_wstring(column_name);
-
-    for (int i = 0; i < query_data.size(); i++) {
-        TQueryOptions queryOptions;
-        auto query = QueryFactory::create(InvertedIndexQueryType::EQUAL_QUERY, 
*string_searcher,
-                                          queryOptions, nullptr);
-        EXPECT_TRUE(query != nullptr);
-        InvertedIndexQueryInfo query_info;
-        query_info.field_name = column_name_ws;
-        query_info.terms.emplace_back(query_data[i]);
-        query->add(query_info);
-        auto result = std::make_shared<roaring::Roaring>();
-        query->search(*result);
-        EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
-    }
-    return true;
+TEST_F(IndexCompactionTest, test_tablet_index_id_not_equal) {
+    // replace unique id from 2 to 1 in tablet index 10002 and rebuild 
tablet_schema
+    TabletSchemaPB schema_pb;
+    _tablet_schema->to_schema_pb(&schema_pb);
+    auto* index_pb = schema_pb.mutable_index(2);
+    index_pb->set_col_unique_id(0, 1);
+    _tablet_schema->init_from_pb(schema_pb);
+
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 3); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10001 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(1));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    // index 10002 cannot be found in idx file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10002, 
"");
+    EXPECT_TRUE(!dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+    EXPECT_THAT(dir_idx_compaction.error().to_string(),
+                testing::HasSubstr("No index with id 10002 found"));
 }
 
-bool query_fulltext(const TabletIndex* index,
-                    std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
-                    const std::string& column_name, const 
std::vector<std::string>& query_data,
-                    const std::vector<int>& query_result) {
-    const auto& idx_reader = FullTextIndexReader::create_shared(index, 
inverted_index_file_reader);
-    const auto& index_searcher_builder = 
std::make_unique<FulltextIndexSearcherBuilder>();
-    auto dir = inverted_index_file_reader->open(index);
-    EXPECT_TRUE(dir.has_value());
-    auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
-    EXPECT_TRUE(searcher_result.has_value());
-    auto string_searcher = 
std::get_if<FulltextIndexSearcherPtr>(&searcher_result.value());
-    EXPECT_TRUE(string_searcher != nullptr);
-    std::wstring column_name_ws = StringUtil::string_to_wstring(column_name);
-
-    for (int i = 0; i < query_data.size(); i++) {
-        TQueryOptions queryOptions;
-        auto query = 
QueryFactory::create(InvertedIndexQueryType::MATCH_ANY_QUERY, *string_searcher,
-                                          queryOptions, nullptr);
-        EXPECT_TRUE(query != nullptr);
-        InvertedIndexQueryInfo query_info;
-        query_info.field_name = column_name_ws;
-        query_info.terms.emplace_back(query_data[i]);
-        query->add(query_info);
-        auto result = std::make_shared<roaring::Roaring>();
-        query->search(*result);
-        EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
-    }
-    return true;
+TEST_F(IndexCompactionTest, test_tablet_schema_tablet_index_is_null) {
+    // set index suffix in tablet index 10001 and rebuild tablet_schema
+    // simulate the case that index is null, tablet_schema->inverted_index(1) 
will return nullptr
+    TabletSchemaPB schema_pb;
+    _tablet_schema->to_schema_pb(&schema_pb);
+    auto* index_pb = schema_pb.mutable_index(1);
+    index_pb->set_index_suffix_name("mock");
+    _tablet_schema->init_from_pb(schema_pb);
+
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 3); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    // index 10001 cannot be found in idx file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(!dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+    EXPECT_THAT(dir_idx_compaction.error().to_string(),
+                testing::HasSubstr("No index with id 10001 found"));
 }
 
-TEST_F(IndexCompactionTest, write_index_test) {
+TEST_F(IndexCompactionTest, test_rowset_schema_tablet_index_is_null) {
     
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
     
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
     std::string data_file1 =
-            _curreent_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
     std::string data_file2 =
-            _curreent_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
-
-    std::vector<std::vector<DataRow>> data;
-    data.emplace_back(read_data(data_file1));
-    data.emplace_back(read_data(data_file2));
-
-    std::vector<RowsetSharedPtr> rowsets(data.size());
-    for (int i = 0; i < data.size(); i++) {
-        const auto& res =
-                RowsetFactory::create_rowset_writer(*_engine_ref, 
rowset_writer_context(), false);
-        EXPECT_TRUE(res.has_value()) << res.error();
-        const auto& rowset_writer = res.value();
-
-        Block block = _tablet_schema->create_block();
-        auto columns = block.mutate_columns();
-        for (const auto& row : data[i]) {
-            vectorized::Field key = Int32(row.key);
-            vectorized::Field v1(row.word);
-            vectorized::Field v2(row.url);
-            vectorized::Field v3 = Int32(row.num);
-            columns[0]->insert(key);
-            columns[1]->insert(v1);
-            columns[2]->insert(v2);
-            columns[3]->insert(v3);
-        }
-        EXPECT_TRUE(rowset_writer->add_block(&block).ok());
-        EXPECT_TRUE(rowset_writer->flush().ok());
-        const auto& dst_writer = 
dynamic_cast<BaseBetaRowsetWriter*>(rowset_writer.get());
-
-        // inverted index file writer
-        for (const auto& [seg_id, idx_file_writer] : 
dst_writer->_idx_files.get_file_writers()) {
-            EXPECT_TRUE(idx_file_writer->_closed);
-        }
-
-        EXPECT_TRUE(rowset_writer->build(rowsets[i]).ok());
-        EXPECT_TRUE(_tablet->add_rowset(rowsets[i]).ok());
-        EXPECT_TRUE(rowsets[i]->num_segments() == 5);
-
-        // check rowset meta and file
-        for (int seg_id = 0; seg_id < rowsets[i]->num_segments(); seg_id++) {
-            const auto& index_info = 
rowsets[i]->_rowset_meta->inverted_index_file_info(seg_id);
-            EXPECT_TRUE(index_info.has_index_size());
-            const auto& fs = rowsets[i]->_rowset_meta->fs();
-            const auto& file_name = fmt::format("{}/{}_{}.idx", 
rowsets[i]->tablet_path(),
-                                                
rowsets[i]->rowset_id().to_string(), seg_id);
-            int64_t file_size = 0;
-            EXPECT_TRUE(fs->file_size(file_name, &file_size).ok());
-            EXPECT_EQ(index_info.index_size(), file_size);
-
-            const auto& seg_path = rowsets[i]->segment_path(seg_id);
-            EXPECT_TRUE(seg_path.has_value());
-            const auto& index_file_path_prefix =
-                    
InvertedIndexDescriptor::get_index_file_path_prefix(seg_path.value());
-            auto inverted_index_file_reader = 
std::make_shared<InvertedIndexFileReader>(
-                    fs, std::string(index_file_path_prefix),
-                    _tablet_schema->get_inverted_index_storage_format(), 
index_info);
-            EXPECT_TRUE(inverted_index_file_reader->init().ok());
-            const auto& dirs = 
inverted_index_file_reader->get_all_directories();
-            EXPECT_TRUE(dirs.has_value());
-            EXPECT_EQ(dirs.value().size(), 4);
-        }
-    }
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    // set index suffix in tablet index 10001 and rebuild tablet_schema
+    // simulate the case that index is null, tablet_schema->inverted_index(1) 
will return nullptr
+    TabletSchemaPB schema_pb;
+    TabletSchemaSPtr mock_schema = std::make_shared<TabletSchema>();
+    _tablet_schema->to_schema_pb(&schema_pb);
+    auto* index_pb = schema_pb.mutable_index(1);
+    index_pb->set_index_suffix_name("mock");
+    mock_schema->init_from_pb(schema_pb);
+    rowsets[0]->_schema = mock_schema;
+
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok()) << st.to_string();
 
-    CumulativeCompaction compaction(*_engine_ref, _tablet);
-    compaction._input_rowsets = std::move(rowsets);
-    compaction.build_basic_info();
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    // index 10001 cannot be found in idx file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+    // check index 10001 term stats
+    std::ostringstream oss;
+    IndexCompactionUtils::check_terms_stats(dir_idx_compaction.value().get(), 
oss);
+    std::string output = oss.str();
+    EXPECT_EQ(output, expected_output);
+}
 
-    std::vector<RowsetReaderSharedPtr> input_rs_readers;
-    input_rs_readers.reserve(compaction._input_rowsets.size());
-    for (auto& rowset : compaction._input_rowsets) {
-        RowsetReaderSharedPtr rs_reader;
-        EXPECT_TRUE(rowset->create_reader(&rs_reader).ok());
-        input_rs_readers.push_back(std::move(rs_reader));
-    }
+TEST_F(IndexCompactionTest, test_tablet_index_properties_not_equal) {
+    // add mock property in tablet index 10001 and rebuild tablet_schema
+    // simulate the case that index properties not equal among input rowsets
+    TabletSchemaSPtr mock_schema = std::make_shared<TabletSchema>();
+    TabletSchemaPB schema_pb;
+    _tablet_schema->to_schema_pb(&schema_pb);
+    auto* index_pb = schema_pb.mutable_index(1);
+    (*index_pb->mutable_properties())["mock_key"] = "mock_value";
+    mock_schema->init_from_pb(schema_pb);
 
-    RowsetWriterContext ctx;
-    EXPECT_TRUE(compaction.construct_output_rowset_writer(ctx).ok());
-
-    // col word
-    EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(1));
-    // col url
-    EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
-
-    compaction._stats.rowid_conversion = compaction._rowid_conversion.get();
-    EXPECT_TRUE(Merger::vertical_merge_rowsets(_tablet, 
compaction.compaction_type(),
-                                               
*(compaction._cur_tablet_schema), input_rs_readers,
-                                               
compaction._output_rs_writer.get(), 100000, 5,
-                                               &compaction._stats)
-                        .ok());
-    const auto& dst_writer =
-            
dynamic_cast<BaseBetaRowsetWriter*>(compaction._output_rs_writer.get());
-    for (const auto& [seg_id, idx_file_writer] : 
dst_writer->_idx_files.get_file_writers()) {
-        EXPECT_FALSE(idx_file_writer->_closed);
-    }
-    auto st = compaction.do_inverted_index_compaction();
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    // set mock_schema to the first input rowset
+    rowsets[0]->_schema = mock_schema;
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
     EXPECT_TRUE(st.ok()) << st.to_string();
 
-    st = compaction._output_rs_writer->build(compaction._output_rowset);
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+
+    // check index 10001 term stats
+    std::ostringstream oss;
+    IndexCompactionUtils::check_terms_stats(dir_idx_compaction.value().get(), 
oss);
+    std::string output = oss.str();
+    EXPECT_EQ(output, expected_output);
+}
+
+TEST_F(IndexCompactionTest, test_is_skip_index_compaction_not_empty) {
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    // set col_unique_id=1(index_id=10001) to skip index compaction
+    rowsets[0]->set_skip_index_compaction(1);
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
     EXPECT_TRUE(st.ok()) << st.to_string();
 
-    for (const auto& [seg_id, idx_file_writer] : 
dst_writer->_idx_files.get_file_writers()) {
-        EXPECT_TRUE(idx_file_writer->_closed);
-    }
-    EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
-
-    const auto& output_rowset = compaction._output_rowset;
-
-    // check rowset meta and file
-    for (int seg_id = 0; seg_id < output_rowset->num_segments(); seg_id++) {
-        // meta
-        const auto& index_info = 
output_rowset->_rowset_meta->inverted_index_file_info(seg_id);
-        EXPECT_TRUE(index_info.has_index_size());
-        const auto& fs = output_rowset->_rowset_meta->fs();
-        const auto& file_name = fmt::format("{}/{}_{}.idx", 
output_rowset->tablet_path(),
-                                            
output_rowset->rowset_id().to_string(), seg_id);
-        int64_t file_size = 0;
-        EXPECT_TRUE(fs->file_size(file_name, &file_size).ok());
-        EXPECT_EQ(index_info.index_size(), file_size);
-
-        // file
-        const auto& seg_path = output_rowset->segment_path(seg_id);
-        EXPECT_TRUE(seg_path.has_value());
-        const auto& index_file_path_prefix =
-                
InvertedIndexDescriptor::get_index_file_path_prefix(seg_path.value());
-        auto inverted_index_file_reader = 
std::make_shared<InvertedIndexFileReader>(
-                fs, std::string(index_file_path_prefix),
-                _tablet_schema->get_inverted_index_storage_format(), 
index_info);
-        EXPECT_TRUE(inverted_index_file_reader->init().ok());
-        const auto& dirs = inverted_index_file_reader->get_all_directories();
-        EXPECT_TRUE(dirs.has_value());
-        EXPECT_EQ(dirs.value().size(), 4);
-
-        // read col key
-        const auto& key = _tablet_schema->column_by_uid(0);
-        const auto* key_index = _tablet_schema->inverted_index(key);
-        EXPECT_TRUE(key_index != nullptr);
-        std::vector<int> query_data {99, 66, 56, 87, 85, 96, 20000};
-        std::vector<int> query_result {21, 25, 22, 18, 14, 18, 0};
-        EXPECT_TRUE(query_bkd(key_index, inverted_index_file_reader, 
query_data, query_result));
-
-        // read col v3
-        const auto& v3_column = _tablet_schema->column_by_uid(3);
-        const auto* v3_index = _tablet_schema->inverted_index(v3_column);
-        EXPECT_TRUE(v3_index != nullptr);
-        std::vector<int> query_data3 {99, 66, 56, 87, 85, 96, 10000};
-        std::vector<int> query_result3 {12, 20, 25, 23, 16, 24, 0};
-        EXPECT_TRUE(query_bkd(v3_index, inverted_index_file_reader, 
query_data3, query_result3));
-
-        // read col v1
-        const auto& v1_column = _tablet_schema->column_by_uid(1);
-        const auto* v1_index = _tablet_schema->inverted_index(v1_column);
-        EXPECT_TRUE(v1_index != nullptr);
-        std::vector<std::string> query_data1 {"good", "maybe", "great", 
"null"};
-        std::vector<int> query_result1 {197, 191, 194, 0};
-        EXPECT_TRUE(query_string(v1_index, inverted_index_file_reader, "1", 
query_data1,
-                                 query_result1));
-
-        // read col v2
-        const auto& v2_column = _tablet_schema->column_by_uid(2);
-        const auto* v2_index = _tablet_schema->inverted_index(v2_column);
-        EXPECT_TRUE(v2_index != nullptr);
-        std::vector<std::string> query_data2 {"musicstream.com", "http", 
"https", "null"};
-        std::vector<int> query_result2 {191, 799, 1201, 0};
-        EXPECT_TRUE(query_fulltext(v2_index, inverted_index_file_reader, "2", 
query_data2,
-                                   query_result2));
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+
+    // check index 10001 term stats
+    std::ostringstream oss;
+    IndexCompactionUtils::check_terms_stats(dir_idx_compaction.value().get(), 
oss);
+    std::string output = oss.str();
+    EXPECT_EQ(output, expected_output);
+}
+
+TEST_F(IndexCompactionTest, test_rowset_fs_nullptr) {
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 1);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    // set mock_id to resource_id to simulate getting fs nullptr
+    RowsetMetaSharedPtr mock_rowset_meta = std::make_shared<RowsetMeta>();
+    RowsetMetaPB rs_meta_pb;
+    rowsets[0]->to_rowset_pb(&rs_meta_pb);
+    rs_meta_pb.set_resource_id("mock_id");
+    mock_rowset_meta->init_from_pb(rs_meta_pb);
+    rowsets[0]->_rowset_meta = mock_rowset_meta;
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(!st.ok());
+    EXPECT_THAT(st.to_string(), testing::HasSubstr("[E-206]get fs failed"));
+}
+
+TEST_F(IndexCompactionTest, test_input_row_num_zero) {
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // only index id 10002 will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 2);
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(1));
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.contains(2));
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    // set num_rows to 0 to simulate input_row_num = 0
+    for (auto rowset : rowsets) {
+        RowsetMetaSharedPtr mock_rowset_meta = std::make_shared<RowsetMeta>();
+        RowsetMetaPB rs_meta_pb;
+        rowset->to_rowset_pb(&rs_meta_pb);
+        rs_meta_pb.set_num_rows(0);
+        mock_rowset_meta->init_from_pb(rs_meta_pb);
+        rowset->_rowset_meta = mock_rowset_meta;
     }
+
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok());
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    // index 10001 cannot be found in idx file
+    auto dir_idx_compaction = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(!dir_idx_compaction.has_value()) << dir_idx_compaction.error();
+    EXPECT_THAT(dir_idx_compaction.error().to_string(),
+                testing::HasSubstr("No index with id 10001 found"));
+}
+
+TEST_F(IndexCompactionTest, test_cols_to_do_index_compaction_empty) {
+    // add mock property in tablet index 10001, 10002 and rebuild tablet_schema
+    // simulate the case that index properties not equal among input rowsets
+    // the two cols will skip index compaction and make 
ctx.columns_to_do_index_compaction empty
+    TabletSchemaSPtr mock_schema = std::make_shared<TabletSchema>();
+    TabletSchemaPB schema_pb;
+    _tablet_schema->to_schema_pb(&schema_pb);
+    auto* index_pb_1 = schema_pb.mutable_index(1);
+    (*index_pb_1->mutable_properties())["mock_key"] = "mock_value";
+    auto* index_pb_2 = schema_pb.mutable_index(2);
+    (*index_pb_2->mutable_properties())["mock_key"] = "mock_value";
+    mock_schema->init_from_pb(schema_pb);
+
+    
EXPECT_TRUE(io::global_local_filesystem()->delete_directory(_tablet->tablet_path()).ok());
+    
EXPECT_TRUE(io::global_local_filesystem()->create_directory(_tablet->tablet_path()).ok());
+    std::string data_file1 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data1.csv";
+    std::string data_file2 =
+            _current_dir + 
"/be/test/olap/rowset/segment_v2/inverted_index/data/data2.csv";
+    std::vector<std::string> data_files;
+    data_files.push_back(data_file1);
+    data_files.push_back(data_file2);
+
+    std::vector<RowsetSharedPtr> rowsets(data_files.size());
+    auto custom_check_build_rowsets = [](const int32_t& size) { 
EXPECT_EQ(size, 4); };
+    IndexCompactionUtils::build_rowsets(_data_dir, _tablet_schema, _tablet, 
_engine_ref, rowsets,
+                                        data_files, 
custom_check_build_rowsets);
+
+    auto custom_check_index = [](const BaseCompaction& compaction, const 
RowsetWriterContext& ctx) {
+        EXPECT_EQ(compaction._cur_tablet_schema->inverted_indexes().size(), 4);
+        // none index will do index compaction
+        EXPECT_TRUE(ctx.columns_to_do_index_compaction.size() == 0);
+        EXPECT_TRUE(compaction._output_rowset->num_segments() == 1);
+    };
+
+    // set mock_schema to the first input rowset
+    rowsets[0]->_schema = mock_schema;
+    RowsetSharedPtr output_rowset_index;
+    auto st = IndexCompactionUtils::do_compaction(rowsets, _engine_ref, 
_tablet, true,
+                                                  output_rowset_index, 
custom_check_index);
+    EXPECT_TRUE(st.ok()) << st.to_string();
+
+    const auto& seg_path = output_rowset_index->segment_path(0);
+    EXPECT_TRUE(seg_path.has_value()) << seg_path.error();
+    auto inverted_index_file_reader_index = 
IndexCompactionUtils::init_index_file_reader(
+            output_rowset_index, seg_path.value(),
+            _tablet_schema->get_inverted_index_storage_format());
+
+    // check index file
+    auto dir_idx_compaction_1 = inverted_index_file_reader_index->_open(10001, 
"");
+    EXPECT_TRUE(dir_idx_compaction_1.has_value()) << 
dir_idx_compaction_1.error();
+
+    // check index 10001 term stats
+    std::ostringstream oss;
+    
IndexCompactionUtils::check_terms_stats(dir_idx_compaction_1.value().get(), 
oss);
+    std::string output = oss.str();
+    EXPECT_EQ(output, expected_output);
+
+    auto dir_idx_compaction_2 = inverted_index_file_reader_index->_open(10002, 
"");
+    EXPECT_TRUE(dir_idx_compaction_2.has_value()) << 
dir_idx_compaction_2.error();
+}
+
+TEST_F(IndexCompactionTest, test_index_compaction_with_delete) {

Review Comment:
   warning: function 'TEST_F' exceeds recommended size/complexity thresholds 
[readability-function-size]
   ```cpp
   TEST_F(IndexCompactionTest, test_index_compaction_with_delete) {
   ^
   ```
   <details>
   <summary>Additional context</summary>
   
   
**be/test/olap/rowset/segment_v2/inverted_index/compaction/index_compaction_test.cpp:645:**
 86 lines including whitespace and comments (threshold 80)
   ```cpp
   TEST_F(IndexCompactionTest, test_index_compaction_with_delete) {
   ^
   ```
   
   </details>
   



##########
be/test/olap/rowset/segment_v2/inverted_index/compaction/util/index_compaction_utils.cpp:
##########
@@ -0,0 +1,621 @@
+// 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 <gtest/gtest.h>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "CLucene/StdHeader.h"
+#include "CLucene/config/repl_wchar.h"
+#include "json2pb/json_to_pb.h"
+#include "json2pb/pb_to_json.h"
+#include "olap/base_compaction.h"
+#include "olap/rowset/beta_rowset.h"
+#include "olap/rowset/beta_rowset_writer.h"
+#include "olap/rowset/rowset_factory.h"
+#include "olap/rowset/segment_v2/inverted_index/query/query_factory.h"
+#include "olap/rowset/segment_v2/inverted_index_file_reader.h"
+#include "olap/storage_engine.h"
+
+namespace doris {
+
+static int64_t inc_id = 1000;
+const static std::string expected_output =
+        "Max Docs: 2000\n"
+        "Num Docs: 2000\n"
+        "Field: 1 Term: bad Freq: 196\n"
+        "Field: 1 Term: excellent Freq: 227\n"
+        "Field: 1 Term: fine Freq: 190\n"
+        "Field: 1 Term: good Freq: 197\n"
+        "Field: 1 Term: great Freq: 194\n"
+        "Field: 1 Term: maybe Freq: 191\n"
+        "Field: 1 Term: no Freq: 205\n"
+        "Field: 1 Term: ok Freq: 175\n"
+        "Field: 1 Term: terrible Freq: 205\n"
+        "Field: 1 Term: yes Freq: 220\n"
+        "Term count: 10\n\n";
+const static std::string expected_delete_output =
+        "Max Docs: 1806\n"
+        "Num Docs: 1806\n"
+        "Field: 1 Term: bad Freq: 196\n"
+        "Field: 1 Term: excellent Freq: 227\n"
+        "Field: 1 Term: fine Freq: 190\n"
+        "Field: 1 Term: good Freq: 197\n"
+        "Field: 1 Term: maybe Freq: 191\n"
+        "Field: 1 Term: no Freq: 205\n"
+        "Field: 1 Term: ok Freq: 175\n"
+        "Field: 1 Term: terrible Freq: 205\n"
+        "Field: 1 Term: yes Freq: 220\n"
+        "Term count: 9\n\n";
+
+using QueryData = std::pair<std::vector<std::string>, std::vector<int>>;
+
+class IndexCompactionUtils {
+    struct DataRow {
+        int key;
+        std::string word;
+        std::string url;
+        int num;
+    };
+
+    static std::vector<DataRow> read_data(const std::string file_name) {
+        std::ifstream file(file_name);
+        EXPECT_TRUE(file.is_open());
+
+        std::string line;
+        std::vector<DataRow> data;
+
+        while (std::getline(file, line)) {
+            std::stringstream ss(line);
+            std::string item;
+            DataRow row;
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.key = std::stoi(item);
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.word = item;
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.url = item;
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.num = std::stoi(item);
+            data.emplace_back(std::move(row));
+        }
+
+        file.close();
+        return data;
+    }
+
+    static bool query_bkd(const TabletIndex* index,
+                          std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
+                          const std::vector<int>& query_data,
+                          const std::vector<int>& query_result) {
+        const auto& idx_reader = BkdIndexReader::create_shared(index, 
inverted_index_file_reader);
+        const auto& index_searcher_builder = 
std::make_unique<BKDIndexSearcherBuilder>();
+        auto dir = inverted_index_file_reader->open(index);
+        EXPECT_TRUE(dir.has_value());
+        auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
+        EXPECT_TRUE(searcher_result.has_value());
+        auto bkd_searcher = 
std::get_if<BKDIndexSearcherPtr>(&searcher_result.value());
+        EXPECT_TRUE(bkd_searcher != nullptr);
+        idx_reader->_type_info = 
get_scalar_type_info((FieldType)(*bkd_searcher)->type);
+        EXPECT_TRUE(idx_reader->_type_info != nullptr);
+        idx_reader->_value_key_coder = 
get_key_coder(idx_reader->_type_info->type());
+
+        for (int i = 0; i < query_data.size(); i++) {
+            vectorized::Field param_value = int32_t(query_data[i]);
+            std::unique_ptr<segment_v2::InvertedIndexQueryParamFactory> 
query_param = nullptr;
+            
EXPECT_TRUE(segment_v2::InvertedIndexQueryParamFactory::create_query_value(
+                                PrimitiveType::TYPE_INT, &param_value, 
query_param)
+                                .ok());
+            auto result = std::make_shared<roaring::Roaring>();
+            EXPECT_TRUE(idx_reader
+                                ->invoke_bkd_query(query_param->get_value(),
+                                                   
InvertedIndexQueryType::EQUAL_QUERY,
+                                                   *bkd_searcher, result)
+                                .ok());
+            EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
+        }
+        return true;
+    }
+
+    static bool query_string(const TabletIndex* index,
+                             std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
+                             const std::string& column_name,
+                             const std::vector<std::string>& query_data,
+                             const std::vector<int>& query_result) {
+        const auto& idx_reader =
+                StringTypeInvertedIndexReader::create_shared(index, 
inverted_index_file_reader);
+        const auto& index_searcher_builder = 
std::make_unique<FulltextIndexSearcherBuilder>();
+        auto dir = inverted_index_file_reader->open(index);
+        EXPECT_TRUE(dir.has_value());
+        auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
+        EXPECT_TRUE(searcher_result.has_value());
+        auto string_searcher = 
std::get_if<FulltextIndexSearcherPtr>(&searcher_result.value());
+        EXPECT_TRUE(string_searcher != nullptr);
+        std::wstring column_name_ws = 
StringUtil::string_to_wstring(column_name);
+
+        for (int i = 0; i < query_data.size(); i++) {
+            TQueryOptions queryOptions;
+            auto query = 
QueryFactory::create(InvertedIndexQueryType::EQUAL_QUERY, *string_searcher,
+                                              queryOptions, nullptr);
+            EXPECT_TRUE(query != nullptr);
+            InvertedIndexQueryInfo query_info;
+            query_info.field_name = column_name_ws;
+            query_info.terms.emplace_back(query_data[i]);
+            query->add(query_info);
+            auto result = std::make_shared<roaring::Roaring>();
+            query->search(*result);
+            EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
+        }
+        return true;
+    }
+
+    static bool query_fulltext(const TabletIndex* index,
+                               std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
+                               const std::string& column_name,
+                               const std::vector<std::string>& query_data,
+                               const std::vector<int>& query_result) {
+        const auto& idx_reader =
+                FullTextIndexReader::create_shared(index, 
inverted_index_file_reader);
+        const auto& index_searcher_builder = 
std::make_unique<FulltextIndexSearcherBuilder>();
+        auto dir = inverted_index_file_reader->open(index);
+        EXPECT_TRUE(dir.has_value());
+        auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
+        EXPECT_TRUE(searcher_result.has_value());
+        auto string_searcher = 
std::get_if<FulltextIndexSearcherPtr>(&searcher_result.value());
+        EXPECT_TRUE(string_searcher != nullptr);
+        std::wstring column_name_ws = 
StringUtil::string_to_wstring(column_name);
+
+        for (int i = 0; i < query_data.size(); i++) {
+            TQueryOptions queryOptions;
+            auto query = 
QueryFactory::create(InvertedIndexQueryType::MATCH_ANY_QUERY,
+                                              *string_searcher, queryOptions, 
nullptr);
+            EXPECT_TRUE(query != nullptr);
+            InvertedIndexQueryInfo query_info;
+            query_info.field_name = column_name_ws;
+            query_info.terms.emplace_back(query_data[i]);
+            query->add(query_info);
+            auto result = std::make_shared<roaring::Roaring>();
+            query->search(*result);
+            EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
+        }
+        return true;
+    }
+
+    static void check_terms_stats(lucene::store::Directory* dir, std::ostream& 
os = std::cout) {
+        IndexReader* r = IndexReader::open(dir);
+
+        os << "Max Docs: " << r->maxDoc() << "\n";
+        os << "Num Docs: " << r->numDocs() << "\n";
+
+        TermEnum* te = r->terms();
+        int32_t nterms;
+        for (nterms = 0; te->next(); nterms++) {
+            std::string token =
+                    lucene_wcstoutf8string(te->term(false)->text(), 
te->term(false)->textLength());
+            std::string field = 
lucene_wcstoutf8string(te->term(false)->field(),
+                                                       
lenOfString(te->term(false)->field()));
+
+            os << "Field: " << field << " ";
+            os << "Term: " << token << " ";
+            os << "Freq: " << te->docFreq() << "\n";
+            if (false) {
+                TermDocs* td = r->termDocs(te->term());
+                while (td->next()) {
+                    os << "DocID: " << td->doc() << " ";
+                    os << "TermFreq: " << td->freq() << "\n";
+                }
+                _CLLDELETE(td);
+            }

Review Comment:
   warning: redundant boolean literal in if statement condition 
[readability-simplify-boolean-expr]
   
   ```suggestion
               
   ```
   



##########
be/test/olap/rowset/segment_v2/inverted_index/compaction/util/index_compaction_utils.cpp:
##########
@@ -0,0 +1,621 @@
+// 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 <gtest/gtest.h>
+
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "CLucene/StdHeader.h"
+#include "CLucene/config/repl_wchar.h"
+#include "json2pb/json_to_pb.h"
+#include "json2pb/pb_to_json.h"
+#include "olap/base_compaction.h"
+#include "olap/rowset/beta_rowset.h"
+#include "olap/rowset/beta_rowset_writer.h"
+#include "olap/rowset/rowset_factory.h"
+#include "olap/rowset/segment_v2/inverted_index/query/query_factory.h"
+#include "olap/rowset/segment_v2/inverted_index_file_reader.h"
+#include "olap/storage_engine.h"
+
+namespace doris {
+
+static int64_t inc_id = 1000;
+const static std::string expected_output =
+        "Max Docs: 2000\n"
+        "Num Docs: 2000\n"
+        "Field: 1 Term: bad Freq: 196\n"
+        "Field: 1 Term: excellent Freq: 227\n"
+        "Field: 1 Term: fine Freq: 190\n"
+        "Field: 1 Term: good Freq: 197\n"
+        "Field: 1 Term: great Freq: 194\n"
+        "Field: 1 Term: maybe Freq: 191\n"
+        "Field: 1 Term: no Freq: 205\n"
+        "Field: 1 Term: ok Freq: 175\n"
+        "Field: 1 Term: terrible Freq: 205\n"
+        "Field: 1 Term: yes Freq: 220\n"
+        "Term count: 10\n\n";
+const static std::string expected_delete_output =
+        "Max Docs: 1806\n"
+        "Num Docs: 1806\n"
+        "Field: 1 Term: bad Freq: 196\n"
+        "Field: 1 Term: excellent Freq: 227\n"
+        "Field: 1 Term: fine Freq: 190\n"
+        "Field: 1 Term: good Freq: 197\n"
+        "Field: 1 Term: maybe Freq: 191\n"
+        "Field: 1 Term: no Freq: 205\n"
+        "Field: 1 Term: ok Freq: 175\n"
+        "Field: 1 Term: terrible Freq: 205\n"
+        "Field: 1 Term: yes Freq: 220\n"
+        "Term count: 9\n\n";
+
+using QueryData = std::pair<std::vector<std::string>, std::vector<int>>;
+
+class IndexCompactionUtils {
+    struct DataRow {
+        int key;
+        std::string word;
+        std::string url;
+        int num;
+    };
+
+    static std::vector<DataRow> read_data(const std::string file_name) {
+        std::ifstream file(file_name);
+        EXPECT_TRUE(file.is_open());
+
+        std::string line;
+        std::vector<DataRow> data;
+
+        while (std::getline(file, line)) {
+            std::stringstream ss(line);
+            std::string item;
+            DataRow row;
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.key = std::stoi(item);
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.word = item;
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.url = item;
+            EXPECT_TRUE(std::getline(ss, item, ','));
+            row.num = std::stoi(item);
+            data.emplace_back(std::move(row));
+        }
+
+        file.close();
+        return data;
+    }
+
+    static bool query_bkd(const TabletIndex* index,
+                          std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
+                          const std::vector<int>& query_data,
+                          const std::vector<int>& query_result) {
+        const auto& idx_reader = BkdIndexReader::create_shared(index, 
inverted_index_file_reader);
+        const auto& index_searcher_builder = 
std::make_unique<BKDIndexSearcherBuilder>();
+        auto dir = inverted_index_file_reader->open(index);
+        EXPECT_TRUE(dir.has_value());
+        auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
+        EXPECT_TRUE(searcher_result.has_value());
+        auto bkd_searcher = 
std::get_if<BKDIndexSearcherPtr>(&searcher_result.value());
+        EXPECT_TRUE(bkd_searcher != nullptr);
+        idx_reader->_type_info = 
get_scalar_type_info((FieldType)(*bkd_searcher)->type);
+        EXPECT_TRUE(idx_reader->_type_info != nullptr);
+        idx_reader->_value_key_coder = 
get_key_coder(idx_reader->_type_info->type());
+
+        for (int i = 0; i < query_data.size(); i++) {
+            vectorized::Field param_value = int32_t(query_data[i]);
+            std::unique_ptr<segment_v2::InvertedIndexQueryParamFactory> 
query_param = nullptr;
+            
EXPECT_TRUE(segment_v2::InvertedIndexQueryParamFactory::create_query_value(
+                                PrimitiveType::TYPE_INT, &param_value, 
query_param)
+                                .ok());
+            auto result = std::make_shared<roaring::Roaring>();
+            EXPECT_TRUE(idx_reader
+                                ->invoke_bkd_query(query_param->get_value(),
+                                                   
InvertedIndexQueryType::EQUAL_QUERY,
+                                                   *bkd_searcher, result)
+                                .ok());
+            EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
+        }
+        return true;
+    }
+
+    static bool query_string(const TabletIndex* index,
+                             std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
+                             const std::string& column_name,
+                             const std::vector<std::string>& query_data,
+                             const std::vector<int>& query_result) {
+        const auto& idx_reader =
+                StringTypeInvertedIndexReader::create_shared(index, 
inverted_index_file_reader);
+        const auto& index_searcher_builder = 
std::make_unique<FulltextIndexSearcherBuilder>();
+        auto dir = inverted_index_file_reader->open(index);
+        EXPECT_TRUE(dir.has_value());
+        auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
+        EXPECT_TRUE(searcher_result.has_value());
+        auto string_searcher = 
std::get_if<FulltextIndexSearcherPtr>(&searcher_result.value());
+        EXPECT_TRUE(string_searcher != nullptr);
+        std::wstring column_name_ws = 
StringUtil::string_to_wstring(column_name);
+
+        for (int i = 0; i < query_data.size(); i++) {
+            TQueryOptions queryOptions;
+            auto query = 
QueryFactory::create(InvertedIndexQueryType::EQUAL_QUERY, *string_searcher,
+                                              queryOptions, nullptr);
+            EXPECT_TRUE(query != nullptr);
+            InvertedIndexQueryInfo query_info;
+            query_info.field_name = column_name_ws;
+            query_info.terms.emplace_back(query_data[i]);
+            query->add(query_info);
+            auto result = std::make_shared<roaring::Roaring>();
+            query->search(*result);
+            EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
+        }
+        return true;
+    }
+
+    static bool query_fulltext(const TabletIndex* index,
+                               std::shared_ptr<InvertedIndexFileReader>& 
inverted_index_file_reader,
+                               const std::string& column_name,
+                               const std::vector<std::string>& query_data,
+                               const std::vector<int>& query_result) {
+        const auto& idx_reader =
+                FullTextIndexReader::create_shared(index, 
inverted_index_file_reader);
+        const auto& index_searcher_builder = 
std::make_unique<FulltextIndexSearcherBuilder>();
+        auto dir = inverted_index_file_reader->open(index);
+        EXPECT_TRUE(dir.has_value());
+        auto searcher_result = 
index_searcher_builder->get_index_searcher(dir.value().release());
+        EXPECT_TRUE(searcher_result.has_value());
+        auto string_searcher = 
std::get_if<FulltextIndexSearcherPtr>(&searcher_result.value());
+        EXPECT_TRUE(string_searcher != nullptr);
+        std::wstring column_name_ws = 
StringUtil::string_to_wstring(column_name);
+
+        for (int i = 0; i < query_data.size(); i++) {
+            TQueryOptions queryOptions;
+            auto query = 
QueryFactory::create(InvertedIndexQueryType::MATCH_ANY_QUERY,
+                                              *string_searcher, queryOptions, 
nullptr);
+            EXPECT_TRUE(query != nullptr);
+            InvertedIndexQueryInfo query_info;
+            query_info.field_name = column_name_ws;
+            query_info.terms.emplace_back(query_data[i]);
+            query->add(query_info);
+            auto result = std::make_shared<roaring::Roaring>();
+            query->search(*result);
+            EXPECT_EQ(query_result[i], result->cardinality()) << query_data[i];
+        }
+        return true;
+    }
+
+    static void check_terms_stats(lucene::store::Directory* dir, std::ostream& 
os = std::cout) {
+        IndexReader* r = IndexReader::open(dir);
+
+        os << "Max Docs: " << r->maxDoc() << "\n";
+        os << "Num Docs: " << r->numDocs() << "\n";
+
+        TermEnum* te = r->terms();
+        int32_t nterms;
+        for (nterms = 0; te->next(); nterms++) {
+            std::string token =
+                    lucene_wcstoutf8string(te->term(false)->text(), 
te->term(false)->textLength());
+            std::string field = 
lucene_wcstoutf8string(te->term(false)->field(),
+                                                       
lenOfString(te->term(false)->field()));
+
+            os << "Field: " << field << " ";
+            os << "Term: " << token << " ";
+            os << "Freq: " << te->docFreq() << "\n";
+            if (false) {
+                TermDocs* td = r->termDocs(te->term());
+                while (td->next()) {
+                    os << "DocID: " << td->doc() << " ";
+                    os << "TermFreq: " << td->freq() << "\n";
+                }
+                _CLLDELETE(td);
+            }
+        }
+        os << "Term count: " << nterms << "\n\n";
+        te->close();
+        _CLLDELETE(te);
+
+        r->close();
+        _CLLDELETE(r);
+    }
+    static Status check_idx_file_correctness(lucene::store::Directory* 
index_reader,

Review Comment:
   warning: function 'check_idx_file_correctness' exceeds recommended 
size/complexity thresholds [readability-function-size]
   ```cpp
       static Status check_idx_file_correctness(lucene::store::Directory* 
index_reader,
                     ^
   ```
   <details>
   <summary>Additional context</summary>
   
   
**be/test/olap/rowset/segment_v2/inverted_index/compaction/util/index_compaction_utils.cpp:235:**
 98 lines including whitespace and comments (threshold 80)
   ```cpp
       static Status check_idx_file_correctness(lucene::store::Directory* 
index_reader,
                     ^
   ```
   
   </details>
   



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@doris.apache.org
For additional commands, e-mail: commits-h...@doris.apache.org

Reply via email to