mirror of
https://github.com/hanzoai/datastore.git
synced 2026-09-26 13:55:07 +00:00
Inspect a per-part Roaring delete-bitmap (.rbm) sidecar and print a header + stats summary: format magic/version, CRC validity, cardinality (deleted-row count), row range, and -- with --values -- a dump of all set bits (the deleted row offsets) in ascending order. Tolerant of corruption: a bad CRC still prints the header and flags CORRUPT, a non-.rbm file reports bad magic, a malformed roaring body is reported rather than crashed on, and a missing path is rejected up front (FILE_DOESNT_EXIST). The non-throwing inspector (inspectDeleteBitmap) is declared in DeleteBitmap.h and implemented in DeleteBitmap.cpp next to serialize/deserialize, so the .rbm byte layout keeps one authority. clickhouse-disks is a symlink to the unified clickhouse binary, which links dbms, so the command resolves it directly. Tests: tests/queries/0_stateless/04400_clickhouse_disks_read_bitmap.sh and DeleteBitmapInspectTest unit cases. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
120 lines
4.2 KiB
C++
120 lines
4.2 KiB
C++
#include <IO/ReadBufferFromFile.h>
|
|
#include <Interpreters/Context.h>
|
|
#include <Storages/MergeTree/UniqueKey/DeleteBitmap.h>
|
|
#include <Common/TerminalSize.h>
|
|
#include <ICommand.h>
|
|
|
|
#include <iostream>
|
|
|
|
namespace DB
|
|
{
|
|
|
|
namespace ErrorCodes
|
|
{
|
|
extern const int CORRUPTED_DATA;
|
|
extern const int FILE_DOESNT_EXIST;
|
|
}
|
|
|
|
/// Inspect a per-part Roaring delete-bitmap (`.rbm`) sidecar: print its header
|
|
/// + stats summary. Tolerant of corruption — reports it rather than aborting.
|
|
/// Parses via `DeleteBitmap.h`'s non-throwing `inspectDeleteBitmap` (the
|
|
/// throwing `DeleteBitmap::deserialize` is unfit for an inspector). The
|
|
/// implementation lives in `DeleteBitmap.cpp` (dbms), which the unified
|
|
/// `clickhouse` binary — what `clickhouse-disks` is a symlink to — already links.
|
|
class CommandReadBitmap final : public ICommand
|
|
{
|
|
public:
|
|
CommandReadBitmap() : ICommand("CommandReadBitmap")
|
|
{
|
|
command_name = "read-bitmap";
|
|
description = "Inspect a delete-bitmap (.rbm) sidecar: print header + stats from `path-from`";
|
|
options_description.add_options()(
|
|
"path-from", po::value<String>(), "delete-bitmap (.rbm) file to inspect (mandatory, positional)")(
|
|
"values", "dump all set bits (the deleted row offsets) in ascending order");
|
|
positional_options_description.add("path-from", 1);
|
|
}
|
|
|
|
void executeImpl(const CommandLineOptions & options, DisksClient & client) override
|
|
{
|
|
const auto & disk = client.getCurrentDiskWithPath();
|
|
String path_from = disk.getRelativeFromRoot(getValueFromCommandLineOptionsThrow<String>(options, "path-from"));
|
|
|
|
if (!disk.getDisk()->existsFile(path_from))
|
|
throw Exception(ErrorCodes::FILE_DOESNT_EXIST,
|
|
"delete-bitmap file {} does not exist on disk {}", path_from, disk.getDisk()->getName());
|
|
|
|
const bool dump_values = options.contains("values");
|
|
|
|
const UInt64 file_size = disk.getDisk()->getFileSize(path_from);
|
|
auto in = disk.getDisk()->readFile(path_from, getReadSettings());
|
|
|
|
auto info = inspectDeleteBitmap(*in, dump_values);
|
|
|
|
auto & out = std::cout;
|
|
out << "file: " << path_from << " (" << file_size << " bytes)\n";
|
|
|
|
if (!info.header_read)
|
|
{
|
|
out.flush();
|
|
throw Exception(ErrorCodes::CORRUPTED_DATA,
|
|
"not a delete-bitmap (.rbm) file (truncated: {} bytes, need at least a {}-byte header)",
|
|
file_size, DeleteBitmap::HEADER_SIZE + DeleteBitmap::CRC_SIZE);
|
|
}
|
|
|
|
if (!info.magic_ok)
|
|
{
|
|
out << "magic: BAD\n";
|
|
out.flush();
|
|
throw Exception(ErrorCodes::CORRUPTED_DATA, "not a delete-bitmap (.rbm) file (bad magic)");
|
|
}
|
|
out << "magic: OK\n";
|
|
|
|
out << "version: " << info.version;
|
|
if (info.version == DeleteBitmap::VERSION_R32)
|
|
out << " (Roaring32)\n";
|
|
else if (info.version == DeleteBitmap::VERSION_R64)
|
|
out << " (Roaring64; body is host-endian, not cross-arch portable)\n";
|
|
else
|
|
out << " (UNKNOWN)\n";
|
|
|
|
out << "body_size: " << info.body_size << " bytes";
|
|
if (!info.body_read)
|
|
out << " (TRUNCATED — fewer bytes on disk than declared)";
|
|
out << "\n";
|
|
|
|
out << "crc: stored=" << fmt::format("{:#010x}", info.crc_stored)
|
|
<< " computed=" << fmt::format("{:#010x}", info.crc_computed) << " -> "
|
|
<< (info.crc_ok ? "VALID" : "CORRUPT") << "\n";
|
|
|
|
if (info.decoded)
|
|
{
|
|
out << "cardinality: " << info.cardinality << " (deleted rows)\n";
|
|
if (info.has_minmax)
|
|
out << "row range: [" << info.min_row << ", " << info.max_row << "]\n";
|
|
}
|
|
else
|
|
{
|
|
out << "cardinality: <undecodable> (roaring body did not parse";
|
|
if (!info.decode_error.empty())
|
|
out << ": " << info.decode_error;
|
|
out << ")\n";
|
|
}
|
|
|
|
if (dump_values && info.decoded)
|
|
{
|
|
out << "values:";
|
|
for (UInt64 v : info.sample)
|
|
out << ' ' << v;
|
|
out << "\n";
|
|
}
|
|
out.flush();
|
|
}
|
|
};
|
|
|
|
CommandPtr makeCommandReadBitmap()
|
|
{
|
|
return std::make_shared<DB::CommandReadBitmap>();
|
|
}
|
|
|
|
}
|