/** * Copyright (C) 2016 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * 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 * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General 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 GNU Affero General 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/platform/basic.h" #include #include #include "mongo/base/simple_string_data_comparator.h" #include "mongo/bson/bsonelement.h" #include "mongo/bson/bsonelement_comparator.h" #include "mongo/bson/bsonmisc.h" #include "mongo/bson/bsonobj.h" #include "mongo/bson/bsonobjbuilder.h" #include "mongo/bson/json.h" #include "mongo/db/bson/dotted_path_support.h" #include "mongo/unittest/unittest.h" namespace mongo { namespace { namespace dps = ::mongo::dotted_path_support; TEST(DottedPathSupport, CompareObjectsAccordingToSort) { ASSERT_LT(dps::compareObjectsAccordingToSort( BSON("a" << 1), BSON("a" << 2), BSON("b" << 1 << "a" << 1)), 0); ASSERT_EQ( dps::compareObjectsAccordingToSort(BSON("a" << BSONNULL), BSON("b" << 1), BSON("a" << 1)), 0); } TEST(DottedPathSupport, ExtractElementAtPath) { BSONObj obj = BSON("a" << 1 << "b" << BSON("a" << 2) << "c" << BSON_ARRAY(BSON("a" << 3) << BSON("a" << 4))); ASSERT_EQUALS(1, dps::extractElementAtPath(obj, "a").numberInt()); ASSERT_EQUALS(2, dps::extractElementAtPath(obj, "b.a").numberInt()); ASSERT_EQUALS(3, dps::extractElementAtPath(obj, "c.0.a").numberInt()); ASSERT_EQUALS(4, dps::extractElementAtPath(obj, "c.1.a").numberInt()); ASSERT_TRUE(dps::extractElementAtPath(obj, "x").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "a.x").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "x.y").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, ".").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "..").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "...").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "a.").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, ".a").eoo()); ASSERT_TRUE(dps::extractElementAtPath(obj, "b.a.").eoo()); } TEST(DottedPathSupport, ExtractElementsBasedOnTemplate) { BSONObj obj = BSON("a" << 10 << "b" << 11); ASSERT_EQ(BSON("a" << 10).woCompare(dps::extractElementsBasedOnTemplate(obj, BSON("a" << 1))), 0); ASSERT_EQ(BSON("b" << 11).woCompare(dps::extractElementsBasedOnTemplate(obj, BSON("b" << 1))), 0); ASSERT_EQ(obj.woCompare(dps::extractElementsBasedOnTemplate(obj, BSON("a" << 1 << "b" << 1))), 0); ASSERT_EQ(dps::extractElementsBasedOnTemplate(obj, BSON("a" << 1 << "c" << 1)) .firstElement() .fieldNameStringData(), "a"); } void dumpBSONElementSet(const BSONElementSet& elements, StringBuilder* sb) { *sb << "[ "; bool firstIteration = true; for (auto&& elem : elements) { if (!firstIteration) { *sb << ", "; } *sb << "'" << elem << "'"; firstIteration = false; } *sb << " ]"; } void assertBSONElementSetsAreEqual(const std::vector& expectedObjs, const BSONElementSet& actualElements) { BSONElementSet expectedElements; for (auto&& obj : expectedObjs) { expectedElements.insert(obj.firstElement()); } if (expectedElements.size() != actualElements.size()) { StringBuilder sb; sb << "Expected set to contain " << expectedElements.size() << " element(s), but actual set contains " << actualElements.size() << " element(s); Expected set: "; dumpBSONElementSet(expectedElements, &sb); sb << ", Actual set: "; dumpBSONElementSet(actualElements, &sb); FAIL(sb.str()); } // We do our own comparison of the two BSONElementSets because BSONElement::operator== considers // the field name. auto expectedIt = expectedElements.begin(); auto actualIt = actualElements.begin(); BSONElementComparator eltCmp(BSONElementComparator::FieldNamesMode::kIgnore, &SimpleStringDataComparator::kInstance); for (size_t i = 0; i < expectedElements.size(); ++i) { if (eltCmp.evaluate(*expectedIt != *actualIt)) { StringBuilder sb; sb << "Element '" << *expectedIt << "' doesn't have the same value as element '" << *actualIt << "'; Expected set: "; dumpBSONElementSet(expectedElements, &sb); sb << ", Actual set: "; dumpBSONElementSet(actualElements, &sb); FAIL(sb.str()); } ++expectedIt; ++actualIt; } } void dumpArrayComponents(const std::set& arrayComponents, StringBuilder* sb) { *sb << "[ "; bool firstIteration = true; for (const auto pos : arrayComponents) { if (!firstIteration) { *sb << ", "; } *sb << pos; firstIteration = false; } *sb << " ]"; } void assertArrayComponentsAreEqual(const std::set& expectedArrayComponents, const std::set& actualArrayComponents) { if (expectedArrayComponents != actualArrayComponents) { StringBuilder sb; sb << "Expected: "; dumpArrayComponents(expectedArrayComponents, &sb); sb << ", Actual: "; dumpArrayComponents(actualArrayComponents, &sb); FAIL(sb.str()); } } TEST(ExtractAllElementsAlongPath, NestedObjectWithScalarValue) { BSONObj obj = BSON("a" << BSON("b" << 1)); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1)}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, NestedObjectWithEmptyArrayValue) { BSONObj obj = BSON("a" << BSON("b" << BSONArrayBuilder().arr())); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual(std::vector{}, actualElements); assertArrayComponentsAreEqual(std::set{1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, NestedObjectWithEmptyArrayValueAndExpandParamIsFalse) { BSONObj obj(fromjson("{a: {b: []}}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = false; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << BSONArray())}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, NestedObjectWithSingletonArrayValue) { BSONObj obj = BSON("a" << BSON("b" << BSON_ARRAY(1))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1)}, actualElements); assertArrayComponentsAreEqual(std::set{1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, NestedObjectWithSingletonArrayValueAndExpandParamIsFalse) { BSONObj obj(fromjson("{a: {b: {c: [3]}}}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = false; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b.c", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << BSON_ARRAY(3))}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, NestedObjectWithArrayValue) { BSONObj obj = BSON("a" << BSON("b" << BSON_ARRAY(1 << 2 << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1), BSON("" << 2), BSON("" << 3)}, actualElements); assertArrayComponentsAreEqual({1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, ObjectWithArrayOfSubobjectsWithScalarValue) { BSONObj obj = BSON("a" << BSON_ARRAY(BSON("b" << 1) << BSON("b" << 2) << BSON("b" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1), BSON("" << 2), BSON("" << 3)}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, ObjectWithArrayOfSubobjectsWithArrayValues) { BSONObj obj = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1 << 2)) << BSON("b" << BSON_ARRAY(2 << 3)) << BSON("b" << BSON_ARRAY(3 << 1)))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1), BSON("" << 2), BSON("" << 3)}, actualElements); assertArrayComponentsAreEqual({0U, 1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, ObjectWithArrayOfSubobjectsWithArrayValuesButNotExpandingTrailingArrayValues) { BSONObj obj = BSON("a" << BSON_ARRAY(BSON("b" << BSON_ARRAY(1)) << BSON("b" << BSON_ARRAY(2)) << BSON("b" << BSON_ARRAY(3)))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = false; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual( {BSON("" << BSON_ARRAY(1)), BSON("" << BSON_ARRAY(2)), BSON("" << BSON_ARRAY(3))}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesNotExpandArrayWithinTrailingArray) { BSONObj obj = BSON("a" << BSON("b" << BSON_ARRAY(BSON_ARRAY(1 << 2) << BSON_ARRAY(3 << 4)))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << BSON_ARRAY(1 << 2)), BSON("" << BSON_ARRAY(3 << 4))}, actualElements); assertArrayComponentsAreEqual({1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, ObjectWithTwoDimensionalArrayOfSubobjects) { // Does not expand the array within the array. BSONObj obj = fromjson("{a: [[{b: 0}, {b: 1}], [{b: 2}, {b: 3}]]}"); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, ObjectWithDiverseStructure) { BSONObj obj = fromjson( "{a: [" " {b: 0}," " [{b: 1}, {b: {c: -1}}]," " 'no b here!'," " {b: [{c: -2}, 'no c here!']}," " {b: {c: [-3, -4]}}" "]}"); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b.c", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << -2), BSON("" << -3), BSON("" << -4)}, actualElements); assertArrayComponentsAreEqual({0U, 1U, 2U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, AcceptsNumericFieldNames) { BSONObj obj = BSON("a" << BSON("0" << 1)); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.0", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1)}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, UsesNumericFieldNameToExtractElementFromArray) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("0" << 2))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.0", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1)}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, TreatsNegativeIndexAsFieldName) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("-1" << 2) << BSON("b" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.-1", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, ExtractsNoValuesFromOutOfBoundsIndex) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("b" << 2) << BSON("10" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.10", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesNotTreatHexStringAsIndexSpecification) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("0x2" << 2) << BSON("NOT THIS ONE" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.0x2", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesNotAcceptLeadingPlusAsArrayIndex) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("+2" << 2) << BSON("NOT THIS ONE" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.+2", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesNotAcceptTrailingCharactersForArrayIndex) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("2xyz" << 2) << BSON("NOT THIS ONE" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.2xyz", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesNotAcceptNonDigitsForArrayIndex) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("2x4" << 2) << BSON("NOT THIS ONE" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.2x4", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual({0U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesExtractNestedValuesFromWithinArraysTraversedWithPositionalPaths) { BSONObj obj = BSON("a" << BSON_ARRAY(1 << BSON("2" << 2) << BSON("target" << 3))); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.2.target", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 3)}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesExpandMultiplePositionalPathSpecifications) { BSONObj obj(fromjson("{a: [[{b: '(0, 0)'}, {b: '(0, 1)'}], [{b: '(1, 0)'}, {b: '(1, 1)'}]]}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.1.0.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << "(1, 0)")}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesAcceptNumericInitialField) { BSONObj obj = BSON("a" << 1 << "0" << 2); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "0", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, DoesExpandArrayFoundAfterPositionalSpecification) { BSONObj obj(fromjson("{a: [[{b: '(0, 0)'}, {b: '(0, 1)'}], [{b: '(1, 0)'}, {b: '(1, 1)'}]]}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.1.b", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << "(1, 0)"), BSON("" << "(1, 1)")}, actualElements); assertArrayComponentsAreEqual({1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, PositionalElementsNotConsideredArrayComponents) { BSONObj obj(fromjson("{a: [{b: [1, 2]}]}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.0.b.1", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, TrailingArrayIsExpandedEvenIfPositional) { BSONObj obj(fromjson("{a: {b: [0, [1, 2]]}}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b.1", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 1), BSON("" << 2)}, actualElements); assertArrayComponentsAreEqual({2U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, PositionalTrailingArrayNotExpandedIfExpandParameterIsFalse) { BSONObj obj(fromjson("{a: {b: [0, [1, 2]]}}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = false; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b.1", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << BSON_ARRAY(1 << 2))}, actualElements); assertArrayComponentsAreEqual(std::set{}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, MidPathEmptyArrayIsConsideredAnArrayComponent) { BSONObj obj(fromjson("{a: [{b: []}]}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b.c", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual(std::vector{}, actualElements); assertArrayComponentsAreEqual({0U, 1U}, actualArrayComponents); } TEST(ExtractAllElementsAlongPath, MidPathSingletonArrayIsConsideredAnArrayComponent) { BSONObj obj(fromjson("{a: {b: [{c: 3}]}}")); BSONElementSet actualElements; const bool expandArrayOnTrailingField = true; std::set actualArrayComponents; dps::extractAllElementsAlongPath( obj, "a.b.c", actualElements, expandArrayOnTrailingField, &actualArrayComponents); assertBSONElementSetsAreEqual({BSON("" << 3)}, actualElements); assertArrayComponentsAreEqual({1U}, actualArrayComponents); } TEST(ExtractElementAtPathOrArrayAlongPath, ReturnsArrayEltWithEmptyPathWhenArrayIsAtEndOfPath) { BSONObj obj(fromjson("{a: {b: {c: [1, 2, 3]}}}")); StringData path("a.b.c"); const char* pathData = path.rawData(); auto resultElt = dps::extractElementAtPathOrArrayAlongPath(obj, pathData); ASSERT_BSONELT_EQ(resultElt, fromjson("{c: [1, 2, 3]}").firstElement()); ASSERT(StringData(pathData).empty()); } TEST(ExtractElementAtPathOrArrayAlongPath, ReturnsArrayEltWithNonEmptyPathForArrayInMiddleOfPath) { BSONObj obj(fromjson("{a: {b: [{c: 1}, {c: 2}]}}")); StringData path("a.b.c"); const char* pathData = path.rawData(); auto resultElt = dps::extractElementAtPathOrArrayAlongPath(obj, pathData); ASSERT_BSONELT_EQ(resultElt, fromjson("{b: [{c: 1}, {c: 2}]}").firstElement()); ASSERT_EQ(StringData(pathData), StringData("c")); } TEST(ExtractElementAtPathOrArrayAlongPath, NumericalPathElementNotTreatedAsArrayIndex) { BSONObj obj(fromjson("{a: [{'0': 'foo'}]}")); StringData path("a.0"); const char* pathData = path.rawData(); auto resultElt = dps::extractElementAtPathOrArrayAlongPath(obj, pathData); ASSERT_BSONELT_EQ(resultElt, obj.firstElement()); ASSERT_EQ(StringData(pathData), StringData("0")); } TEST(ExtractElementAtPathOrArrayAlongPath, NumericalPathElementTreatedAsFieldNameForNestedObject) { BSONObj obj(fromjson("{a: {'0': 'foo'}}")); StringData path("a.0"); const char* pathData = path.rawData(); auto resultElt = dps::extractElementAtPathOrArrayAlongPath(obj, pathData); ASSERT_BSONELT_EQ(resultElt, fromjson("{'0': 'foo'}").firstElement()); ASSERT(StringData(pathData).empty()); } } // namespace } // namespace mongo