Remove examples and instead point user to action-specific help for more information about specific actions.
277 lines
9.6 KiB
C++
277 lines
9.6 KiB
C++
/*
|
|
* Copyright (C) 2020-present ScyllaDB
|
|
*/
|
|
|
|
/*
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
#include <boost/algorithm/string/join.hpp>
|
|
#include <seastar/core/app-template.hh>
|
|
|
|
#include "compound.hh"
|
|
#include "db/marshal/type_parser.hh"
|
|
#include "log.hh"
|
|
#include "tools/utils.hh"
|
|
|
|
using namespace seastar;
|
|
|
|
namespace {
|
|
|
|
using type_variant = std::variant<
|
|
data_type,
|
|
compound_type<allow_prefixes::yes>,
|
|
compound_type<allow_prefixes::no>>;
|
|
|
|
struct printing_visitor {
|
|
bytes_view value;
|
|
|
|
sstring operator()(const data_type& type) {
|
|
return type->to_string(value);
|
|
}
|
|
template <allow_prefixes AllowPrefixes>
|
|
sstring operator()(const compound_type<AllowPrefixes>& type) {
|
|
std::vector<sstring> printable_values;
|
|
printable_values.reserve(type.types().size());
|
|
|
|
const auto types = type.types();
|
|
const auto values = type.deserialize_value(value);
|
|
|
|
for (size_t i = 0; i != values.size(); ++i) {
|
|
printable_values.emplace_back(types.at(i)->to_string(values.at(i)));
|
|
}
|
|
return format("({})", boost::algorithm::join(printable_values, ", "));
|
|
}
|
|
};
|
|
|
|
sstring to_printable_string(const type_variant& type, bytes_view value) {
|
|
return std::visit(printing_visitor{value}, type);
|
|
}
|
|
|
|
void print_handler(type_variant type, std::vector<bytes> values) {
|
|
for (const auto& value : values) {
|
|
fmt::print("{}\n", to_printable_string(type, value));
|
|
}
|
|
}
|
|
|
|
void compare_handler(type_variant type, std::vector<bytes> values) {
|
|
if (values.size() != 2) {
|
|
throw std::runtime_error(fmt::format("compare_handler(): expected 2 values, got {}", values.size()));
|
|
}
|
|
|
|
struct {
|
|
bytes_view lhs, rhs;
|
|
|
|
std::strong_ordering operator()(const data_type& type) {
|
|
return type->compare(lhs, rhs);
|
|
}
|
|
std::strong_ordering operator()(const compound_type<allow_prefixes::yes>& type) {
|
|
return type.compare(lhs, rhs);
|
|
}
|
|
std::strong_ordering operator()(const compound_type<allow_prefixes::no>& type) {
|
|
return type.compare(lhs, rhs);
|
|
}
|
|
} compare_visitor{values[0], values[1]};
|
|
|
|
const auto res = std::visit(compare_visitor, type);
|
|
sstring_view res_str;
|
|
|
|
if (res == 0) {
|
|
res_str = "==";
|
|
} else if (res < 0) {
|
|
res_str = "<";
|
|
} else {
|
|
res_str = ">";
|
|
}
|
|
fmt::print("{} {} {}\n", to_printable_string(type, values[0]), res_str, to_printable_string(type, values[1]));
|
|
}
|
|
|
|
void validate_handler(type_variant type, std::vector<bytes> values) {
|
|
struct validate_visitor {
|
|
bytes_view value;
|
|
|
|
void operator()(const data_type& type) {
|
|
type->validate(value, cql_serialization_format::internal());
|
|
}
|
|
void operator()(const compound_type<allow_prefixes::yes>& type) {
|
|
type.validate(value);
|
|
}
|
|
void operator()(const compound_type<allow_prefixes::no>& type) {
|
|
type.validate(value);
|
|
}
|
|
};
|
|
|
|
for (const auto& value : values) {
|
|
std::exception_ptr ex;
|
|
try {
|
|
std::visit(validate_visitor{value}, type);
|
|
} catch (...) {
|
|
ex = std::current_exception();
|
|
}
|
|
if (ex) {
|
|
fmt::print("{}: INVALID - {}\n", to_hex(value), ex);
|
|
} else {
|
|
fmt::print("{}: VALID - {}\n", to_hex(value), to_printable_string(type, value));
|
|
}
|
|
}
|
|
}
|
|
|
|
using action_handler_func = void(*)(type_variant, std::vector<bytes>);
|
|
|
|
class action_handler {
|
|
std::string _name;
|
|
std::string _summary;
|
|
std::string _description;
|
|
action_handler_func _func;
|
|
|
|
public:
|
|
action_handler(std::string name, std::string summary, action_handler_func func, std::string description)
|
|
: _name(std::move(name)), _summary(std::move(summary)), _description(std::move(description)), _func(func) {
|
|
}
|
|
|
|
const std::string& name() const { return _name; }
|
|
const std::string& summary() const { return _summary; }
|
|
const std::string& description() const { return _description; }
|
|
|
|
void operator()(type_variant type, std::vector<bytes> values) const {
|
|
_func(std::move(type), std::move(values));
|
|
}
|
|
};
|
|
|
|
const std::vector<action_handler> action_handlers = {
|
|
{"print", "print the value(s) in a human readable form", print_handler,
|
|
R"(
|
|
Deserialize and print the value(s) in a human-readable form.
|
|
|
|
Arguments: 1 or more serialized values.
|
|
|
|
Examples:
|
|
|
|
$ scylla types print -t Int32Type b34b62d4
|
|
-1286905132
|
|
|
|
$ scylla types print --prefix-compound -t TimeUUIDType -t Int32Type 0010d00819896f6b11ea00000000001c571b000400000010
|
|
(d0081989-6f6b-11ea-0000-0000001c571b, 16)
|
|
)"},
|
|
{"compare", "compare two values", compare_handler,
|
|
R"(
|
|
Compare two values and print the result.
|
|
|
|
Arguments: 2 serialized values.
|
|
|
|
Examples:
|
|
|
|
$ scylla types compare -t 'ReversedType(TimeUUIDType)' b34b62d46a8d11ea0000005000237906 d00819896f6b11ea00000000001c571b
|
|
b34b62d4-6a8d-11ea-0000-005000237906 > d0081989-6f6b-11ea-0000-0000001c571b
|
|
)"},
|
|
{"validate", "validate the value(s)", validate_handler,
|
|
R"(
|
|
Check that the value(s) are valid for the type according to the requirements of
|
|
the type.
|
|
|
|
Arguments: 1 or more serialized values.
|
|
|
|
Examples:
|
|
|
|
$ scylla types validate -t Int32Type b34b62d4
|
|
b34b62d4: VALID - -1286905132
|
|
)"},
|
|
};
|
|
|
|
}
|
|
|
|
namespace tools {
|
|
|
|
int scylla_types_main(int argc, char** argv) {
|
|
namespace bpo = boost::program_options;
|
|
|
|
const action_handler* found_ah = nullptr;
|
|
if (std::strcmp(argv[1], "--help") != 0 && std::strcmp(argv[1], "-h") != 0) {
|
|
found_ah = &tools::utils::get_selected_operation(argc, argv, action_handlers, "action");
|
|
}
|
|
|
|
app_template::seastar_options app_cfg;
|
|
app_cfg.name = "scylla-types";
|
|
|
|
auto description_template =
|
|
R"(scylla-types - a command-line tool to examine values belonging to scylla types.
|
|
|
|
Usage: scylla types {{action}} [--option1] [--option2] ... {{hex_value1}} [{{hex_value2}}] ...
|
|
|
|
Allows examining raw values obtained from e.g. sstables, logs or coredumps and
|
|
executing various actions on them. Values should be provided in hex form,
|
|
without a leading 0x prefix, e.g. 00783562. For scylla-types to be able to
|
|
examine the values, their type has to be provided. Types should be provided by
|
|
their cassandra class names, e.g. org.apache.cassandra.db.marshal.Int32Type for
|
|
the int32_type. The org.apache.cassandra.db.marshal. prefix can be ommited.
|
|
See https://github.com/scylladb/scylla/blob/master/docs/design-notes/cql3-type-mapping.md
|
|
for a mapping of cql3 types to Cassandra type class names.
|
|
Compound types specify their subtypes inside () separated by comma, e.g.:
|
|
MapType(Int32Type, BytesType). All provided values have to share the same type.
|
|
scylla-types executes so called actions on the provided values. Each action has
|
|
a required number of arguments. The supported actions are:
|
|
{}
|
|
|
|
For more information about individual actions, see their specific help:
|
|
|
|
$ scylla types {action} --help
|
|
)";
|
|
|
|
if (found_ah) {
|
|
app_cfg.description = found_ah->description();
|
|
} else {
|
|
app_cfg.description = format(description_template, boost::algorithm::join(action_handlers | boost::adaptors::transformed(
|
|
[] (const action_handler& ah) { return format("* {} - {}", ah.name(), ah.summary()); } ), "\n"));
|
|
}
|
|
|
|
tools::utils::configure_tool_mode(app_cfg);
|
|
|
|
app_template app(std::move(app_cfg));
|
|
|
|
app.add_options()
|
|
("type,t", bpo::value<std::vector<sstring>>(), "the type of the values, all values must be of the same type;"
|
|
" when values are compounds, multiple types can be specified, one for each type making up the compound, "
|
|
"note that the order of the types on the command line will be their order in the compound too")
|
|
("prefix-compound", "values are prefixable compounds (e.g. clustering key), composed of multiple values of possibly different types")
|
|
("full-compound", "values are full compounds (e.g. partition key), composed of multiple values of possibly different types")
|
|
;
|
|
|
|
app.add_positional_options({
|
|
{"value", bpo::value<std::vector<sstring>>(), "value(s) to process, can also be provided as positional arguments", -1}
|
|
});
|
|
|
|
return app.run(argc, argv, [&app, found_ah] {
|
|
const action_handler& handler = *found_ah;
|
|
|
|
if (!app.configuration().contains("type")) {
|
|
throw std::invalid_argument("error: missing required option '--type'");
|
|
}
|
|
type_variant type = [&app] () -> type_variant {
|
|
auto types = boost::copy_range<std::vector<data_type>>(app.configuration()["type"].as<std::vector<sstring>>()
|
|
| boost::adaptors::transformed([] (const sstring_view type_name) { return db::marshal::type_parser::parse(type_name); }));
|
|
if (app.configuration().contains("prefix-compound")) {
|
|
return compound_type<allow_prefixes::yes>(std::move(types));
|
|
} else if (app.configuration().contains("full-compound")) {
|
|
return compound_type<allow_prefixes::no>(std::move(types));
|
|
} else { // non-compound type
|
|
if (types.size() != 1) {
|
|
throw std::invalid_argument(fmt::format("error: expected a single '--type' argument, got {}", types.size()));
|
|
}
|
|
return std::move(types.front());
|
|
}
|
|
}();
|
|
|
|
if (!app.configuration().contains("value")) {
|
|
throw std::invalid_argument("error: no values specified");
|
|
}
|
|
auto values = boost::copy_range<std::vector<bytes>>(
|
|
app.configuration()["value"].as<std::vector<sstring>>() | boost::adaptors::transformed(from_hex));
|
|
|
|
handler(std::move(type), std::move(values));
|
|
|
|
return make_ready_future<>();
|
|
});
|
|
}
|
|
|
|
} // namespace tools
|