mirror of https://github.com/mongodb/mongo
SERVER-98371 Improve multikey handling (#44858)
GitOrigin-RevId: 81a986240829784adb922cba4ee1fa4c0d282668
This commit is contained in:
parent
2c704757f1
commit
a4a1ea4953
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue