/** * Copyright (C) 2018-present MongoDB, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the Server Side Public License, version 1, * as published by MongoDB, Inc. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License * along with this program. If not, see * . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the Server Side Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/unittest/unittest.h" #include "mongo/db/jsobj.h" #include "mongo/db/json.h" #include "mongo/db/matcher/path.h" namespace mongo { using std::string; TEST(Path, Root1) { ElementPath p{"a"}; BSONObj doc = BSON("x" << 4 << "a" << 5); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); ElementIterator::Context e = cursor.next(); ASSERT_EQUALS((string) "a", e.element().fieldName()); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(!cursor.more()); } TEST(Path, RootArray1) { ElementPath p{"a"}; BSONObj doc = BSON("x" << 4 << "a" << BSON_ARRAY(5 << 6)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(6, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT(!cursor.more()); } TEST(Path, RootArray2) { ElementPath p{"a"}; p.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kNoTraversal); BSONObj doc = BSON("x" << 4 << "a" << BSON_ARRAY(5 << 6)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT(e.element().type() == Array); ASSERT(!cursor.more()); } TEST(Path, Nested1) { ElementPath p{"a.b"}; BSONObj doc = BSON("a" << BSON_ARRAY(BSON("b" << 5) << 3 << BSONObj() << BSON("b" << BSON_ARRAY(9 << 11)) << BSON("b" << 7))); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT(e.element().eoo()); ASSERT_EQUALS((string) "2", e.arrayOffset().fieldName()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(9, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(11, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT_EQUALS(2, e.element().Obj().nFields()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(7, e.element().numberInt()); ASSERT(!cursor.more()); } TEST(Path, NestedPartialMatchScalar) { ElementPath p{"a.b"}; BSONObj doc = BSON("a" << 4); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT(e.element().eoo()); ASSERT(e.arrayOffset().eoo()); ASSERT(!cursor.more()); } // When the path (partially or in its entirety) refers to an array, // the iteration logic does not return an EOO. // what we want ideally. TEST(Path, NestedPartialMatchArray) { ElementPath p{"a.b"}; BSONObj doc = BSON("a" << BSON_ARRAY(4)); BSONElementIterator cursor(&p, doc); ASSERT(!cursor.more()); } // Note that this describes existing behavior and not necessarily TEST(Path, NestedEmptyArray) { ElementPath p{"a.b"}; BSONObj doc = BSON("a" << BSON("b" << BSONArray())); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT_EQUALS(0, e.element().Obj().nFields()); ASSERT(!cursor.more()); } TEST(Path, NestedNoLeaf1) { ElementPath p{"a.b"}; p.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kNoTraversal); BSONObj doc = BSON("a" << BSON_ARRAY(BSON("b" << 5) << 3 << BSONObj() << BSON("b" << BSON_ARRAY(9 << 11)) << BSON("b" << 7))); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT(e.element().eoo()); ASSERT_EQUALS((string) "2", e.arrayOffset().fieldName()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT_EQUALS(2, e.element().Obj().nFields()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(7, e.element().numberInt()); ASSERT(!cursor.more()); } TEST(Path, MatchSubpathReturnsArrayOnSubpath) { ElementPath path{"a.b.c"}; path.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kNoTraversal); path.setNonLeafArrayBehavior(ElementPath::NonLeafArrayBehavior::kMatchSubpath); BSONObj doc = BSON("a" << BSON_ARRAY(BSON("b" << 5))); BSONElementIterator cursor(&path, doc); ASSERT(cursor.more()); auto context = cursor.next(); ASSERT_BSONELT_EQ(doc.firstElement(), context.element()); ASSERT(!cursor.more()); } TEST(Path, MatchSubpathWithTraverseLeafFalseReturnsLeafArrayOnPath) { ElementPath path{"a.b.c"}; path.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kNoTraversal); path.setNonLeafArrayBehavior(ElementPath::NonLeafArrayBehavior::kMatchSubpath); BSONObj doc = BSON("a" << BSON("b" << BSON("c" << BSON_ARRAY(1 << 2)))); BSONElementIterator cursor(&path, doc); ASSERT(cursor.more()); auto context = cursor.next(); ASSERT_BSONELT_EQ(fromjson("{c: [1, 2]}").firstElement(), context.element()); ASSERT(!cursor.more()); } TEST(Path, MatchSubpathWithTraverseLeafTrueReturnsLeafArrayAndValuesOnPath) { ElementPath path{"a.b.c"}; path.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kTraverse); path.setNonLeafArrayBehavior(ElementPath::NonLeafArrayBehavior::kMatchSubpath); BSONObj doc = BSON("a" << BSON("b" << BSON("c" << BSON_ARRAY(1 << 2)))); BSONElementIterator cursor(&path, doc); ASSERT(cursor.more()); BSONElementIterator::Context context = cursor.next(); ASSERT_EQUALS(1, context.element().numberInt()); ASSERT(cursor.more()); context = cursor.next(); ASSERT_EQUALS(2, context.element().numberInt()); ASSERT(cursor.more()); context = cursor.next(); ASSERT_BSONELT_EQ(fromjson("{c: [1, 2]}").firstElement(), context.element()); ASSERT(!cursor.more()); } TEST(Path, MatchSubpathWithMultipleArraysReturnsOutermostArray) { ElementPath path{"a.b.c"}; path.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kTraverse); path.setNonLeafArrayBehavior(ElementPath::NonLeafArrayBehavior::kMatchSubpath); BSONObj doc = fromjson("{a: [{b: [{c: [1]}]}]}"); BSONElementIterator cursor(&path, doc); ASSERT(cursor.more()); auto context = cursor.next(); ASSERT_BSONELT_EQ(fromjson("{a: [{b: [{c: [1]}]}]}").firstElement(), context.element()); ASSERT(!cursor.more()); } TEST(Path, NoTraversalOfNonLeafArrayReturnsNothingWithNonLeafArrayInDoc) { ElementPath path{"a.b"}; path.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kTraverse); path.setNonLeafArrayBehavior(ElementPath::NonLeafArrayBehavior::kNoTraversal); BSONObj doc = fromjson("{a: [{b: 1}]}"); BSONElementIterator cursor(&path, doc); ASSERT(!cursor.more()); } TEST(Path, MatchSubpathWithNumericalPathComponentReturnsEntireArray) { ElementPath path{"a.0.b"}; path.setLeafArrayBehavior(ElementPath::LeafArrayBehavior::kTraverse); path.setNonLeafArrayBehavior(ElementPath::NonLeafArrayBehavior::kMatchSubpath); BSONObj doc = fromjson("{a: [{b: 1}]}"); BSONElementIterator cursor(&path, doc); ASSERT(cursor.more()); auto context = cursor.next(); ASSERT_BSONELT_EQ(fromjson("{a: [{b: 1}]}").firstElement(), context.element()); ASSERT(!cursor.more()); } TEST(Path, ArrayIndex1) { ElementPath p{"a.1"}; BSONObj doc = BSON("a" << BSON_ARRAY(5 << 7 << 3)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(7, e.element().numberInt()); ASSERT(!cursor.more()); } TEST(Path, ArrayIndex2) { ElementPath p{"a.1"}; BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON_ARRAY(2 << 4) << 3)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT(!cursor.more()); } TEST(Path, ArrayIndex3) { ElementPath p{"a.1"}; BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON("1" << 4) << 3)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(4, e.element().numberInt()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_BSONOBJ_EQ(BSON("1" << 4), e.element().Obj()); ASSERT(!cursor.more()); } TEST(Path, ArrayIndexNested1) { ElementPath p{"a.1.b"}; BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON("b" << 4) << 3)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT(e.element().eoo()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(4, e.element().numberInt()); ASSERT(!cursor.more()); } TEST(Path, ArrayIndexNested2) { ElementPath p{"a.1.b"}; BSONObj doc = BSON("a" << BSON_ARRAY(5 << BSON_ARRAY(BSON("b" << 4)) << 3)); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); BSONElementIterator::Context e = cursor.next(); ASSERT_EQUALS(4, e.element().numberInt()); ASSERT(!cursor.more()); } // SERVER-15899: test iteration using a path that generates no elements, but traverses a long // array containing subdocuments with nested arrays. TEST(Path, NonMatchingLongArrayOfSubdocumentsWithNestedArrays) { ElementPath p{"a.b.x"}; // Build the document {a: [{b: []}, {b: []}, {b: []}, ...]}. BSONObj subdoc = BSON("b" << BSONArray()); BSONArrayBuilder builder; for (int i = 0; i < 100 * 1000; ++i) { builder.append(subdoc); } BSONObj doc = BSON("a" << builder.arr()); BSONElementIterator cursor(&p, doc); // The path "a.b.x" matches no elements. ASSERT(!cursor.more()); } // When multiple arrays are traversed implicitly in the same path, // ElementIterator::Context::arrayOffset() should always refer to the current offset of the // outermost array that is implicitly traversed. TEST(Path, NestedArrayImplicitTraversal) { ElementPath p{"a.b"}; BSONObj doc = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); ElementIterator::Context e = cursor.next(); ASSERT_EQUALS(NumberInt, e.element().type()); ASSERT_EQUALS(2, e.element().numberInt()); ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(NumberInt, e.element().type()); ASSERT_EQUALS(3, e.element().numberInt()); ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT_BSONOBJ_EQ(BSON("0" << 2 << "1" << 3), e.element().Obj()); ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(NumberInt, e.element().type()); ASSERT_EQUALS(4, e.element().numberInt()); ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(NumberInt, e.element().type()); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT_BSONOBJ_EQ(BSON("0" << 4 << "1" << 5), e.element().Obj()); ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); ASSERT(!cursor.more()); } // SERVER-14886: when an array is being traversed explictly at the same time that a nested array // is being traversed implicitly, ElementIterator::Context::arrayOffset() should return the // current offset of the array being implicitly traversed. TEST(Path, ArrayOffsetWithImplicitAndExplicitTraversal) { ElementPath p{"a.0.b"}; BSONObj doc = fromjson("{a: [{b: [2, 3]}, {b: [4, 5]}]}"); BSONElementIterator cursor(&p, doc); ASSERT(cursor.more()); ElementIterator::Context e = cursor.next(); ASSERT_EQUALS(EOO, e.element().type()); ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); // First elt of outer array. ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(NumberInt, e.element().type()); ASSERT_EQUALS(2, e.element().numberInt()); ASSERT_EQUALS("0", e.arrayOffset().fieldNameStringData()); // First elt of inner array. ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(NumberInt, e.element().type()); ASSERT_EQUALS(3, e.element().numberInt()); ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); // Second elt of inner array. ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT_BSONOBJ_EQ(BSON("0" << 2 << "1" << 3), e.element().Obj()); ASSERT(e.arrayOffset().eoo()); ASSERT(cursor.more()); e = cursor.next(); ASSERT_EQUALS(EOO, e.element().type()); ASSERT_EQUALS("1", e.arrayOffset().fieldNameStringData()); // Second elt of outer array. ASSERT(!cursor.more()); } TEST(Path, LeafArrayBehaviorTraverseOmitArrayWithNonEmptyArray) { ElementPath path{"a", ElementPath::LeafArrayBehavior::kTraverseOmitArray}; BSONObj doc = fromjson("{a: [1, 2]}"); BSONElementIterator cursor(&path, doc); // Verifies that only array elements are returned by the iterator, that is the array [1, 2] is // not returned. ASSERT_TRUE(cursor.more()); ElementIterator::Context context = cursor.next(); ASSERT_EQUALS(1, context.element().Int()); ASSERT_TRUE(cursor.more()); context = cursor.next(); ASSERT_EQUALS(2, context.element().Int()); ASSERT_FALSE(cursor.more()); } TEST(Path, LeafArrayBehaviorTraverseOmitArrayWithEmptyArray) { ElementPath path{"a", ElementPath::LeafArrayBehavior::kTraverseOmitArray}; BSONObj doc = fromjson("{a: []}"); BSONElementIterator cursor(&path, doc); // Verifies that no elements are returned by the iterator since the array is empty. ASSERT_FALSE(cursor.more()); } TEST(Path, LeafArrayBehaviorTraverseOmitArrayNested) { ElementPath path{"a.b", ElementPath::LeafArrayBehavior::kTraverseOmitArray}; BSONObj doc = fromjson("{a: [{b: [1]}, {b: []}, {b: [2, 3]}]}"); BSONElementIterator cursor(&path, doc); // Verifies that all elements of nested arrays are returned. for (auto&& element : {1, 2, 3}) { ASSERT_TRUE(cursor.more()); ASSERT_EQUALS(element, cursor.next().element().Int()); } ASSERT_FALSE(cursor.more()); } TEST(Path, LeafArrayBehaviorTraverseOmitArrayNestedEmptyArray) { ElementPath path{"a.b", ElementPath::LeafArrayBehavior::kTraverseOmitArray}; BSONObj doc = fromjson("{a: [{b: []}, {b: []}]}"); BSONElementIterator cursor(&path, doc); // Verifies that no elements are returned. ASSERT_FALSE(cursor.more()); } TEST(SimpleArrayElementIterator, SimpleNoArrayLast1) { BSONObj obj = BSON("a" << BSON_ARRAY(5 << BSON("x" << 6) << BSON_ARRAY(7 << 9) << 11)); SimpleArrayElementIterator i(obj["a"], false); ASSERT(i.more()); ElementIterator::Context e = i.next(); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(6, e.element().Obj()["x"].numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(7, e.element().Obj().firstElement().numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(11, e.element().numberInt()); ASSERT(!i.more()); } TEST(SimpleArrayElementIterator, SimpleArrayLast1) { BSONObj obj = BSON("a" << BSON_ARRAY(5 << BSON("x" << 6) << BSON_ARRAY(7 << 9) << 11)); SimpleArrayElementIterator i(obj["a"], true); ASSERT(i.more()); ElementIterator::Context e = i.next(); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(6, e.element().Obj()["x"].numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(7, e.element().Obj().firstElement().numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(11, e.element().numberInt()); ASSERT(i.more()); e = i.next(); ASSERT_EQUALS(Array, e.element().type()); ASSERT(!i.more()); } TEST(SingleElementElementIterator, Simple1) { BSONObj obj = BSON("x" << 3 << "y" << 5); SingleElementElementIterator i(obj["y"]); ASSERT(i.more()); ElementIterator::Context e = i.next(); ASSERT_EQUALS(5, e.element().numberInt()); ASSERT(!i.more()); } } // namespace mongo