diff options
Diffstat (limited to 'src/rocksdb/tools/ldb_cmd.cc')
-rw-r--r-- | src/rocksdb/tools/ldb_cmd.cc | 3437 |
1 files changed, 3437 insertions, 0 deletions
diff --git a/src/rocksdb/tools/ldb_cmd.cc b/src/rocksdb/tools/ldb_cmd.cc new file mode 100644 index 000000000..48d32d2ef --- /dev/null +++ b/src/rocksdb/tools/ldb_cmd.cc @@ -0,0 +1,3437 @@ + +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). +// +#ifndef ROCKSDB_LITE +#include "rocksdb/utilities/ldb_cmd.h" + +#include <cinttypes> + +#include "db/db_impl/db_impl.h" +#include "db/dbformat.h" +#include "db/log_reader.h" +#include "db/write_batch_internal.h" +#include "env/composite_env_wrapper.h" +#include "file/filename.h" +#include "port/port_dirent.h" +#include "rocksdb/cache.h" +#include "rocksdb/file_checksum.h" +#include "rocksdb/table_properties.h" +#include "rocksdb/utilities/backupable_db.h" +#include "rocksdb/utilities/checkpoint.h" +#include "rocksdb/utilities/debug.h" +#include "rocksdb/utilities/options_util.h" +#include "rocksdb/write_batch.h" +#include "rocksdb/write_buffer_manager.h" +#include "table/scoped_arena_iterator.h" +#include "tools/ldb_cmd_impl.h" +#include "tools/sst_dump_tool_imp.h" +#include "util/cast_util.h" +#include "util/coding.h" +#include "util/file_checksum_helper.h" +#include "util/stderr_logger.h" +#include "util/string_util.h" +#include "utilities/merge_operators.h" +#include "utilities/ttl/db_ttl_impl.h" + +#include <cstdlib> +#include <ctime> +#include <fstream> +#include <functional> +#include <iostream> +#include <limits> +#include <sstream> +#include <stdexcept> +#include <string> + +namespace ROCKSDB_NAMESPACE { + +class FileChecksumFuncCrc32c; + +const std::string LDBCommand::ARG_ENV_URI = "env_uri"; +const std::string LDBCommand::ARG_DB = "db"; +const std::string LDBCommand::ARG_PATH = "path"; +const std::string LDBCommand::ARG_SECONDARY_PATH = "secondary_path"; +const std::string LDBCommand::ARG_HEX = "hex"; +const std::string LDBCommand::ARG_KEY_HEX = "key_hex"; +const std::string LDBCommand::ARG_VALUE_HEX = "value_hex"; +const std::string LDBCommand::ARG_CF_NAME = "column_family"; +const std::string LDBCommand::ARG_TTL = "ttl"; +const std::string LDBCommand::ARG_TTL_START = "start_time"; +const std::string LDBCommand::ARG_TTL_END = "end_time"; +const std::string LDBCommand::ARG_TIMESTAMP = "timestamp"; +const std::string LDBCommand::ARG_TRY_LOAD_OPTIONS = "try_load_options"; +const std::string LDBCommand::ARG_IGNORE_UNKNOWN_OPTIONS = + "ignore_unknown_options"; +const std::string LDBCommand::ARG_FROM = "from"; +const std::string LDBCommand::ARG_TO = "to"; +const std::string LDBCommand::ARG_MAX_KEYS = "max_keys"; +const std::string LDBCommand::ARG_BLOOM_BITS = "bloom_bits"; +const std::string LDBCommand::ARG_FIX_PREFIX_LEN = "fix_prefix_len"; +const std::string LDBCommand::ARG_COMPRESSION_TYPE = "compression_type"; +const std::string LDBCommand::ARG_COMPRESSION_MAX_DICT_BYTES = + "compression_max_dict_bytes"; +const std::string LDBCommand::ARG_BLOCK_SIZE = "block_size"; +const std::string LDBCommand::ARG_AUTO_COMPACTION = "auto_compaction"; +const std::string LDBCommand::ARG_DB_WRITE_BUFFER_SIZE = "db_write_buffer_size"; +const std::string LDBCommand::ARG_WRITE_BUFFER_SIZE = "write_buffer_size"; +const std::string LDBCommand::ARG_FILE_SIZE = "file_size"; +const std::string LDBCommand::ARG_CREATE_IF_MISSING = "create_if_missing"; +const std::string LDBCommand::ARG_NO_VALUE = "no_value"; + +const char* LDBCommand::DELIM = " ==> "; + +namespace { + +void DumpWalFile(Options options, std::string wal_file, bool print_header, + bool print_values, bool is_write_committed, + LDBCommandExecuteResult* exec_state); + +void DumpSstFile(Options options, std::string filename, bool output_hex, + bool show_properties); +}; + +LDBCommand* LDBCommand::InitFromCmdLineArgs( + int argc, char** argv, const Options& options, + const LDBOptions& ldb_options, + const std::vector<ColumnFamilyDescriptor>* column_families) { + std::vector<std::string> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + return InitFromCmdLineArgs(args, options, ldb_options, column_families, + SelectCommand); +} + +/** + * Parse the command-line arguments and create the appropriate LDBCommand2 + * instance. + * The command line arguments must be in the following format: + * ./ldb --db=PATH_TO_DB [--commonOpt1=commonOpt1Val] .. + * COMMAND <PARAM1> <PARAM2> ... [-cmdSpecificOpt1=cmdSpecificOpt1Val] .. + * This is similar to the command line format used by HBaseClientTool. + * Command name is not included in args. + * Returns nullptr if the command-line cannot be parsed. + */ +LDBCommand* LDBCommand::InitFromCmdLineArgs( + const std::vector<std::string>& args, const Options& options, + const LDBOptions& ldb_options, + const std::vector<ColumnFamilyDescriptor>* /*column_families*/, + const std::function<LDBCommand*(const ParsedParams&)>& selector) { + // --x=y command line arguments are added as x->y map entries in + // parsed_params.option_map. + // + // Command-line arguments of the form --hex end up in this array as hex to + // parsed_params.flags + ParsedParams parsed_params; + + // Everything other than option_map and flags. Represents commands + // and their parameters. For eg: put key1 value1 go into this vector. + std::vector<std::string> cmdTokens; + + const std::string OPTION_PREFIX = "--"; + + for (const auto& arg : args) { + if (arg[0] == '-' && arg[1] == '-'){ + std::vector<std::string> splits = StringSplit(arg, '='); + // --option_name=option_value + if (splits.size() == 2) { + std::string optionKey = splits[0].substr(OPTION_PREFIX.size()); + parsed_params.option_map[optionKey] = splits[1]; + } else if (splits.size() == 1) { + // --flag_name + std::string optionKey = splits[0].substr(OPTION_PREFIX.size()); + parsed_params.flags.push_back(optionKey); + } else { + // --option_name=option_value, option_value contains '=' + std::string optionKey = splits[0].substr(OPTION_PREFIX.size()); + parsed_params.option_map[optionKey] = + arg.substr(splits[0].length() + 1); + } + } else { + cmdTokens.push_back(arg); + } + } + + if (cmdTokens.size() < 1) { + fprintf(stderr, "Command not specified!"); + return nullptr; + } + + parsed_params.cmd = cmdTokens[0]; + parsed_params.cmd_params.assign(cmdTokens.begin() + 1, cmdTokens.end()); + + LDBCommand* command = selector(parsed_params); + + if (command) { + command->SetDBOptions(options); + command->SetLDBOptions(ldb_options); + } + return command; +} + +LDBCommand* LDBCommand::SelectCommand(const ParsedParams& parsed_params) { + if (parsed_params.cmd == GetCommand::Name()) { + return new GetCommand(parsed_params.cmd_params, parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == PutCommand::Name()) { + return new PutCommand(parsed_params.cmd_params, parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == BatchPutCommand::Name()) { + return new BatchPutCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == ScanCommand::Name()) { + return new ScanCommand(parsed_params.cmd_params, parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == DeleteCommand::Name()) { + return new DeleteCommand(parsed_params.cmd_params, parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == DeleteRangeCommand::Name()) { + return new DeleteRangeCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == ApproxSizeCommand::Name()) { + return new ApproxSizeCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == DBQuerierCommand::Name()) { + return new DBQuerierCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == CompactorCommand::Name()) { + return new CompactorCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == WALDumperCommand::Name()) { + return new WALDumperCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == ReduceDBLevelsCommand::Name()) { + return new ReduceDBLevelsCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == ChangeCompactionStyleCommand::Name()) { + return new ChangeCompactionStyleCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == DBDumperCommand::Name()) { + return new DBDumperCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == DBLoaderCommand::Name()) { + return new DBLoaderCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == ManifestDumpCommand::Name()) { + return new ManifestDumpCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == FileChecksumDumpCommand::Name()) { + return new FileChecksumDumpCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == ListColumnFamiliesCommand::Name()) { + return new ListColumnFamiliesCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == CreateColumnFamilyCommand::Name()) { + return new CreateColumnFamilyCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == DropColumnFamilyCommand::Name()) { + return new DropColumnFamilyCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == DBFileDumperCommand::Name()) { + return new DBFileDumperCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == InternalDumpCommand::Name()) { + return new InternalDumpCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == CheckConsistencyCommand::Name()) { + return new CheckConsistencyCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == CheckPointCommand::Name()) { + return new CheckPointCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == RepairCommand::Name()) { + return new RepairCommand(parsed_params.cmd_params, parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == BackupCommand::Name()) { + return new BackupCommand(parsed_params.cmd_params, parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == RestoreCommand::Name()) { + return new RestoreCommand(parsed_params.cmd_params, + parsed_params.option_map, parsed_params.flags); + } else if (parsed_params.cmd == WriteExternalSstFilesCommand::Name()) { + return new WriteExternalSstFilesCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == IngestExternalSstFilesCommand::Name()) { + return new IngestExternalSstFilesCommand(parsed_params.cmd_params, + parsed_params.option_map, + parsed_params.flags); + } else if (parsed_params.cmd == ListFileRangeDeletesCommand::Name()) { + return new ListFileRangeDeletesCommand(parsed_params.option_map, + parsed_params.flags); + } + return nullptr; +} + +/* Run the command, and return the execute result. */ +void LDBCommand::Run() { + if (!exec_state_.IsNotStarted()) { + return; + } + + if (!options_.env || options_.env == Env::Default()) { + Env* env = Env::Default(); + Status s = Env::LoadEnv(env_uri_, &env, &env_guard_); + if (!s.ok() && !s.IsNotFound()) { + fprintf(stderr, "LoadEnv: %s\n", s.ToString().c_str()); + exec_state_ = LDBCommandExecuteResult::Failed(s.ToString()); + return; + } + options_.env = env; + } + + options_.file_system.reset(new LegacyFileSystemWrapper(options_.env)); + + if (db_ == nullptr && !NoDBOpen()) { + OpenDB(); + if (exec_state_.IsFailed() && try_load_options_) { + // We don't always return if there is a failure because a WAL file or + // manifest file can be given to "dump" command so we should continue. + // --try_load_options is not valid in those cases. + return; + } + } + + // We'll intentionally proceed even if the DB can't be opened because users + // can also specify a filename, not just a directory. + DoCommand(); + + if (exec_state_.IsNotStarted()) { + exec_state_ = LDBCommandExecuteResult::Succeed(""); + } + + if (db_ != nullptr) { + CloseDB(); + } +} + +LDBCommand::LDBCommand(const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags, bool is_read_only, + const std::vector<std::string>& valid_cmd_line_options) + : db_(nullptr), + db_ttl_(nullptr), + is_read_only_(is_read_only), + is_key_hex_(false), + is_value_hex_(false), + is_db_ttl_(false), + timestamp_(false), + try_load_options_(false), + ignore_unknown_options_(false), + create_if_missing_(false), + option_map_(options), + flags_(flags), + valid_cmd_line_options_(valid_cmd_line_options) { + std::map<std::string, std::string>::const_iterator itr = options.find(ARG_DB); + if (itr != options.end()) { + db_path_ = itr->second; + } + + itr = options.find(ARG_ENV_URI); + if (itr != options.end()) { + env_uri_ = itr->second; + } + + itr = options.find(ARG_CF_NAME); + if (itr != options.end()) { + column_family_name_ = itr->second; + } else { + column_family_name_ = kDefaultColumnFamilyName; + } + + itr = options.find(ARG_SECONDARY_PATH); + secondary_path_ = ""; + if (itr != options.end()) { + secondary_path_ = itr->second; + } + + is_key_hex_ = IsKeyHex(options, flags); + is_value_hex_ = IsValueHex(options, flags); + is_db_ttl_ = IsFlagPresent(flags, ARG_TTL); + timestamp_ = IsFlagPresent(flags, ARG_TIMESTAMP); + try_load_options_ = IsFlagPresent(flags, ARG_TRY_LOAD_OPTIONS); + ignore_unknown_options_ = IsFlagPresent(flags, ARG_IGNORE_UNKNOWN_OPTIONS); +} + +void LDBCommand::OpenDB() { + if (!create_if_missing_ && try_load_options_) { + Status s = LoadLatestOptions(db_path_, options_.env, &options_, + &column_families_, ignore_unknown_options_); + if (!s.ok() && !s.IsNotFound()) { + // Option file exists but load option file error. + std::string msg = s.ToString(); + exec_state_ = LDBCommandExecuteResult::Failed(msg); + db_ = nullptr; + return; + } + if (options_.env->FileExists(options_.wal_dir).IsNotFound()) { + options_.wal_dir = db_path_; + fprintf( + stderr, + "wal_dir loaded from the option file doesn't exist. Ignore it.\n"); + } + + // If merge operator is not set, set a string append operator. There is + // no harm doing it. + for (auto& cf_entry : column_families_) { + if (!cf_entry.options.merge_operator) { + cf_entry.options.merge_operator = + MergeOperators::CreateStringAppendOperator(':'); + } + } + } + options_ = PrepareOptionsForOpenDB(); + if (!exec_state_.IsNotStarted()) { + return; + } + if (column_families_.empty() && !options_.merge_operator) { + // No harm to add a general merge operator if it is not specified. + options_.merge_operator = MergeOperators::CreateStringAppendOperator(':'); + } + // Open the DB. + Status st; + std::vector<ColumnFamilyHandle*> handles_opened; + if (is_db_ttl_) { + // ldb doesn't yet support TTL DB with multiple column families + if (!column_family_name_.empty() || !column_families_.empty()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "ldb doesn't support TTL DB with multiple column families"); + } + if (!secondary_path_.empty()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Open as secondary is not supported for TTL DB yet."); + } + if (is_read_only_) { + st = DBWithTTL::Open(options_, db_path_, &db_ttl_, 0, true); + } else { + st = DBWithTTL::Open(options_, db_path_, &db_ttl_); + } + db_ = db_ttl_; + } else { + if (column_families_.empty()) { + // Try to figure out column family lists + std::vector<std::string> cf_list; + st = DB::ListColumnFamilies(options_, db_path_, &cf_list); + // There is possible the DB doesn't exist yet, for "create if not + // "existing case". The failure is ignored here. We rely on DB::Open() + // to give us the correct error message for problem with opening + // existing DB. + if (st.ok() && cf_list.size() > 1) { + // Ignore single column family DB. + for (auto cf_name : cf_list) { + column_families_.emplace_back(cf_name, options_); + } + } + } + if (is_read_only_ && secondary_path_.empty()) { + if (column_families_.empty()) { + st = DB::OpenForReadOnly(options_, db_path_, &db_); + } else { + st = DB::OpenForReadOnly(options_, db_path_, column_families_, + &handles_opened, &db_); + } + } else { + if (column_families_.empty()) { + if (secondary_path_.empty()) { + st = DB::Open(options_, db_path_, &db_); + } else { + st = DB::OpenAsSecondary(options_, db_path_, secondary_path_, &db_); + } + } else { + if (secondary_path_.empty()) { + st = DB::Open(options_, db_path_, column_families_, &handles_opened, + &db_); + } else { + st = DB::OpenAsSecondary(options_, db_path_, secondary_path_, + column_families_, &handles_opened, &db_); + } + } + } + } + if (!st.ok()) { + std::string msg = st.ToString(); + exec_state_ = LDBCommandExecuteResult::Failed(msg); + } else if (!handles_opened.empty()) { + assert(handles_opened.size() == column_families_.size()); + bool found_cf_name = false; + for (size_t i = 0; i < handles_opened.size(); i++) { + cf_handles_[column_families_[i].name] = handles_opened[i]; + if (column_family_name_ == column_families_[i].name) { + found_cf_name = true; + } + } + if (!found_cf_name) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Non-existing column family " + column_family_name_); + CloseDB(); + } + } else { + // We successfully opened DB in single column family mode. + assert(column_families_.empty()); + if (column_family_name_ != kDefaultColumnFamilyName) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Non-existing column family " + column_family_name_); + CloseDB(); + } + } +} + +void LDBCommand::CloseDB() { + if (db_ != nullptr) { + for (auto& pair : cf_handles_) { + delete pair.second; + } + delete db_; + db_ = nullptr; + } +} + +ColumnFamilyHandle* LDBCommand::GetCfHandle() { + if (!cf_handles_.empty()) { + auto it = cf_handles_.find(column_family_name_); + if (it == cf_handles_.end()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Cannot find column family " + column_family_name_); + } else { + return it->second; + } + } + return db_->DefaultColumnFamily(); +} + +std::vector<std::string> LDBCommand::BuildCmdLineOptions( + std::vector<std::string> options) { + std::vector<std::string> ret = {ARG_ENV_URI, + ARG_DB, + ARG_SECONDARY_PATH, + ARG_BLOOM_BITS, + ARG_BLOCK_SIZE, + ARG_AUTO_COMPACTION, + ARG_COMPRESSION_TYPE, + ARG_COMPRESSION_MAX_DICT_BYTES, + ARG_WRITE_BUFFER_SIZE, + ARG_FILE_SIZE, + ARG_FIX_PREFIX_LEN, + ARG_TRY_LOAD_OPTIONS, + ARG_IGNORE_UNKNOWN_OPTIONS, + ARG_CF_NAME}; + ret.insert(ret.end(), options.begin(), options.end()); + return ret; +} + +/** + * Parses the specific integer option and fills in the value. + * Returns true if the option is found. + * Returns false if the option is not found or if there is an error parsing the + * value. If there is an error, the specified exec_state is also + * updated. + */ +bool LDBCommand::ParseIntOption( + const std::map<std::string, std::string>& /*options*/, + const std::string& option, int& value, + LDBCommandExecuteResult& exec_state) { + std::map<std::string, std::string>::const_iterator itr = + option_map_.find(option); + if (itr != option_map_.end()) { + try { +#if defined(CYGWIN) + value = strtol(itr->second.c_str(), 0, 10); +#else + value = std::stoi(itr->second); +#endif + return true; + } catch (const std::invalid_argument&) { + exec_state = + LDBCommandExecuteResult::Failed(option + " has an invalid value."); + } catch (const std::out_of_range&) { + exec_state = LDBCommandExecuteResult::Failed( + option + " has a value out-of-range."); + } + } + return false; +} + +/** + * Parses the specified option and fills in the value. + * Returns true if the option is found. + * Returns false otherwise. + */ +bool LDBCommand::ParseStringOption( + const std::map<std::string, std::string>& /*options*/, + const std::string& option, std::string* value) { + auto itr = option_map_.find(option); + if (itr != option_map_.end()) { + *value = itr->second; + return true; + } + return false; +} + +Options LDBCommand::PrepareOptionsForOpenDB() { + ColumnFamilyOptions* cf_opts; + auto column_families_iter = + std::find_if(column_families_.begin(), column_families_.end(), + [this](const ColumnFamilyDescriptor& cf_desc) { + return cf_desc.name == column_family_name_; + }); + if (column_families_iter != column_families_.end()) { + cf_opts = &column_families_iter->options; + } else { + cf_opts = static_cast<ColumnFamilyOptions*>(&options_); + } + DBOptions* db_opts = static_cast<DBOptions*>(&options_); + db_opts->create_if_missing = false; + + std::map<std::string, std::string>::const_iterator itr; + + BlockBasedTableOptions table_options; + bool use_table_options = false; + int bits; + if (ParseIntOption(option_map_, ARG_BLOOM_BITS, bits, exec_state_)) { + if (bits > 0) { + use_table_options = true; + table_options.filter_policy.reset(NewBloomFilterPolicy(bits)); + } else { + exec_state_ = + LDBCommandExecuteResult::Failed(ARG_BLOOM_BITS + " must be > 0."); + } + } + + int block_size; + if (ParseIntOption(option_map_, ARG_BLOCK_SIZE, block_size, exec_state_)) { + if (block_size > 0) { + use_table_options = true; + table_options.block_size = block_size; + } else { + exec_state_ = + LDBCommandExecuteResult::Failed(ARG_BLOCK_SIZE + " must be > 0."); + } + } + + if (use_table_options) { + cf_opts->table_factory.reset(NewBlockBasedTableFactory(table_options)); + } + + itr = option_map_.find(ARG_AUTO_COMPACTION); + if (itr != option_map_.end()) { + cf_opts->disable_auto_compactions = !StringToBool(itr->second); + } + + itr = option_map_.find(ARG_COMPRESSION_TYPE); + if (itr != option_map_.end()) { + std::string comp = itr->second; + if (comp == "no") { + cf_opts->compression = kNoCompression; + } else if (comp == "snappy") { + cf_opts->compression = kSnappyCompression; + } else if (comp == "zlib") { + cf_opts->compression = kZlibCompression; + } else if (comp == "bzip2") { + cf_opts->compression = kBZip2Compression; + } else if (comp == "lz4") { + cf_opts->compression = kLZ4Compression; + } else if (comp == "lz4hc") { + cf_opts->compression = kLZ4HCCompression; + } else if (comp == "xpress") { + cf_opts->compression = kXpressCompression; + } else if (comp == "zstd") { + cf_opts->compression = kZSTD; + } else { + // Unknown compression. + exec_state_ = + LDBCommandExecuteResult::Failed("Unknown compression level: " + comp); + } + } + + int compression_max_dict_bytes; + if (ParseIntOption(option_map_, ARG_COMPRESSION_MAX_DICT_BYTES, + compression_max_dict_bytes, exec_state_)) { + if (compression_max_dict_bytes >= 0) { + cf_opts->compression_opts.max_dict_bytes = compression_max_dict_bytes; + } else { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_COMPRESSION_MAX_DICT_BYTES + " must be >= 0."); + } + } + + int db_write_buffer_size; + if (ParseIntOption(option_map_, ARG_DB_WRITE_BUFFER_SIZE, + db_write_buffer_size, exec_state_)) { + if (db_write_buffer_size >= 0) { + db_opts->db_write_buffer_size = db_write_buffer_size; + } else { + exec_state_ = LDBCommandExecuteResult::Failed(ARG_DB_WRITE_BUFFER_SIZE + + " must be >= 0."); + } + } + + int write_buffer_size; + if (ParseIntOption(option_map_, ARG_WRITE_BUFFER_SIZE, write_buffer_size, + exec_state_)) { + if (write_buffer_size > 0) { + cf_opts->write_buffer_size = write_buffer_size; + } else { + exec_state_ = LDBCommandExecuteResult::Failed(ARG_WRITE_BUFFER_SIZE + + " must be > 0."); + } + } + + int file_size; + if (ParseIntOption(option_map_, ARG_FILE_SIZE, file_size, exec_state_)) { + if (file_size > 0) { + cf_opts->target_file_size_base = file_size; + } else { + exec_state_ = + LDBCommandExecuteResult::Failed(ARG_FILE_SIZE + " must be > 0."); + } + } + + if (db_opts->db_paths.size() == 0) { + db_opts->db_paths.emplace_back(db_path_, + std::numeric_limits<uint64_t>::max()); + } + + int fix_prefix_len; + if (ParseIntOption(option_map_, ARG_FIX_PREFIX_LEN, fix_prefix_len, + exec_state_)) { + if (fix_prefix_len > 0) { + cf_opts->prefix_extractor.reset( + NewFixedPrefixTransform(static_cast<size_t>(fix_prefix_len))); + } else { + exec_state_ = + LDBCommandExecuteResult::Failed(ARG_FIX_PREFIX_LEN + " must be > 0."); + } + } + + // TODO(ajkr): this return value doesn't reflect the CF options changed, so + // subcommands that rely on this won't see the effect of CF-related CLI args. + // Such subcommands need to be changed to properly support CFs. + return options_; +} + +bool LDBCommand::ParseKeyValue(const std::string& line, std::string* key, + std::string* value, bool is_key_hex, + bool is_value_hex) { + size_t pos = line.find(DELIM); + if (pos != std::string::npos) { + *key = line.substr(0, pos); + *value = line.substr(pos + strlen(DELIM)); + if (is_key_hex) { + *key = HexToString(*key); + } + if (is_value_hex) { + *value = HexToString(*value); + } + return true; + } else { + return false; + } +} + +/** + * Make sure that ONLY the command-line options and flags expected by this + * command are specified on the command-line. Extraneous options are usually + * the result of user error. + * Returns true if all checks pass. Else returns false, and prints an + * appropriate error msg to stderr. + */ +bool LDBCommand::ValidateCmdLineOptions() { + for (std::map<std::string, std::string>::const_iterator itr = + option_map_.begin(); + itr != option_map_.end(); ++itr) { + if (std::find(valid_cmd_line_options_.begin(), + valid_cmd_line_options_.end(), + itr->first) == valid_cmd_line_options_.end()) { + fprintf(stderr, "Invalid command-line option %s\n", itr->first.c_str()); + return false; + } + } + + for (std::vector<std::string>::const_iterator itr = flags_.begin(); + itr != flags_.end(); ++itr) { + if (std::find(valid_cmd_line_options_.begin(), + valid_cmd_line_options_.end(), + *itr) == valid_cmd_line_options_.end()) { + fprintf(stderr, "Invalid command-line flag %s\n", itr->c_str()); + return false; + } + } + + if (!NoDBOpen() && option_map_.find(ARG_DB) == option_map_.end() && + option_map_.find(ARG_PATH) == option_map_.end()) { + fprintf(stderr, "Either %s or %s must be specified.\n", ARG_DB.c_str(), + ARG_PATH.c_str()); + return false; + } + + return true; +} + +std::string LDBCommand::HexToString(const std::string& str) { + std::string result; + std::string::size_type len = str.length(); + if (len < 2 || str[0] != '0' || str[1] != 'x') { + fprintf(stderr, "Invalid hex input %s. Must start with 0x\n", str.c_str()); + throw "Invalid hex input"; + } + if (!Slice(str.data() + 2, len - 2).DecodeHex(&result)) { + throw "Invalid hex input"; + } + return result; +} + +std::string LDBCommand::StringToHex(const std::string& str) { + std::string result("0x"); + result.append(Slice(str).ToString(true)); + return result; +} + +std::string LDBCommand::PrintKeyValue(const std::string& key, + const std::string& value, bool is_key_hex, + bool is_value_hex) { + std::string result; + result.append(is_key_hex ? StringToHex(key) : key); + result.append(DELIM); + result.append(is_value_hex ? StringToHex(value) : value); + return result; +} + +std::string LDBCommand::PrintKeyValue(const std::string& key, + const std::string& value, bool is_hex) { + return PrintKeyValue(key, value, is_hex, is_hex); +} + +std::string LDBCommand::HelpRangeCmdArgs() { + std::ostringstream str_stream; + str_stream << " "; + str_stream << "[--" << ARG_FROM << "] "; + str_stream << "[--" << ARG_TO << "] "; + return str_stream.str(); +} + +bool LDBCommand::IsKeyHex(const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) { + return (IsFlagPresent(flags, ARG_HEX) || IsFlagPresent(flags, ARG_KEY_HEX) || + ParseBooleanOption(options, ARG_HEX, false) || + ParseBooleanOption(options, ARG_KEY_HEX, false)); +} + +bool LDBCommand::IsValueHex(const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) { + return (IsFlagPresent(flags, ARG_HEX) || + IsFlagPresent(flags, ARG_VALUE_HEX) || + ParseBooleanOption(options, ARG_HEX, false) || + ParseBooleanOption(options, ARG_VALUE_HEX, false)); +} + +bool LDBCommand::ParseBooleanOption( + const std::map<std::string, std::string>& options, + const std::string& option, bool default_val) { + std::map<std::string, std::string>::const_iterator itr = options.find(option); + if (itr != options.end()) { + std::string option_val = itr->second; + return StringToBool(itr->second); + } + return default_val; +} + +bool LDBCommand::StringToBool(std::string val) { + std::transform(val.begin(), val.end(), val.begin(), + [](char ch) -> char { return (char)::tolower(ch); }); + + if (val == "true") { + return true; + } else if (val == "false") { + return false; + } else { + throw "Invalid value for boolean argument"; + } +} + +CompactorCommand::CompactorCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions({ARG_FROM, ARG_TO, ARG_HEX, ARG_KEY_HEX, + ARG_VALUE_HEX, ARG_TTL})), + null_from_(true), + null_to_(true) { + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_FROM); + if (itr != options.end()) { + null_from_ = false; + from_ = itr->second; + } + + itr = options.find(ARG_TO); + if (itr != options.end()) { + null_to_ = false; + to_ = itr->second; + } + + if (is_key_hex_) { + if (!null_from_) { + from_ = HexToString(from_); + } + if (!null_to_) { + to_ = HexToString(to_); + } + } +} + +void CompactorCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(CompactorCommand::Name()); + ret.append(HelpRangeCmdArgs()); + ret.append("\n"); +} + +void CompactorCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + + Slice* begin = nullptr; + Slice* end = nullptr; + if (!null_from_) { + begin = new Slice(from_); + } + if (!null_to_) { + end = new Slice(to_); + } + + CompactRangeOptions cro; + cro.bottommost_level_compaction = BottommostLevelCompaction::kForceOptimized; + + db_->CompactRange(cro, GetCfHandle(), begin, end); + exec_state_ = LDBCommandExecuteResult::Succeed(""); + + delete begin; + delete end; +} + +// --------------------------------------------------------------------------- +const std::string DBLoaderCommand::ARG_DISABLE_WAL = "disable_wal"; +const std::string DBLoaderCommand::ARG_BULK_LOAD = "bulk_load"; +const std::string DBLoaderCommand::ARG_COMPACT = "compact"; + +DBLoaderCommand::DBLoaderCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, false, + BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, + ARG_TO, ARG_CREATE_IF_MISSING, ARG_DISABLE_WAL, + ARG_BULK_LOAD, ARG_COMPACT})), + disable_wal_(false), + bulk_load_(false), + compact_(false) { + create_if_missing_ = IsFlagPresent(flags, ARG_CREATE_IF_MISSING); + disable_wal_ = IsFlagPresent(flags, ARG_DISABLE_WAL); + bulk_load_ = IsFlagPresent(flags, ARG_BULK_LOAD); + compact_ = IsFlagPresent(flags, ARG_COMPACT); +} + +void DBLoaderCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DBLoaderCommand::Name()); + ret.append(" [--" + ARG_CREATE_IF_MISSING + "]"); + ret.append(" [--" + ARG_DISABLE_WAL + "]"); + ret.append(" [--" + ARG_BULK_LOAD + "]"); + ret.append(" [--" + ARG_COMPACT + "]"); + ret.append("\n"); +} + +Options DBLoaderCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + opt.create_if_missing = create_if_missing_; + if (bulk_load_) { + opt.PrepareForBulkLoad(); + } + return opt; +} + +void DBLoaderCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + + WriteOptions write_options; + if (disable_wal_) { + write_options.disableWAL = true; + } + + int bad_lines = 0; + std::string line; + // prefer ifstream getline performance vs that from std::cin istream + std::ifstream ifs_stdin("/dev/stdin"); + std::istream* istream_p = ifs_stdin.is_open() ? &ifs_stdin : &std::cin; + while (getline(*istream_p, line, '\n')) { + std::string key; + std::string value; + if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) { + db_->Put(write_options, GetCfHandle(), Slice(key), Slice(value)); + } else if (0 == line.find("Keys in range:")) { + // ignore this line + } else if (0 == line.find("Created bg thread 0x")) { + // ignore this line + } else { + bad_lines ++; + } + } + + if (bad_lines > 0) { + std::cout << "Warning: " << bad_lines << " bad lines ignored." << std::endl; + } + if (compact_) { + db_->CompactRange(CompactRangeOptions(), GetCfHandle(), nullptr, nullptr); + } +} + +// ---------------------------------------------------------------------------- + +namespace { + +void DumpManifestFile(Options options, std::string file, bool verbose, bool hex, + bool json) { + EnvOptions sopt; + std::string dbname("dummy"); + std::shared_ptr<Cache> tc(NewLRUCache(options.max_open_files - 10, + options.table_cache_numshardbits)); + // Notice we are using the default options not through SanitizeOptions(), + // if VersionSet::DumpManifest() depends on any option done by + // SanitizeOptions(), we need to initialize it manually. + options.db_paths.emplace_back("dummy", 0); + options.num_levels = 64; + WriteController wc(options.delayed_write_rate); + WriteBufferManager wb(options.db_write_buffer_size); + ImmutableDBOptions immutable_db_options(options); + VersionSet versions(dbname, &immutable_db_options, sopt, tc.get(), &wb, &wc, + /*block_cache_tracer=*/nullptr); + Status s = versions.DumpManifest(options, file, verbose, hex, json); + if (!s.ok()) { + fprintf(stderr, "Error in processing file %s %s\n", file.c_str(), + s.ToString().c_str()); + } +} + +} // namespace + +const std::string ManifestDumpCommand::ARG_VERBOSE = "verbose"; +const std::string ManifestDumpCommand::ARG_JSON = "json"; +const std::string ManifestDumpCommand::ARG_PATH = "path"; + +void ManifestDumpCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ManifestDumpCommand::Name()); + ret.append(" [--" + ARG_VERBOSE + "]"); + ret.append(" [--" + ARG_JSON + "]"); + ret.append(" [--" + ARG_PATH + "=<path_to_manifest_file>]"); + ret.append("\n"); +} + +ManifestDumpCommand::ManifestDumpCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, false, + BuildCmdLineOptions({ARG_VERBOSE, ARG_PATH, ARG_HEX, ARG_JSON})), + verbose_(false), + json_(false), + path_("") { + verbose_ = IsFlagPresent(flags, ARG_VERBOSE); + json_ = IsFlagPresent(flags, ARG_JSON); + + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_PATH); + if (itr != options.end()) { + path_ = itr->second; + if (path_.empty()) { + exec_state_ = LDBCommandExecuteResult::Failed("--path: missing pathname"); + } + } +} + +void ManifestDumpCommand::DoCommand() { + + std::string manifestfile; + + if (!path_.empty()) { + manifestfile = path_; + } else { + // We need to find the manifest file by searching the directory + // containing the db for files of the form MANIFEST_[0-9]+ + + std::vector<std::string> files; + Status s = options_.env->GetChildren(db_path_, &files); + if (!s.ok()) { + std::string err_msg = s.ToString(); + err_msg.append(": Failed to list the content of "); + err_msg.append(db_path_); + exec_state_ = LDBCommandExecuteResult::Failed(err_msg); + return; + } + const std::string kManifestNamePrefix = "MANIFEST-"; + std::string matched_file; +#ifdef OS_WIN + const char kPathDelim = '\\'; +#else + const char kPathDelim = '/'; +#endif + for (const auto& file_path : files) { + // Some Env::GetChildren() return absolute paths. Some directories' path + // end with path delim, e.g. '/' or '\\'. + size_t pos = file_path.find_last_of(kPathDelim); + if (pos == file_path.size() - 1) { + continue; + } + std::string fname; + if (pos != std::string::npos) { + // Absolute path. + fname.assign(file_path, pos + 1, file_path.size() - pos - 1); + } else { + fname = file_path; + } + uint64_t file_num = 0; + FileType file_type = kLogFile; // Just for initialization + if (ParseFileName(fname, &file_num, &file_type) && + file_type == kDescriptorFile) { + if (!matched_file.empty()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Multiple MANIFEST files found; use --path to select one"); + return; + } else { + matched_file.swap(fname); + } + } + } + if (matched_file.empty()) { + std::string err_msg("No MANIFEST found in "); + err_msg.append(db_path_); + exec_state_ = LDBCommandExecuteResult::Failed(err_msg); + return; + } + if (db_path_[db_path_.length() - 1] != '/') { + db_path_.append("/"); + } + manifestfile = db_path_ + matched_file; + } + + if (verbose_) { + fprintf(stdout, "Processing Manifest file %s\n", manifestfile.c_str()); + } + + DumpManifestFile(options_, manifestfile, verbose_, is_key_hex_, json_); + + if (verbose_) { + fprintf(stdout, "Processing Manifest file %s done\n", manifestfile.c_str()); + } +} + +// ---------------------------------------------------------------------------- +namespace { + +void GetLiveFilesChecksumInfoFromVersionSet(Options options, + const std::string& db_path, + FileChecksumList* checksum_list) { + EnvOptions sopt; + Status s; + std::string dbname(db_path); + std::shared_ptr<Cache> tc(NewLRUCache(options.max_open_files - 10, + options.table_cache_numshardbits)); + // Notice we are using the default options not through SanitizeOptions(), + // if VersionSet::GetLiveFilesChecksumInfo depends on any option done by + // SanitizeOptions(), we need to initialize it manually. + options.db_paths.emplace_back(db_path, 0); + options.num_levels = 64; + WriteController wc(options.delayed_write_rate); + WriteBufferManager wb(options.db_write_buffer_size); + ImmutableDBOptions immutable_db_options(options); + VersionSet versions(dbname, &immutable_db_options, sopt, tc.get(), &wb, &wc, + /*block_cache_tracer=*/nullptr); + std::vector<std::string> cf_name_list; + s = versions.ListColumnFamilies(&cf_name_list, db_path, + options.file_system.get()); + if (s.ok()) { + std::vector<ColumnFamilyDescriptor> cf_list; + for (const auto& name : cf_name_list) { + cf_list.emplace_back(name, ColumnFamilyOptions(options)); + } + s = versions.Recover(cf_list, true); + } + if (s.ok()) { + s = versions.GetLiveFilesChecksumInfo(checksum_list); + } + if (!s.ok()) { + fprintf(stderr, "Error Status: %s", s.ToString().c_str()); + } +} + +} // namespace + +const std::string FileChecksumDumpCommand::ARG_PATH = "path"; + +void FileChecksumDumpCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(FileChecksumDumpCommand::Name()); + ret.append(" [--" + ARG_PATH + "=<path_to_manifest_file>]"); + ret.append("\n"); +} + +FileChecksumDumpCommand::FileChecksumDumpCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, BuildCmdLineOptions({ARG_PATH})), + path_("") { + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_PATH); + if (itr != options.end()) { + path_ = itr->second; + if (path_.empty()) { + exec_state_ = LDBCommandExecuteResult::Failed("--path: missing pathname"); + } + } +} + +void FileChecksumDumpCommand::DoCommand() { + // print out the checksum information in the following format: + // sst file number, checksum function name, checksum value + // sst file number, checksum function name, checksum value + // ...... + + std::unique_ptr<FileChecksumList> checksum_list(NewFileChecksumList()); + GetLiveFilesChecksumInfoFromVersionSet(options_, db_path_, + checksum_list.get()); + if (checksum_list != nullptr) { + std::vector<uint64_t> file_numbers; + std::vector<std::string> checksums; + std::vector<std::string> checksum_func_names; + Status s = checksum_list->GetAllFileChecksums(&file_numbers, &checksums, + &checksum_func_names); + if (s.ok()) { + for (size_t i = 0; i < file_numbers.size(); i++) { + assert(i < file_numbers.size()); + assert(i < checksums.size()); + assert(i < checksum_func_names.size()); + fprintf(stdout, "%" PRId64 ", %s, %s\n", file_numbers[i], + checksum_func_names[i].c_str(), checksums[i].c_str()); + } + } + fprintf(stdout, "Print SST file checksum information finished \n"); + } +} + +// ---------------------------------------------------------------------------- + +void ListColumnFamiliesCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ListColumnFamiliesCommand::Name()); + ret.append("\n"); +} + +ListColumnFamiliesCommand::ListColumnFamiliesCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, BuildCmdLineOptions({})) {} + +void ListColumnFamiliesCommand::DoCommand() { + std::vector<std::string> column_families; + Status s = DB::ListColumnFamilies(options_, db_path_, &column_families); + if (!s.ok()) { + fprintf(stderr, "Error in processing db %s %s\n", db_path_.c_str(), + s.ToString().c_str()); + } else { + fprintf(stdout, "Column families in %s: \n{", db_path_.c_str()); + bool first = true; + for (auto cf : column_families) { + if (!first) { + fprintf(stdout, ", "); + } + first = false; + fprintf(stdout, "%s", cf.c_str()); + } + fprintf(stdout, "}\n"); + } +} + +void CreateColumnFamilyCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(CreateColumnFamilyCommand::Name()); + ret.append(" --db=<db_path> <new_column_family_name>"); + ret.append("\n"); +} + +CreateColumnFamilyCommand::CreateColumnFamilyCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, {ARG_DB}) { + if (params.size() != 1) { + exec_state_ = LDBCommandExecuteResult::Failed( + "new column family name must be specified"); + } else { + new_cf_name_ = params[0]; + } +} + +void CreateColumnFamilyCommand::DoCommand() { + ColumnFamilyHandle* new_cf_handle = nullptr; + Status st = db_->CreateColumnFamily(options_, new_cf_name_, &new_cf_handle); + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed( + "Fail to create new column family: " + st.ToString()); + } + delete new_cf_handle; + CloseDB(); +} + +void DropColumnFamilyCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DropColumnFamilyCommand::Name()); + ret.append(" --db=<db_path> <column_family_name_to_drop>"); + ret.append("\n"); +} + +DropColumnFamilyCommand::DropColumnFamilyCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, {ARG_DB}) { + if (params.size() != 1) { + exec_state_ = LDBCommandExecuteResult::Failed( + "The name of column family to drop must be specified"); + } else { + cf_name_to_drop_ = params[0]; + } +} + +void DropColumnFamilyCommand::DoCommand() { + auto iter = cf_handles_.find(cf_name_to_drop_); + if (iter == cf_handles_.end()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Column family: " + cf_name_to_drop_ + " doesn't exist in db."); + return; + } + ColumnFamilyHandle* cf_handle_to_drop = iter->second; + Status st = db_->DropColumnFamily(cf_handle_to_drop); + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed( + "Fail to drop column family: " + st.ToString()); + } + CloseDB(); +} + +// ---------------------------------------------------------------------------- +namespace { + +// This function only called when it's the sane case of >1 buckets in time-range +// Also called only when timekv falls between ttl_start and ttl_end provided +void IncBucketCounts(std::vector<uint64_t>& bucket_counts, int ttl_start, + int time_range, int bucket_size, int timekv, + int num_buckets) { +#ifdef NDEBUG + (void)time_range; + (void)num_buckets; +#endif + assert(time_range > 0 && timekv >= ttl_start && bucket_size > 0 && + timekv < (ttl_start + time_range) && num_buckets > 1); + int bucket = (timekv - ttl_start) / bucket_size; + bucket_counts[bucket]++; +} + +void PrintBucketCounts(const std::vector<uint64_t>& bucket_counts, + int ttl_start, int ttl_end, int bucket_size, + int num_buckets) { + int time_point = ttl_start; + for(int i = 0; i < num_buckets - 1; i++, time_point += bucket_size) { + fprintf(stdout, "Keys in range %s to %s : %lu\n", + TimeToHumanString(time_point).c_str(), + TimeToHumanString(time_point + bucket_size).c_str(), + (unsigned long)bucket_counts[i]); + } + fprintf(stdout, "Keys in range %s to %s : %lu\n", + TimeToHumanString(time_point).c_str(), + TimeToHumanString(ttl_end).c_str(), + (unsigned long)bucket_counts[num_buckets - 1]); +} + +} // namespace + +const std::string InternalDumpCommand::ARG_COUNT_ONLY = "count_only"; +const std::string InternalDumpCommand::ARG_COUNT_DELIM = "count_delim"; +const std::string InternalDumpCommand::ARG_STATS = "stats"; +const std::string InternalDumpCommand::ARG_INPUT_KEY_HEX = "input_key_hex"; + +InternalDumpCommand::InternalDumpCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, true, + BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, + ARG_TO, ARG_MAX_KEYS, ARG_COUNT_ONLY, + ARG_COUNT_DELIM, ARG_STATS, ARG_INPUT_KEY_HEX})), + has_from_(false), + has_to_(false), + max_keys_(-1), + delim_("."), + count_only_(false), + count_delim_(false), + print_stats_(false), + is_input_key_hex_(false) { + has_from_ = ParseStringOption(options, ARG_FROM, &from_); + has_to_ = ParseStringOption(options, ARG_TO, &to_); + + ParseIntOption(options, ARG_MAX_KEYS, max_keys_, exec_state_); + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_COUNT_DELIM); + if (itr != options.end()) { + delim_ = itr->second; + count_delim_ = true; + // fprintf(stdout,"delim = %c\n",delim_[0]); + } else { + count_delim_ = IsFlagPresent(flags, ARG_COUNT_DELIM); + delim_="."; + } + + print_stats_ = IsFlagPresent(flags, ARG_STATS); + count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY); + is_input_key_hex_ = IsFlagPresent(flags, ARG_INPUT_KEY_HEX); + + if (is_input_key_hex_) { + if (has_from_) { + from_ = HexToString(from_); + } + if (has_to_) { + to_ = HexToString(to_); + } + } +} + +void InternalDumpCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(InternalDumpCommand::Name()); + ret.append(HelpRangeCmdArgs()); + ret.append(" [--" + ARG_INPUT_KEY_HEX + "]"); + ret.append(" [--" + ARG_MAX_KEYS + "=<N>]"); + ret.append(" [--" + ARG_COUNT_ONLY + "]"); + ret.append(" [--" + ARG_COUNT_DELIM + "=<char>]"); + ret.append(" [--" + ARG_STATS + "]"); + ret.append("\n"); +} + +void InternalDumpCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + + if (print_stats_) { + std::string stats; + if (db_->GetProperty(GetCfHandle(), "rocksdb.stats", &stats)) { + fprintf(stdout, "%s\n", stats.c_str()); + } + } + + // Cast as DBImpl to get internal iterator + std::vector<KeyVersion> key_versions; + Status st = GetAllKeyVersions(db_, GetCfHandle(), from_, to_, max_keys_, + &key_versions); + if (!st.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + return; + } + std::string rtype1, rtype2, row, val; + rtype2 = ""; + uint64_t c=0; + uint64_t s1=0,s2=0; + + long long count = 0; + for (auto& key_version : key_versions) { + InternalKey ikey(key_version.user_key, key_version.sequence, + static_cast<ValueType>(key_version.type)); + if (has_to_ && ikey.user_key() == to_) { + // GetAllKeyVersions() includes keys with user key `to_`, but idump has + // traditionally excluded such keys. + break; + } + ++count; + int k; + if (count_delim_) { + rtype1 = ""; + s1=0; + row = ikey.Encode().ToString(); + val = key_version.value; + for(k=0;row[k]!='\x01' && row[k]!='\0';k++) + s1++; + for(k=0;val[k]!='\x01' && val[k]!='\0';k++) + s1++; + for(int j=0;row[j]!=delim_[0] && row[j]!='\0' && row[j]!='\x01';j++) + rtype1+=row[j]; + if(rtype2.compare("") && rtype2.compare(rtype1)!=0) { + fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n", + rtype2.c_str(), c, s2); + c=1; + s2=s1; + rtype2 = rtype1; + } else { + c++; + s2+=s1; + rtype2=rtype1; + } + } + + if (!count_only_ && !count_delim_) { + std::string key = ikey.DebugString(is_key_hex_); + std::string value = Slice(key_version.value).ToString(is_value_hex_); + std::cout << key << " => " << value << "\n"; + } + + // Terminate if maximum number of keys have been dumped + if (max_keys_ > 0 && count >= max_keys_) break; + } + if(count_delim_) { + fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n", + rtype2.c_str(), c, s2); + } else { + fprintf(stdout, "Internal keys in range: %lld\n", count); + } +} + +const std::string DBDumperCommand::ARG_COUNT_ONLY = "count_only"; +const std::string DBDumperCommand::ARG_COUNT_DELIM = "count_delim"; +const std::string DBDumperCommand::ARG_STATS = "stats"; +const std::string DBDumperCommand::ARG_TTL_BUCKET = "bucket"; + +DBDumperCommand::DBDumperCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, + BuildCmdLineOptions( + {ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, + ARG_TO, ARG_MAX_KEYS, ARG_COUNT_ONLY, ARG_COUNT_DELIM, + ARG_STATS, ARG_TTL_START, ARG_TTL_END, ARG_TTL_BUCKET, + ARG_TIMESTAMP, ARG_PATH})), + null_from_(true), + null_to_(true), + max_keys_(-1), + count_only_(false), + count_delim_(false), + print_stats_(false) { + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_FROM); + if (itr != options.end()) { + null_from_ = false; + from_ = itr->second; + } + + itr = options.find(ARG_TO); + if (itr != options.end()) { + null_to_ = false; + to_ = itr->second; + } + + itr = options.find(ARG_MAX_KEYS); + if (itr != options.end()) { + try { +#if defined(CYGWIN) + max_keys_ = strtol(itr->second.c_str(), 0, 10); +#else + max_keys_ = std::stoi(itr->second); +#endif + } catch (const std::invalid_argument&) { + exec_state_ = LDBCommandExecuteResult::Failed(ARG_MAX_KEYS + + " has an invalid value"); + } catch (const std::out_of_range&) { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_MAX_KEYS + " has a value out-of-range"); + } + } + itr = options.find(ARG_COUNT_DELIM); + if (itr != options.end()) { + delim_ = itr->second; + count_delim_ = true; + } else { + count_delim_ = IsFlagPresent(flags, ARG_COUNT_DELIM); + delim_="."; + } + + print_stats_ = IsFlagPresent(flags, ARG_STATS); + count_only_ = IsFlagPresent(flags, ARG_COUNT_ONLY); + + if (is_key_hex_) { + if (!null_from_) { + from_ = HexToString(from_); + } + if (!null_to_) { + to_ = HexToString(to_); + } + } + + itr = options.find(ARG_PATH); + if (itr != options.end()) { + path_ = itr->second; + if (db_path_.empty()) { + db_path_ = path_; + } + } +} + +void DBDumperCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DBDumperCommand::Name()); + ret.append(HelpRangeCmdArgs()); + ret.append(" [--" + ARG_TTL + "]"); + ret.append(" [--" + ARG_MAX_KEYS + "=<N>]"); + ret.append(" [--" + ARG_TIMESTAMP + "]"); + ret.append(" [--" + ARG_COUNT_ONLY + "]"); + ret.append(" [--" + ARG_COUNT_DELIM + "=<char>]"); + ret.append(" [--" + ARG_STATS + "]"); + ret.append(" [--" + ARG_TTL_BUCKET + "=<N>]"); + ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]"); + ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]"); + ret.append(" [--" + ARG_PATH + "=<path_to_a_file>]"); + ret.append("\n"); +} + +/** + * Handles two separate cases: + * + * 1) --db is specified - just dump the database. + * + * 2) --path is specified - determine based on file extension what dumping + * function to call. Please note that we intentionally use the extension + * and avoid probing the file contents under the assumption that renaming + * the files is not a supported scenario. + * + */ +void DBDumperCommand::DoCommand() { + if (!db_) { + assert(!path_.empty()); + std::string fileName = GetFileNameFromPath(path_); + uint64_t number; + FileType type; + + exec_state_ = LDBCommandExecuteResult::Succeed(""); + + if (!ParseFileName(fileName, &number, &type)) { + exec_state_ = + LDBCommandExecuteResult::Failed("Can't parse file type: " + path_); + return; + } + + switch (type) { + case kLogFile: + // TODO(myabandeh): allow configuring is_write_commited + DumpWalFile(options_, path_, /* print_header_ */ true, + /* print_values_ */ true, true /* is_write_commited */, + &exec_state_); + break; + case kTableFile: + DumpSstFile(options_, path_, is_key_hex_, /* show_properties */ true); + break; + case kDescriptorFile: + DumpManifestFile(options_, path_, /* verbose_ */ false, is_key_hex_, + /* json_ */ false); + break; + default: + exec_state_ = LDBCommandExecuteResult::Failed( + "File type not supported: " + path_); + break; + } + + } else { + DoDumpCommand(); + } +} + +void DBDumperCommand::DoDumpCommand() { + assert(nullptr != db_); + assert(path_.empty()); + + // Parse command line args + uint64_t count = 0; + if (print_stats_) { + std::string stats; + if (db_->GetProperty("rocksdb.stats", &stats)) { + fprintf(stdout, "%s\n", stats.c_str()); + } + } + + // Setup key iterator + ReadOptions scan_read_opts; + scan_read_opts.total_order_seek = true; + Iterator* iter = db_->NewIterator(scan_read_opts, GetCfHandle()); + Status st = iter->status(); + if (!st.ok()) { + exec_state_ = + LDBCommandExecuteResult::Failed("Iterator error." + st.ToString()); + } + + if (!null_from_) { + iter->Seek(from_); + } else { + iter->SeekToFirst(); + } + + int max_keys = max_keys_; + int ttl_start; + if (!ParseIntOption(option_map_, ARG_TTL_START, ttl_start, exec_state_)) { + ttl_start = DBWithTTLImpl::kMinTimestamp; // TTL introduction time + } + int ttl_end; + if (!ParseIntOption(option_map_, ARG_TTL_END, ttl_end, exec_state_)) { + ttl_end = DBWithTTLImpl::kMaxTimestamp; // Max time allowed by TTL feature + } + if (ttl_end < ttl_start) { + fprintf(stderr, "Error: End time can't be less than start time\n"); + delete iter; + return; + } + int time_range = ttl_end - ttl_start; + int bucket_size; + if (!ParseIntOption(option_map_, ARG_TTL_BUCKET, bucket_size, exec_state_) || + bucket_size <= 0) { + bucket_size = time_range; // Will have just 1 bucket by default + } + //cretaing variables for row count of each type + std::string rtype1, rtype2, row, val; + rtype2 = ""; + uint64_t c=0; + uint64_t s1=0,s2=0; + + // At this point, bucket_size=0 => time_range=0 + int num_buckets = (bucket_size >= time_range) + ? 1 + : ((time_range + bucket_size - 1) / bucket_size); + std::vector<uint64_t> bucket_counts(num_buckets, 0); + if (is_db_ttl_ && !count_only_ && timestamp_ && !count_delim_) { + fprintf(stdout, "Dumping key-values from %s to %s\n", + TimeToHumanString(ttl_start).c_str(), + TimeToHumanString(ttl_end).c_str()); + } + + HistogramImpl vsize_hist; + + for (; iter->Valid(); iter->Next()) { + int rawtime = 0; + // If end marker was specified, we stop before it + if (!null_to_ && (iter->key().ToString() >= to_)) + break; + // Terminate if maximum number of keys have been dumped + if (max_keys == 0) + break; + if (is_db_ttl_) { + TtlIterator* it_ttl = static_cast_with_check<TtlIterator, Iterator>(iter); + rawtime = it_ttl->timestamp(); + if (rawtime < ttl_start || rawtime >= ttl_end) { + continue; + } + } + if (max_keys > 0) { + --max_keys; + } + if (is_db_ttl_ && num_buckets > 1) { + IncBucketCounts(bucket_counts, ttl_start, time_range, bucket_size, + rawtime, num_buckets); + } + ++count; + if (count_delim_) { + rtype1 = ""; + row = iter->key().ToString(); + val = iter->value().ToString(); + s1 = row.size()+val.size(); + for(int j=0;row[j]!=delim_[0] && row[j]!='\0';j++) + rtype1+=row[j]; + if(rtype2.compare("") && rtype2.compare(rtype1)!=0) { + fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n", + rtype2.c_str(), c, s2); + c=1; + s2=s1; + rtype2 = rtype1; + } else { + c++; + s2+=s1; + rtype2=rtype1; + } + + } + + if (count_only_) { + vsize_hist.Add(iter->value().size()); + } + + if (!count_only_ && !count_delim_) { + if (is_db_ttl_ && timestamp_) { + fprintf(stdout, "%s ", TimeToHumanString(rawtime).c_str()); + } + std::string str = + PrintKeyValue(iter->key().ToString(), iter->value().ToString(), + is_key_hex_, is_value_hex_); + fprintf(stdout, "%s\n", str.c_str()); + } + } + + if (num_buckets > 1 && is_db_ttl_) { + PrintBucketCounts(bucket_counts, ttl_start, ttl_end, bucket_size, + num_buckets); + } else if(count_delim_) { + fprintf(stdout, "%s => count:%" PRIu64 "\tsize:%" PRIu64 "\n", + rtype2.c_str(), c, s2); + } else { + fprintf(stdout, "Keys in range: %" PRIu64 "\n", count); + } + + if (count_only_) { + fprintf(stdout, "Value size distribution: \n"); + fprintf(stdout, "%s\n", vsize_hist.ToString().c_str()); + } + // Clean up + delete iter; +} + +const std::string ReduceDBLevelsCommand::ARG_NEW_LEVELS = "new_levels"; +const std::string ReduceDBLevelsCommand::ARG_PRINT_OLD_LEVELS = + "print_old_levels"; + +ReduceDBLevelsCommand::ReduceDBLevelsCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions({ARG_NEW_LEVELS, ARG_PRINT_OLD_LEVELS})), + old_levels_(1 << 7), + new_levels_(-1), + print_old_levels_(false) { + ParseIntOption(option_map_, ARG_NEW_LEVELS, new_levels_, exec_state_); + print_old_levels_ = IsFlagPresent(flags, ARG_PRINT_OLD_LEVELS); + + if(new_levels_ <= 0) { + exec_state_ = LDBCommandExecuteResult::Failed( + " Use --" + ARG_NEW_LEVELS + " to specify a new level number\n"); + } +} + +std::vector<std::string> ReduceDBLevelsCommand::PrepareArgs( + const std::string& db_path, int new_levels, bool print_old_level) { + std::vector<std::string> ret; + ret.push_back("reduce_levels"); + ret.push_back("--" + ARG_DB + "=" + db_path); + ret.push_back("--" + ARG_NEW_LEVELS + "=" + + ROCKSDB_NAMESPACE::ToString(new_levels)); + if(print_old_level) { + ret.push_back("--" + ARG_PRINT_OLD_LEVELS); + } + return ret; +} + +void ReduceDBLevelsCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ReduceDBLevelsCommand::Name()); + ret.append(" --" + ARG_NEW_LEVELS + "=<New number of levels>"); + ret.append(" [--" + ARG_PRINT_OLD_LEVELS + "]"); + ret.append("\n"); +} + +Options ReduceDBLevelsCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + opt.num_levels = old_levels_; + opt.max_bytes_for_level_multiplier_additional.resize(opt.num_levels, 1); + // Disable size compaction + opt.max_bytes_for_level_base = 1ULL << 50; + opt.max_bytes_for_level_multiplier = 1; + return opt; +} + +Status ReduceDBLevelsCommand::GetOldNumOfLevels(Options& opt, + int* levels) { + ImmutableDBOptions db_options(opt); + EnvOptions soptions; + std::shared_ptr<Cache> tc( + NewLRUCache(opt.max_open_files - 10, opt.table_cache_numshardbits)); + const InternalKeyComparator cmp(opt.comparator); + WriteController wc(opt.delayed_write_rate); + WriteBufferManager wb(opt.db_write_buffer_size); + VersionSet versions(db_path_, &db_options, soptions, tc.get(), &wb, &wc, + /*block_cache_tracer=*/nullptr); + std::vector<ColumnFamilyDescriptor> dummy; + ColumnFamilyDescriptor dummy_descriptor(kDefaultColumnFamilyName, + ColumnFamilyOptions(opt)); + dummy.push_back(dummy_descriptor); + // We rely the VersionSet::Recover to tell us the internal data structures + // in the db. And the Recover() should never do any change + // (like LogAndApply) to the manifest file. + Status st = versions.Recover(dummy); + if (!st.ok()) { + return st; + } + int max = -1; + auto default_cfd = versions.GetColumnFamilySet()->GetDefault(); + for (int i = 0; i < default_cfd->NumberLevels(); i++) { + if (default_cfd->current()->storage_info()->NumLevelFiles(i)) { + max = i; + } + } + + *levels = max + 1; + return st; +} + +void ReduceDBLevelsCommand::DoCommand() { + if (new_levels_ <= 1) { + exec_state_ = + LDBCommandExecuteResult::Failed("Invalid number of levels.\n"); + return; + } + + Status st; + Options opt = PrepareOptionsForOpenDB(); + int old_level_num = -1; + opt.file_system.reset(new LegacyFileSystemWrapper(opt.env)); + ; + st = GetOldNumOfLevels(opt, &old_level_num); + if (!st.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + return; + } + + if (print_old_levels_) { + fprintf(stdout, "The old number of levels in use is %d\n", old_level_num); + } + + if (old_level_num <= new_levels_) { + return; + } + + old_levels_ = old_level_num; + + OpenDB(); + if (exec_state_.IsFailed()) { + return; + } + assert(db_ != nullptr); + // Compact the whole DB to put all files to the highest level. + fprintf(stdout, "Compacting the db...\n"); + db_->CompactRange(CompactRangeOptions(), GetCfHandle(), nullptr, nullptr); + CloseDB(); + + EnvOptions soptions; + st = VersionSet::ReduceNumberOfLevels(db_path_, &opt, soptions, new_levels_); + if (!st.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + return; + } +} + +const std::string ChangeCompactionStyleCommand::ARG_OLD_COMPACTION_STYLE = + "old_compaction_style"; +const std::string ChangeCompactionStyleCommand::ARG_NEW_COMPACTION_STYLE = + "new_compaction_style"; + +ChangeCompactionStyleCommand::ChangeCompactionStyleCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions( + {ARG_OLD_COMPACTION_STYLE, ARG_NEW_COMPACTION_STYLE})), + old_compaction_style_(-1), + new_compaction_style_(-1) { + ParseIntOption(option_map_, ARG_OLD_COMPACTION_STYLE, old_compaction_style_, + exec_state_); + if (old_compaction_style_ != kCompactionStyleLevel && + old_compaction_style_ != kCompactionStyleUniversal) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Use --" + ARG_OLD_COMPACTION_STYLE + " to specify old compaction " + + "style. Check ldb help for proper compaction style value.\n"); + return; + } + + ParseIntOption(option_map_, ARG_NEW_COMPACTION_STYLE, new_compaction_style_, + exec_state_); + if (new_compaction_style_ != kCompactionStyleLevel && + new_compaction_style_ != kCompactionStyleUniversal) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Use --" + ARG_NEW_COMPACTION_STYLE + " to specify new compaction " + + "style. Check ldb help for proper compaction style value.\n"); + return; + } + + if (new_compaction_style_ == old_compaction_style_) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Old compaction style is the same as new compaction style. " + "Nothing to do.\n"); + return; + } + + if (old_compaction_style_ == kCompactionStyleUniversal && + new_compaction_style_ == kCompactionStyleLevel) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Convert from universal compaction to level compaction. " + "Nothing to do.\n"); + return; + } +} + +void ChangeCompactionStyleCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ChangeCompactionStyleCommand::Name()); + ret.append(" --" + ARG_OLD_COMPACTION_STYLE + "=<Old compaction style: 0 " + + "for level compaction, 1 for universal compaction>"); + ret.append(" --" + ARG_NEW_COMPACTION_STYLE + "=<New compaction style: 0 " + + "for level compaction, 1 for universal compaction>"); + ret.append("\n"); +} + +Options ChangeCompactionStyleCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + + if (old_compaction_style_ == kCompactionStyleLevel && + new_compaction_style_ == kCompactionStyleUniversal) { + // In order to convert from level compaction to universal compaction, we + // need to compact all data into a single file and move it to level 0. + opt.disable_auto_compactions = true; + opt.target_file_size_base = INT_MAX; + opt.target_file_size_multiplier = 1; + opt.max_bytes_for_level_base = INT_MAX; + opt.max_bytes_for_level_multiplier = 1; + } + + return opt; +} + +void ChangeCompactionStyleCommand::DoCommand() { + // print db stats before we have made any change + std::string property; + std::string files_per_level; + for (int i = 0; i < db_->NumberLevels(GetCfHandle()); i++) { + db_->GetProperty(GetCfHandle(), + "rocksdb.num-files-at-level" + NumberToString(i), + &property); + + // format print string + char buf[100]; + snprintf(buf, sizeof(buf), "%s%s", (i ? "," : ""), property.c_str()); + files_per_level += buf; + } + fprintf(stdout, "files per level before compaction: %s\n", + files_per_level.c_str()); + + // manual compact into a single file and move the file to level 0 + CompactRangeOptions compact_options; + compact_options.change_level = true; + compact_options.target_level = 0; + db_->CompactRange(compact_options, GetCfHandle(), nullptr, nullptr); + + // verify compaction result + files_per_level = ""; + int num_files = 0; + for (int i = 0; i < db_->NumberLevels(GetCfHandle()); i++) { + db_->GetProperty(GetCfHandle(), + "rocksdb.num-files-at-level" + NumberToString(i), + &property); + + // format print string + char buf[100]; + snprintf(buf, sizeof(buf), "%s%s", (i ? "," : ""), property.c_str()); + files_per_level += buf; + + num_files = atoi(property.c_str()); + + // level 0 should have only 1 file + if (i == 0 && num_files != 1) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Number of db files at " + "level 0 after compaction is " + + ToString(num_files) + ", not 1.\n"); + return; + } + // other levels should have no file + if (i > 0 && num_files != 0) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Number of db files at " + "level " + + ToString(i) + " after compaction is " + ToString(num_files) + + ", not 0.\n"); + return; + } + } + + fprintf(stdout, "files per level after compaction: %s\n", + files_per_level.c_str()); +} + +// ---------------------------------------------------------------------------- + +namespace { + +struct StdErrReporter : public log::Reader::Reporter { + void Corruption(size_t /*bytes*/, const Status& s) override { + std::cerr << "Corruption detected in log file " << s.ToString() << "\n"; + } +}; + +class InMemoryHandler : public WriteBatch::Handler { + public: + InMemoryHandler(std::stringstream& row, bool print_values, + bool write_after_commit = false) + : Handler(), + row_(row), + print_values_(print_values), + write_after_commit_(write_after_commit) {} + + void commonPutMerge(const Slice& key, const Slice& value) { + std::string k = LDBCommand::StringToHex(key.ToString()); + if (print_values_) { + std::string v = LDBCommand::StringToHex(value.ToString()); + row_ << k << " : "; + row_ << v << " "; + } else { + row_ << k << " "; + } + } + + Status PutCF(uint32_t cf, const Slice& key, const Slice& value) override { + row_ << "PUT(" << cf << ") : "; + commonPutMerge(key, value); + return Status::OK(); + } + + Status MergeCF(uint32_t cf, const Slice& key, const Slice& value) override { + row_ << "MERGE(" << cf << ") : "; + commonPutMerge(key, value); + return Status::OK(); + } + + Status MarkNoop(bool) override { + row_ << "NOOP "; + return Status::OK(); + } + + Status DeleteCF(uint32_t cf, const Slice& key) override { + row_ << "DELETE(" << cf << ") : "; + row_ << LDBCommand::StringToHex(key.ToString()) << " "; + return Status::OK(); + } + + Status SingleDeleteCF(uint32_t cf, const Slice& key) override { + row_ << "SINGLE_DELETE(" << cf << ") : "; + row_ << LDBCommand::StringToHex(key.ToString()) << " "; + return Status::OK(); + } + + Status DeleteRangeCF(uint32_t cf, const Slice& begin_key, + const Slice& end_key) override { + row_ << "DELETE_RANGE(" << cf << ") : "; + row_ << LDBCommand::StringToHex(begin_key.ToString()) << " "; + row_ << LDBCommand::StringToHex(end_key.ToString()) << " "; + return Status::OK(); + } + + Status MarkBeginPrepare(bool unprepare) override { + row_ << "BEGIN_PREPARE("; + row_ << (unprepare ? "true" : "false") << ") "; + return Status::OK(); + } + + Status MarkEndPrepare(const Slice& xid) override { + row_ << "END_PREPARE("; + row_ << LDBCommand::StringToHex(xid.ToString()) << ") "; + return Status::OK(); + } + + Status MarkRollback(const Slice& xid) override { + row_ << "ROLLBACK("; + row_ << LDBCommand::StringToHex(xid.ToString()) << ") "; + return Status::OK(); + } + + Status MarkCommit(const Slice& xid) override { + row_ << "COMMIT("; + row_ << LDBCommand::StringToHex(xid.ToString()) << ") "; + return Status::OK(); + } + + ~InMemoryHandler() override {} + + protected: + bool WriteAfterCommit() const override { return write_after_commit_; } + + private: + std::stringstream& row_; + bool print_values_; + bool write_after_commit_; +}; + +void DumpWalFile(Options options, std::string wal_file, bool print_header, + bool print_values, bool is_write_committed, + LDBCommandExecuteResult* exec_state) { + Env* env = options.env; + EnvOptions soptions(options); + std::unique_ptr<SequentialFileReader> wal_file_reader; + + Status status; + { + std::unique_ptr<SequentialFile> file; + status = env->NewSequentialFile(wal_file, &file, soptions); + if (status.ok()) { + wal_file_reader.reset(new SequentialFileReader( + NewLegacySequentialFileWrapper(file), wal_file)); + } + } + if (!status.ok()) { + if (exec_state) { + *exec_state = LDBCommandExecuteResult::Failed("Failed to open WAL file " + + status.ToString()); + } else { + std::cerr << "Error: Failed to open WAL file " << status.ToString() + << std::endl; + } + } else { + StdErrReporter reporter; + uint64_t log_number; + FileType type; + + // we need the log number, but ParseFilename expects dbname/NNN.log. + std::string sanitized = wal_file; + size_t lastslash = sanitized.rfind('/'); + if (lastslash != std::string::npos) + sanitized = sanitized.substr(lastslash + 1); + if (!ParseFileName(sanitized, &log_number, &type)) { + // bogus input, carry on as best we can + log_number = 0; + } + log::Reader reader(options.info_log, std::move(wal_file_reader), &reporter, + true /* checksum */, log_number); + std::string scratch; + WriteBatch batch; + Slice record; + std::stringstream row; + if (print_header) { + std::cout << "Sequence,Count,ByteSize,Physical Offset,Key(s)"; + if (print_values) { + std::cout << " : value "; + } + std::cout << "\n"; + } + while (reader.ReadRecord(&record, &scratch)) { + row.str(""); + if (record.size() < WriteBatchInternal::kHeader) { + reporter.Corruption(record.size(), + Status::Corruption("log record too small")); + } else { + WriteBatchInternal::SetContents(&batch, record); + row << WriteBatchInternal::Sequence(&batch) << ","; + row << WriteBatchInternal::Count(&batch) << ","; + row << WriteBatchInternal::ByteSize(&batch) << ","; + row << reader.LastRecordOffset() << ","; + InMemoryHandler handler(row, print_values, is_write_committed); + batch.Iterate(&handler); + row << "\n"; + } + std::cout << row.str(); + } + } +} + +} // namespace + +const std::string WALDumperCommand::ARG_WAL_FILE = "walfile"; +const std::string WALDumperCommand::ARG_WRITE_COMMITTED = "write_committed"; +const std::string WALDumperCommand::ARG_PRINT_VALUE = "print_value"; +const std::string WALDumperCommand::ARG_PRINT_HEADER = "header"; + +WALDumperCommand::WALDumperCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, + BuildCmdLineOptions({ARG_WAL_FILE, ARG_WRITE_COMMITTED, + ARG_PRINT_HEADER, ARG_PRINT_VALUE})), + print_header_(false), + print_values_(false), + is_write_committed_(false) { + wal_file_.clear(); + + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_WAL_FILE); + if (itr != options.end()) { + wal_file_ = itr->second; + } + + + print_header_ = IsFlagPresent(flags, ARG_PRINT_HEADER); + print_values_ = IsFlagPresent(flags, ARG_PRINT_VALUE); + is_write_committed_ = ParseBooleanOption(options, ARG_WRITE_COMMITTED, true); + + if (wal_file_.empty()) { + exec_state_ = LDBCommandExecuteResult::Failed("Argument " + ARG_WAL_FILE + + " must be specified."); + } +} + +void WALDumperCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(WALDumperCommand::Name()); + ret.append(" --" + ARG_WAL_FILE + "=<write_ahead_log_file_path>"); + ret.append(" [--" + ARG_PRINT_HEADER + "] "); + ret.append(" [--" + ARG_PRINT_VALUE + "] "); + ret.append(" [--" + ARG_WRITE_COMMITTED + "=true|false] "); + ret.append("\n"); +} + +void WALDumperCommand::DoCommand() { + DumpWalFile(options_, wal_file_, print_header_, print_values_, + is_write_committed_, &exec_state_); +} + +// ---------------------------------------------------------------------------- + +GetCommand::GetCommand(const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, true, + BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) { + if (params.size() != 1) { + exec_state_ = LDBCommandExecuteResult::Failed( + "<key> must be specified for the get command"); + } else { + key_ = params.at(0); + } + + if (is_key_hex_) { + key_ = HexToString(key_); + } +} + +void GetCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(GetCommand::Name()); + ret.append(" <key>"); + ret.append(" [--" + ARG_TTL + "]"); + ret.append("\n"); +} + +void GetCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + std::string value; + Status st = db_->Get(ReadOptions(), GetCfHandle(), key_, &value); + if (st.ok()) { + fprintf(stdout, "%s\n", + (is_value_hex_ ? StringToHex(value) : value).c_str()); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +// ---------------------------------------------------------------------------- + +ApproxSizeCommand::ApproxSizeCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, + BuildCmdLineOptions( + {ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, ARG_TO})) { + if (options.find(ARG_FROM) != options.end()) { + start_key_ = options.find(ARG_FROM)->second; + } else { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_FROM + " must be specified for approxsize command"); + return; + } + + if (options.find(ARG_TO) != options.end()) { + end_key_ = options.find(ARG_TO)->second; + } else { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_TO + " must be specified for approxsize command"); + return; + } + + if (is_key_hex_) { + start_key_ = HexToString(start_key_); + end_key_ = HexToString(end_key_); + } +} + +void ApproxSizeCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ApproxSizeCommand::Name()); + ret.append(HelpRangeCmdArgs()); + ret.append("\n"); +} + +void ApproxSizeCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + Range ranges[1]; + ranges[0] = Range(start_key_, end_key_); + uint64_t sizes[1]; + db_->GetApproximateSizes(GetCfHandle(), ranges, 1, sizes); + fprintf(stdout, "%lu\n", (unsigned long)sizes[0]); + /* Weird that GetApproximateSizes() returns void, although documentation + * says that it returns a Status object. + if (!st.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } + */ +} + +// ---------------------------------------------------------------------------- + +BatchPutCommand::BatchPutCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, + ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) { + if (params.size() < 2) { + exec_state_ = LDBCommandExecuteResult::Failed( + "At least one <key> <value> pair must be specified batchput."); + } else if (params.size() % 2 != 0) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Equal number of <key>s and <value>s must be specified for batchput."); + } else { + for (size_t i = 0; i < params.size(); i += 2) { + std::string key = params.at(i); + std::string value = params.at(i + 1); + key_values_.push_back(std::pair<std::string, std::string>( + is_key_hex_ ? HexToString(key) : key, + is_value_hex_ ? HexToString(value) : value)); + } + } + create_if_missing_ = IsFlagPresent(flags_, ARG_CREATE_IF_MISSING); +} + +void BatchPutCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(BatchPutCommand::Name()); + ret.append(" <key> <value> [<key> <value>] [..]"); + ret.append(" [--" + ARG_TTL + "]"); + ret.append("\n"); +} + +void BatchPutCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + WriteBatch batch; + + for (std::vector<std::pair<std::string, std::string>>::const_iterator itr = + key_values_.begin(); + itr != key_values_.end(); ++itr) { + batch.Put(GetCfHandle(), itr->first, itr->second); + } + Status st = db_->Write(WriteOptions(), &batch); + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +Options BatchPutCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + opt.create_if_missing = create_if_missing_; + return opt; +} + +// ---------------------------------------------------------------------------- + +ScanCommand::ScanCommand(const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, true, + BuildCmdLineOptions({ARG_TTL, ARG_NO_VALUE, ARG_HEX, ARG_KEY_HEX, + ARG_TO, ARG_VALUE_HEX, ARG_FROM, ARG_TIMESTAMP, + ARG_MAX_KEYS, ARG_TTL_START, ARG_TTL_END})), + start_key_specified_(false), + end_key_specified_(false), + max_keys_scanned_(-1), + no_value_(false) { + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_FROM); + if (itr != options.end()) { + start_key_ = itr->second; + if (is_key_hex_) { + start_key_ = HexToString(start_key_); + } + start_key_specified_ = true; + } + itr = options.find(ARG_TO); + if (itr != options.end()) { + end_key_ = itr->second; + if (is_key_hex_) { + end_key_ = HexToString(end_key_); + } + end_key_specified_ = true; + } + + std::vector<std::string>::const_iterator vitr = + std::find(flags.begin(), flags.end(), ARG_NO_VALUE); + if (vitr != flags.end()) { + no_value_ = true; + } + + itr = options.find(ARG_MAX_KEYS); + if (itr != options.end()) { + try { +#if defined(CYGWIN) + max_keys_scanned_ = strtol(itr->second.c_str(), 0, 10); +#else + max_keys_scanned_ = std::stoi(itr->second); +#endif + } catch (const std::invalid_argument&) { + exec_state_ = LDBCommandExecuteResult::Failed(ARG_MAX_KEYS + + " has an invalid value"); + } catch (const std::out_of_range&) { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_MAX_KEYS + " has a value out-of-range"); + } + } +} + +void ScanCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ScanCommand::Name()); + ret.append(HelpRangeCmdArgs()); + ret.append(" [--" + ARG_TTL + "]"); + ret.append(" [--" + ARG_TIMESTAMP + "]"); + ret.append(" [--" + ARG_MAX_KEYS + "=<N>q] "); + ret.append(" [--" + ARG_TTL_START + "=<N>:- is inclusive]"); + ret.append(" [--" + ARG_TTL_END + "=<N>:- is exclusive]"); + ret.append(" [--" + ARG_NO_VALUE + "]"); + ret.append("\n"); +} + +void ScanCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + + int num_keys_scanned = 0; + ReadOptions scan_read_opts; + scan_read_opts.total_order_seek = true; + Iterator* it = db_->NewIterator(scan_read_opts, GetCfHandle()); + if (start_key_specified_) { + it->Seek(start_key_); + } else { + it->SeekToFirst(); + } + int ttl_start; + if (!ParseIntOption(option_map_, ARG_TTL_START, ttl_start, exec_state_)) { + ttl_start = DBWithTTLImpl::kMinTimestamp; // TTL introduction time + } + int ttl_end; + if (!ParseIntOption(option_map_, ARG_TTL_END, ttl_end, exec_state_)) { + ttl_end = DBWithTTLImpl::kMaxTimestamp; // Max time allowed by TTL feature + } + if (ttl_end < ttl_start) { + fprintf(stderr, "Error: End time can't be less than start time\n"); + delete it; + return; + } + if (is_db_ttl_ && timestamp_) { + fprintf(stdout, "Scanning key-values from %s to %s\n", + TimeToHumanString(ttl_start).c_str(), + TimeToHumanString(ttl_end).c_str()); + } + for ( ; + it->Valid() && (!end_key_specified_ || it->key().ToString() < end_key_); + it->Next()) { + if (is_db_ttl_) { + TtlIterator* it_ttl = static_cast_with_check<TtlIterator, Iterator>(it); + int rawtime = it_ttl->timestamp(); + if (rawtime < ttl_start || rawtime >= ttl_end) { + continue; + } + if (timestamp_) { + fprintf(stdout, "%s ", TimeToHumanString(rawtime).c_str()); + } + } + + Slice key_slice = it->key(); + + std::string formatted_key; + if (is_key_hex_) { + formatted_key = "0x" + key_slice.ToString(true /* hex */); + key_slice = formatted_key; + } else if (ldb_options_.key_formatter) { + formatted_key = ldb_options_.key_formatter->Format(key_slice); + key_slice = formatted_key; + } + + if (no_value_) { + fprintf(stdout, "%.*s\n", static_cast<int>(key_slice.size()), + key_slice.data()); + } else { + Slice val_slice = it->value(); + std::string formatted_value; + if (is_value_hex_) { + formatted_value = "0x" + val_slice.ToString(true /* hex */); + val_slice = formatted_value; + } + fprintf(stdout, "%.*s : %.*s\n", static_cast<int>(key_slice.size()), + key_slice.data(), static_cast<int>(val_slice.size()), + val_slice.data()); + } + + num_keys_scanned++; + if (max_keys_scanned_ >= 0 && num_keys_scanned >= max_keys_scanned_) { + break; + } + } + if (!it->status().ok()) { // Check for any errors found during the scan + exec_state_ = LDBCommandExecuteResult::Failed(it->status().ToString()); + } + delete it; +} + +// ---------------------------------------------------------------------------- + +DeleteCommand::DeleteCommand(const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) { + if (params.size() != 1) { + exec_state_ = LDBCommandExecuteResult::Failed( + "KEY must be specified for the delete command"); + } else { + key_ = params.at(0); + if (is_key_hex_) { + key_ = HexToString(key_); + } + } +} + +void DeleteCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DeleteCommand::Name() + " <key>"); + ret.append("\n"); +} + +void DeleteCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + Status st = db_->Delete(WriteOptions(), GetCfHandle(), key_); + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +DeleteRangeCommand::DeleteRangeCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) { + if (params.size() != 2) { + exec_state_ = LDBCommandExecuteResult::Failed( + "begin and end keys must be specified for the delete command"); + } else { + begin_key_ = params.at(0); + end_key_ = params.at(1); + if (is_key_hex_) { + begin_key_ = HexToString(begin_key_); + end_key_ = HexToString(end_key_); + } + } +} + +void DeleteRangeCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DeleteRangeCommand::Name() + " <begin key> <end key>"); + ret.append("\n"); +} + +void DeleteRangeCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + Status st = + db_->DeleteRange(WriteOptions(), GetCfHandle(), begin_key_, end_key_); + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +PutCommand::PutCommand(const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, + BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, + ARG_VALUE_HEX, ARG_CREATE_IF_MISSING})) { + if (params.size() != 2) { + exec_state_ = LDBCommandExecuteResult::Failed( + "<key> and <value> must be specified for the put command"); + } else { + key_ = params.at(0); + value_ = params.at(1); + } + + if (is_key_hex_) { + key_ = HexToString(key_); + } + + if (is_value_hex_) { + value_ = HexToString(value_); + } + create_if_missing_ = IsFlagPresent(flags_, ARG_CREATE_IF_MISSING); +} + +void PutCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(PutCommand::Name()); + ret.append(" <key> <value> "); + ret.append(" [--" + ARG_TTL + "]"); + ret.append("\n"); +} + +void PutCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + Status st = db_->Put(WriteOptions(), GetCfHandle(), key_, value_); + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +Options PutCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + opt.create_if_missing = create_if_missing_; + return opt; +} + +// ---------------------------------------------------------------------------- + +const char* DBQuerierCommand::HELP_CMD = "help"; +const char* DBQuerierCommand::GET_CMD = "get"; +const char* DBQuerierCommand::PUT_CMD = "put"; +const char* DBQuerierCommand::DELETE_CMD = "delete"; + +DBQuerierCommand::DBQuerierCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, false, + BuildCmdLineOptions({ARG_TTL, ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX})) { + +} + +void DBQuerierCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DBQuerierCommand::Name()); + ret.append(" [--" + ARG_TTL + "]"); + ret.append("\n"); + ret.append(" Starts a REPL shell. Type help for list of available " + "commands."); + ret.append("\n"); +} + +void DBQuerierCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + + ReadOptions read_options; + WriteOptions write_options; + + std::string line; + std::string key; + std::string value; + while (getline(std::cin, line, '\n')) { + // Parse line into std::vector<std::string> + std::vector<std::string> tokens; + size_t pos = 0; + while (true) { + size_t pos2 = line.find(' ', pos); + if (pos2 == std::string::npos) { + break; + } + tokens.push_back(line.substr(pos, pos2-pos)); + pos = pos2 + 1; + } + tokens.push_back(line.substr(pos)); + + const std::string& cmd = tokens[0]; + + if (cmd == HELP_CMD) { + fprintf(stdout, + "get <key>\n" + "put <key> <value>\n" + "delete <key>\n"); + } else if (cmd == DELETE_CMD && tokens.size() == 2) { + key = (is_key_hex_ ? HexToString(tokens[1]) : tokens[1]); + db_->Delete(write_options, GetCfHandle(), Slice(key)); + fprintf(stdout, "Successfully deleted %s\n", tokens[1].c_str()); + } else if (cmd == PUT_CMD && tokens.size() == 3) { + key = (is_key_hex_ ? HexToString(tokens[1]) : tokens[1]); + value = (is_value_hex_ ? HexToString(tokens[2]) : tokens[2]); + db_->Put(write_options, GetCfHandle(), Slice(key), Slice(value)); + fprintf(stdout, "Successfully put %s %s\n", + tokens[1].c_str(), tokens[2].c_str()); + } else if (cmd == GET_CMD && tokens.size() == 2) { + key = (is_key_hex_ ? HexToString(tokens[1]) : tokens[1]); + if (db_->Get(read_options, GetCfHandle(), Slice(key), &value).ok()) { + fprintf(stdout, "%s\n", PrintKeyValue(key, value, + is_key_hex_, is_value_hex_).c_str()); + } else { + fprintf(stdout, "Not found %s\n", tokens[1].c_str()); + } + } else { + fprintf(stdout, "Unknown command %s\n", line.c_str()); + } + } +} + +// ---------------------------------------------------------------------------- + +CheckConsistencyCommand::CheckConsistencyCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, BuildCmdLineOptions({})) {} + +void CheckConsistencyCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(CheckConsistencyCommand::Name()); + ret.append("\n"); +} + +void CheckConsistencyCommand::DoCommand() { + Options opt = PrepareOptionsForOpenDB(); + opt.paranoid_checks = true; + if (!exec_state_.IsNotStarted()) { + return; + } + DB* db; + Status st = DB::OpenForReadOnly(opt, db_path_, &db, false); + delete db; + if (st.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +// ---------------------------------------------------------------------------- + +const std::string CheckPointCommand::ARG_CHECKPOINT_DIR = "checkpoint_dir"; + +CheckPointCommand::CheckPointCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false /* is_read_only */, + BuildCmdLineOptions({ARG_CHECKPOINT_DIR})) { + auto itr = options.find(ARG_CHECKPOINT_DIR); + if (itr != options.end()) { + checkpoint_dir_ = itr->second; + } +} + +void CheckPointCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(CheckPointCommand::Name()); + ret.append(" [--" + ARG_CHECKPOINT_DIR + "] "); + ret.append("\n"); +} + +void CheckPointCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + Checkpoint* checkpoint; + Status status = Checkpoint::Create(db_, &checkpoint); + status = checkpoint->CreateCheckpoint(checkpoint_dir_); + if (status.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(status.ToString()); + } +} + +// ---------------------------------------------------------------------------- + +RepairCommand::RepairCommand(const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false, BuildCmdLineOptions({})) {} + +void RepairCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(RepairCommand::Name()); + ret.append("\n"); +} + +void RepairCommand::DoCommand() { + Options options = PrepareOptionsForOpenDB(); + options.info_log.reset(new StderrLogger(InfoLogLevel::WARN_LEVEL)); + Status status = RepairDB(db_path_, options); + if (status.ok()) { + fprintf(stdout, "OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(status.ToString()); + } +} + +// ---------------------------------------------------------------------------- + +const std::string BackupableCommand::ARG_NUM_THREADS = "num_threads"; +const std::string BackupableCommand::ARG_BACKUP_ENV_URI = "backup_env_uri"; +const std::string BackupableCommand::ARG_BACKUP_DIR = "backup_dir"; +const std::string BackupableCommand::ARG_STDERR_LOG_LEVEL = "stderr_log_level"; + +BackupableCommand::BackupableCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, false /* is_read_only */, + BuildCmdLineOptions({ARG_BACKUP_ENV_URI, ARG_BACKUP_DIR, + ARG_NUM_THREADS, ARG_STDERR_LOG_LEVEL})), + num_threads_(1) { + auto itr = options.find(ARG_NUM_THREADS); + if (itr != options.end()) { + num_threads_ = std::stoi(itr->second); + } + itr = options.find(ARG_BACKUP_ENV_URI); + if (itr != options.end()) { + backup_env_uri_ = itr->second; + } + itr = options.find(ARG_BACKUP_DIR); + if (itr == options.end()) { + exec_state_ = LDBCommandExecuteResult::Failed("--" + ARG_BACKUP_DIR + + ": missing backup directory"); + } else { + backup_dir_ = itr->second; + } + + itr = options.find(ARG_STDERR_LOG_LEVEL); + if (itr != options.end()) { + int stderr_log_level = std::stoi(itr->second); + if (stderr_log_level < 0 || + stderr_log_level >= InfoLogLevel::NUM_INFO_LOG_LEVELS) { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_STDERR_LOG_LEVEL + " must be >= 0 and < " + + std::to_string(InfoLogLevel::NUM_INFO_LOG_LEVELS) + "."); + } else { + logger_.reset( + new StderrLogger(static_cast<InfoLogLevel>(stderr_log_level))); + } + } +} + +void BackupableCommand::Help(const std::string& name, std::string& ret) { + ret.append(" "); + ret.append(name); + ret.append(" [--" + ARG_BACKUP_ENV_URI + "] "); + ret.append(" [--" + ARG_BACKUP_DIR + "] "); + ret.append(" [--" + ARG_NUM_THREADS + "] "); + ret.append(" [--" + ARG_STDERR_LOG_LEVEL + "=<int (InfoLogLevel)>] "); + ret.append("\n"); +} + +// ---------------------------------------------------------------------------- + +BackupCommand::BackupCommand(const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : BackupableCommand(params, options, flags) {} + +void BackupCommand::Help(std::string& ret) { + BackupableCommand::Help(Name(), ret); +} + +void BackupCommand::DoCommand() { + BackupEngine* backup_engine; + Status status; + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + fprintf(stdout, "open db OK\n"); + Env* custom_env = nullptr; + Env::LoadEnv(backup_env_uri_, &custom_env, &backup_env_guard_); + assert(custom_env != nullptr); + + BackupableDBOptions backup_options = + BackupableDBOptions(backup_dir_, custom_env); + backup_options.info_log = logger_.get(); + backup_options.max_background_operations = num_threads_; + status = BackupEngine::Open(custom_env, backup_options, &backup_engine); + if (status.ok()) { + fprintf(stdout, "open backup engine OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(status.ToString()); + return; + } + status = backup_engine->CreateNewBackup(db_); + if (status.ok()) { + fprintf(stdout, "create new backup OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(status.ToString()); + return; + } +} + +// ---------------------------------------------------------------------------- + +RestoreCommand::RestoreCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : BackupableCommand(params, options, flags) {} + +void RestoreCommand::Help(std::string& ret) { + BackupableCommand::Help(Name(), ret); +} + +void RestoreCommand::DoCommand() { + Env* custom_env = nullptr; + Env::LoadEnv(backup_env_uri_, &custom_env, &backup_env_guard_); + assert(custom_env != nullptr); + + std::unique_ptr<BackupEngineReadOnly> restore_engine; + Status status; + { + BackupableDBOptions opts(backup_dir_, custom_env); + opts.info_log = logger_.get(); + opts.max_background_operations = num_threads_; + BackupEngineReadOnly* raw_restore_engine_ptr; + status = + BackupEngineReadOnly::Open(custom_env, opts, &raw_restore_engine_ptr); + if (status.ok()) { + restore_engine.reset(raw_restore_engine_ptr); + } + } + if (status.ok()) { + fprintf(stdout, "open restore engine OK\n"); + status = restore_engine->RestoreDBFromLatestBackup(db_path_, db_path_); + } + if (status.ok()) { + fprintf(stdout, "restore from backup OK\n"); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(status.ToString()); + } +} + +// ---------------------------------------------------------------------------- + +namespace { + +void DumpSstFile(Options options, std::string filename, bool output_hex, + bool show_properties) { + std::string from_key; + std::string to_key; + if (filename.length() <= 4 || + filename.rfind(".sst") != filename.length() - 4) { + std::cout << "Invalid sst file name." << std::endl; + return; + } + // no verification + // TODO: add support for decoding blob indexes in ldb as well + ROCKSDB_NAMESPACE::SstFileDumper dumper( + options, filename, /* verify_checksum */ false, output_hex, + /* decode_blob_index */ false); + Status st = dumper.ReadSequential(true, std::numeric_limits<uint64_t>::max(), + false, // has_from + from_key, false, // has_to + to_key); + if (!st.ok()) { + std::cerr << "Error in reading SST file " << filename << st.ToString() + << std::endl; + return; + } + + if (show_properties) { + const ROCKSDB_NAMESPACE::TableProperties* table_properties; + + std::shared_ptr<const ROCKSDB_NAMESPACE::TableProperties> + table_properties_from_reader; + st = dumper.ReadTableProperties(&table_properties_from_reader); + if (!st.ok()) { + std::cerr << filename << ": " << st.ToString() + << ". Try to use initial table properties" << std::endl; + table_properties = dumper.GetInitTableProperties(); + } else { + table_properties = table_properties_from_reader.get(); + } + if (table_properties != nullptr) { + std::cout << std::endl << "Table Properties:" << std::endl; + std::cout << table_properties->ToString("\n") << std::endl; + } + } +} + +} // namespace + +DBFileDumperCommand::DBFileDumperCommand( + const std::vector<std::string>& /*params*/, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, BuildCmdLineOptions({})) {} + +void DBFileDumperCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(DBFileDumperCommand::Name()); + ret.append("\n"); +} + +void DBFileDumperCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + Status s; + + std::cout << "Manifest File" << std::endl; + std::cout << "==============================" << std::endl; + std::string manifest_filename; + s = ReadFileToString(db_->GetEnv(), CurrentFileName(db_->GetName()), + &manifest_filename); + if (!s.ok() || manifest_filename.empty() || + manifest_filename.back() != '\n') { + std::cerr << "Error when reading CURRENT file " + << CurrentFileName(db_->GetName()) << std::endl; + } + // remove the trailing '\n' + manifest_filename.resize(manifest_filename.size() - 1); + std::string manifest_filepath = db_->GetName() + "/" + manifest_filename; + std::cout << manifest_filepath << std::endl; + DumpManifestFile(options_, manifest_filepath, false, false, false); + std::cout << std::endl; + + std::cout << "SST Files" << std::endl; + std::cout << "==============================" << std::endl; + std::vector<LiveFileMetaData> metadata; + db_->GetLiveFilesMetaData(&metadata); + for (auto& fileMetadata : metadata) { + std::string filename = fileMetadata.db_path + fileMetadata.name; + std::cout << filename << " level:" << fileMetadata.level << std::endl; + std::cout << "------------------------------" << std::endl; + DumpSstFile(options_, filename, false, true); + std::cout << std::endl; + } + std::cout << std::endl; + + std::cout << "Write Ahead Log Files" << std::endl; + std::cout << "==============================" << std::endl; + ROCKSDB_NAMESPACE::VectorLogPtr wal_files; + s = db_->GetSortedWalFiles(wal_files); + if (!s.ok()) { + std::cerr << "Error when getting WAL files" << std::endl; + } else { + for (auto& wal : wal_files) { + // TODO(qyang): option.wal_dir should be passed into ldb command + std::string filename = db_->GetOptions().wal_dir + wal->PathName(); + std::cout << filename << std::endl; + // TODO(myabandeh): allow configuring is_write_commited + DumpWalFile(options_, filename, true, true, true /* is_write_commited */, + &exec_state_); + } + } +} + +void WriteExternalSstFilesCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(WriteExternalSstFilesCommand::Name()); + ret.append(" <output_sst_path>"); + ret.append("\n"); +} + +WriteExternalSstFilesCommand::WriteExternalSstFilesCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, false /* is_read_only */, + BuildCmdLineOptions({ARG_HEX, ARG_KEY_HEX, ARG_VALUE_HEX, ARG_FROM, + ARG_TO, ARG_CREATE_IF_MISSING})) { + create_if_missing_ = + IsFlagPresent(flags, ARG_CREATE_IF_MISSING) || + ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false); + if (params.size() != 1) { + exec_state_ = LDBCommandExecuteResult::Failed( + "output SST file path must be specified"); + } else { + output_sst_path_ = params.at(0); + } +} + +void WriteExternalSstFilesCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + ColumnFamilyHandle* cfh = GetCfHandle(); + SstFileWriter sst_file_writer(EnvOptions(), db_->GetOptions(), cfh); + Status status = sst_file_writer.Open(output_sst_path_); + if (!status.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed("failed to open SST file: " + + status.ToString()); + return; + } + + int bad_lines = 0; + std::string line; + std::ifstream ifs_stdin("/dev/stdin"); + std::istream* istream_p = ifs_stdin.is_open() ? &ifs_stdin : &std::cin; + while (getline(*istream_p, line, '\n')) { + std::string key; + std::string value; + if (ParseKeyValue(line, &key, &value, is_key_hex_, is_value_hex_)) { + status = sst_file_writer.Put(key, value); + if (!status.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "failed to write record to file: " + status.ToString()); + return; + } + } else if (0 == line.find("Keys in range:")) { + // ignore this line + } else if (0 == line.find("Created bg thread 0x")) { + // ignore this line + } else { + bad_lines++; + } + } + + status = sst_file_writer.Finish(); + if (!status.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "Failed to finish writing to file: " + status.ToString()); + return; + } + + if (bad_lines > 0) { + fprintf(stderr, "Warning: %d bad lines ignored.\n", bad_lines); + } + exec_state_ = LDBCommandExecuteResult::Succeed( + "external SST file written to " + output_sst_path_); +} + +Options WriteExternalSstFilesCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + opt.create_if_missing = create_if_missing_; + return opt; +} + +const std::string IngestExternalSstFilesCommand::ARG_MOVE_FILES = "move_files"; +const std::string IngestExternalSstFilesCommand::ARG_SNAPSHOT_CONSISTENCY = + "snapshot_consistency"; +const std::string IngestExternalSstFilesCommand::ARG_ALLOW_GLOBAL_SEQNO = + "allow_global_seqno"; +const std::string IngestExternalSstFilesCommand::ARG_ALLOW_BLOCKING_FLUSH = + "allow_blocking_flush"; +const std::string IngestExternalSstFilesCommand::ARG_INGEST_BEHIND = + "ingest_behind"; +const std::string IngestExternalSstFilesCommand::ARG_WRITE_GLOBAL_SEQNO = + "write_global_seqno"; + +void IngestExternalSstFilesCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(IngestExternalSstFilesCommand::Name()); + ret.append(" <input_sst_path>"); + ret.append(" [--" + ARG_MOVE_FILES + "] "); + ret.append(" [--" + ARG_SNAPSHOT_CONSISTENCY + "] "); + ret.append(" [--" + ARG_ALLOW_GLOBAL_SEQNO + "] "); + ret.append(" [--" + ARG_ALLOW_BLOCKING_FLUSH + "] "); + ret.append(" [--" + ARG_INGEST_BEHIND + "] "); + ret.append(" [--" + ARG_WRITE_GLOBAL_SEQNO + "] "); + ret.append("\n"); +} + +IngestExternalSstFilesCommand::IngestExternalSstFilesCommand( + const std::vector<std::string>& params, + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand( + options, flags, false /* is_read_only */, + BuildCmdLineOptions({ARG_MOVE_FILES, ARG_SNAPSHOT_CONSISTENCY, + ARG_ALLOW_GLOBAL_SEQNO, ARG_CREATE_IF_MISSING, + ARG_ALLOW_BLOCKING_FLUSH, ARG_INGEST_BEHIND, + ARG_WRITE_GLOBAL_SEQNO})), + move_files_(false), + snapshot_consistency_(true), + allow_global_seqno_(true), + allow_blocking_flush_(true), + ingest_behind_(false), + write_global_seqno_(true) { + create_if_missing_ = + IsFlagPresent(flags, ARG_CREATE_IF_MISSING) || + ParseBooleanOption(options, ARG_CREATE_IF_MISSING, false); + move_files_ = IsFlagPresent(flags, ARG_MOVE_FILES) || + ParseBooleanOption(options, ARG_MOVE_FILES, false); + snapshot_consistency_ = + IsFlagPresent(flags, ARG_SNAPSHOT_CONSISTENCY) || + ParseBooleanOption(options, ARG_SNAPSHOT_CONSISTENCY, true); + allow_global_seqno_ = + IsFlagPresent(flags, ARG_ALLOW_GLOBAL_SEQNO) || + ParseBooleanOption(options, ARG_ALLOW_GLOBAL_SEQNO, true); + allow_blocking_flush_ = + IsFlagPresent(flags, ARG_ALLOW_BLOCKING_FLUSH) || + ParseBooleanOption(options, ARG_ALLOW_BLOCKING_FLUSH, true); + ingest_behind_ = IsFlagPresent(flags, ARG_INGEST_BEHIND) || + ParseBooleanOption(options, ARG_INGEST_BEHIND, false); + write_global_seqno_ = + IsFlagPresent(flags, ARG_WRITE_GLOBAL_SEQNO) || + ParseBooleanOption(options, ARG_WRITE_GLOBAL_SEQNO, true); + + if (allow_global_seqno_) { + if (!write_global_seqno_) { + fprintf(stderr, + "Warning: not writing global_seqno to the ingested SST can\n" + "prevent older versions of RocksDB from being able to open it\n"); + } + } else { + if (write_global_seqno_) { + exec_state_ = LDBCommandExecuteResult::Failed( + "ldb cannot write global_seqno to the ingested SST when global_seqno " + "is not allowed"); + } + } + + if (params.size() != 1) { + exec_state_ = + LDBCommandExecuteResult::Failed("input SST path must be specified"); + } else { + input_sst_path_ = params.at(0); + } +} + +void IngestExternalSstFilesCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + if (GetExecuteState().IsFailed()) { + return; + } + ColumnFamilyHandle* cfh = GetCfHandle(); + IngestExternalFileOptions ifo; + ifo.move_files = move_files_; + ifo.snapshot_consistency = snapshot_consistency_; + ifo.allow_global_seqno = allow_global_seqno_; + ifo.allow_blocking_flush = allow_blocking_flush_; + ifo.ingest_behind = ingest_behind_; + ifo.write_global_seqno = write_global_seqno_; + Status status = db_->IngestExternalFile(cfh, {input_sst_path_}, ifo); + if (!status.ok()) { + exec_state_ = LDBCommandExecuteResult::Failed( + "failed to ingest external SST: " + status.ToString()); + } else { + exec_state_ = + LDBCommandExecuteResult::Succeed("external SST files ingested"); + } +} + +Options IngestExternalSstFilesCommand::PrepareOptionsForOpenDB() { + Options opt = LDBCommand::PrepareOptionsForOpenDB(); + opt.create_if_missing = create_if_missing_; + return opt; +} + +ListFileRangeDeletesCommand::ListFileRangeDeletesCommand( + const std::map<std::string, std::string>& options, + const std::vector<std::string>& flags) + : LDBCommand(options, flags, true, BuildCmdLineOptions({ARG_MAX_KEYS})) { + std::map<std::string, std::string>::const_iterator itr = + options.find(ARG_MAX_KEYS); + if (itr != options.end()) { + try { +#if defined(CYGWIN) + max_keys_ = strtol(itr->second.c_str(), 0, 10); +#else + max_keys_ = std::stoi(itr->second); +#endif + } catch (const std::invalid_argument&) { + exec_state_ = LDBCommandExecuteResult::Failed(ARG_MAX_KEYS + + " has an invalid value"); + } catch (const std::out_of_range&) { + exec_state_ = LDBCommandExecuteResult::Failed( + ARG_MAX_KEYS + " has a value out-of-range"); + } + } +} + +void ListFileRangeDeletesCommand::Help(std::string& ret) { + ret.append(" "); + ret.append(ListFileRangeDeletesCommand::Name()); + ret.append(" [--" + ARG_MAX_KEYS + "=<N>]"); + ret.append(" : print tombstones in SST files.\n"); +} + +void ListFileRangeDeletesCommand::DoCommand() { + if (!db_) { + assert(GetExecuteState().IsFailed()); + return; + } + + DBImpl* db_impl = static_cast_with_check<DBImpl, DB>(db_->GetRootDB()); + + std::string out_str; + + Status st = + db_impl->TablesRangeTombstoneSummary(GetCfHandle(), max_keys_, &out_str); + if (st.ok()) { + TEST_SYNC_POINT_CALLBACK( + "ListFileRangeDeletesCommand::DoCommand:BeforePrint", &out_str); + fprintf(stdout, "%s\n", out_str.c_str()); + } else { + exec_state_ = LDBCommandExecuteResult::Failed(st.ToString()); + } +} + +} // namespace ROCKSDB_NAMESPACE +#endif // ROCKSDB_LITE |