| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- #include <smartbotic/database/client.hpp>
- #include <nlohmann/json.hpp>
- #include <iostream>
- #include <sstream>
- #include <string>
- #include <vector>
- using json = nlohmann::json;
- namespace {
- struct Args {
- std::string address = "localhost:9004";
- std::string command;
- std::vector<std::string> params;
- };
- // ANSI colors
- constexpr auto C_RESET = "\033[0m";
- constexpr auto C_BOLD = "\033[1m";
- constexpr auto C_DIM = "\033[2m";
- constexpr auto C_CYAN = "\033[36m";
- constexpr auto C_GREEN = "\033[32m";
- constexpr auto C_RED = "\033[31m";
- constexpr auto C_YELLOW = "\033[33m";
- void printJson(const json& j) {
- std::cout << j.dump(2) << "\n";
- }
- void printError(const std::string& msg) {
- std::cerr << C_RED << "error: " << C_RESET << msg << "\n";
- }
- void printUsage() {
- std::cout << C_BOLD << "smartbotic-db-cli" << C_RESET << " — admin tool for smartbotic-database\n\n"
- << C_BOLD << "Usage:" << C_RESET << "\n"
- << " smartbotic-db-cli [--address HOST:PORT] <command> [args...]\n"
- << " smartbotic-db-cli [--address HOST:PORT] " << C_DIM << "# interactive mode" << C_RESET << "\n\n"
- << C_BOLD << "Commands:" << C_RESET << "\n"
- << " " << C_CYAN << "collections" << C_RESET << " List all collections\n"
- << " " << C_CYAN << "info" << C_RESET << " <collection> Collection info\n"
- << " " << C_CYAN << "find" << C_RESET << " <collection> List documents\n"
- << " " << C_CYAN << "get" << C_RESET << " <collection> <id> Get a document\n"
- << " " << C_CYAN << "upsert" << C_RESET << " <collection> <id> '<json>' Insert or update\n"
- << " " << C_CYAN << "remove" << C_RESET << " <collection> <id> Delete a document\n"
- << " " << C_CYAN << "count" << C_RESET << " <collection> Count documents\n"
- << " " << C_CYAN << "help" << C_RESET << " Show this help\n\n"
- << C_BOLD << "Options:" << C_RESET << "\n"
- << " --address HOST:PORT Database address (default: localhost:9004)\n";
- }
- bool execCommand(smartbotic::database::Client& client,
- const std::string& cmd, const std::vector<std::string>& params) {
- try {
- if (cmd == "collections") {
- auto collections = client.listCollections();
- std::cout << C_BOLD << "Collections:" << C_RESET << "\n";
- for (const auto& c : collections) {
- auto info = client.getCollectionInfo(c);
- int64_t count = 0;
- if (info) count = info->documentCount;
- std::cout << " " << C_CYAN << c << C_RESET
- << C_DIM << " (" << count << " docs)" << C_RESET << "\n";
- }
- return true;
- }
- if (cmd == "info") {
- if (params.empty()) { printError("usage: info <collection>"); return false; }
- auto info = client.getCollectionInfo(params[0]);
- if (!info) { printError("collection not found: " + params[0]); return false; }
- std::cout << C_BOLD << params[0] << C_RESET << ":\n"
- << " documents: " << info->documentCount << "\n"
- << " size: " << info->sizeBytes << " bytes\n"
- << " encrypted: " << (info->encrypted ? "yes" : "no") << "\n"
- << " max_versions: " << info->maxVersions << "\n";
- if (info->defaultTtlSeconds > 0)
- std::cout << " ttl: " << info->defaultTtlSeconds << "s\n";
- return true;
- }
- if (cmd == "find") {
- if (params.empty()) { printError("usage: find <collection>"); return false; }
- auto docs = client.find(params[0]);
- std::cout << C_DIM << "(" << docs.size() << " documents)" << C_RESET << "\n";
- for (const auto& doc : docs) {
- auto id = doc.value("_id", "");
- // Print compact summary line
- std::cout << C_GREEN << id << C_RESET;
- // Show a few key fields
- for (const auto& [k, v] : doc.items()) {
- if (k == "_id" || k == "_created_at" || k == "_updated_at") continue;
- auto val = v.dump();
- if (val.size() > 60) val = val.substr(0, 57) + "...";
- std::cout << " " << C_DIM << k << "=" << C_RESET << val;
- // Limit to 3 fields per line
- static int field_count = 0;
- if (++field_count >= 3) { field_count = 0; break; }
- }
- std::cout << "\n";
- }
- return true;
- }
- if (cmd == "get") {
- if (params.size() < 2) { printError("usage: get <collection> <id>"); return false; }
- auto doc = client.get(params[0], params[1]);
- if (!doc) { printError("not found: " + params[0] + "/" + params[1]); return false; }
- printJson(*doc);
- return true;
- }
- if (cmd == "upsert") {
- if (params.size() < 3) { printError("usage: upsert <collection> <id> '<json>'"); return false; }
- auto data = json::parse(params[2], nullptr, false);
- if (!data.is_object()) { printError("invalid JSON: " + params[2]); return false; }
- client.upsert(params[0], data, params[1]);
- std::cout << C_GREEN << "ok" << C_RESET << " " << params[0] << "/" << params[1] << "\n";
- return true;
- }
- if (cmd == "remove" || cmd == "delete") {
- if (params.size() < 2) { printError("usage: remove <collection> <id>"); return false; }
- client.remove(params[0], params[1]);
- std::cout << C_GREEN << "ok" << C_RESET << " removed " << params[0] << "/" << params[1] << "\n";
- return true;
- }
- if (cmd == "count") {
- if (params.empty()) { printError("usage: count <collection>"); return false; }
- auto info = client.getCollectionInfo(params[0]);
- if (!info) { printError("collection not found: " + params[0]); return false; }
- std::cout << info->documentCount << "\n";
- return true;
- }
- if (cmd == "help" || cmd == "?") {
- printUsage();
- return true;
- }
- printError("unknown command: " + cmd + " (try 'help')");
- return false;
- } catch (const std::exception& e) {
- printError(e.what());
- return false;
- }
- }
- // Tokenize a line, respecting single/double quotes and JSON braces
- std::vector<std::string> tokenize(const std::string& line) {
- std::vector<std::string> tokens;
- std::string current;
- int brace_depth = 0;
- char in_quote = 0;
- for (size_t i = 0; i < line.size(); ++i) {
- char c = line[i];
- if (in_quote) {
- current += c;
- if (c == in_quote && (i == 0 || line[i-1] != '\\')) {
- in_quote = 0;
- // Strip surrounding quotes for simple string tokens
- if (brace_depth == 0 && current.size() >= 2
- && (current.front() == '\'' || current.front() == '"')
- && current.front() == current.back()) {
- current = current.substr(1, current.size() - 2);
- }
- }
- continue;
- }
- if (c == '\'' || c == '"') {
- in_quote = c;
- current += c;
- continue;
- }
- if (c == '{') { brace_depth++; current += c; continue; }
- if (c == '}') {
- brace_depth--;
- current += c;
- if (brace_depth <= 0) {
- brace_depth = 0;
- tokens.push_back(current);
- current.clear();
- }
- continue;
- }
- if (brace_depth > 0) { current += c; continue; }
- if (c == ' ' || c == '\t') {
- if (!current.empty()) {
- tokens.push_back(current);
- current.clear();
- }
- continue;
- }
- current += c;
- }
- if (!current.empty()) tokens.push_back(current);
- return tokens;
- }
- } // anonymous namespace
- int main(int argc, char* argv[]) {
- Args args;
- // Parse flags
- std::vector<std::string> positional;
- for (int i = 1; i < argc; ++i) {
- std::string arg = argv[i];
- if (arg == "--address" && i + 1 < argc) {
- args.address = argv[++i];
- } else if (arg == "--help" || arg == "-h") {
- printUsage();
- return 0;
- } else {
- positional.push_back(arg);
- }
- }
- if (!positional.empty()) {
- args.command = positional[0];
- args.params.assign(positional.begin() + 1, positional.end());
- }
- // Connect
- smartbotic::database::Client client({.address = args.address});
- client.connect();
- // Scriptable mode: single command
- if (!args.command.empty()) {
- return execCommand(client, args.command, args.params) ? 0 : 1;
- }
- // Interactive mode
- std::cout << C_BOLD << "smartbotic-db-cli" << C_RESET
- << " connected to " << C_CYAN << args.address << C_RESET << "\n"
- << C_DIM << "Type 'help' for commands, 'exit' to quit." << C_RESET << "\n";
- std::string line;
- while (true) {
- std::cout << C_YELLOW << "> " << C_RESET;
- if (!std::getline(std::cin, line)) break;
- // Trim
- auto start = line.find_first_not_of(" \t");
- if (start == std::string::npos) continue;
- line = line.substr(start);
- if (line == "exit" || line == "quit" || line == "q") break;
- if (line.empty()) continue;
- auto tokens = tokenize(line);
- if (tokens.empty()) continue;
- auto cmd = tokens[0];
- std::vector<std::string> params(tokens.begin() + 1, tokens.end());
- execCommand(client, cmd, params);
- }
- return 0;
- }
|