SERVER-98371 Improve multikey handling (#44858)

GitOrigin-RevId: 81a986240829784adb922cba4ee1fa4c0d282668
This commit is contained in:
karlbozdogan 2025-12-15 11:49:17 +01:00 committed by MongoDB Bot
parent 2c704757f1
commit a4a1ea4953
4 changed files with 301 additions and 128 deletions

View File

@ -45,17 +45,12 @@ static std::vector<std::string> verifyPath(std::vector<std::string> path) {
return path;
}
MultiKeyDottedPathIterator::MultiKeyDottedPathIterator(const BSONObj* obj, const std::string& path)
: _obj(obj), _components(verifyPath(absl::StrSplit(path, "."))) {
MultiKeyDottedPathIterator::MultiKeyDottedPathIterator(const std::string& path)
: _obj(&nullObj), _components(verifyPath(absl::StrSplit(path, "."))) {
_stack.reserve(_components.size());
}
void MultiKeyDottedPathIterator::resetObj(const BSONObj* obj) {
_obj = obj;
_initialized = false;
_stack.clear();
}
std::pair<BSONElement, bool> MultiKeyDottedPathIterator::_nextElementResume() {
BSONElement MultiKeyDottedPathIterator::getNext() {
auto&& [iterator, pathIndex] = *_stack.rbegin();
auto elem = iterator.next();
@ -68,15 +63,27 @@ std::pair<BSONElement, bool> MultiKeyDottedPathIterator::_nextElementResume() {
return _getFirstElementRooted(pathIndex + 1, elem);
}
static bool isNumericPathComponent(const std::string& component) {
return (component.size() == 1 || component[0] != '0') && str::isAllDigits(component);
}
std::pair<BSONElement, bool> MultiKeyDottedPathIterator::_getFirstElementRootedArray(
size_t idx, BSONElement arr) {
BSONElement MultiKeyDottedPathIterator::_getFirstElementRootedArray(size_t idx, BSONElement arr) {
if (idx + 1 < _components.size() && isNumericPathComponent(_components[idx + 1])) {
const BSONElement nestedElement = arr.embeddedObject()[_components[idx + 1]];
if (nestedElement.type() != BSONType::array ||
(idx + 2 == _components.size() &&
!nestedElement.embeddedObject().firstElement().eoo())) {
return _getFirstElementRooted(idx + 2, nestedElement);
}
arr = nestedElement;
idx++;
}
auto it = BSONObjIterator(arr.embeddedObject());
if (!it.more()) {
// Empty array
// Yield undefined if leaf, null otherwise.
return std::make_pair((idx == _components.size() - 1) ? undefinedElt : nullElt,
_stack.size() == 0);
return (idx == _components.size() - 1) ? undefinedElt : nullElt;
}
auto item = it.next();
if (it.more()) {

View File

@ -44,73 +44,60 @@ namespace mongo::ce {
* Returns undefined for leaf empty arrays, null for non-leaf
* empty arrays.
* This essentially emulates the behaviour of multikey indices.
* [obj] must stay valid throughout its iteration.
*/
class MultiKeyDottedPathIterator {
public:
MultiKeyDottedPathIterator(const BSONObj* obj, const std::string& path);
MultiKeyDottedPathIterator(const std::string& path);
// Changes the object the iterator is working with.
// The next call to nextElement() starts from scratch.
void resetObj(const BSONObj* obj);
// Returns the next BSONElement the btree key generator
// Returns the first BSONElement the btree key generator
// would use in the keystring for this field (referred to
// later as a "multikey element").
// If the second field is true, there are no more
// elements to iterate over.
// Note that because of the way the btree key generator
// works, iterating over an object always returns at least
// one multikey element.
// works, the first multikey element always exists.
// The next call to getNext/hasNext starts from scratch.
// [obj] must stay valid throughout its iteration.
MONGO_COMPILER_ALWAYS_INLINE BSONElement resetObj(const BSONObj* obj) {
_obj = obj;
_stack.clear();
return _getFirstElementRooted(0, *_obj);
}
// Returns the next multikey element for this field.
// Do not call this past the end of the iteration.
MONGO_COMPILER_ALWAYS_INLINE std::pair<BSONElement, bool> nextElement() {
if (!_initialized) {
// Accessing the very first element
_initialized = true;
return _getFirstElementRooted(0, *_obj);
} else {
return _nextElementResume();
}
BSONElement getNext();
// Returns if there are further multikey elements to
// iterate.
MONGO_COMPILER_ALWAYS_INLINE bool hasNext() const {
return _stack.size() > 0;
}
private:
// Resumes an existing iteration. Called by nextElement().
std::pair<BSONElement, bool> _nextElementResume();
// Given an array, extracts the first multikey element from it
// by unwinding it.
// [rootIdx] is the index of the current path component.
std::pair<BSONElement, bool> _getFirstElementRootedArray(size_t rootIdx, BSONElement arr);
BSONElement _getFirstElementRootedArray(size_t rootIdx, BSONElement arr);
// Given a BSON element, extracts the first multikey element from it.
// If the given element is an array, unwinds it.
MONGO_COMPILER_ALWAYS_INLINE std::pair<BSONElement, bool> _getFirstElementRooted(
size_t idx, BSONElement elem) {
MONGO_COMPILER_ALWAYS_INLINE BSONElement _getFirstElementRooted(size_t idx, BSONElement elem) {
for (; idx < _components.size(); idx++) {
const BSONType elemType = elem.type();
if (elemType != BSONType::array && elemType != BSONType::object) {
return std::make_pair(nullElt, _stack.size() == 0);
if (elem.type() != BSONType::array && elem.type() != BSONType::object) {
return nullElt;
}
const BSONObj obj = elem.embeddedObject();
const BSONElement nestedElem = obj[_components[idx]];
if (nestedElem.type() == BSONType::eoo) {
return std::make_pair(nullElt, _stack.size() == 0);
}
elem = obj[_components[idx]];
if (nestedElem.type() == BSONType::array) {
return _getFirstElementRootedArray(idx, nestedElem);
} else {
elem = nestedElem;
if (elem.type() == BSONType::array) {
return _getFirstElementRootedArray(idx, elem);
}
}
return std::make_pair(elem, _stack.size() == 0);
return elem.eoo() ? nullElt : elem;
}
// Given a BSON object, extracts the first multikey element from it.
// If the given element is an array, unwinds it.
MONGO_COMPILER_ALWAYS_INLINE std::pair<BSONElement, bool> _getFirstElementRooted(
size_t idx, const BSONObj& obj) {
MONGO_COMPILER_ALWAYS_INLINE BSONElement _getFirstElementRooted(size_t idx,
const BSONObj& obj) {
const BSONElement nestedElem = obj[_components[idx]];
if (nestedElem.type() == BSONType::eoo) {
return std::make_pair(nullElt, _stack.size() == 0);
}
if (nestedElem.type() == BSONType::array) {
return _getFirstElementRootedArray(idx, nestedElem);
@ -121,11 +108,7 @@ private:
private:
const BSONObj* _obj;
std::vector<std::string> _components;
// True if nextElement() has ever been called
// since the iterator was constructed or the last
// call to resetObj().
bool _initialized = false;
const std::vector<std::string> _components;
// As we are iterating the object, we need to keep track of
// the arrays (as we need to unwind them).
// We do this by pushing (iterator, path index) pairs to a stack.

View File

@ -33,67 +33,72 @@
#include "mongo/bson/json.h"
#include "mongo/unittest/unittest.h"
#define ASSERT_PAIR_EQ(a, b) \
if (true) { \
auto a2 = (a); \
auto b2 = (b); \
ASSERT_BSONELT_EQ(a2.first, b2.first); \
ASSERT_EQ(a2.second, b2.second); \
}
TEST(MultikeyDottedPathSupport, HandlesScalars) {
const auto obj = mongo::fromjson("{a : 1}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a");
auto it = mongo::ce::MultiKeyDottedPathIterator("a");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]);
ASSERT_EQ(it.hasNext(), false);
}
TEST(MultikeyDottedPathSupport, ResetObj) {
const auto obj1 = mongo::fromjson("{a : 1}");
const auto obj2 = mongo::fromjson("{a : 2}");
const auto obj1 = mongo::fromjson("{a : [1, 2]}");
const auto obj2 = mongo::fromjson("{a : 3}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj1, "a");
auto it = mongo::ce::MultiKeyDottedPathIterator("a");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj1["a"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj1), obj1["a"]["0"]);
ASSERT_EQ(it.hasNext(), true);
it.resetObj(&obj2);
ASSERT_BSONELT_EQ(it.resetObj(&obj2), obj2["a"]);
ASSERT_EQ(it.hasNext(), false);
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj2["a"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj1), obj1["a"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj1["a"]["1"]);
ASSERT_EQ(it.hasNext(), false);
it.resetObj(&obj1);
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj1["a"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj2), obj2["a"]);
ASSERT_EQ(it.hasNext(), false);
}
TEST(MultikeyDottedPathSupport, HandlesLeafArrays) {
const auto obj = mongo::fromjson("{a : [1, 2]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a");
auto it = mongo::ce::MultiKeyDottedPathIterator("a");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["1"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj["a"]["1"]);
ASSERT_EQ(it.hasNext(), false);
}
TEST(MultikeyDottedPathSupport, HandlesNestedArrays) {
const auto obj = mongo::fromjson("{a: [{b: [1, 2]}, {b: [3, 4]}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"]["b"]["0"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"]["b"]["1"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["1"]["b"]["0"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["1"]["b"]["1"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["b"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj["a"]["0"]["b"]["1"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj["a"]["1"]["b"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj["a"]["1"]["b"]["1"]);
ASSERT_EQ(it.hasNext(), false);
}
TEST(MultikeyDottedPathSupport, NoCascadingUnwind) {
const auto obj = mongo::fromjson("{a: [[0], [0]]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a");
auto it = mongo::ce::MultiKeyDottedPathIterator("a");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["1"], true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj["a"]["1"]);
ASSERT_EQ(it.hasNext(), false);
}
namespace {
@ -107,36 +112,43 @@ TEST(MultikeyDottedPathSupport, HandlesEmptyArrays) {
{
// For leaf nested arrays
const auto obj = mongo::fromjson("{a: []}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(undefinedElt, true));
auto it = mongo::ce::MultiKeyDottedPathIterator("a");
ASSERT_BSONELT_EQ(it.resetObj(&obj), undefinedElt);
ASSERT_EQ(it.hasNext(), false);
}
{
// For leaf nested arrays - 2
const auto obj = mongo::fromjson("{a: {b: []}}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(undefinedElt, true));
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), undefinedElt);
ASSERT_EQ(it.hasNext(), false);
}
{
// For leaf nested arrays - bigger
const auto obj = mongo::fromjson("{a: [{b: [1]}, {b: []}, {b: [2]}, {b: []}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"]["b"]["0"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(undefinedElt, false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["2"]["b"]["0"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(undefinedElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["b"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), undefinedElt);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), obj["a"]["2"]["b"]["0"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), undefinedElt);
ASSERT_EQ(it.hasNext(), false);
}
{
// For non-leaf nested array
const auto obj = mongo::fromjson("{a: []}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
}
@ -145,18 +157,175 @@ TEST(MultikeyDottedPathSupport, HandlesElementAbsence) {
// Is an object, doesn't have the key
const auto obj = mongo::BSONObj();
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a");
auto it = mongo::ce::MultiKeyDottedPathIterator("a");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
// Is not an object
const auto obj = mongo::fromjson("{a: 0}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
}
TEST(MultikeyDottedPathSupport, NumberComponents) {
{
const auto obj = mongo::fromjson("{a: [{b: 0}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["b"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [{b: 0}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: {\"1\": {b: 0}}}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["1"]["b"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [[0, {b: 1}]]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [0, {b: 1}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["1"]["b"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: {\"1\": [{b: 0}]}}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["1"]["0"]["b"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: []}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: []}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.1");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: {\"0\": 0}}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: {\"0\": [0]}}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["0"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [[{b: 0}]]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0.b");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["0"]["b"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [0]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [0]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.00");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [[0]]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: []}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: 0}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [[]]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.0");
ASSERT_BSONELT_EQ(it.resetObj(&obj), undefinedElt);
ASSERT_EQ(it.hasNext(), false);
}
}
@ -165,47 +334,65 @@ TEST(MultikeyDottedPathSupport, Mixed) {
// Missing field + empty leaf array
const auto obj = mongo::fromjson("{a: [{}, {b: []}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(undefinedElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), undefinedElt);
ASSERT_EQ(it.hasNext(), false);
}
{
// Missing field + no cascading unwind
const auto obj = mongo::fromjson("{a: [[{b: 0}]]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [{b:1}, {b: []}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"]["b"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(undefinedElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["b"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), undefinedElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [{b: [{c:1}]}, {b: []}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b.c");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b.c");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"]["b"]["0"]["c"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["b"]["0"]["c"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [{b:1}, {}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator(&obj, "a.b");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.b");
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(obj["a"]["0"]["b"], false));
ASSERT_PAIR_EQ(it.nextElement(), std::make_pair(nullElt, true));
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["b"]);
ASSERT_EQ(it.hasNext(), true);
ASSERT_BSONELT_EQ(it.getNext(), nullElt);
ASSERT_EQ(it.hasNext(), false);
}
{
const auto obj = mongo::fromjson("{a: [{\"00\": 1}]}");
auto it = mongo::ce::MultiKeyDottedPathIterator("a.00");
ASSERT_BSONELT_EQ(it.resetObj(&obj), obj["a"]["0"]["00"]);
ASSERT_EQ(it.hasNext(), false);
}
}

View File

@ -190,20 +190,16 @@ public:
BSONComparatorInterfaceBase<BSONElement>::Hasher,
BSONComparatorInterfaceBase<BSONElement>::EqualTo>;
if (sample.size() == 0) {
return;
}
const auto bsonElmComparator =
BSONElementComparator(BSONElementComparator::FieldNamesMode::kIgnore, nullptr);
const auto hasher = BSONComparatorInterfaceBase<BSONElement>::Hasher(&bsonElmComparator);
const auto equalTo = BSONComparatorInterfaceBase<BSONElement>::EqualTo(&bsonElmComparator);
std::vector<MultiKeyDottedPathIterator> iterators;
// TODO(SERVER-114759) Optimize non-multikey indices
std::transform(
bounds.fields.begin(),
bounds.fields.end(),
std::back_inserter(iterators),
[&](auto&& oil) { return MultiKeyDottedPathIterator(&sample[0], oil.name); });
std::transform(bounds.fields.begin(),
bounds.fields.end(),
std::back_inserter(iterators),
[&](auto&& oil) { return MultiKeyDottedPathIterator(oil.name); });
BSONElementSet elemSet(0, hasher, equalTo);
// TODO(SERVER-114756): We can be more clever with retrieving the fields
@ -215,23 +211,23 @@ public:
for (size_t fieldIdx = 0; fieldIdx < iterators.size() && count > 0; fieldIdx++) {
auto&& it = iterators[fieldIdx];
const auto& oil = bounds.fields[fieldIdx];
it.resetObj(&sample[sampleIdx]);
elemSet.clear();
auto [element, isLast] = it.nextElement();
// The first element will always be there
size_t elementCount = 0;
BSONElement element = it.resetObj(&sample[sampleIdx]);
bool hasNext;
while (true) {
hasNext = it.hasNext();
if (elemSet.insert(element).second) {
elementCount += matches(oil, element);
if (elementCount > 0 && skipDuplicateMatches) {
break;
}
}
if (isLast) {
if (!hasNext) {
break;
}
std::tie(element, isLast) = it.nextElement();
element = it.getNext();
}
if (elementCount != 1) {
count = elementCount;