You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@doris.apache.org by ga...@apache.org on 2026/03/29 11:48:28 UTC

(doris) branch master updated: [tool](meta_tool) Add gen_empty_segment operation to generate empty segment files (#61703)

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

gavinchou 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 a6e657fde5b [tool](meta_tool) Add gen_empty_segment operation to generate empty segment files (#61703)
a6e657fde5b is described below

commit a6e657fde5b127f4c810e341273c7f658a40ee05
Author: Gavin Chou <ga...@selectdb.com>
AuthorDate: Sun Mar 29 19:48:21 2026 +0800

    [tool](meta_tool) Add gen_empty_segment operation to generate empty segment files (#61703)
    
    ## Proposed changes
    
    Add a new operation `gen_empty_segment` to the meta_tool that allows
    generating empty segment files (0 rows). This is useful for testing and
    debugging purposes.
    
    ### Usage
    
    ```bash
    ./meta_tool --operation=gen_empty_segment [--output_path=/path/to/output]
    ```
    
    Default output file name: `empty.dat`
    
    ### Generated file structure
    
    - Short key index page with 0 keys
    - Segment footer with `num_rows=0`
    - Total file size: ~47 bytes
    
    ### Example output
    
    ```
    Generated empty segment file: /tmp/empty.dat
      - Index page size: 27 bytes
      - Footer size: 4 slices, 20 bytes
      - Total file size: 47 bytes
      - num_rows: 0
    ```
    
    ## Checklist
    
    - [x] I have created an issue and described the feature request.
    - [x] I have checked the version labels which the pr will be
    auto-backported to the target branch.
      - [ ] 3.0
      - [ ] 2.1
      - [ ] 2.0
    - [x] I have added test cases for my feature.
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
---
 be/src/tools/meta_tool.cpp | 127 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 126 insertions(+), 1 deletion(-)

diff --git a/be/src/tools/meta_tool.cpp b/be/src/tools/meta_tool.cpp
index 2cea839fa32..c4f7595678c 100644
--- a/be/src/tools/meta_tool.cpp
+++ b/be/src/tools/meta_tool.cpp
@@ -52,6 +52,7 @@
 #include "storage/options.h"
 #include "storage/segment/column_reader.h"
 #include "storage/segment/encoding_info.h"
+#include "storage/segment/page_pointer.h"
 #include "storage/storage_engine.h"
 #include "storage/tablet/tablet_meta.h"
 #include "storage/tablet/tablet_meta_manager.h"
@@ -75,13 +76,18 @@ using namespace doris;
 DEFINE_string(root_path, "", "storage root path");
 DEFINE_string(operation, "get_meta",
               "valid operation: get_meta, flag, load_meta, delete_meta, show_meta, "
-              "show_segment_footer, show_segment_data");
+              "show_segment_footer, show_segment_data, gen_empty_segment");
 DEFINE_int64(tablet_id, 0, "tablet_id for tablet meta");
+DEFINE_int32(num_rows_per_block, 1024, "num rows per block");
 DEFINE_int32(schema_hash, 0, "schema_hash for tablet meta");
 DEFINE_string(json_meta_path, "", "absolute json meta file path");
 DEFINE_string(pb_meta_path, "", "pb meta file path");
 DEFINE_string(tablet_file, "", "file to save a set of tablets");
 DEFINE_string(file, "", "segment file path");
+DEFINE_string(output_path, "", "output directory path (default: current directory)");
+DEFINE_int32(num_short_key_columns, 0, "number of short key columns");
+DEFINE_bool(has_sequence_col, false, "whether has sequence column");
+DEFINE_bool(enable_unique_key_merge_on_write, false, "whether enable unique key merge on write");
 
 std::string get_usage(const std::string& progname) {
     std::stringstream ss;
@@ -99,6 +105,9 @@ std::string get_usage(const std::string& progname) {
     ss << "./meta_tool --operation=show_meta --pb_meta_path=path\n";
     ss << "./meta_tool --operation=show_segment_footer --file=/path/to/segment/file\n";
     ss << "./meta_tool --operation=show_segment_data --file=/path/to/segment/file\n";
+    ss << "./meta_tool --operation=gen_empty_segment [--output_path=/path/to/output]\n";
+    ss << "  Generates an empty segment file (0 rows) at specified path or current directory\n";
+    ss << "  Default output file name: empty.dat\n";
     return ss.str();
 }
 
@@ -899,6 +908,120 @@ void show_segment_data(const std::string& file_name) {
     }
 }
 
+void gen_empty_segment() {
+    std::string output_path = FLAGS_output_path.empty() ? "." : FLAGS_output_path;
+
+    // Create output file path
+    std::string file_path = output_path + "/empty.dat";
+
+    // Open file for writing
+    std::ofstream out_file(file_path, std::ios::binary);
+    if (!out_file.is_open()) {
+        std::cout << "failed to open output file: " << file_path << std::endl;
+        return;
+    }
+
+    // 1. Build empty short key index page
+    std::vector<Slice> index_body;
+    segment_v2::PageFooterPB index_footer;
+    index_footer.set_type(segment_v2::SHORT_KEY_PAGE);
+    index_footer.set_uncompressed_size(0); // empty body
+
+    segment_v2::ShortKeyFooterPB* sk_footer = index_footer.mutable_short_key_page_footer();
+    sk_footer->set_num_items(0);    // 0 keys
+    sk_footer->set_key_bytes(0);    // empty key buffer
+    sk_footer->set_offset_bytes(0); // empty offset buffer
+    sk_footer->set_segment_id(0);
+    sk_footer->set_num_rows_per_block(FLAGS_num_rows_per_block);
+    sk_footer->set_num_segment_rows(0);
+
+    // Empty key and offset buffers
+    std::string key_buf;
+    std::string offset_buf;
+
+    index_body.push_back(Slice(key_buf.data(), key_buf.size()));
+    index_body.push_back(Slice(offset_buf.data(), offset_buf.size()));
+
+    // Serialize index footer
+    std::string index_footer_buf;
+    index_footer.SerializeToString(&index_footer_buf);
+    doris::put_fixed32_le(&index_footer_buf, static_cast<uint32_t>(index_footer_buf.size()));
+    index_body.push_back(Slice(index_footer_buf.data(), index_footer_buf.size()));
+
+    // Calculate checksum for index page
+    uint32_t index_checksum = 0;
+    for (const auto& slice : index_body) {
+        index_checksum = crc32c::Extend(index_checksum, (const uint8_t*)slice.data, slice.size);
+    }
+    uint8_t index_checksum_buf[sizeof(uint32_t)];
+    doris::encode_fixed32_le(index_checksum_buf, index_checksum);
+    index_body.push_back(Slice(index_checksum_buf, sizeof(uint32_t)));
+
+    // 2. Build segment footer
+    SegmentFooterPB footer;
+    footer.set_num_rows(0);
+
+    // Calculate total index page size
+    uint64_t index_page_size = 0;
+    for (const auto& slice : index_body) {
+        index_page_size += slice.size;
+    }
+
+    // Set short key index page pointer
+    segment_v2::PagePointer index_pp;
+    index_pp.offset = 0;
+    index_pp.size = static_cast<uint32_t>(index_page_size);
+    index_pp.to_proto(footer.mutable_short_key_index_page());
+
+    // Serialize footer
+    std::string footer_buf;
+    if (!footer.SerializeToString(&footer_buf)) {
+        std::cout << "failed to serialize footer" << std::endl;
+        return;
+    }
+
+    // 3. Write footer data to file
+    std::vector<Slice> footer_slices = {footer_buf};
+
+    // Footer size (4 bytes, little-endian)
+    uint32_t footer_size = static_cast<uint32_t>(footer_buf.size());
+    uint8_t footer_size_buf[4];
+    doris::encode_fixed32_le(footer_size_buf, footer_size);
+    footer_slices.push_back(Slice(footer_size_buf, 4));
+
+    // Footer checksum (4 bytes, crc32c)
+    uint32_t footer_checksum = crc32c::Crc32c(footer_buf.data(), footer_buf.size());
+    uint8_t footer_checksum_buf[4];
+    doris::encode_fixed32_le(footer_checksum_buf, footer_checksum);
+    footer_slices.push_back(Slice(footer_checksum_buf, 4));
+
+    // Magic number (4 bytes): "D0R1"
+    const char* k_segment_magic = "D0R1";
+    const uint32_t k_segment_magic_length = 4;
+    footer_slices.push_back(Slice(k_segment_magic, k_segment_magic_length));
+
+    // Write index page first, then footer
+    for (const auto& slice : index_body) {
+        out_file.write(slice.data, slice.size);
+    }
+
+    // Write footer
+    for (const auto& slice : footer_slices) {
+        out_file.write(slice.data, slice.size);
+    }
+
+    out_file.close();
+
+    // Print summary
+    std::cout << "Generated empty segment file: " << file_path << std::endl;
+    std::cout << "  - Index page size: " << index_page_size << " bytes" << std::endl;
+    std::cout << "  - Footer size: " << footer_slices.size() << " slices, "
+              << (footer_buf.size() + 12) << " bytes" << std::endl;
+    std::cout << "  - Total file size: " << (index_page_size + footer_buf.size() + 12) << " bytes"
+              << std::endl;
+    std::cout << "  - num_rows: 0" << std::endl;
+}
+
 int main(int argc, char** argv) {
     SCOPED_INIT_THREAD_CONTEXT();
     std::string usage = get_usage(argv[0]);
@@ -930,6 +1053,8 @@ int main(int argc, char** argv) {
             return -1;
         }
         show_segment_data(FLAGS_file);
+    } else if (FLAGS_operation == "gen_empty_segment") {
+        gen_empty_segment();
     } else {
         // operations that need root path should be written here
         std::set<std::string> valid_operations = {"get_meta", "load_meta", "delete_meta"};


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