/
smychkov
/
SStorage
Обзор
Документация
Войти
/
smychkov
/
SStorage
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/cli/command.cpp
275 строк
9 KB
Андрей
feat: top N keys + scan stream (range/top/all) with pagination
14 июл 2026, 12:56
14 июл 2026, 12:56
56c6264
Код
Авторство
О чём код?
#include "command.hpp" #include "../util/utils.hpp" #include <iostream> #include <limits> #include <sstream> #include <string> #include <vector> namespace sstorage { //============================================================================ // Максимальная длина строки ввода (защита от DoS) //============================================================================ static const size_t kMaxInputLength = 10 * 1024 * 1024; // 10 MB // Фиксированный размер страницы CLI-пагинации. См. contract S2. static const size_t kCliPageSize = 20; // Разбор N для режима TOP. Возвращает false на любом невалидном вводе // (пусто, знак '-', не-цифра, переполнение size_t). См. contract S2 §4.1. static bool parseTopN(const std::string& s, size_t& out) { if (s.empty()) return false; size_t acc = 0; const size_t maxv = std::numeric_limits<size_t>::max(); for (char c : s) { if (c < '0' || c > '9') return false; size_t digit = static_cast<size_t>(c - '0'); if (acc > (maxv - digit) / 10) return false; acc = acc * 10 + digit; } out = acc; return true; } CommandHandler::CommandHandler(Database& db) : db_(db), in_(std::cin), out_(std::cout) {} CommandHandler::CommandHandler(Database& db, std::istream& in, std::ostream& out) : db_(db), in_(in), out_(out) {} //============================================================================ // Dispatch //============================================================================ int CommandHandler::execute(const std::string& command, const std::string& args) { if (command == "put" || command == "p") return cmdPut(args); if (command == "get" || command == "g") return cmdGet(args); if (command == "del" || command == "d" || command == "rm") return cmdDel(args); if (command == "scan" || command == "s") return cmdScan(args); if (command == "stats") return cmdStats(); if (command == "flush") return cmdFlush(); if (command == "compact") return cmdCompact(); if (command == "help" || command == "h") { printHelp(); return 0; } if (command == "exit" || command == "quit" || command == "q") return -1; std::cerr << "Unknown command: " << command << "\n"; std::cerr << "Type 'help' for available commands\n"; return 1; } //============================================================================ // put <key> <value> //============================================================================ int CommandHandler::cmdPut(const std::string& args) { // Разделяем по первому пробелу auto spacePos = args.find(' '); if (spacePos == std::string::npos) { std::cerr << "Usage: put <key> <value>\n"; return 1; } std::string key = args.substr(0, spacePos); std::string value = args.substr(spacePos + 1); if (db_.put(key, value)) { std::cout << "OK\n"; return 0; } std::cerr << "Error: put failed\n"; return 1; } //============================================================================ // get <key> //============================================================================ int CommandHandler::cmdGet(const std::string& args) { if (args.empty()) { std::cerr << "Usage: get <key>\n"; return 1; } auto v = db_.get(args); if (v.has_value()) { std::cout << *v << "\n"; return 0; } std::cout << "(not found)\n"; return 1; } //============================================================================ // del <key> //============================================================================ int CommandHandler::cmdDel(const std::string& args) { if (args.empty()) { std::cerr << "Usage: del <key>\n"; return 1; } if (db_.remove(args)) { std::cout << "OK\n"; return 0; } std::cerr << "Error: del failed\n"; return 1; } //============================================================================ // scan — режимы range / top N / all + постраничный вывод. См. contract S2. //============================================================================ int CommandHandler::cmdScan(const std::string& args) { std::istringstream iss(args); std::vector<std::string> toks; { std::string t; while (iss >> t) toks.push_back(t); } struct PageState { size_t printedOnPage = 0; }; PageState state; ScanCallback cb = [this, &state](const std::string& key, const std::string& value) -> bool { out_ << " " << key << " -> " << value << "\n"; ++state.printedOnPage; if (state.printedOnPage == kCliPageSize) { out_ << "-- more (Enter=next, q=quit) --\n" << std::flush; std::string line; if (!std::getline(in_, line)) return false; if (!line.empty() && (line[0] == 'q' || line[0] == 'Q')) return false; state.printedOnPage = 0; } return true; }; if (toks.empty()) { db_.scanAll(0, cb); return 0; } if (toks.size() == 1) { std::cerr << "Usage: scan <from> <to> | scan top <N> | scan\n"; return 1; } if (toks.size() > 2) { std::cerr << "Usage: scan <from> <to> | scan top <N> | scan\n"; return 1; } if (toks[0] == "top") { size_t n = 0; if (!parseTopN(toks[1], n) || n == 0) { return 0; } db_.scanAll(n, cb); return 0; } db_.scanStream(toks[0], toks[1], 0, cb); return 0; } //============================================================================ // stats //============================================================================ int CommandHandler::cmdStats() { auto s = db_.stats(); std::cout << "MemTable size: " << s.memtableSize << " bytes\n"; std::cout << "Immutable MemTables: " << s.immutableCount << "\n"; std::cout << "Total records: " << s.totalRecords << "\n"; for (size_t i = 0; i < s.levelFileCounts.size(); ++i) { if (s.levelFileCounts[i] > 0) { std::cout << "L" << i << ": " << s.levelFileCounts[i] << " files, " << s.levelRecords[i] << " records\n"; } } return 0; } //============================================================================ // flush / compact //============================================================================ int CommandHandler::cmdFlush() { if (db_.flush()) { std::cout << "OK\n"; return 0; } std::cerr << "Error: flush failed\n"; return 1; } int CommandHandler::cmdCompact() { std::cout << "Compacting...\n"; if (db_.compact()) { std::cout << "OK\n"; return 0; } std::cerr << "Error: compact failed\n"; return 1; } //============================================================================ // help //============================================================================ void CommandHandler::printHelp(std::ostream& out) { out << "Commands:\n"; out << " put <key> <value> Insert/upsert (alias: p)\n"; out << " get <key> Read (alias: g)\n"; out << " del <key> Delete (alias: d, rm)\n"; out << " scan <from> <to> Range query (alias: s)\n"; out << " scan top <N> First N keys in ascending order\n"; out << " scan All records (paged, 20 per page; Enter=next, q=quit)\n"; out << " stats Statistics\n"; out << " flush Force MemTable flush\n"; out << " compact Force compaction\n"; out << " help This help (alias: h)\n"; out << " exit / quit / q Exit\n"; } //============================================================================ // Разбор строки на команду и аргументы //============================================================================ bool parseCommand(const std::string& line, std::string& command, std::string& args) { std::istringstream iss(line); if (!(iss >> command)) return false; std::getline(iss, args); if (!args.empty() && args[0] == ' ') { args = args.substr(1); } return true; } //============================================================================ // runInteractive — главный цикл //============================================================================ int runInteractive(Database& db) { CommandHandler handler(db); std::cout << "\n=== SStorage Interactive Mode ===\n"; std::cout << "Type 'help' for commands, 'exit' to quit\n\n"; std::string line; while (true) { std::cout << "> " << std::flush; if (!std::getline(std::cin, line)) break; if (line.size() > kMaxInputLength) { std::cerr << "Error: input too long\n"; continue; } if (line.empty()) continue; std::string command, args; if (!parseCommand(line, command, args)) continue; int result = handler.execute(command, args); if (result == -1) { std::cout << "Exiting...\n"; break; } } return 0; } }