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"
19 using std::string;
20 using std::cerr;
21 using std::endl;
22 using Subdoc::Operation;
23 using Subdoc::Match;
24 using Subdoc::Loc;
25 using Subdoc::Error;
26 using Subdoc::Command;
27 using Subdoc::Validator;
28 using Subdoc::Util;
29 using Subdoc::Limits;
30 using Subdoc::Result;
31 
32 class OpTests : public ::testing::Test {
33 protected:
SetUp()34   virtual void SetUp() {
35       op.clear();
36   }
37 
38   Operation op;
39   Result res;
40 
41   string getNewDoc();
returnedMatch()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 
49 static ::testing::AssertionResult
ensureErrorResult(const char *expr, Error got, Error wanted = Error::SUCCESS)50 ensureErrorResult(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 }
61 static ::testing::AssertionResult
ensureErrorResult2(const char *a, const char *, Error got, Error wanted)62 ensureErrorResult2(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 
72 string
getNewDoc()73 OpTests::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 
91 void
getAssignNewDoc(string& newdoc)92 OpTests::getAssignNewDoc(string& newdoc)
93 {
94     newdoc = getNewDoc();
95     op.set_doc(newdoc);
96 }
97 
98 Error
runOp(Command opcode, const char *path, const char *value, size_t nvalue)99 OpTests::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"
TEST_F(OpTests, testOperations)114 TEST_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
TEST_F(OpTests, testGenericOps)157 TEST_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 
TEST_F(OpTests, testReplaceArrayDeep)187 TEST_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 
TEST_F(OpTests, testListOps)233 TEST_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 
TEST_F(OpTests, testArrayMultivalue)327 TEST_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 
TEST_F(OpTests, testArrayOpsNested)358 TEST_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.
TEST_F(OpTests, testArrayDelete)376 TEST_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 
TEST_F(OpTests, testDictDelete)407 TEST_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 
TEST_F(OpTests, testUnique)424 TEST_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 
TEST_F(OpTests, testUniqueToplevel)462 TEST_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 
TEST_F(OpTests, testNumeric)483 TEST_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 
TEST_F(OpTests, testBadNumFormat)555 TEST_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 
TEST_F(OpTests, testNumericLimits)567 TEST_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 
TEST_F(OpTests, testValueValidation)600 TEST_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 
TEST_F(OpTests, testNegativeIndex)655 TEST_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 
TEST_F(OpTests, testRootOps)698 TEST_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 
TEST_F(OpTests, testMismatch)720 TEST_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 
TEST_F(OpTests, testWhitespace)749 TEST_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 
TEST_F(OpTests, testTooDeep)760 TEST_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 
TEST_F(OpTests, testTooDeepDict)784 TEST_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 
TEST_F(OpTests, testArrayInsert)823 TEST_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 
TEST_F(OpTests, testEmpty)877 TEST_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
TEST_F(OpTests, ensureRepeatable)889 TEST_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 
TEST_F(OpTests, testDeleteNestedArray)904 TEST_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 
TEST_F(OpTests, testEscapedJson)940 TEST_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 
TEST_F(OpTests, testUpsertArrayIndex)958 TEST_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 
TEST_F(OpTests, testRootAppend)991 TEST_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 
TEST_F(OpTests, testGetCount)1078 TEST_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.
1127 enum 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
1137 static std::string
genDeepPath(size_t depth, LastElementType final_type = LAST_ELEM_KEY)1138 genDeepPath(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 
TEST_F(OpTests, TestDeepPath)1153 TEST_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 
TEST_F(OpTests, testUtf8Path)1221 TEST_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