xref: /6.0.3/subjson/tests/t_ops.cc (revision 80a9543e)
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#include "subdoc-tests-common.h"
18#include "subdoc/validate.h"
19using std::string;
20using std::cerr;
21using std::endl;
22using Subdoc::Operation;
23using Subdoc::Match;
24using Subdoc::Loc;
25using Subdoc::Error;
26using Subdoc::Command;
27using Subdoc::Validator;
28using Subdoc::Util;
29using Subdoc::Limits;
30using Subdoc::Result;
31
32class OpTests : public ::testing::Test {
33protected:
34  virtual void SetUp() {
35      op.clear();
36  }
37
38  Operation op;
39  Result res;
40
41  string getNewDoc();
42  string returnedMatch() {
43      return res.matchloc().to_string();
44  }
45  void getAssignNewDoc(string& newdoc);
46  Error runOp(Command, const char *path, const char *value = NULL, size_t nvalue = 0);
47};
48
49static ::testing::AssertionResult
50ensureErrorResult(const char *expr, Error got, Error wanted = Error::SUCCESS)
51{
52    using namespace testing;
53    if (got != wanted) {
54        return AssertionFailure() << expr << ": Expected "
55                << wanted << std::hex << wanted <<
56                " (" << wanted.description() << "), Got "
57                << got << std::hex << got << " (" << got.description() << ")";
58    }
59    return AssertionSuccess();
60}
61static ::testing::AssertionResult
62ensureErrorResult2(const char *a, const char *, Error got, Error wanted) {
63    return ensureErrorResult(a, got, wanted);
64}
65
66#define ASSERT_ERROK(exp) \
67    ASSERT_PRED_FORMAT1(ensureErrorResult, exp)
68
69#define ASSERT_ERREQ(exp, err) \
70    ASSERT_PRED_FORMAT2(ensureErrorResult2, exp, err)
71
72string
73OpTests::getNewDoc()
74{
75    string ret;
76    for (auto ii : res.newdoc()) {
77        ret.append(ii.at, ii.length);
78    }
79
80    // validate
81    int rv = Validator::validate(ret, op.parser());
82    std::stringstream ss;
83    if (rv != JSONSL_ERROR_SUCCESS) {
84        Util::dump_newdoc(res);
85    }
86    EXPECT_EQ(JSONSL_ERROR_SUCCESS, rv)
87        << Util::jsonerr(static_cast<jsonsl_error_t>(rv));
88    return ret;
89}
90
91void
92OpTests::getAssignNewDoc(string& newdoc)
93{
94    newdoc = getNewDoc();
95    op.set_doc(newdoc);
96}
97
98Error
99OpTests::runOp(Command opcode, const char *path, const char *value, size_t nvalue)
100{
101    op.clear();
102    if (value != NULL) {
103        if (nvalue == 0) {
104            nvalue = strlen(value);
105        }
106        op.set_value(value, nvalue);
107    }
108    op.set_code(opcode);
109    op.set_result_buf(&res);
110    return op.op_exec(path, strlen(path));
111}
112
113#include "big_json.inc.h"
114TEST_F(OpTests, testOperations)
115{
116    string newdoc;
117    op.set_doc(SAMPLE_big_json, strlen(SAMPLE_big_json));
118    ASSERT_EQ(Error::SUCCESS, runOp(Command::GET, "name"));
119    ASSERT_EQ("\"Allagash Brewing\"", Util::match_match(op.match()));
120    ASSERT_EQ(Error::SUCCESS, runOp(Command::EXISTS, "name"));
121
122    Error rv = runOp(Command::REMOVE, "address");
123    ASSERT_TRUE(rv.success());
124
125    getAssignNewDoc(newdoc);
126    // Should return in KEY_ENOENT
127    ASSERT_EQ(Error::PATH_ENOENT, runOp(Command::GET, "address"));
128
129    // Insert something back, maybe :)
130    rv = runOp(Command::DICT_ADD, "address", "\"123 Main St.\"");
131    ASSERT_TRUE(rv.success());
132
133    getAssignNewDoc(newdoc);
134    ASSERT_EQ(Error::SUCCESS, runOp(Command::GET, "address"));
135    ASSERT_EQ("\"123 Main St.\"", Util::match_match(op.match()));
136
137    // Replace the value now:
138    rv = runOp(Command::REPLACE, "address", "\"33 Marginal Rd.\"");
139    ASSERT_TRUE(rv.success());
140    getAssignNewDoc(newdoc);
141    ASSERT_EQ(Error::SUCCESS, runOp(Command::GET, "address"));
142    ASSERT_EQ("\"33 Marginal Rd.\"", Util::match_match(op.match()));
143
144    // Get it back:
145    op.set_doc(SAMPLE_big_json, strlen(SAMPLE_big_json));
146    // add non-existent path
147    rv = runOp(Command::DICT_ADD, "foo.bar.baz", "[1,2,3]");
148    ASSERT_EQ(Error::PATH_ENOENT, rv);
149
150    rv = runOp(Command::DICT_ADD_P, "foo.bar.baz", "[1,2,3]");
151    ASSERT_TRUE(rv.success());
152    getNewDoc();
153}
154
155// Mainly checks that we can perform generic DELETE and GET operations
156// on array indices
157TEST_F(OpTests, testGenericOps)
158{
159    op.set_doc(SAMPLE_big_json, strlen(SAMPLE_big_json));
160    Error rv;
161    string newdoc;
162
163    rv = runOp(Command::REMOVE, "address[0]");
164    ASSERT_TRUE(rv.success());
165
166    getAssignNewDoc(newdoc);
167    rv = runOp(Command::GET, "address[0]");
168    ASSERT_EQ(Error::PATH_ENOENT, rv);
169
170    rv = runOp(Command::REPLACE, "address",
171        "[\"500 B St.\", \"Anytown\", \"USA\"]");
172    ASSERT_TRUE(rv.success());
173    getAssignNewDoc(newdoc);
174    rv = runOp(Command::GET, "address[2]");
175    ASSERT_TRUE(rv.success());
176    ASSERT_EQ("\"USA\"", Util::match_match(op.match()));
177
178    rv = runOp(Command::REPLACE, "address[1]", "\"Sacramento\"");
179    ASSERT_TRUE(rv.success());
180    getAssignNewDoc(newdoc);
181
182    rv = runOp(Command::GET, "address[1]");
183    ASSERT_TRUE(rv.success());
184    ASSERT_EQ("\"Sacramento\"", Util::match_match(op.match()));
185}
186
187TEST_F(OpTests, testReplaceArrayDeep)
188{
189    // Create an array at max level.
190    string deep;
191    for (size_t ii = 0; ii < Limits::MAX_COMPONENTS - 1; ii++) {
192        deep += "[";
193    }
194    deep += "1";
195    for (size_t ii = 0; ii < Limits::MAX_COMPONENTS - 1; ii++) {
196        deep += "]";
197    }
198    op.set_doc(deep);
199
200    // Sanity check - should be able to access maximum depth.
201    string one_minus_max_path;
202    for (size_t ii = 0; ii < Limits::MAX_COMPONENTS - 2; ii++) {
203        one_minus_max_path += "[0]";
204    }
205    string max_path(one_minus_max_path + "[0]");
206    Error rv = runOp(Command::GET, one_minus_max_path.c_str());
207    ASSERT_TRUE(rv.success());
208    ASSERT_EQ("[1]", Util::match_match(op.match()));
209
210    // Should be able to replace the array element with a different one.
211    rv = runOp(Command::REPLACE, max_path.c_str(), "2");
212    ASSERT_TRUE(rv.success());
213    string newdoc;
214    getAssignNewDoc(newdoc);
215    rv = runOp(Command::GET, one_minus_max_path.c_str());
216    ASSERT_TRUE(rv.success());
217    EXPECT_EQ("[2]", Util::match_match(op.match()));
218
219    // Should be able to replace the last level array with a different
220    // (larger) one.
221    rv = runOp(Command::REPLACE, one_minus_max_path.c_str(), "[3,4]");
222    ASSERT_TRUE(rv.success());
223    getAssignNewDoc(newdoc);
224    rv = runOp(Command::GET, one_minus_max_path.c_str());
225    ASSERT_TRUE(rv.success());
226    ASSERT_EQ("[3,4]", Util::match_match(op.match()));
227
228    // Should not be able to make it any deeper (already at maximum).
229    rv = runOp(Command::REPLACE, one_minus_max_path.c_str(), "[[5]]");
230    ASSERT_EQ(Error::VALUE_ETOODEEP, rv);
231}
232
233TEST_F(OpTests, testListOps)
234{
235    string doc = "{}";
236    op.set_doc(doc);
237
238    Error rv = runOp(Command::DICT_UPSERT, "array", "[]");
239    ASSERT_TRUE(rv.success());
240    getAssignNewDoc(doc);
241
242    // Test append:
243    rv = runOp(Command::ARRAY_APPEND, "array", "1");
244    ASSERT_TRUE(rv.success());
245    getAssignNewDoc(doc);
246
247    rv = runOp(Command::GET, "array[0]");
248    ASSERT_TRUE(rv.success());
249    ASSERT_EQ("1", Util::match_match(op.match()));
250
251    rv = runOp(Command::ARRAY_PREPEND, "array", "0");
252    ASSERT_TRUE(rv.success());
253    getAssignNewDoc(doc);
254
255    rv = runOp(Command::GET, "array[0]");
256    ASSERT_TRUE(rv.success());
257    ASSERT_EQ("0", Util::match_match(op.match()));
258    rv = runOp(Command::GET, "array[1]");
259    ASSERT_TRUE(rv.success());
260    ASSERT_EQ("1", Util::match_match(op.match()));
261
262    rv = runOp(Command::ARRAY_APPEND, "array", "2");
263    ASSERT_TRUE(rv.success());
264    getAssignNewDoc(doc);
265
266    rv = runOp(Command::GET, "array[2]");
267    ASSERT_TRUE(rv.success());
268    ASSERT_EQ("2", Util::match_match(op.match()));
269
270    rv = runOp(Command::ARRAY_APPEND, "array", "{\"foo\":\"bar\"}");
271    ASSERT_TRUE(rv.success());
272    getAssignNewDoc(doc);
273
274    rv = runOp(Command::GET, "array[3].foo");
275    ASSERT_TRUE(rv.success());
276    ASSERT_EQ("\"bar\"", Util::match_match(op.match()));
277
278    // Test the various POP commands
279    rv = runOp(Command::REMOVE, "array[0]");
280    ASSERT_TRUE(rv.success());
281    ASSERT_EQ("0", Util::match_match(op.match()));
282    getAssignNewDoc(doc);
283
284    rv = runOp(Command::GET, "array[0]");
285
286    rv = runOp(Command::REMOVE, "array[-1]");
287    ASSERT_TRUE(rv.success());
288    ASSERT_EQ("{\"foo\":\"bar\"}", Util::match_match(op.match()));
289    getAssignNewDoc(doc);
290
291    rv = runOp(Command::REMOVE, "array[-1]");
292    ASSERT_TRUE(rv.success());
293    ASSERT_EQ("2", Util::match_match(op.match()));
294
295    // Special prepend operations
296    doc = "{}";
297    op.set_doc(doc);
298
299    // test prepend without _p
300    rv = runOp(Command::ARRAY_PREPEND, "array", "123");
301    ASSERT_EQ(Error::PATH_ENOENT, rv);
302
303    rv = runOp(Command::ARRAY_PREPEND_P, "array", "123");
304    ASSERT_TRUE(rv.success());
305    getAssignNewDoc(doc);
306
307    // Now ensure the contents are the same
308    rv = runOp(Command::GET, "array[0]");
309    ASSERT_TRUE(rv.success());
310    ASSERT_EQ("123", Util::match_match(op.match()));
311
312    // Remove the first element, making it empty
313    rv = runOp(Command::REMOVE, "array[0]");
314    ASSERT_TRUE(rv.success());
315    getAssignNewDoc(doc);
316
317    // Prepend the first element (singleton)
318    rv = runOp(Command::ARRAY_PREPEND, "array", "123");
319    ASSERT_TRUE(rv.success());
320    getAssignNewDoc(doc);
321
322    rv = runOp(Command::GET, "array[0]");
323    ASSERT_TRUE(rv.success());
324    ASSERT_EQ("123", Util::match_match(op.match()));
325}
326
327TEST_F(OpTests, testArrayMultivalue)
328{
329    string doc = "{\"array\":[4,5,6]}";
330    Error rv;
331    op.set_doc(doc);
332
333    rv = runOp(Command::ARRAY_PREPEND, "array", "1,2,3");
334    ASSERT_TRUE(rv.success()) << rv;
335    getAssignNewDoc(doc);
336
337    rv = runOp(Command::GET, "array");
338    ASSERT_TRUE(rv.success());
339    ASSERT_EQ("[1,2,3,4,5,6]", Util::match_match(op.match()));
340
341    rv = runOp(Command::ARRAY_APPEND, "array", "7,8,9");
342    ASSERT_TRUE(rv.success());
343    getAssignNewDoc(doc);
344
345    rv = runOp(Command::GET, "array");
346    ASSERT_TRUE(rv.success());
347    ASSERT_EQ("[1,2,3,4,5,6,7,8,9]", Util::match_match(op.match()));
348
349    rv = runOp(Command::ARRAY_INSERT, "array[3]", "-3,-2,-1");
350    ASSERT_TRUE(rv.success());
351    getAssignNewDoc(doc);
352
353    rv = runOp(Command::GET, "array[4]");
354    ASSERT_TRUE(rv.success());
355    ASSERT_EQ("-2", Util::match_match(op.match()));
356}
357
358TEST_F(OpTests, testArrayOpsNested)
359{
360    const string array("[0,[1,[2]],{\"key\":\"val\"}]");
361    op.set_doc(array);
362    Error rv;
363
364    rv = runOp(Command::REMOVE, "[1][1][0]");
365    EXPECT_TRUE(rv.success());
366    EXPECT_EQ("[0,[1,[]],{\"key\":\"val\"}]", getNewDoc());
367
368    string array2;
369    getAssignNewDoc(array2);
370    rv = runOp(Command::REMOVE, "[1][1]");
371    EXPECT_TRUE(rv.success());
372    EXPECT_EQ("[0,[1],{\"key\":\"val\"}]", getNewDoc());
373}
374
375// Toplevel array with two elements.
376TEST_F(OpTests, testArrayDelete)
377{
378    // Toplevel array deletions
379    const string array("[1,2]");
380    op.set_doc(array);
381    Error rv;
382
383    // Delete beginning element.
384    rv = runOp(Command::REMOVE, "[0]");
385    EXPECT_TRUE(rv.success());
386    EXPECT_EQ("[2]", getNewDoc());
387
388    // Delete end element.
389    rv = runOp(Command::REMOVE, "[1]");
390    EXPECT_TRUE(rv.success());
391    EXPECT_EQ("[1]", getNewDoc());
392
393    // One element array. Delete last (final) element (via [0]).
394    const string array2("[1]");
395    op.set_doc(array2);
396
397    rv = runOp(Command::REMOVE, "[0]");
398    EXPECT_TRUE(rv.success());
399    EXPECT_EQ("[]", getNewDoc());
400
401    // Delete last element via [-1].
402    rv = runOp(Command::REMOVE, "[-1]");
403    EXPECT_TRUE(rv.success());
404    EXPECT_EQ("[]", getNewDoc());
405}
406
407TEST_F(OpTests, testDictDelete)
408{
409    const string dict("{\"0\": 1,\"1\": 2.0}");
410    op.set_doc(dict);
411    Error rv;
412
413    // Delete element
414    rv = runOp(Command::REMOVE, "0");
415    EXPECT_TRUE(rv.success());
416
417    // Check it's gone.
418    string doc;
419    getAssignNewDoc(doc);
420    rv = runOp(Command::EXISTS, "0");
421    ASSERT_EQ(Error::PATH_ENOENT, rv);
422}
423
424TEST_F(OpTests, testUnique)
425{
426    string json = "{}";
427    string doc;
428    Error rv;
429
430    op.set_doc(json);
431
432    rv = runOp(Command::ARRAY_ADD_UNIQUE_P, "unique", "\"value\"");
433    ASSERT_TRUE(rv.success());
434    getAssignNewDoc(doc);
435
436    rv = runOp(Command::ARRAY_ADD_UNIQUE, "unique", "\"value\"");
437    ASSERT_EQ(Error::DOC_EEXISTS, rv);
438
439    rv = runOp(Command::ARRAY_ADD_UNIQUE, "unique", "1");
440    ASSERT_TRUE(rv.success());
441    getAssignNewDoc(doc);
442
443    rv = runOp(Command::ARRAY_ADD_UNIQUE, "unique", "\"1\"");
444    ASSERT_TRUE(rv.success());
445    getAssignNewDoc(doc);
446
447    rv = runOp(Command::ARRAY_ADD_UNIQUE, "unique", "[]");
448    ASSERT_EQ(Error::VALUE_CANTINSERT, rv) << "Cannot unique-add non-primitive";
449
450    rv = runOp(Command::ARRAY_ADD_UNIQUE, "unique", "1,2,3");
451    ASSERT_EQ(Error::VALUE_CANTINSERT, rv) << "Cannot unique-add multivalue";
452
453    rv = runOp(Command::ARRAY_APPEND, "unique", "[]");
454    ASSERT_TRUE(rv.success());
455    getAssignNewDoc(doc);
456
457    rv = runOp(Command::ARRAY_ADD_UNIQUE, "unique", "2");
458    ASSERT_EQ(Error::PATH_MISMATCH, rv) <<
459            "Mismatch with array containing non-primitive elements";
460}
461
462TEST_F(OpTests, testUniqueToplevel)
463{
464    string json("[]");
465    string doc;
466    Error rv;
467
468    op.set_doc(json);
469
470    rv = runOp(Command::ARRAY_ADD_UNIQUE_P, "", "0");
471    ASSERT_TRUE(rv.success());
472    getAssignNewDoc(doc);
473
474    rv = runOp(Command::ARRAY_ADD_UNIQUE_P, "", "0");
475    ASSERT_EQ(Error::DOC_EEXISTS, rv);
476}
477
478#ifndef INT64_MIN
479#define INT64_MIN (-9223372036854775807LL-1)
480#define INT64_MAX 9223372036854775807LL
481#endif
482
483TEST_F(OpTests, testNumeric)
484{
485    string doc = "{}";
486    Error rv;
487    op.set_doc(doc);
488
489    // Can we make a simple counter?
490    rv = runOp(Command::COUNTER_P, "counter", "1");
491    ASSERT_TRUE(rv.success());
492    ASSERT_EQ("1", Util::match_match(op.match()));
493    getAssignNewDoc(doc);
494
495    rv = runOp(Command::COUNTER, "counter", "-101");
496    ASSERT_TRUE(rv.success());
497    ASSERT_EQ("-100", Util::match_match(op.match()));
498    getAssignNewDoc(doc);
499
500    // Get it raw
501    rv = runOp(Command::GET, "counter");
502    ASSERT_TRUE(rv.success());
503    ASSERT_EQ("-100", Util::match_match(op.match()));
504
505    rv = runOp(Command::COUNTER, "counter", "1");
506    ASSERT_TRUE(rv.success());
507    ASSERT_EQ("-99", Util::match_match(op.match()));
508    getAssignNewDoc(doc);
509
510    // Try with other things
511    string dummy = std::to_string(INT64_MAX);
512    rv = runOp(Command::COUNTER, "counter", dummy.c_str());
513    ASSERT_TRUE(rv.success());
514    ASSERT_EQ(std::to_string(INT64_MAX-99), Util::match_match(op.match()));
515    getAssignNewDoc(doc);
516
517    dummy = "-" + std::to_string(INT64_MAX-99);
518    rv = runOp(Command::COUNTER, "counter", dummy.c_str());
519    ASSERT_TRUE(rv.success());
520    ASSERT_EQ("0", Util::match_match(op.match()));
521    getAssignNewDoc(doc);
522
523    rv = runOp(Command::DICT_ADD_P, "counter2", "9999999999999999999999999999999");
524    ASSERT_TRUE(rv.success());
525    getAssignNewDoc(doc);
526
527    rv = runOp(Command::COUNTER, "counter2", "1");
528    ASSERT_EQ(Error::NUM_E2BIG, rv);
529
530    rv = runOp(Command::DICT_ADD_P, "counter3", "3.14");
531    ASSERT_TRUE(rv.success());
532    getAssignNewDoc(doc);
533
534    rv = runOp(Command::COUNTER, "counter3", "1");
535    ASSERT_EQ(Error::PATH_MISMATCH, rv);
536
537    doc = "[]";
538    op.set_doc(doc);
539    rv = runOp(Command::COUNTER, "[0]", "42");
540    ASSERT_EQ(Error::PATH_ENOENT, rv);
541
542    // Try with a _P variant. Should still be the same
543    rv = runOp(Command::COUNTER_P, "[0]", "42");
544    ASSERT_EQ(Error::PATH_ENOENT, rv);
545
546    rv = runOp(Command::ARRAY_APPEND, "", "-20");
547    ASSERT_TRUE(rv.success());
548    getAssignNewDoc(doc);
549
550    rv = runOp(Command::COUNTER, "[0]", "1");
551    ASSERT_TRUE(rv.success());
552    ASSERT_EQ("-19", Util::match_match(op.match()));
553}
554
555TEST_F(OpTests, testBadNumFormat)
556{
557    string doc = "{}";
558    op.set_doc(doc);
559
560    ASSERT_EQ(Error::DELTA_EINVAL, runOp(Command::COUNTER_P, "pth", "bad"));
561    ASSERT_EQ(Error::DELTA_EINVAL, runOp(Command::COUNTER_P, "pth", "3.14"));
562    ASSERT_EQ(Error::DELTA_EINVAL, runOp(Command::COUNTER_P, "pth", "-"));
563    ASSERT_EQ(Error::DELTA_EINVAL, runOp(Command::COUNTER_P, "pth", "43f"));
564    ASSERT_EQ(Error::DELTA_EINVAL, runOp(Command::COUNTER_P, "pth", "0"));
565}
566
567TEST_F(OpTests, testNumericLimits)
568{
569    // Check we can increment from int64_t::max()-1 to max() successfully.
570    const int64_t max = std::numeric_limits<int64_t>::max();
571    const string one_minus_max("{\"counter\":" + std::to_string(max - 1) + "}");
572    op.set_doc(one_minus_max);
573
574    Error rv = runOp(Command::COUNTER, "counter", "1");
575    ASSERT_TRUE(rv.success());
576    ASSERT_EQ(std::to_string(max), Util::match_match(op.match()));
577
578    // Incrementing across the limit (max()-1 incremented by 2) should fail.
579    op.set_doc(one_minus_max);
580
581    rv = runOp(Command::COUNTER, "counter", "2");
582    ASSERT_EQ(Error::DELTA_OVERFLOW, rv);
583
584    // Same for int64_t::min() - 1 and decrement.
585    const int64_t min = std::numeric_limits<int64_t>::min();
586    const string one_plus_min("{\"counter\":" + std::to_string(min + 1) + "}");
587    op.set_doc(one_plus_min);
588
589    rv = runOp(Command::COUNTER, "counter", "-1");
590    ASSERT_TRUE(rv.success());
591    ASSERT_EQ(std::to_string(min), Util::match_match(op.match()));
592
593    // Decrementing across the limit (min()-1 decremented by 2) should fail.
594    op.set_doc(one_plus_min);
595
596    rv = runOp(Command::COUNTER, "counter", "-2");
597    ASSERT_EQ(Error::DELTA_OVERFLOW, rv);
598}
599
600TEST_F(OpTests, testValueValidation)
601{
602    string json = "{}";
603    string doc;
604    Error rv;
605    op.set_doc(doc);
606
607    rv = runOp(Command::DICT_ADD_P, "foo.bar.baz", "INVALID");
608    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
609
610    rv = runOp(Command::DICT_ADD_P, "foo.bar.baz", "1,2,3,4");
611    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
612
613    // FIXME: Should we allow this? Could be more performant, but might also
614    // be confusing!
615    rv = runOp(Command::DICT_ADD_P, "foo.bar.baz", "1,\"k2\":2");
616    ASSERT_TRUE(rv.success());
617
618    // Dict key without a colon or value.
619    rv = runOp(Command::DICT_ADD, "bad_dict", "{ \"foo\" }");
620    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
621
622    rv = runOp(Command::DICT_ADD, "bad_dict", "{ \"foo\": }");
623    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
624
625    // Dict without a colon or value.
626    rv = runOp(Command::DICT_ADD_P, "bad_dict", "{ \"foo\" }");
627    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
628
629    // Dict without a colon.
630    rv = runOp(Command::DICT_ADD_P, "bad_dict", "{ \"foo\": }");
631    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
632
633    // null with incorrect name.
634    rv = runOp(Command::DICT_ADD_P, "bad_null", "nul");
635    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
636
637    // invalid float (more than one decimal point).
638    rv = runOp(Command::DICT_ADD_P, "bad_float1", "2.0.0");
639    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
640
641    // invalid float (no digit after the '.').
642    rv = runOp(Command::DICT_ADD_P, "bad_float2", "2.");
643    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
644
645    // invalid float (no exponential after the 'e').
646    rv = runOp(Command::DICT_ADD_P, "bad_float3", "2.0e");
647    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
648
649    // invalid float (no digits after the exponential sign).
650    rv = runOp(Command::DICT_ADD_P, "bad_float4", "2.0e+");
651    EXPECT_EQ(Error::VALUE_CANTINSERT, rv);
652}
653
654
655TEST_F(OpTests, testNegativeIndex)
656{
657    string json = "[1,2,3,4,5,6]";
658    op.set_doc(json);
659
660    Error rv = runOp(Command::GET, "[-1]");
661    ASSERT_TRUE(rv.success());
662    ASSERT_EQ("6", Util::match_match(op.match()));
663
664    json = "[1,2,3,[4,5,6,[7,8,9]]]";
665    op.set_doc(json);
666    rv = runOp(Command::GET, "[-1].[-1].[-1]");
667    ASSERT_TRUE(rv.success());
668    ASSERT_EQ("9", Util::match_match(op.match()));
669
670    string doc;
671    rv = runOp(Command::REMOVE, "[-1].[-1].[-1]");
672    ASSERT_TRUE(rv.success());
673    getAssignNewDoc(doc);
674
675    // Can we PUSH values with a negative index?
676    rv = runOp(Command::ARRAY_APPEND, "[-1].[-1]", "10");
677    ASSERT_TRUE(rv.success());
678    getAssignNewDoc(doc);
679
680
681    rv = runOp(Command::GET, "[-1].[-1].[-1]");
682    ASSERT_TRUE(rv.success());
683    ASSERT_EQ("10", Util::match_match(op.match()));
684
685    // Intermixed paths:
686    json = "{\"k1\": [\"first\", {\"k2\":[6,7,8]},\"last\"] }";
687    op.set_doc(json);
688
689    rv = runOp(Command::GET, "k1[-1]");
690    ASSERT_TRUE(rv.success());
691    ASSERT_EQ("\"last\"", Util::match_match(op.match()));
692
693    rv = runOp(Command::GET, "k1[1].k2[-1]");
694    ASSERT_TRUE(rv.success());
695    ASSERT_EQ("8", Util::match_match(op.match()));
696}
697
698TEST_F(OpTests, testRootOps)
699{
700    string json = "[]";
701    op.set_doc(json);
702    Error rv;
703
704    rv = runOp(Command::GET, "");
705    ASSERT_TRUE(rv.success());
706    ASSERT_EQ("[]", Util::match_match(op.match()));
707
708    rv = runOp(Command::ARRAY_APPEND, "", "null");
709    ASSERT_TRUE(rv.success());
710    getAssignNewDoc(json);
711
712    rv = runOp(Command::GET, "");
713    ASSERT_EQ("[null]", Util::match_match(op.match()));
714
715    // Deleting root element should be CANTINSERT
716    rv = runOp(Command::REMOVE, "");
717    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
718}
719
720TEST_F(OpTests, testMismatch)
721{
722    string doc = "{}";
723    op.set_doc(doc);
724    Error rv;
725
726    rv = runOp(Command::ARRAY_APPEND, "", "null");
727    ASSERT_EQ(Error::PATH_MISMATCH, rv);
728
729    doc = "[]";
730    op.set_doc(doc);
731    rv = runOp(Command::DICT_UPSERT, "", "blah");
732    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
733
734    rv = runOp(Command::DICT_UPSERT, "key", "blah");
735    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
736
737    doc = "[null]";
738    op.set_doc(doc);
739    rv = runOp(Command::DICT_UPSERT, "", "blah");
740    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
741
742    rv = runOp(Command::DICT_UPSERT, "key", "blah");
743    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
744
745    rv = runOp(Command::ARRAY_APPEND_P, "foo.bar", "null");
746    ASSERT_EQ(Error::PATH_MISMATCH, rv);
747}
748
749TEST_F(OpTests, testWhitespace)
750{
751    string doc = "[ 1, 2, 3,       4        ]";
752    op.set_doc(doc);
753    Error rv;
754
755    rv = runOp(Command::GET, "[-1]");
756    ASSERT_TRUE(rv.success());
757    ASSERT_EQ("4", Util::match_match(op.match()));
758}
759
760TEST_F(OpTests, testTooDeep)
761{
762    std::string deep = "{\"array\":";
763    for (size_t ii = 0; ii < (Limits::MAX_COMPONENTS+1) * 2; ii++) {
764        deep += "[";
765    }
766    for (size_t ii = 0; ii < (Limits::MAX_COMPONENTS+1) * 2; ii++) {
767        deep += "]";
768    }
769
770    op.set_doc(deep);
771
772    Error rv = runOp(Command::GET, "dummy.path");
773    ASSERT_EQ(Error::DOC_ETOODEEP, rv);
774
775    // Try with a really deep path:
776    std::string dp = "dummy";
777    for (size_t ii = 0; ii < (Limits::MAX_COMPONENTS+1) * 2; ii++) {
778        dp += ".dummy";
779    }
780    rv = runOp(Command::GET, dp.c_str());
781    ASSERT_EQ(Error::PATH_E2BIG, rv);
782}
783
784TEST_F(OpTests, testTooDeepDict) {
785    // Verify that we cannot create too deep a document with DICT_ADD
786    // Should be able to do the maximum depth:
787    std::string deep_dict("{");
788    for (size_t ii = 1; ii < Limits::MAX_COMPONENTS; ii++) {
789        deep_dict += "\"" + std::to_string(ii) + "\": {";
790    }
791    for (size_t ii = 0; ii < Limits::MAX_COMPONENTS; ii++) {
792        deep_dict += "}";
793    }
794    op.set_doc(deep_dict);
795
796    // Create base path at one less than the max.
797    std::string one_less_max_path(std::to_string(1));
798    for (size_t depth = 2; depth < Limits::MAX_COMPONENTS -1; depth++) {
799        one_less_max_path += std::string(".") + std::to_string(depth);
800    }
801
802    const std::string max_valid_path(one_less_max_path + "." +
803                                     std::to_string(Limits::MAX_COMPONENTS-1));
804    // Assert we can access elements at the max depth (before we start
805    // attempting to add more).
806    Error rv = runOp(Command::GET, max_valid_path.c_str());
807    ASSERT_TRUE(rv.success());
808    ASSERT_EQ("{}", Util::match_match(op.match()));
809
810    // Should be able to add an element as the same level as the max.
811    const std::string equal_max_path(one_less_max_path + ".sibling_max");
812    rv = runOp(Command::DICT_ADD, equal_max_path.c_str(), "\"also at max depth\"");
813    EXPECT_TRUE(rv.success()) << rv;
814    std::string newDoc;
815    getAssignNewDoc(newDoc);
816
817    // Attempts to add one level deeper should fail.
818    std::string too_long_path(max_valid_path + ".too_long");
819    rv = runOp(Command::DICT_ADD, too_long_path.c_str(), "\"past max depth\"");
820    EXPECT_EQ(Error::PATH_E2BIG, rv);
821}
822
823TEST_F(OpTests, testArrayInsert) {
824    string doc("[1,2,4,5]");
825    op.set_doc(doc);
826    Error rv = runOp(Command::ARRAY_INSERT, "[2]", "3");
827    ASSERT_TRUE(rv.success()) << "Insert op recognized";
828    getAssignNewDoc(doc);
829    ASSERT_EQ("[1,2,3,4,5]", doc) << "Insert works correctly in-between";
830
831    // Do an effective 'prepend'
832    rv = runOp(Command::ARRAY_INSERT, "[0]", "0");
833    ASSERT_TRUE(rv.success()) << "Insert at position 0 OK";
834    getAssignNewDoc(doc);
835    ASSERT_EQ("[0,1,2,3,4,5]", doc) << "Insert at posititon 0 matches";
836
837    // Do an effective 'append'
838    rv = runOp(Command::ARRAY_INSERT, "[6]", "6");
839    ASSERT_TRUE(rv.success()) << "Insert at posititon $SIZE ok";
840    getAssignNewDoc(doc);
841    ASSERT_EQ("[0,1,2,3,4,5,6]", doc) << "Insert at position $SIZE matches";
842
843    // Reset the doc
844    doc = "[1,2,3,5]";
845    op.set_doc(doc);
846    // -1 is not a valid insertion point.
847    rv = runOp(Command::ARRAY_INSERT, "[-1]", "4");
848    ASSERT_EQ(Error::PATH_EINVAL, rv) << "Terminal negative index invalid for insert.";
849
850    // Insert at out-of-bounds element
851    doc = "[1,2,3]";
852    op.set_doc(doc);
853    rv = runOp(Command::ARRAY_INSERT, "[4]", "null");
854    ASSERT_EQ(Error::PATH_ENOENT, rv) << "Fails with out-of-bound index";
855
856    // Insert not using array syntax
857    rv = runOp(Command::ARRAY_INSERT, "[0].anything", "null");
858    ASSERT_EQ(Error::PATH_EINVAL, rv) << "Using non-array parent in path fails";
859
860    doc = "{}";
861    op.set_doc(doc);
862    // Insert with missing parent
863    rv = runOp(Command::ARRAY_INSERT, "non_exist[0]", "null");
864    ASSERT_EQ(Error::PATH_ENOENT, rv) << "Fails with missing parent";
865
866    doc = "[]";
867    op.set_doc(doc);
868    rv = runOp(Command::ARRAY_INSERT, "[0]", "blah");
869    ASSERT_EQ(Error::VALUE_CANTINSERT, rv) << "CANT_INSERT on invalid JSON value";
870
871    doc = "{}";
872    op.set_doc(doc);
873    rv = runOp(Command::ARRAY_INSERT, "[0]", "null");
874    ASSERT_EQ(Error::PATH_MISMATCH, rv) << "Fails with dict parent";
875}
876
877TEST_F(OpTests, testEmpty) {
878    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::DICT_ADD, "p"));
879    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::DICT_UPSERT, "p"));
880    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::REPLACE, "p"));
881    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::ARRAY_APPEND, "p"));
882    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::ARRAY_PREPEND, "p"));
883    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::ARRAY_ADD_UNIQUE, "p"));
884    ASSERT_EQ(Error::VALUE_EMPTY, runOp(Command::ARRAY_INSERT, "p[0]"));
885}
886
887// When using the built-in result context, ensure the internal buffers are
888// cleared between operations
889TEST_F(OpTests, ensureRepeatable) {
890    string doc = "{}";
891    Error rv;
892
893    op.set_doc(doc);
894    rv = runOp(Command::DICT_UPSERT_P, "foo.bar", "true");
895    ASSERT_TRUE(rv.success());
896    getAssignNewDoc(doc);
897
898    res.clear();
899    rv = runOp(Command::DICT_UPSERT_P, "bar.baz", "false");
900    ASSERT_TRUE(rv.success());
901    getAssignNewDoc(doc);
902}
903
904TEST_F(OpTests, testDeleteNestedArray)
905{
906    string doc = "[0,[10,20,[100]],{\"key\":\"value\"}]";
907    Error rv;
908    op.set_doc(doc);
909
910    rv = runOp(Command::GET, "[1]");
911    ASSERT_EQ(Error::SUCCESS, rv);
912    ASSERT_EQ("[10,20,[100]]", Util::match_match(op.match()));
913
914    rv = runOp(Command::REMOVE, "[1][2][0]");
915    ASSERT_EQ(Error::SUCCESS, rv);
916    getAssignNewDoc(doc);
917
918    rv = runOp(Command::GET, "[1]");
919    ASSERT_EQ(Error::SUCCESS, rv);
920    ASSERT_EQ("[10,20,[]]", Util::match_match(op.match()));
921
922    rv = runOp(Command::REMOVE, "[1][2]");
923    ASSERT_EQ(Error::SUCCESS, rv);
924    getAssignNewDoc(doc);
925
926    rv = runOp(Command::GET, "[1]");
927    ASSERT_EQ(Error::SUCCESS, rv);
928    ASSERT_EQ("[10,20]", Util::match_match(op.match()));
929
930    rv = runOp(Command::REMOVE, "[1]");
931    ASSERT_EQ(Error::SUCCESS, rv);
932    getAssignNewDoc(doc);
933
934    rv = runOp(Command::GET, "[1]");
935    ASSERT_EQ(Error::SUCCESS, rv);
936    ASSERT_EQ("{\"key\":\"value\"}", Util::match_match(op.match()));
937
938}
939
940TEST_F(OpTests, testEscapedJson)
941{
942    string doc = "{\"" "\\" "\"quoted\":true}";
943    Error rv;
944    op.set_doc(doc);
945    rv = runOp(Command::GET, "\\\"quoted");
946    ASSERT_EQ(Error::SUCCESS, rv);
947    ASSERT_EQ("true", Util::match_match(op.match()));
948
949    // Try with insertion
950    rv = runOp(Command::DICT_UPSERT_P, "another.\\\"nested.field", "null");
951    ASSERT_EQ(Error::SUCCESS, rv);
952
953    getAssignNewDoc(doc);
954
955    ASSERT_EQ(Error::PATH_EINVAL, runOp(Command::GET, "\"missing.quote"));
956}
957
958TEST_F(OpTests, testUpsertArrayIndex)
959{
960    // This test verifies some corner cases where there is a missing
961    // array index which would normally be treated like a dictionary.
962    // Ensure that we never automatically add an array index without
963    // explicit array operations.
964
965    string doc = "{\"array\":[null]}";
966    Error rv;
967    op.set_doc(doc);
968
969    rv = runOp(Command::DICT_UPSERT, "array[0]", "true");
970    ASSERT_EQ(Error::PATH_EINVAL, rv);
971
972    rv = runOp(Command::DICT_UPSERT, "array[1]", "true");
973    ASSERT_EQ(Error::PATH_EINVAL, rv);
974
975    rv = runOp(Command::DICT_UPSERT, "array[-1]", "true");
976    ASSERT_EQ(Error::PATH_EINVAL, rv);
977
978    rv = runOp(Command::ARRAY_APPEND_P, "array[1]", "true");
979    ASSERT_EQ(Error::PATH_ENOENT, rv);
980
981    rv = runOp(Command::ARRAY_APPEND_P, "array[2]", "true");
982    ASSERT_EQ(Error::PATH_ENOENT, rv);
983
984    rv = runOp(Command::ARRAY_ADD_UNIQUE_P, "array[2]", "true");
985    ASSERT_EQ(Error::PATH_ENOENT, rv);
986
987    rv = runOp(Command::COUNTER_P, "array[1]", "100");
988    ASSERT_EQ(Error::PATH_ENOENT, rv);
989}
990
991TEST_F(OpTests, testRootAppend)
992{
993    // Tests append on an empty path, which should use an optimized codebase
994    string doc("[]");
995    Error rv;
996    op.set_doc(doc);
997
998    rv = runOp(Command::ARRAY_APPEND, "", "1");
999    ASSERT_TRUE(rv.success());
1000    getAssignNewDoc(doc);
1001
1002    rv = runOp(Command::GET, "[0]");
1003    ASSERT_TRUE(rv.success());
1004    ASSERT_EQ("1", Util::match_match(op.match()));
1005
1006    // Perform add_unique again
1007    rv = runOp(Command::ARRAY_ADD_UNIQUE, "", "1");
1008    ASSERT_EQ(Error::DOC_EEXISTS, rv);
1009
1010    rv = runOp(Command::ARRAY_APPEND, "", "2");
1011    ASSERT_TRUE(rv.success());
1012    getAssignNewDoc(doc);
1013    rv = runOp(Command::GET, "[1]");
1014    ASSERT_TRUE(rv.success());
1015    ASSERT_EQ("2", Util::match_match(op.match()));
1016
1017    // Try one more
1018    rv = runOp(Command::ARRAY_APPEND, "", "3");
1019    ASSERT_TRUE(rv.success());
1020    getAssignNewDoc(doc);
1021    rv = runOp(Command::GET, "[2]");
1022    ASSERT_TRUE(rv.success());
1023    ASSERT_EQ("3", Util::match_match(op.match()));
1024
1025    // See how well we handle errors
1026    doc = "nonjson";
1027    op.set_doc(doc);
1028    rv = runOp(Command::ARRAY_APPEND, "", "123");
1029    ASSERT_EQ(Error::DOC_NOTJSON, rv);
1030
1031    doc = "{}";
1032    op.set_doc(doc);
1033    rv = runOp(Command::ARRAY_APPEND, "", "123");
1034    ASSERT_EQ(Error::PATH_MISMATCH, rv);
1035
1036    doc = "[[]]";
1037    op.set_doc(doc);
1038    rv = runOp(Command::ARRAY_APPEND, "[0]", "123");
1039    ASSERT_TRUE(rv.success());
1040    getAssignNewDoc(doc);
1041    rv = runOp(Command::GET, "[0][0]");
1042    ASSERT_TRUE(rv.success());
1043    ASSERT_EQ("123", Util::match_match(op.match()));
1044
1045    doc = "[0, {\"1\": 1}]";
1046    op.set_doc(doc);
1047    rv = runOp(Command::ARRAY_APPEND, "", "123");
1048    ASSERT_TRUE(rv.success());
1049    getAssignNewDoc(doc);
1050    rv = runOp(Command::GET, "[-1]");
1051    ASSERT_TRUE(rv.success());
1052    ASSERT_EQ("123", Util::match_match(op.match()));
1053
1054    // Because we don't parse the array, ]] is valid.
1055    /*
1056    doc = "]]";
1057    op.set_doc(doc);
1058    rv = runOp(Command::ARRAY_APPEND, "", "123");
1059    ASSERT_EQ(Error::DOC_NOTJSON, rv);
1060    */
1061
1062    // This won't work either because we don't validate against terminating
1063    // JSON tokens.
1064    /*
1065    doc = "{]";
1066    op.set_doc(doc);
1067    rv = runOp(Command::ARRAY_APPEND, "", "123");
1068    ASSERT_EQ(Error::DOC_NOTJSON, rv);
1069    */
1070
1071    // This follows the same codepath as "normal", but good to verify just
1072    // in case
1073    op.set_doc("[]");
1074    rv = runOp(Command::ARRAY_APPEND, "", "notjson");
1075    ASSERT_EQ(Error::VALUE_CANTINSERT, rv);
1076}
1077
1078TEST_F(OpTests, testGetCount)
1079{
1080    std::string doc = "{\"array\":[1,2,3,4]}";
1081    op.set_doc(doc);
1082    ASSERT_ERROK(runOp(Command::GET_COUNT, "array"));
1083    ASSERT_EQ("4", returnedMatch());
1084
1085    ASSERT_ERREQ(runOp(Command::GET_COUNT, "array[0]"), Error::PATH_MISMATCH);
1086
1087    doc = "[]";
1088    op.set_doc(doc);
1089    ASSERT_ERROK(runOp(Command::GET_COUNT, ""));
1090    ASSERT_EQ("0", returnedMatch());
1091
1092    doc = "{}";
1093    op.set_doc(doc);
1094    ASSERT_ERREQ(runOp(Command::GET_COUNT, "non.exist.path"), Error::PATH_ENOENT);
1095
1096    doc = "{\"a\":\"k\"}";
1097    op.set_doc(doc);
1098    ASSERT_ERREQ(runOp(Command::GET_COUNT, "n"), Error::PATH_ENOENT);
1099
1100    doc = "{\"array\":[1]}";
1101    op.set_doc(doc);
1102    ASSERT_ERROK(runOp(Command::GET_COUNT, "array"));
1103
1104    doc = "{\"array\":[[]]}";
1105    op.set_doc(doc);
1106    ASSERT_ERROK(runOp(Command::GET_COUNT, "array[-1]"));
1107    ASSERT_EQ("0", returnedMatch());
1108
1109    ASSERT_ERROK(runOp(Command::GET_COUNT, ""));
1110    ASSERT_EQ("1", returnedMatch());
1111
1112    doc = "{}";
1113    op.set_doc(doc);
1114    ASSERT_ERROK(runOp(Command::GET_COUNT, ""));
1115    ASSERT_EQ("0", returnedMatch());
1116
1117    doc = "{\"a\":1,\"b\":2}";
1118    op.set_doc(doc);
1119    ASSERT_ERROK(runOp(Command::GET_COUNT, ""));
1120    ASSERT_EQ("2", returnedMatch());
1121}
1122
1123// Some commands require paths with specific endings. For example, DICT_UPSERT
1124// requires its last element MUST NOT be an array element, while ARRAY_INSERT
1125// requires its last element MUST be an array element. Some commands
1126// don't care.
1127enum LastElementType {
1128    LAST_ELEM_KEY, // Last element must be a dict key
1129    LAST_ELEM_INDEX, // Last element must be array index
1130    LAST_ELEM_ANY // Both dict keys and array indexes are acceptable
1131};
1132
1133// Generate a path of a specific depth
1134// @param depth the nominal depth of the path
1135// @param final_type the type of last element, either an array index or dict key
1136// @return the path
1137static std::string
1138genDeepPath(size_t depth, LastElementType final_type = LAST_ELEM_KEY)
1139{
1140    std::stringstream ss;
1141    size_t ii = 0;
1142    for (; ii < depth-1; ++ii) {
1143        ss << "P" << std::to_string(ii) << ".";
1144    }
1145    if (final_type == LAST_ELEM_KEY) {
1146        ss << "P" << std::to_string(ii);
1147    } else {
1148        ss << "[0]";
1149    }
1150    return ss.str();
1151}
1152
1153TEST_F(OpTests, TestDeepPath)
1154{
1155    using Subdoc::Path;
1156
1157    // "Traits" structure for the path
1158    struct CommandInfo {
1159        Command code; // Command code
1160        LastElementType last_elem_type; // Expected last element type
1161        bool implicit_child; // Whether the real size of the path is actually n+1
1162    };
1163
1164    static const std::vector<CommandInfo> cinfo({
1165        {Command::GET, LAST_ELEM_ANY, false},
1166        {Command::EXISTS, LAST_ELEM_ANY, false},
1167        {Command::REPLACE, LAST_ELEM_ANY, false},
1168        {Command::REMOVE, LAST_ELEM_ANY, false},
1169        {Command::DICT_UPSERT, LAST_ELEM_KEY, false},
1170        {Command::DICT_ADD, LAST_ELEM_KEY, false},
1171        {Command::ARRAY_PREPEND, LAST_ELEM_ANY, true},
1172        {Command::ARRAY_APPEND, LAST_ELEM_ANY, true},
1173        {Command::ARRAY_ADD_UNIQUE, LAST_ELEM_ANY, true},
1174        {Command::ARRAY_INSERT, LAST_ELEM_INDEX, false},
1175        {Command::COUNTER, LAST_ELEM_ANY, false},
1176        {Command::GET_COUNT, LAST_ELEM_ANY, false}
1177    });
1178
1179    for (auto opinfo : cinfo) {
1180        // List of final-element-types to try:
1181        std::vector<LastElementType> elemTypes;
1182
1183        if (opinfo.last_elem_type == LAST_ELEM_ANY) {
1184            // If both element types are supported, simply add them here
1185            elemTypes.push_back(LAST_ELEM_INDEX);
1186            elemTypes.push_back(LAST_ELEM_KEY);
1187        } else {
1188            elemTypes.push_back(opinfo.last_elem_type);
1189        }
1190
1191        for (auto etype : elemTypes) {
1192            size_t ncomps = Subdoc::Limits::MAX_COMPONENTS-1;
1193            if (opinfo.implicit_child) {
1194                // If an implicit child is involved, the maximum allowable
1195                // path length is actually one less because the child occupies
1196                // ncomps+1
1197                ncomps--;
1198            }
1199
1200            string path = genDeepPath(ncomps, etype);
1201            Error rv = runOp(opinfo.code, path.c_str(), "123");
1202            ASSERT_NE(Error::PATH_E2BIG, rv) << "Opcode: " << std::to_string(opinfo.code);
1203
1204            // Failure case:
1205            ncomps++;
1206            path = genDeepPath(ncomps, etype);
1207            rv = runOp(opinfo.code, path.c_str(), "123");
1208
1209            if (opinfo.implicit_child) {
1210                // If the path is one that contains an implicit child, then
1211                // the failure is not on the path itself, but on the fact that
1212                // the value is too deep (by virtue of it having a depth of >0)
1213                ASSERT_EQ(Error::VALUE_ETOODEEP, rv);
1214            } else {
1215                ASSERT_EQ(Error::PATH_E2BIG, rv) << "Opcode: " << std::to_string(opinfo.code);
1216            }
1217        }
1218    }
1219}
1220
1221TEST_F(OpTests, testUtf8Path) {
1222    // Ensure we can set and retrieve paths with non-ascii utf8
1223    // \xc3\xba = ú
1224    string path("F\xC3\xBAtbol");
1225    string value("\"value\"");
1226    string doc("{}");
1227    op.set_doc(doc);
1228    ASSERT_EQ(Error::SUCCESS, runOp(Command::DICT_UPSERT,
1229                              path.c_str(), value.c_str()));
1230    getAssignNewDoc(doc);
1231
1232    // Try to retrieve the value
1233    ASSERT_EQ(Error::SUCCESS, runOp(Command::GET, path.c_str()));
1234    ASSERT_EQ("\"value\"", returnedMatch());
1235}
1236