1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* 3 * Copyright 2017 Couchbase, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #define LCB_NO_DEPR_CXX_CTORS 19 20 #include "config.h" 21 #include <sys/types.h> 22 #include <libcouchbase/couchbase.h> 23 #include <libcouchbase/api3.h> 24 #include <iostream> 25 #include <map> 26 #include <cassert> 27 #include <cstdio> 28 #include <cerrno> 29 #include <stdexcept> 30 #include <sstream> 31 #include "common/options.h" 32 #include "common/histogram.h" 33 #include <libcouchbase/metrics.h> 34 35 #include "linenoise/linenoise.h" 36 37 static std::string get_resp_key(const lcb_RESPBASE *resp) 38 { 39 if (!resp->nkey) { 40 return ""; 41 } 42 return std::string((const char *)resp->key, resp->nkey); 43 } 44 45 extern "C" { 46 void subdoc_callback(lcb_t, int cbtype, const lcb_RESPBASE *rb) 47 { 48 const lcb_RESPSUBDOC *resp = (const lcb_RESPSUBDOC *)rb; 49 lcb_SDENTRY cur; 50 std::string key = get_resp_key(rb); 51 52 if (rb->rc == LCB_SUCCESS || rb->rc == LCB_SUBDOC_MULTI_FAILURE) { 53 fprintf(stderr, "%-20s CAS=0x%" PRIx64 "\n", key.c_str(), resp->cas); 54 } else { 55 fprintf(stderr, "%-20s %s\n", key.c_str(), lcb_strerror_short(rb->rc)); 56 const char *ctx = lcb_resp_get_error_context(cbtype, rb); 57 if (ctx != NULL) { 58 fprintf(stderr, "%-20s %s\n", "", ctx); 59 } 60 const char *ref = lcb_resp_get_error_ref(cbtype, rb); 61 if (ref != NULL) { 62 fprintf(stderr, "%-20s Ref: %s\n", "", ref); 63 } 64 } 65 size_t vii = 0, oix = 0; 66 while (lcb_sdresult_next(resp, &cur, &vii)) { 67 int index = oix++; 68 69 if (cbtype == LCB_CALLBACK_SDMUTATE) { 70 index = cur.index; 71 } 72 printf("%d. Size=%lu, RC=%s\n", index, (unsigned long)cur.nvalue, lcb_strerror_short(cur.status)); 73 fflush(stdout); 74 if (cur.nvalue > 0) { 75 fwrite(cur.value, 1, cur.nvalue, stdout); 76 printf("\n"); 77 fflush(stdout); 78 } 79 } 80 } 81 } 82 83 #define CBCSUBDOC_HISTORY_FILENAME ".cbcsubdoc_history" 84 85 using namespace cbc; 86 using namespace cliopts; 87 88 static void do_or_die(lcb_error_t rc, std::string msg = "") 89 { 90 if (rc != LCB_SUCCESS) { 91 std::stringstream ss; 92 if (!msg.empty()) { 93 ss << msg << ". "; 94 } 95 ss << lcb_strerror_short(rc); 96 throw std::runtime_error(ss.str()); 97 } 98 } 99 100 static lcb_t instance = NULL; 101 static Histogram hg; 102 103 class Configuration 104 { 105 public: 106 Configuration() 107 { 108 } 109 110 ~Configuration() 111 { 112 } 113 114 void addToParser(Parser &parser) 115 { 116 m_params.addToParser(parser); 117 } 118 119 void processOptions() 120 { 121 } 122 123 void fillCropts(lcb_create_st &opts) 124 { 125 m_params.fillCropts(opts); 126 } 127 lcb_error_t doCtls() 128 { 129 return m_params.doCtls(instance); 130 } 131 bool useTimings() 132 { 133 return m_params.useTimings(); 134 } 135 bool shouldDump() 136 { 137 return m_params.shouldDump(); 138 } 139 140 private: 141 ConnParams m_params; 142 }; 143 144 static Configuration config; 145 146 static const char *handlers_sorted[] = {"help", 147 "dump", 148 "get", 149 "set", 150 "exists", 151 "remove", 152 "replace", 153 "array-insert", 154 "array-add-first", 155 "array-add-last", 156 "array-add-unique", 157 "dict-add", 158 "dict-upsert", 159 "counter", 160 "size", 161 NULL}; 162 163 static void command_completion(const char *buf, linenoiseCompletions *lc) 164 { 165 size_t nbuf = strlen(buf); 166 for (const char **cur = handlers_sorted; *cur; cur++) { 167 if (memcmp(buf, *cur, nbuf) == 0) { 168 linenoiseAddCompletion(lc, *cur); 169 } 170 } 171 } 172 173 namespace subdoc 174 { 175 class Handler; 176 } 177 178 static std::map< std::string, subdoc::Handler * > handlers; 179 180 namespace subdoc 181 { 182 #define HANDLER_DESCRIPTION(s) \ 183 const char *description() const \ 184 { \ 185 return s; \ 186 } 187 #define HANDLER_USAGE(s) \ 188 const char *usagestr() const \ 189 { \ 190 return s; \ 191 } 192 193 class Handler 194 { 195 public: 196 Handler(const char *name) : parser(name) 197 { 198 if (name != NULL) { 199 cmdname = name; 200 } 201 parser.default_settings.error_noexit = 1; 202 parser.default_settings.help_noexit = 1; 203 } 204 205 virtual ~Handler() 206 { 207 } 208 virtual const char *description() const 209 { 210 return NULL; 211 } 212 virtual const char *usagestr() const 213 { 214 return NULL; 215 } 216 void execute(int argc, char **argv) 217 { 218 parser.reset(); 219 parser.default_settings.argstring = usagestr(); 220 parser.default_settings.shortdesc = description(); 221 addOptions(); 222 if (parser.parse(argc, argv, true)) { 223 run(); 224 } 225 } 226 227 protected: 228 virtual const std::string &getLoneArg(bool required = false) 229 { 230 static std::string empty(""); 231 232 const std::vector< std::string > &args = parser.getRestArgs(); 233 if (args.empty() || args.size() != 1) { 234 if (required) { 235 throw BadArg("Command requires single argument"); 236 } 237 return empty; 238 } 239 return args[0]; 240 } 241 242 virtual const std::string &getRequiredArg() 243 { 244 return getLoneArg(true); 245 } 246 247 virtual void addOptions() 248 { 249 } 250 251 virtual void run() = 0; 252 253 void splitNameValue(std::string &arg, const char **name, size_t *nname, const char **value, size_t *nvalue) 254 { 255 size_t sep = arg.find("="); 256 if (sep == std::string::npos) { 257 throw BadArg("Name and value have to be separated with '='"); 258 } 259 260 const char *k = arg.c_str(); 261 size_t nk = sep; 262 for (size_t j = nk - 1; j > 0; j--, nk--) { 263 if (k[j] != ' ' && k[j] != '\t') { 264 break; 265 } 266 } 267 if (nk == 0) { 268 throw BadArg("Name cannot be empty"); 269 } 270 271 *name = k; 272 *nname = nk; 273 *value = arg.c_str() + sep + 1; 274 *nvalue = arg.size() - sep - 1; 275 } 276 277 cliopts::Parser parser; 278 std::string cmdname; 279 }; 280 281 class LookupHandler : public Handler 282 { 283 public: 284 HANDLER_USAGE("[OPTIONS...] KEY...") 285 286 LookupHandler(const char *name, lcb_SUBDOCOP opcode, const char *description_) 287 : Handler(name), m_opcode(opcode), m_description(description_), o_paths("path"), o_xattrs("xattr"), 288 o_deleted("deleted") 289 { 290 o_paths.abbrev('p').argdesc("PATH").description("JSON path in the document"); 291 o_xattrs.abbrev('x').argdesc("PATH").description("Access XATTR path (exentnded attributes)"); 292 o_deleted.abbrev('d').description("Access XATTR attributes of deleted documents"); 293 } 294 295 const char *description() const 296 { 297 return m_description; 298 } 299 300 protected: 301 void addOptions() 302 { 303 Handler::addOptions(); 304 parser.addOption(o_paths.reset()); 305 parser.addOption(o_xattrs.reset()); 306 parser.addOption(o_deleted.reset()); 307 } 308 309 void run() 310 { 311 lcb_error_t err; 312 313 const std::vector< std::string > &keys = parser.getRestArgs(); 314 if (keys.empty()) { 315 throw BadArg("At least one key has to be specified"); 316 } 317 std::vector< std::string > paths = o_paths.result(); 318 std::vector< std::string > xattrs = o_xattrs.result(); 319 320 if (m_opcode != LCB_SDCMD_GET) { 321 if (paths.empty() && xattrs.empty()) { 322 throw BadArg("At least one path has to be specified"); 323 } 324 } 325 326 lcb_sched_enter(instance); 327 for (size_t ii = 0; ii < keys.size(); ++ii) { 328 const std::string &key = keys[ii]; 329 lcb_CMDSUBDOC cmd = {0}; 330 331 LCB_KREQ_SIMPLE(&cmd.key, key.c_str(), key.size()); 332 cmd.nspecs = 0; 333 std::vector< lcb_SDSPEC > specs; 334 335 if (o_xattrs.passed()) { 336 for (size_t i = 0; i < xattrs.size(); i++) { 337 lcb_SDSPEC spec = lcb_SDSPEC(); 338 spec.sdcmd = m_opcode; 339 spec.options |= LCB_SDSPEC_F_XATTRPATH; 340 if (o_deleted.passed()) { 341 spec.options |= LCB_SDSPEC_F_XATTR_DELETED_OK; 342 } 343 LCB_SDSPEC_SET_PATH(&spec, xattrs[i].c_str(), xattrs[i].size()); 344 specs.push_back(spec); 345 } 346 } 347 if (o_paths.passed()) { 348 for (size_t i = 0; i < paths.size(); i++) { 349 lcb_SDSPEC spec = lcb_SDSPEC(); 350 spec.sdcmd = m_opcode; 351 LCB_SDSPEC_SET_PATH(&spec, paths[i].c_str(), paths[i].size()); 352 specs.push_back(spec); 353 } 354 } else if (m_opcode == LCB_SDCMD_GET) { 355 lcb_SDSPEC spec = lcb_SDSPEC(); 356 spec.sdcmd = LCB_SDCMD_GET_FULLDOC; 357 specs.push_back(spec); 358 } 359 cmd.specs = specs.data(); 360 cmd.nspecs = specs.size(); 361 err = lcb_subdoc3(instance, this, &cmd); 362 if (err != LCB_SUCCESS) { 363 throw LcbError(err, "Failed to schedule " + cmdname + " command"); 364 } 365 } 366 lcb_sched_leave(instance); 367 err = lcb_wait(instance); 368 if (err != LCB_SUCCESS) { 369 throw LcbError(err, "Failed to execute " + cmdname + " command"); 370 } 371 } 372 373 protected: 374 lcb_SUBDOCOP m_opcode; 375 const char *m_description; 376 377 cliopts::ListOption o_paths; 378 cliopts::ListOption o_xattrs; 379 cliopts::BoolOption o_deleted; 380 }; 381 382 class RemoveHandler : public Handler 383 { 384 public: 385 HANDLER_DESCRIPTION("Remove path in the item on the server") 386 HANDLER_USAGE("[OPTIONS...] KEY...") 387 388 RemoveHandler(const char *name = "remove") : Handler(name), o_paths("path"), o_xattrs("xattr") 389 { 390 o_paths.abbrev('p').argdesc("PATH").description( 391 "JSON path in the document. When skipped, the operation applied to full document."); 392 o_xattrs.abbrev('x').argdesc("PATH").description("Access XATTR path (exentnded attributes)"); 393 } 394 395 protected: 396 void addOptions() 397 { 398 Handler::addOptions(); 399 parser.addOption(o_paths.reset()); 400 parser.addOption(o_xattrs.reset()); 401 } 402 403 void run() 404 { 405 lcb_error_t err; 406 407 const std::vector< std::string > &keys = parser.getRestArgs(); 408 if (keys.empty()) { 409 throw BadArg("At least one key has to be specified"); 410 } 411 std::vector< std::string > paths = o_paths.result(); 412 std::vector< std::string > xattrs = o_xattrs.result(); 413 414 lcb_sched_enter(instance); 415 for (size_t ii = 0; ii < keys.size(); ++ii) { 416 const std::string &key = keys[ii]; 417 lcb_CMDSUBDOC cmd = {0}; 418 419 LCB_KREQ_SIMPLE(&cmd.key, key.c_str(), key.size()); 420 std::vector< lcb_SDSPEC > specs; 421 422 if (o_xattrs.passed()) { 423 for (size_t i = 0; i < xattrs.size(); i++) { 424 lcb_SDSPEC spec = lcb_SDSPEC(); 425 spec.sdcmd = LCB_SDCMD_REMOVE; 426 spec.options |= LCB_SDSPEC_F_XATTRPATH; 427 LCB_SDSPEC_SET_PATH(&spec, xattrs[i].c_str(), xattrs[i].size()); 428 specs.push_back(spec); 429 } 430 } 431 if (o_paths.passed()) { 432 for (size_t i = 0; i < paths.size(); i++) { 433 lcb_SDSPEC spec = lcb_SDSPEC(); 434 spec.sdcmd = LCB_SDCMD_REMOVE; 435 LCB_SDSPEC_SET_PATH(&spec, paths[i].c_str(), paths[i].size()); 436 specs.push_back(spec); 437 } 438 } else { 439 lcb_SDSPEC spec = lcb_SDSPEC(); 440 spec.sdcmd = LCB_SDCMD_REMOVE_FULLDOC; 441 specs.push_back(spec); 442 } 443 cmd.specs = specs.data(); 444 cmd.nspecs = specs.size(); 445 err = lcb_subdoc3(instance, this, &cmd); 446 if (err != LCB_SUCCESS) { 447 throw LcbError(err, "Failed to schedule remove command"); 448 } 449 } 450 lcb_sched_leave(instance); 451 err = lcb_wait(instance); 452 if (err != LCB_SUCCESS) { 453 throw LcbError(err, "Failed to execute remove"); 454 } 455 } 456 457 protected: 458 cliopts::ListOption o_paths; 459 cliopts::ListOption o_xattrs; 460 }; 461 462 class UpsertHandler : public Handler 463 { 464 public: 465 HANDLER_DESCRIPTION("Store document on the server") 466 HANDLER_USAGE("[OPTIONS...] KEY VALUE") 467 468 UpsertHandler(const char *name = "upsert") : Handler(name), o_xattrs("xattr"), o_expiry("expiry") 469 { 470 o_xattrs.abbrev('x').argdesc("PATH=VALUE").description("Store XATTR path (exentnded attributes)"); 471 o_expiry.abbrev('e').argdesc("TIME").description( 472 "Expiration time in seconds. Relative (up to 30 days) or absolute (as Unix timestamp)"); 473 } 474 475 protected: 476 void addOptions() 477 { 478 Handler::addOptions(); 479 parser.addOption(o_xattrs.reset()); 480 parser.addOption(o_expiry.reset()); 481 } 482 483 void run() 484 { 485 lcb_error_t err; 486 487 const std::vector< std::string > &args = parser.getRestArgs(); 488 if (args.size() != 2) { 489 throw BadArg("Exactly two arguments required: KEY and VALUE"); 490 } 491 // currently it is not possible to upsert document without XATTRs 492 // so lets allocate "_cbc" object with some useful stuff 493 std::string ver = "\"" LCB_CLIENT_ID "\""; 494 std::string path = "_cbc.version"; 495 496 std::string key = args[0]; 497 std::string value = args[1]; 498 std::vector< std::pair< std::string, std::string > > xattrs = o_xattrs.result(); 499 500 std::vector< lcb_SDSPEC > specs; 501 lcb_CMDSUBDOC cmd = {0}; 502 503 LCB_KREQ_SIMPLE(&cmd.key, key.c_str(), key.size()); 504 cmd.cmdflags = LCB_CMDSUBDOC_F_UPSERT_DOC; 505 506 if (o_xattrs.passed()) { 507 for (size_t i = 0; i < xattrs.size(); i++) { 508 lcb_SDSPEC spec = lcb_SDSPEC(); 509 spec.sdcmd = LCB_SDCMD_DICT_UPSERT; 510 spec.options = LCB_SDSPEC_F_XATTRPATH | LCB_SDSPEC_F_MKINTERMEDIATES; 511 LCB_SDSPEC_SET_PATH(&spec, xattrs[i].first.c_str(), xattrs[i].first.size()); 512 LCB_SDSPEC_SET_VALUE(&spec, xattrs[i].second.c_str(), xattrs[i].second.size()); 513 specs.push_back(spec); 514 } 515 } else { 516 lcb_SDSPEC spec = lcb_SDSPEC(); 517 spec.sdcmd = LCB_SDCMD_DICT_UPSERT; 518 spec.options = LCB_SDSPEC_F_XATTRPATH | LCB_SDSPEC_F_MKINTERMEDIATES; 519 LCB_SDSPEC_SET_PATH(&spec, path.c_str(), path.size()); 520 LCB_SDSPEC_SET_VALUE(&spec, ver.c_str(), ver.size()); 521 specs.push_back(spec); 522 } 523 { 524 lcb_SDSPEC spec = lcb_SDSPEC(); 525 spec.sdcmd = LCB_SDCMD_SET_FULLDOC; 526 LCB_SDSPEC_SET_VALUE(&spec, value.c_str(), value.size()); 527 specs.push_back(spec); 528 } 529 cmd.specs = specs.data(); 530 cmd.nspecs = specs.size(); 531 if (o_expiry.passed()) { 532 cmd.exptime = o_expiry.result(); 533 } 534 535 lcb_sched_enter(instance); 536 err = lcb_subdoc3(instance, this, &cmd); 537 if (err != LCB_SUCCESS) { 538 throw LcbError(err, "Failed to schedule upsert command"); 539 } 540 lcb_sched_leave(instance); 541 542 err = lcb_wait(instance); 543 if (err != LCB_SUCCESS) { 544 throw LcbError(err, "Failed to execute upsert"); 545 } 546 } 547 548 protected: 549 cliopts::PairListOption o_xattrs; 550 cliopts::UIntOption o_expiry; 551 }; 552 553 class MutationHandler : public Handler 554 { 555 public: 556 HANDLER_USAGE("[OPTIONS...] KEY...") 557 558 MutationHandler(const char *name, lcb_SUBDOCOP opcode, const char *description_, bool enable_intermediates = true) 559 : Handler(name), m_opcode(opcode), m_description(description_), o_paths("path"), o_xattrs("xattr"), 560 o_expiry("expiry"), o_intermediates("intermediates"), o_upsert("upsert"), 561 m_enable_intermediates(enable_intermediates) 562 { 563 o_paths.abbrev('p').argdesc("PATH=VALUE").description("JSON path in the document"); 564 o_xattrs.abbrev('x').argdesc("PATH=VALUE").description("XATTR path (exentnded attributes)"); 565 o_expiry.abbrev('e').argdesc("TIME").description( 566 "Expiration time in seconds. Relative (up to 30 days) or absolute (as Unix timestamp)"); 567 o_intermediates.abbrev('i').description("Create intermediate paths"); 568 o_upsert.abbrev('u').description("Create document if it doesn't exist"); 569 } 570 571 const char *description() const 572 { 573 return m_description; 574 } 575 576 protected: 577 void addOptions() 578 { 579 Handler::addOptions(); 580 parser.addOption(o_xattrs.reset()); 581 parser.addOption(o_paths.reset()); 582 parser.addOption(o_expiry.reset()); 583 parser.addOption(o_upsert.reset()); 584 if (m_enable_intermediates) { 585 parser.addOption(o_intermediates.reset()); 586 } 587 } 588 589 void run() 590 { 591 lcb_error_t err; 592 593 const std::vector< std::string > &keys = parser.getRestArgs(); 594 if (keys.empty()) { 595 throw BadArg("At least one key has to be specified"); 596 } 597 std::vector< std::pair< std::string, std::string > > paths = o_paths.result(); 598 std::vector< std::pair< std::string, std::string > > xattrs = o_xattrs.result(); 599 600 if (xattrs.empty() && paths.empty()) { 601 throw BadArg("At least one path has to be specified"); 602 } 603 604 lcb_sched_enter(instance); 605 606 for (size_t ii = 0; ii < keys.size(); ++ii) { 607 const std::string &key = keys[ii]; 608 lcb_CMDSUBDOC cmd = {0}; 609 std::vector< lcb_SDSPEC > specs; 610 611 LCB_KREQ_SIMPLE(&cmd.key, key.c_str(), key.size()); 612 if (o_upsert.passed()) { 613 cmd.cmdflags = LCB_CMDSUBDOC_F_UPSERT_DOC; 614 } 615 if (o_xattrs.passed()) { 616 for (size_t i = 0; i < xattrs.size(); i++) { 617 lcb_SDSPEC spec = lcb_SDSPEC(); 618 spec.sdcmd = m_opcode; 619 spec.options = LCB_SDSPEC_F_XATTRPATH; 620 if (o_intermediates.passed()) { 621 spec.options |= LCB_SDSPEC_F_MKINTERMEDIATES; 622 } 623 LCB_SDSPEC_SET_PATH(&spec, xattrs[i].first.c_str(), xattrs[i].first.size()); 624 LCB_SDSPEC_SET_VALUE(&spec, xattrs[i].second.c_str(), xattrs[i].second.size()); 625 specs.push_back(spec); 626 } 627 } 628 if (o_paths.passed()) { 629 for (size_t i = 0; i < paths.size(); i++) { 630 lcb_SDSPEC spec = lcb_SDSPEC(); 631 spec.sdcmd = m_opcode; 632 spec.options = 0; 633 if (o_intermediates.passed()) { 634 spec.options |= LCB_SDSPEC_F_MKINTERMEDIATES; 635 } 636 LCB_SDSPEC_SET_PATH(&spec, paths[i].first.c_str(), paths[i].first.size()); 637 LCB_SDSPEC_SET_VALUE(&spec, paths[i].second.c_str(), paths[i].second.size()); 638 specs.push_back(spec); 639 } 640 } 641 cmd.specs = specs.data(); 642 cmd.nspecs = specs.size(); 643 if (o_expiry.passed()) { 644 cmd.exptime = o_expiry.result(); 645 } 646 err = lcb_subdoc3(instance, this, &cmd); 647 if (err != LCB_SUCCESS) { 648 throw LcbError(err, "Failed to schedule " + cmdname + " command"); 649 } 650 } 651 lcb_sched_leave(instance); 652 653 err = lcb_wait(instance); 654 if (err != LCB_SUCCESS) { 655 throw LcbError(err, "Failed to execute " + cmdname + " command"); 656 } 657 } 658 659 protected: 660 lcb_SUBDOCOP m_opcode; 661 const char *m_description; 662 663 cliopts::PairListOption o_paths; 664 cliopts::PairListOption o_xattrs; 665 cliopts::UIntOption o_expiry; 666 cliopts::BoolOption o_intermediates; 667 cliopts::BoolOption o_upsert; 668 669 bool m_enable_intermediates; 670 }; 671 672 class HelpHandler : public Handler 673 { 674 public: 675 HANDLER_DESCRIPTION("Show help") 676 HelpHandler() : Handler("help") 677 { 678 } 679 680 protected: 681 void run() 682 { 683 fprintf(stderr, "Usage: <command> [options]\n"); 684 fprintf(stderr, "command may be:\n"); 685 for (const char **cur = handlers_sorted; *cur; cur++) { 686 const Handler *handler = handlers[*cur]; 687 fprintf(stderr, " %-20s", *cur); 688 fprintf(stderr, "%s\n", handler->description()); 689 } 690 } 691 }; 692 693 class DumpHandler : public Handler 694 { 695 public: 696 HANDLER_DESCRIPTION("Dump metrics and internal state of library") 697 DumpHandler() : Handler("dump") {} 698 699 protected: 700 void run() 701 { 702 lcb_METRICS *metrics = NULL; 703 size_t ii; 704 705 lcb_dump(instance, stderr, LCB_DUMP_ALL); 706 lcb_cntl(instance, LCB_CNTL_GET, LCB_CNTL_METRICS, &metrics); 707 708 if (metrics) { 709 fprintf(stderr, "%p: nsrv: %d, retried: %lu\n", (void *)instance, (int)metrics->nservers, 710 (unsigned long)metrics->packets_retried); 711 for (ii = 0; ii < metrics->nservers; ii++) { 712 fprintf(stderr, " [srv-%d] snt: %lu, rcv: %lu, q: %lu, err: %lu, tmo: %lu, nmv: %lu, orph: %lu\n", 713 (int)ii, (unsigned long)metrics->servers[ii]->packets_sent, 714 (unsigned long)metrics->servers[ii]->packets_read, 715 (unsigned long)metrics->servers[ii]->packets_queued, 716 (unsigned long)metrics->servers[ii]->packets_errored, 717 (unsigned long)metrics->servers[ii]->packets_timeout, 718 (unsigned long)metrics->servers[ii]->packets_nmv, 719 (unsigned long)metrics->servers[ii]->packets_ownerless); 720 } 721 } 722 } 723 }; 724 } // namespace subdoc 725 726 static void setupHandlers() 727 { 728 handlers["help"] = new subdoc::HelpHandler(); 729 handlers["dump"] = new subdoc::DumpHandler(); 730 handlers["get"] = new subdoc::LookupHandler("get", LCB_SDCMD_GET, "Retrieve path from the item on the server"); 731 handlers["exists"] = 732 new subdoc::LookupHandler("exists", LCB_SDCMD_EXISTS, "Check if path exists in the item on the server"); 733 handlers["exist"] = handlers["exists"]; 734 handlers["remove"] = new subdoc::RemoveHandler(); 735 handlers["delete"] = handlers["remove"]; 736 handlers["upsert"] = new subdoc::UpsertHandler(); 737 handlers["set"] = handlers["upsert"]; 738 handlers["dict-upsert"] = 739 new subdoc::MutationHandler("dict-upsert", LCB_SDCMD_DICT_UPSERT, "Unconditionally set the value at the path"); 740 handlers["dict-add"] = new subdoc::MutationHandler( 741 "dict-add", LCB_SDCMD_DICT_ADD, "Add the value at the given path, if the given path does not exist"); 742 handlers["replace"] = 743 new subdoc::MutationHandler("replace", LCB_SDCMD_REPLACE, "Replace the value at the specified path", false); 744 handlers["array-add-first"] = 745 new subdoc::MutationHandler("array-add-first", LCB_SDCMD_ARRAY_ADD_FIRST, "Prepend the value(s) to the array"); 746 handlers["array-add-last"] = 747 new subdoc::MutationHandler("array-add-last", LCB_SDCMD_ARRAY_ADD_LAST, "Append the value(s) to the array"); 748 handlers["array-add-unique"] = new subdoc::MutationHandler( 749 "array-add-unique", LCB_SDCMD_ARRAY_ADD_UNIQUE, 750 "Add the value to the array indicated by the path, if the value is not already in the array"); 751 handlers["array-insert"] = new subdoc::MutationHandler( 752 "array-insert", LCB_SDCMD_ARRAY_INSERT, 753 "Add the value at the given array index. Path must include index, e.g. `my.list[4]`"); 754 handlers["counter"] = new subdoc::MutationHandler( 755 "counter", LCB_SDCMD_COUNTER, 756 "Increment or decrement an existing numeric path. The value must be 64-bit integer"); 757 handlers["size"] = new subdoc::LookupHandler("size", LCB_SDCMD_GET_COUNT, 758 "Count the number of elements in an array or dictionary"); 759 handlers["get-count"] = handlers["size"]; 760 } 761 762 static void cleanup() 763 { 764 std::map< std::string, subdoc::Handler * >::iterator iter = handlers.begin(); 765 766 handlers["exists"] = NULL; 767 handlers["delete"] = NULL; 768 handlers["set"] = NULL; 769 handlers["get-count"] = NULL; 770 771 for (; iter != handlers.end(); iter++) { 772 if (iter->second) { 773 delete iter->second; 774 } 775 } 776 777 if (instance) { 778 if (config.shouldDump()) { 779 lcb_dump(instance, stderr, LCB_DUMP_ALL); 780 } 781 if (config.useTimings()) { 782 hg.write(); 783 } 784 if (instance) { 785 lcb_destroy(instance); 786 } 787 } 788 } 789 790 static void real_main(int argc, char **argv) 791 { 792 std::string history_path = ConnParams::getUserHome() + CBCSUBDOC_HISTORY_FILENAME; 793 Parser parser; 794 795 config.addToParser(parser); 796 parser.parse(argc, argv); 797 config.processOptions(); 798 799 lcb_create_st cropts; 800 memset(&cropts, 0, sizeof cropts); 801 config.fillCropts(cropts); 802 do_or_die(lcb_create(&instance, &cropts), "Failed to create connection"); 803 config.doCtls(); 804 do_or_die(lcb_connect(instance), "Failed to connect to cluster"); 805 do_or_die(lcb_wait(instance), "Failed to wait for connection bootstrap"); 806 do_or_die(lcb_get_bootstrap_status(instance), "Failed to bootstrap"); 807 if (config.useTimings()) { 808 hg.install(instance, stdout); 809 } 810 { 811 int activate = 1; 812 lcb_cntl(instance, LCB_CNTL_SET, LCB_CNTL_METRICS, &activate); 813 } 814 setupHandlers(); 815 std::atexit(cleanup); 816 lcb_install_callback3(instance, LCB_CALLBACK_SDLOOKUP, subdoc_callback); 817 lcb_install_callback3(instance, LCB_CALLBACK_SDMUTATE, subdoc_callback); 818 819 linenoiseSetCompletionCallback(command_completion); 820 linenoiseSetMultiLine(1); 821 linenoiseHistoryLoad(history_path.c_str()); 822 823 do { 824 char *line = linenoise("subdoc> "); 825 if (line == NULL) { 826 break; 827 } 828 if (line[0] != '\0') { 829 linenoiseHistoryAdd(line); 830 linenoiseHistorySave(history_path.c_str()); 831 832 int cmd_argc = 0; 833 char **cmd_argv = NULL; 834 int rv = cliopts_split_args(line, &cmd_argc, &cmd_argv); 835 if (rv) { 836 fprintf(stderr, "Invalid input: unterminated single quote\n"); 837 } else { 838 if (rv == 0 && cmd_argc > 0) { 839 char *cmd_name = cmd_argv[0]; 840 subdoc::Handler *handler = handlers[cmd_name]; 841 if (handler == NULL) { 842 fprintf(stderr, "Unknown command %s\n", cmd_name); 843 subdoc::HelpHandler().execute(cmd_argc, cmd_argv); 844 } else { 845 try { 846 handler->execute(cmd_argc, cmd_argv); 847 } catch (std::exception &err) { 848 fprintf(stderr, "%s\n", err.what()); 849 } 850 } 851 free(cmd_argv); 852 } 853 } 854 } 855 free(line); 856 } while (true); 857 } 858 859 int main(int argc, char **argv) 860 { 861 try { 862 real_main(argc, argv); 863 return 0; 864 } catch (std::exception &exc) { 865 std::cerr << exc.what() << std::endl; 866 exit(EXIT_FAILURE); 867 } 868 } 869