xref: /6.6.0/subjson/subdoc/operations.cc (revision e51529ef)
1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3*     Copyright 2015 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 INCLUDE_SUBDOC_STRING_SRC
19#define INCLUDE_SUBDOC_NTOHLL
20#define NOMINMAX // For Visual Studio
21
22#include "operations.h"
23#include "validate.h"
24#include "util.h"
25#include <errno.h>
26#include <inttypes.h>
27#include <string>
28#include <limits>
29
30using Subdoc::Loc;
31using Subdoc::Error;
32using Subdoc::Operation;
33using Subdoc::Path;
34using Subdoc::Match;
35using Subdoc::Command;
36
37static Loc loc_COMMA(",", 1);
38static Loc loc_QUOTE("\"", 1);
39static Loc loc_COMMA_QUOTE(",\"", 2);
40static Loc loc_QUOTE_COLON("\":", 2);
41
42/**
43 * Performs common matching using the currently designated path. Note that
44 * unlike do_get(), a missing match is not an error. This is the reason do_get()
45 * and do_match_common() are separate functions, as do_get() (i.e. a simple get
46 * operation) considers a non-complete match as a failure.
47 *
48 * @return success if no parse error or mismatch happened, failure otherwise.
49 */
50Error
51Operation::do_match_common(Match::SearchOptions options)
52{
53    m_match.extra_options = options;
54    m_match.exec_match(m_doc, m_path, m_jsn);
55
56    if (m_match.matchres == JSONSL_MATCH_TYPE_MISMATCH) {
57        return Error::PATH_MISMATCH;
58    } else if (m_match.status != JSONSL_ERROR_SUCCESS) {
59        if (m_match.status == JSONSL_ERROR_LEVELS_EXCEEDED) {
60            return Error::DOC_ETOODEEP;
61        } else {
62            return Error::DOC_NOTJSON;
63        }
64    } else {
65        return Error::SUCCESS;
66    }
67}
68
69Error
70Operation::do_get()
71{
72    if (m_match.matchres != JSONSL_MATCH_COMPLETE) {
73        return Error::PATH_ENOENT;
74    }
75
76    m_result->m_match = m_match.loc_deepest;
77    return Error::SUCCESS;
78}
79
80/* Start at the beginning of the buffer, stripping first comma */
81#define STRIP_FIRST_COMMA 1
82
83/* Start at the end of the buffer, stripping last comma */
84#define STRIP_LAST_COMMA 2
85
86static void
87strip_comma(Loc *loc, int mode)
88{
89    unsigned ii;
90    if (mode == STRIP_FIRST_COMMA) {
91        for (ii = 0; ii < loc->length; ii++) {
92            if (loc->at[ii] == ',') {
93                loc->at += (ii + 1);
94                loc->length -= (ii + 1);
95                return;
96            }
97        }
98    } else {
99        for (ii = loc->length; ii; ii--) {
100            if (loc->at[ii-1] == ',') {
101                loc->length = ii-1;
102                return;
103            }
104        }
105    }
106}
107
108Error
109Operation::do_remove()
110{
111    Error rv = do_match_common(Match::GET_FOLLOWING_SIBLINGS);
112
113    if (!rv.success()) {
114        return rv;
115    } else if (m_match.matchres != JSONSL_MATCH_COMPLETE) {
116        return Error::PATH_ENOENT;
117    }
118
119    // Deepest is the actual match
120    const Loc& match_loc = m_match.loc_deepest;
121    /*
122     * If match is the _last_ item, we need to strip the _last_ comma from
123     * doc[0] (since doc[1] will never have a trailing comma); e.g.
124     *  NEWDOC[0] = { ..., ..., ... (---> , <---)
125     *  [deleted]
126     *  NEWDOC[1] = } // Never any commas here!
127     *
128     * If match is the _first_ item, we need to strip the _first_ comma
129     * from newdoc[1]:
130     *
131     *  NEWDOC[0] = { // never any commas here!
132     *  [deleted]
133     *  NEWDOC[1] = (---> , <---) ... , ... }
134     *
135     *
136     *
137     * If match is any other item, we need to strip the _last_ comma from
138     * doc[0], which is the comma that preceded this item.
139     */
140
141    /* Remove the matches, starting from the beginning of the key */
142    if (!m_match.loc_key.empty()) {
143        newdoc_at(0).end_at_begin(m_doc, m_match.loc_key, Loc::NO_OVERLAP);
144    } else {
145        newdoc_at(0).end_at_begin(m_doc, match_loc, Loc::NO_OVERLAP);
146    }
147
148    newdoc_at(1).begin_at_end(m_doc, match_loc, Loc::NO_OVERLAP);
149
150    if (m_match.num_siblings) {
151        if (m_match.is_last()) {
152            /*
153             * NEWDOC[0] = [a,b,c, <-- Strip here
154             * MATCH     = d
155             * NEWDOC[1] = ]
156             */
157            strip_comma(&newdoc_at(0), STRIP_LAST_COMMA);
158        } else {
159            /*
160             * NEWDOC[0] = [a,b,
161             * MATCH     = c
162             * NEWDOC[1] = Strip here -->, d]
163             */
164            strip_comma(&newdoc_at(1), STRIP_FIRST_COMMA);
165        }
166    }
167
168    m_result->m_newlen = 2;
169    return Error::SUCCESS;
170}
171
172Error
173Operation::do_store_dict()
174{
175    // Check that the last element is not an array first.
176    if (m_optype != Command::REPLACE &&
177            path()[path().size()-1].ptype == JSONSL_PATH_NUMERIC) {
178        return Error::PATH_EINVAL;
179    }
180    if (m_match.matchres != JSONSL_MATCH_COMPLETE) {
181        if (m_optype == Command::REPLACE) {
182            // Nothing to replace
183            return Error::PATH_ENOENT;
184        }
185        if (!m_match.immediate_parent_found) {
186            if (m_optype.is_mkdir_p()) {
187                return do_mkdir_p(MKDIR_P_DICT);
188            } else {
189                return Error::PATH_ENOENT;
190            }
191        }
192        // Otherwise, the immediate parent is found, and we can
193        // continue to add/upsert
194    } else if (m_match.matchres == JSONSL_MATCH_COMPLETE) {
195        if (m_optype.base() == Command::DICT_ADD) {
196            return Error::DOC_EEXISTS;
197        }
198    }
199
200    if (m_match.matchres == JSONSL_MATCH_COMPLETE) {
201        const Loc& match_loc = m_match.loc_deepest;
202        /* 1. Remove the old value from the first segment */
203        newdoc_at(0).end_at_begin(m_doc, match_loc, Loc::NO_OVERLAP);
204
205        /* 2. Insert the new value */
206        newdoc_at(1) = m_userval;
207
208        /* 3. Insert the rest of the document */
209        newdoc_at(2).begin_at_end(m_doc, match_loc, Loc::NO_OVERLAP);
210        m_result->m_newlen = 3;
211
212    } else if (m_match.immediate_parent_found) {
213        // Deepest is parent
214        const Loc& parent_loc = m_match.loc_deepest;
215
216        newdoc_at(0).end_at_end(m_doc, parent_loc, Loc::NO_OVERLAP);
217        /*TODO: The key might have a literal '"' in it, which has been escaped? */
218        if (m_match.num_siblings) {
219            newdoc_at(1) = loc_COMMA_QUOTE; /* ," */
220        } else {
221            newdoc_at(1) = loc_QUOTE /* " */;
222        }
223
224        /* Create the actual key: */
225        auto& comp = m_path->back();
226        newdoc_at(2).at = comp.pstr;
227        newdoc_at(2).length = comp.len;
228
229        /* Close the quote and add the dictionary key */
230        newdoc_at(3) = loc_QUOTE_COLON; /* ": */
231        /* new value */
232        newdoc_at(4) = m_userval;
233        /* Closing tokens */
234        newdoc_at(5).begin_at_end(m_doc, parent_loc, Loc::OVERLAP);
235        m_result->m_newlen = 6;
236    }
237    return Error::SUCCESS;
238}
239
240/**
241 * This function will inspect the match to find the deepest most parent
242 * and add missing entries in the document from the path. It will then
243 * insert the given value (either as a value to the dictionary key, or
244 * as an array with a single element (being the value) as the value for
245 * the dictionary key.
246 *
247 * In all cases, intermediate non-existing path elements must all refer
248 * to dictionary keys, not array elements (since array elements are in
249 * sequence there is no course of action if the array is empty and the
250 * path refers to a middle element).
251 *
252 * @param mode How to insert the value
253 * @return status
254 */
255Error
256Operation::do_mkdir_p(MkdirPMode mode)
257{
258    unsigned ii;
259    const Loc& parent_loc = m_match.loc_deepest;
260    newdoc_at(0).end_at_end(m_doc, parent_loc, Loc::NO_OVERLAP);
261
262    /* doc_new LAYOUT:
263     *
264     * [0] = HEADER
265     * [1] = _P header (bkbuf_extra)
266     * [2] = USER TEXT
267     * [3] = _P trailer (bkbuf_extra)
268     * [4] = TRAILER
269     */
270
271    /* figure out the components missing */
272    /* THIS IS RESERVED FOR doc_new[1]! */
273    if (m_match.num_siblings) {
274        m_result->m_bkbuf += ',';
275    }
276
277    /* Insert the first item. This is a dictionary key without any object
278     * wrapper: */
279    const Path::Component* comp = &m_path->get_component(m_match.match_level);
280    if (comp->ptype == JSONSL_PATH_NUMERIC) {
281        // If it's *not* a dictionary key, don't insert it!
282        return Error::PATH_ENOENT;
283    }
284    m_result->m_bkbuf += '"';
285    m_result->m_bkbuf.append(comp->pstr, comp->len);
286    m_result->m_bkbuf += "\":";
287
288    /* The next set of components must be created as entries within the
289     * newly created key */
290    for (ii = m_match.match_level + 1; ii < m_path->size(); ii++) {
291        comp = &m_path->get_component(ii);
292        if (comp->ptype != JSONSL_PATH_STRING) {
293            return Error::PATH_ENOENT;
294        }
295        m_result->m_bkbuf += "{\"";
296        m_result->m_bkbuf.append(comp->pstr, comp->len);
297        m_result->m_bkbuf += "\":";
298    }
299    if (mode == MKDIR_P_ARRAY) {
300        m_result->m_bkbuf += '[';
301    }
302
303    newdoc_at(1).length = m_result->m_bkbuf.size();
304
305    if (mode == MKDIR_P_ARRAY) {
306        m_result->m_bkbuf += ']';
307    }
308
309    for (ii = m_match.match_level+1; ii < m_path->size(); ii++) {
310        m_result->m_bkbuf += '}';
311    }
312    newdoc_at(3).length = m_result->m_bkbuf.size() - newdoc_at(1).length;
313
314    /* Set the buffers */
315    newdoc_at(1).at = m_result->m_bkbuf.data();
316    newdoc_at(3).at = m_result->m_bkbuf.data() + newdoc_at(1).length;
317    newdoc_at(2) = m_userval;
318
319    newdoc_at(4).begin_at_end(m_doc, parent_loc, Loc::OVERLAP);
320    m_result->m_newlen = 5;
321
322    return Error::SUCCESS;
323}
324
325/* Inserts a single element into an empty array */
326Error
327Operation::insert_singleton_element()
328{
329    const Loc& array_loc = m_match.loc_deepest;
330    /* First segment is ... [ */
331    newdoc_at(0).end_at_begin(m_doc, array_loc, Loc::OVERLAP);
332    /* User: */
333    newdoc_at(1) = m_userval;
334    /* Last segment is ... ] */
335    newdoc_at(2).begin_at_end(m_doc, array_loc, Loc::OVERLAP);
336
337    m_result->m_newlen = 3;
338    return Error::SUCCESS;
339}
340
341Error
342Operation::do_list_prepend()
343{
344    Error rv;
345
346    // Search for first element, perform the search, then pop it
347    if (m_path->add_array_index(0) != JSONSL_ERROR_SUCCESS) {
348        return Error::PATH_E2BIG;
349    }
350
351    rv = do_match_common(Match::GET_MATCH_ONLY);
352    m_path->pop();
353
354    if (!rv.success()) {
355        return rv;
356    }
357
358    if (m_match.matchres != JSONSL_MATCH_COMPLETE) {
359        if (m_match.immediate_parent_found) {
360            return insert_singleton_element();
361        } else if (m_optype.is_mkdir_p()) {
362            return do_mkdir_p(MKDIR_P_ARRAY);
363        } else {
364            return Error::PATH_ENOENT;
365        }
366    }
367
368    /* LAYOUT:
369     * NEWDOC[0] [....
370     * NEWDOC[1] = USER
371     * NEWDOC[2] = ,
372     * NEWDOC[3] = REST
373     */
374
375    const Loc& orig_firstloc = m_match.loc_deepest;
376    newdoc_at(0).end_at_begin(m_doc, orig_firstloc, Loc::NO_OVERLAP);
377    newdoc_at(1) = m_userval;
378    newdoc_at(2) = loc_COMMA;
379    newdoc_at(3).begin_at_begin(m_doc, orig_firstloc);
380
381    m_result->m_newlen = 4;
382    return Error::SUCCESS;
383}
384
385Error
386Operation::do_container_size()
387{
388    Error rv = do_match_common(Match::GET_MATCH_ONLY);
389    if (rv != Error::SUCCESS) {
390        return rv;
391    }
392    if (m_match.matchres != JSONSL_MATCH_COMPLETE) {
393        return Error::PATH_ENOENT;
394    }
395
396    size_t size = m_match.num_children;
397    if (m_match.type == JSONSL_T_OBJECT) {
398        size /= 2;
399    } else if (m_match.type != JSONSL_T_LIST) {
400        return Error::PATH_MISMATCH;
401    }
402
403    m_result->m_numbuf = std::to_string(size);
404    m_result->m_newlen = 1;
405    m_result->m_match.assign(
406        m_result->m_numbuf.c_str(), m_result->m_numbuf.size());
407
408    return Error::SUCCESS;
409}
410
411static bool
412is_json_ws(char c) {
413    // (i.e. non-whitespace) character. RFC 7159 says:
414    // Insignificant whitespace is allowed before or after any of the six
415    // structural characters.
416    //
417    //   ws = *(
418    //           %x20 /              ; Space
419    //           %x09 /              ; Horizontal tab
420    //           %x0A /              ; Line feed or New line
421    //           %x0D )              ; Carriage return
422
423    switch (c) {
424    case 0x20:
425    case 0x09:
426    case 0x0A:
427    case 0x0D:
428        return true;
429    default:
430        return false;
431    }
432}
433
434Error
435Operation::do_empty_append()
436{
437    // Empty path. Do a custom parse/insertion
438    const char *a_end = (m_doc.at + m_doc.length) - 1;
439
440    // Find terminating bracket
441    for (; a_end != m_doc.at && isspace(*a_end); --a_end) {
442    }
443
444    if (a_end == m_doc.at || *a_end != ']') {
445        switch (*a_end) {
446        case ']':
447        case '}':
448            return Error::PATH_MISMATCH;
449        default:
450            return Error::DOC_NOTJSON;
451        }
452    }
453
454    // Find last comma, element, or beginning of the array.
455    // Rather than fully parse json, simply seek to the last significant
456    // JSON character
457    const char *e_comma = a_end - 1;
458    for (; e_comma != m_doc.at && is_json_ws(*e_comma); --e_comma) {
459    }
460
461    newdoc_at(0).assign(m_doc.at, (e_comma - m_doc.at) + 1);
462
463    if (*e_comma == '[' || *e_comma == ',') {
464        newdoc_at(1) = m_userval;
465        newdoc_at(2).assign(a_end, 1);
466        m_result->m_newlen = 3;
467    } else if (!isspace(*e_comma)) {
468        newdoc_at(1) = loc_COMMA;
469        newdoc_at(2) = m_userval;
470        newdoc_at(3).assign(a_end, 1);
471        m_result->m_newlen = 4;
472    } else {
473        // Couldn't find a non-space, ',' or '[' character
474        return Error::DOC_NOTJSON;
475    }
476    return Error::SUCCESS;
477}
478
479Error
480Operation::do_list_append()
481{
482
483    if (m_path->size() == 1 && m_optype.base() == Command::ARRAY_APPEND) {
484        // i.e. only the root element
485        return do_empty_append();
486    }
487
488    Error rv;
489
490    // Find the match itself, no magic needed!
491    bool is_unique = m_optype.base() == Command::ARRAY_ADD_UNIQUE;
492    if (is_unique) {
493        m_match.ensure_unique = m_userval;
494    }
495
496    rv = do_match_common(Match::GET_MATCH_ONLY);
497    if (!rv.success()) {
498        // Parse error (ENOENT excluded)
499        return rv;
500    }
501
502    if (m_match.matchres != JSONSL_MATCH_COMPLETE) {
503        // Not complete. Determine if mkdir_p should be used
504        if (m_optype.is_mkdir_p()) {
505            return do_mkdir_p(MKDIR_P_ARRAY);
506        } else {
507            return Error::PATH_ENOENT;
508        }
509    }
510
511    // All other errors should be handled above
512    SUBDOC_ASSERT(m_match.matchres == JSONSL_MATCH_COMPLETE);
513
514    // Incorrect terminal type!
515    if (m_match.type != JSONSL_T_LIST) {
516        return Error::PATH_MISMATCH;
517    }
518
519    if (is_unique && m_match.unique_item_found) {
520        // Unique item already exists
521        return Error::DOC_EEXISTS;
522    }
523    const Loc& array_loc = m_match.loc_deepest;
524
525    if (m_match.num_children == 0) {
526        // Special case, no children; append and prepend are the same here!
527        return insert_singleton_element();
528    }
529
530    /* Last element */
531    newdoc_at(0).end_at_end(m_doc, array_loc, Loc::NO_OVERLAP);
532    /* Insert comma */
533    newdoc_at(1) = loc_COMMA;
534    /* User */
535    newdoc_at(2) = m_userval;
536    /* Parent end */
537    newdoc_at(3).begin_at_end(m_doc, array_loc, Loc::OVERLAP);
538
539    m_result->m_newlen = 4;
540    return Error::SUCCESS;
541}
542
543Error
544Operation::do_insert()
545{
546    auto& lastcomp = m_path->get_component(m_path->size()-1);
547    if (lastcomp.ptype != JSONSL_PATH_NUMERIC) {
548        return Error::PATH_EINVAL;
549    }
550    if (lastcomp.is_neg) {
551        // Negative paths are invalid for insert operations
552        return Error::PATH_EINVAL;
553    }
554
555    Error status = do_match_common(Match::GET_MATCH_ONLY);
556    if (!status.success()) {
557        return status;
558    }
559
560    if (m_match.matchres == JSONSL_MATCH_COMPLETE) {
561        const Loc& match_loc = m_match.loc_deepest;
562
563        /*
564         * DOCNEW[0] = ... [
565         * DOCNEW[1] = USER
566         * DOCNEW[2] = ,
567         * DOCNEW[3] = MATCH
568         */
569        m_result->m_newlen = 4;
570        newdoc_at(0).end_at_begin(m_doc, match_loc, Loc::NO_OVERLAP);
571        newdoc_at(1) = m_userval;
572        newdoc_at(2) = loc_COMMA;
573        newdoc_at(3).begin_at_begin(m_doc, match_loc);
574        return Error::SUCCESS;
575
576    } else if (m_match.immediate_parent_found) {
577        const Loc& array_loc = m_match.loc_deepest;
578        if (m_match.num_siblings == 0 && lastcomp.idx == 0) {
579            // Equivalent to prepend/push_first
580            /*
581             * DOCNEW[0] = ... [
582             * DOCNEW[1] = USER
583             * DOCNEW[2] = ] ...
584             */
585            m_result->m_newlen = 3;
586            newdoc_at(0).end_at_begin(m_doc, array_loc, Loc::OVERLAP);
587            newdoc_at(1) = m_userval;
588            newdoc_at(2).begin_at_end(m_doc, array_loc, Loc::OVERLAP);
589            return Error::SUCCESS;
590
591        } else if (lastcomp.idx == m_match.num_siblings) {
592            // Equivalent to append/push_last
593            /*
594             * (assume DOC = [a,b,c,d,e]
595             * DOCNEW[0] = e (last char before ']')
596             * DOCNEW[1] = , (since there are items in the list)
597             * DOCNEW[2] = USER
598             * DOCNEW[3] = ]
599             */
600            m_result->m_newlen = 4;
601            newdoc_at(0).end_at_end(m_doc, array_loc, Loc::NO_OVERLAP);
602            newdoc_at(1) = loc_COMMA;
603            newdoc_at(2) = m_userval;
604            newdoc_at(3).begin_at_end(m_doc, array_loc, Loc::OVERLAP);
605            return Error::SUCCESS;
606
607        } else {
608            return Error::PATH_ENOENT;
609        }
610    } else {
611        return Error::PATH_ENOENT;
612    }
613}
614
615static Error
616parse_int64(const Loc& orig, int64_t& outval)
617{
618    const char *cur = orig.at;
619    size_t n = orig.length;
620
621    if (!n) {
622        return Error::VALUE_EMPTY; // Empty
623    }
624    if (*cur == '-') {
625        cur++;
626        if (!--n) {
627            return Error::DELTA_EINVAL;
628        }
629    }
630    if (*cur == '0') {
631        // Can't start with a zero. It's either a leading zero or an empty
632        // delta
633        return Error::DELTA_EINVAL;
634    }
635
636    outval = 0;
637    for (size_t ii = 0; ii < n; ii++) {
638        if (!isdigit(cur[ii])) {
639            return Error::DELTA_EINVAL; // Not a number!
640        }
641        // Get the numeric value of the digit, with '0' being the lowest
642        // value digit character in the ascii table, and with digits appearing
643        // in order, such that '9' (0x39) - '0' (0x30) == 0
644
645        uint64_t newval = (outval * 10) + (cur[ii] - '0');
646        if (newval < static_cast<uint64_t>(outval) ||
647                newval > std::numeric_limits<int64_t>::max()) {
648            // mismatch
649            return Error::DELTA_EINVAL;
650        }
651        outval = static_cast<int64_t>(newval);
652    }
653    if (*orig.at == '-') {
654        outval *= -1;
655    }
656    return Error::SUCCESS;
657}
658
659Error
660Operation::do_arith_op()
661{
662    Error status;
663    int64_t delta;
664    int64_t numres = 0;
665    // Verify the digit first
666
667    status = parse_int64(m_userval, delta);
668    if (!status.success()) {
669        return status;
670    }
671
672    /* Find the number first */
673    status = do_match_common(m_optype.is_mkdir_p() ?
674            Match::GET_FOLLOWING_SIBLINGS : Match::GET_MATCH_ONLY);
675
676    if (status != Error::SUCCESS) {
677        return status;
678    }
679
680    if (m_match.matchres == JSONSL_MATCH_COMPLETE) {
681        const Loc& num_loc = m_match.loc_deepest;
682        if (m_match.type != JSONSL_T_SPECIAL) {
683            return Error::PATH_MISMATCH;
684        } else if (m_match.sflags & ~(JSONSL_SPECIALf_NUMERIC)) {
685            return Error::PATH_MISMATCH;
686        } else  {
687            errno = 0;
688            numres = strtoll(num_loc.at, NULL, 10);
689
690            if (errno == ERANGE) {
691                return Error::NUM_E2BIG;
692            }
693
694            /* Calculate what to place inside the buffer. We want to be gentle here
695             * and not force 64 bit C arithmetic to confuse users, so use proper
696             * integer overflow/underflow with a 64 (or rather, 63) bit limit. */
697            if (delta >= 0 && numres >= 0) {
698                if (std::numeric_limits<int64_t>::max() - delta < numres) {
699                    return Error::DELTA_OVERFLOW;
700                }
701            } else if (delta < 0 && numres < 0) {
702                if (delta < std::numeric_limits<int64_t>::min() - numres) {
703                    return Error::DELTA_OVERFLOW;
704                }
705            }
706
707            numres += delta;
708            m_result->m_numbuf = std::to_string(numres);
709        }
710    } else {
711        if (!m_optype.is_mkdir_p() && !m_match.immediate_parent_found) {
712            return Error::PATH_ENOENT;
713        }
714
715        if (m_match.type != JSONSL_T_OBJECT) {
716            return Error::PATH_ENOENT;
717        }
718
719        m_result->m_numbuf = std::to_string(delta);
720        m_userval.at = m_result->m_numbuf.data();
721        m_userval.length = m_result->m_numbuf.size();
722        m_optype = Command::DICT_ADD_P;
723        if ((status = do_store_dict()) != Error::SUCCESS) {
724            return status;
725        }
726        m_result->m_match = m_match.loc_deepest = m_userval;
727        return Error::SUCCESS;
728    }
729
730    // Unlike other ops, we modify the match value itself, as this is an
731    // in/out parameter
732
733    /* Preamble */
734    newdoc_at(0).end_at_begin(m_doc, m_match.loc_deepest, Loc::NO_OVERLAP);
735
736    /* New number */
737    newdoc_at(1).at = m_result->m_numbuf.data();
738    newdoc_at(1).length = m_result->m_numbuf.size();
739
740    /* Postamble */
741    newdoc_at(2).begin_at_end(m_doc, m_match.loc_deepest, Loc::NO_OVERLAP);
742    m_result->m_newlen = 3;
743
744    m_match.loc_deepest.at = m_result->m_numbuf.data();
745    m_match.loc_deepest.length = m_result->m_numbuf.size();
746    m_result->m_match = m_match.loc_deepest;
747    return Error::SUCCESS;
748}
749
750Error
751Operation::validate(int mode, int depth)
752{
753    if (!m_userval.empty()) {
754        int rv = Validator::validate(m_userval, m_jsn, depth, mode);
755        switch (rv) {
756        case JSONSL_ERROR_SUCCESS:
757            return Error::SUCCESS;
758        case JSONSL_ERROR_LEVELS_EXCEEDED:
759            return Error::VALUE_ETOODEEP;
760        default:
761            return Error::VALUE_CANTINSERT;
762        }
763    } else {
764        return Error::VALUE_EMPTY;
765    }
766}
767
768int
769Operation::get_maxdepth(DepthMode mode) const
770{
771    if (mode == DepthMode::PATH_HAS_NEWKEY) {
772        return (Limits::MAX_COMPONENTS + 1) - m_path->size();
773    } else {
774        return Limits::MAX_COMPONENTS - m_path->size();
775    }
776}
777
778Error
779Operation::op_exec(const char *pth, size_t npth)
780{
781    int rv = m_path->parse(pth, npth);
782    Error status;
783
784    if (rv != 0) {
785        if (rv == JSONSL_ERROR_LEVELS_EXCEEDED) {
786            return Error::PATH_E2BIG;
787        } else {
788            return Error::PATH_EINVAL;
789        }
790    }
791
792    switch (m_optype) {
793    case Command::GET:
794    case Command::EXISTS:
795        status = do_match_common(Match::GET_MATCH_ONLY);
796        if (status != Error::SUCCESS) {
797            return status;
798        }
799        return do_get();
800
801    case Command::DICT_ADD:
802    case Command::DICT_ADD_P:
803    case Command::DICT_UPSERT:
804    case Command::DICT_UPSERT_P:
805    case Command::REPLACE:
806        if (m_path->size() == 1) {
807            /* Can't perform these operations on the root element since they
808             * will invalidate the JSON or are otherwise meaningless. */
809            return Error::VALUE_CANTINSERT;
810        }
811
812        status = validate(Validator::PARENT_DICT, get_maxdepth(PATH_HAS_NEWKEY));
813        if (!status.success()) {
814            return status;
815        }
816        status = do_match_common(Match::GET_MATCH_ONLY);
817        if (!status.success()) {
818            return status;
819        }
820        return do_store_dict();
821
822    case Command::REMOVE:
823        if (m_path->size() == 1) {
824            // Can't remove root element!
825            return Error::VALUE_CANTINSERT;
826        }
827        return do_remove();
828
829    case Command::ARRAY_PREPEND:
830    case Command::ARRAY_PREPEND_P:
831        status = validate(Validator::PARENT_ARRAY, get_maxdepth(PATH_IS_PARENT));
832        if (!status.success()) {
833            return status;
834        }
835        return do_list_prepend();
836
837    case Command::ARRAY_APPEND:
838    case Command::ARRAY_APPEND_P:
839    case Command::ARRAY_ADD_UNIQUE:
840    case Command::ARRAY_ADD_UNIQUE_P: {
841        int validmode = Validator::PARENT_ARRAY;
842        if (m_optype.base() == Command::ARRAY_ADD_UNIQUE) {
843            // Uniqueness must contain a single, primitive value.
844            validmode |= Validator::VALUE_PRIMITIVE | Validator::VALUE_SINGLE;
845        }
846
847        status = validate(validmode, get_maxdepth(PATH_IS_PARENT));
848        if (!status.success()) {
849            return status;
850        }
851        return do_list_append();
852    }
853
854    case Command::ARRAY_INSERT:
855        status = validate(Validator::PARENT_ARRAY, get_maxdepth(PATH_HAS_NEWKEY));
856        if (!status.success()) {
857            return status;
858        }
859        return do_insert();
860
861    case Command::COUNTER:
862    case Command::COUNTER_P:
863        // no need to check for depth here, since if the path itself is too
864        // big, it will fail during path parse-time
865        return do_arith_op();
866
867    case Command::GET_COUNT:
868        return do_container_size();
869
870    default:
871        return Error::GLOBAL_ENOSUPPORT;
872
873    }
874}
875
876Operation::Operation()
877: m_path(new Path()),
878  m_jsn(Match::jsn_alloc()),
879  m_optype(Command::GET),
880  m_result(NULL)
881{
882}
883
884Operation::~Operation()
885{
886    clear();
887    delete m_path;
888    Match::jsn_free(m_jsn);
889}
890
891void
892Operation::clear()
893{
894    m_path->clear();
895    m_match.clear();
896    m_userval.length = 0;
897    m_userval.at = NULL;
898    m_result = NULL;
899    m_optype = Command::GET;
900}
901
902Operation *
903subdoc_op_alloc()
904{
905    return new Operation();
906}
907
908void
909subdoc_op_free(Operation *op)
910{
911    delete op;
912}
913
914/* Misc */
915const char *
916Error::description() const
917{
918    switch (m_code) {
919    case Error::SUCCESS:
920        return "Success";
921    case Error::PATH_ENOENT:
922        return "Requested path does not exist in document";
923    case Error::PATH_MISMATCH:
924        return "The path specified treats an existing document entry as the wrong type";
925    case Error::PATH_EINVAL:
926        return "Path syntax error";
927    case Error::DOC_NOTJSON:
928        return "The document is not JSON";
929    case Error::DOC_EEXISTS:
930        return "The requested path already exists";
931    case Error::PATH_E2BIG:
932        return "The path is too big";
933    case Error::NUM_E2BIG:
934        return "The number specified by the path is too big";
935    case Error::DELTA_EINVAL:
936        return "Invalid delta: Either not a number, 0, or not within int64 range";
937    case Error::DELTA_OVERFLOW:
938        return "Performing the counter operation would result in an underflow or overflow";
939    case Error::VALUE_CANTINSERT:
940        return "The new value cannot be inserted in the context of the path, as it would invalidate the JSON";
941    case Error::VALUE_EMPTY:
942        return "Expected non-empty value for command";
943    case Error::VALUE_ETOODEEP:
944        return "Adding this value would make the document too deep";
945    case Error::GLOBAL_ENOSUPPORT:
946        return "Operation not implemented";
947    case Error::DOC_ETOODEEP:
948        return "Document is too deep to parse";
949    }
950
951    return "Unknown error code";
952}
953