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