diff options
Diffstat (limited to 'src/mongo/db/timeseries/timeseries_dotted_path_support.cpp')
| -rw-r--r-- | src/mongo/db/timeseries/timeseries_dotted_path_support.cpp | 349 |
1 files changed, 151 insertions, 198 deletions
diff --git a/src/mongo/db/timeseries/timeseries_dotted_path_support.cpp b/src/mongo/db/timeseries/timeseries_dotted_path_support.cpp index f666b93f0de..b28e198429e 100644 --- a/src/mongo/db/timeseries/timeseries_dotted_path_support.cpp +++ b/src/mongo/db/timeseries/timeseries_dotted_path_support.cpp @@ -61,96 +61,83 @@ boost::optional<std::pair<StringData, StringData>> _splitPath(StringData path) { } void _handleElementForExtractAllElementsOnBucketPath(const BSONObj& obj, + BSONElement elem, StringData path, BSONElementSet& elements, bool expandArrayOnTrailingField, BSONDepthIndex depth, - MultikeyComponents* arrayComponents); - -void _handleIntermediateElementForExtractAllElementsOnBucketPath( - BSONElement elem, - StringData path, - BSONElementSet& elements, - bool expandArrayOnTrailingField, - BSONDepthIndex depth, - MultikeyComponents* arrayComponents) { - if (elem.type() == Object) { - BSONObj embedded = elem.embeddedObject(); - _handleElementForExtractAllElementsOnBucketPath( - embedded, path, elements, expandArrayOnTrailingField, depth + 1, arrayComponents); - } else if (elem.type() == Array) { - bool allDigits = false; - if (path.size() > 0 && ctype::isDigit(path[0])) { - unsigned temp = 1; - while (temp < path.size() && ctype::isDigit(path[temp])) - temp++; - allDigits = temp == path.size() || path[temp] == '.'; - } - if (allDigits) { - BSONObj embedded = elem.embeddedObject(); - _handleElementForExtractAllElementsOnBucketPath( - embedded, path, elements, expandArrayOnTrailingField, depth + 1, arrayComponents); - } else { - BSONObjIterator i(elem.embeddedObject()); - while (i.more()) { - BSONElement e2 = i.next(); - if (e2.type() == Object || e2.type() == Array) { - BSONObj embedded = e2.embeddedObject(); + MultikeyComponents* arrayComponents) { + if (elem.eoo()) { + size_t idx = path.find('.'); + if (idx != std::string::npos) { + invariant(depth != std::numeric_limits<BSONDepthIndex>::max()); + StringData left = path.substr(0, idx); + StringData next = path.substr(idx + 1, path.size()); + + BSONElement e = obj.getField(left); + + if (e.type() == Object) { + BSONObj embedded = e.embeddedObject(); + _handleElementForExtractAllElementsOnBucketPath(embedded, + embedded.getField(next), + next, + elements, + expandArrayOnTrailingField, + depth + 1, + arrayComponents); + } else if (e.type() == Array) { + bool allDigits = false; + if (next.size() > 0 && ctype::isDigit(next[0])) { + unsigned temp = 1; + while (temp < next.size() && ctype::isDigit(next[temp])) + temp++; + allDigits = temp == next.size() || next[temp] == '.'; + } + if (allDigits) { + BSONObj embedded = e.embeddedObject(); _handleElementForExtractAllElementsOnBucketPath(embedded, - path, + embedded.getField(next), + next, elements, expandArrayOnTrailingField, depth + 1, arrayComponents); + } else { + BSONObjIterator i(e.embeddedObject()); + while (i.more()) { + BSONElement e2 = i.next(); + if (e2.type() == Object || e2.type() == Array) { + BSONObj embedded = e2.embeddedObject(); + _handleElementForExtractAllElementsOnBucketPath( + embedded, + embedded.getField(next), + next, + elements, + expandArrayOnTrailingField, + depth + 1, + arrayComponents); + } + } + if (arrayComponents) { + arrayComponents->insert(depth); + } } + } else { + // do nothing: no match + } + } + } else { + if (elem.type() == Array && expandArrayOnTrailingField) { + BSONObjIterator i(elem.embeddedObject()); + while (i.more()) { + elements.insert(i.next()); } if (arrayComponents) { arrayComponents->insert(depth); } + } else { + elements.insert(elem); } - } else { - // do nothing: no match - } -} - -void _handleTerminalElementForExtractAllElementsOnBucketPath(BSONElement elem, - BSONElementSet& elements, - bool expandArrayOnTrailingField, - BSONDepthIndex depth, - MultikeyComponents* arrayComponents) { - if (elem.type() == Array && expandArrayOnTrailingField) { - BSONObjIterator i(elem.embeddedObject()); - while (i.more()) { - elements.insert(i.next()); - } - if (arrayComponents) { - arrayComponents->insert(depth); - } - } else if (!elem.eoo()) { - elements.insert(elem); - } -} - -void _handleElementForExtractAllElementsOnBucketPath(const BSONObj& obj, - StringData path, - BSONElementSet& elements, - bool expandArrayOnTrailingField, - BSONDepthIndex depth, - MultikeyComponents* arrayComponents) { - size_t idx = path.find('.'); - if (idx != std::string::npos) { - invariant(depth != std::numeric_limits<BSONDepthIndex>::max()); - StringData left = path.substr(0, idx); - StringData next = path.substr(idx + 1, path.size()); - - BSONElement e = obj.getField(left); - - _handleIntermediateElementForExtractAllElementsOnBucketPath( - e, next, elements, expandArrayOnTrailingField, depth, arrayComponents); - } else { - auto elem = obj.getField(path); - _handleTerminalElementForExtractAllElementsOnBucketPath( - elem, elements, expandArrayOnTrailingField, depth, arrayComponents); } } @@ -178,21 +165,16 @@ boost::optional<BSONColumn> _extractAllElementsAlongBucketPath( depth + 1, arrayComponents); } else if (isCompressed && e.type() == BinData) { - // Unbucketing happens here for nested measurement fields (i.e. data.a.b) in - // compressed buckets. We know that 'e' corresponds to the top-level - // measurement field (i.e. data.a) and we need to iterate over each of the - // numerically-indexed entries (i.e. data.a.1, data.a.5, etc.) and do a - // field lookup to extract the actual field we want (i.e. data.a.1.b). - // Thanks to the bucket structure, we know there is no literal field with a - // dot at this depth (e.g. '1.b'), so we can skip that field lookup, but - // it's possible that the element e2 stores an object, array, or other type, - // and we need to figure out how to resolve the rest of the path based on - // the type. + // Unbucketing magic happens here for nested measurement fields (i.e. + // data.a.b) in compressed buckets. BSONColumn storage{e}; for (const BSONElement& e2 : storage) { if (!e2.eoo()) { - _handleIntermediateElementForExtractAllElementsOnBucketPath( - e2, + BSONObj embedded = + e2.isABSONObj() ? e2.embeddedObject() : BSONObj(); + _handleElementForExtractAllElementsOnBucketPath( + embedded, + embedded.getField(next), next, elements, expandArrayOnTrailingField, @@ -200,8 +182,6 @@ boost::optional<BSONColumn> _extractAllElementsAlongBucketPath( arrayComponents); } } - // Need to pass along the column since it owns the memory referenced by the - // extracted elements. return std::move(storage); } } @@ -216,48 +196,43 @@ boost::optional<BSONColumn> _extractAllElementsAlongBucketPath( depth + 1, arrayComponents); } else if (isCompressed && BinData == e.type()) { - // Unbucketing happens here for top-level measurement fields (i.e. data.a) in - // compressed buckets. We know that 'e' corresponds to the top-level - // measurement field (i.e. data.a) and we need to iterate over each of the - // numerically-indexed entries (i.e. data.a.1, data.a.5, etc.) to extract - // the actual field we want. + // Unbucketing magic happens here for top-level measurement fields (i.e. data.a) + // in compressed buckets. invariant(depth == 1); BSONColumn storage{e}; for (const BSONElement& e2 : storage) { if (!e2.eoo()) { BSONObj embedded = e2.isABSONObj() ? e2.embeddedObject() : BSONObj(); - _handleTerminalElementForExtractAllElementsOnBucketPath( - e2, elements, expandArrayOnTrailingField, depth, arrayComponents); + _handleElementForExtractAllElementsOnBucketPath( + embedded, + e2, + ""_sd, + elements, + expandArrayOnTrailingField, + depth, + arrayComponents); } } - // Need to pass along the column since it owns the memory referenced by the - // extracted elements. return std::move(storage); } } break; } case 2: { - // Unbucketing happens here for uncompressed buckets. We know that 'obj' corresponds to - // the top-level measurement field (i.e. data.a) and we need to iterate over each of the - // numerically-indexed entries (i.e. data.a.1, data.a.5, etc.) to extract the actual - // field we want. If we are after a top-level field, then we already have the element we - // want in 'e'. If we are after a nested field, then we need to recurse. + // Unbucketing magic happens here for uncompressed buckets. invariant(!isCompressed); for (const BSONElement& e : obj) { - if (path.empty()) { - // The top-level measurement field (i.e. data.a) is the indexed field we are - // trying to extract, so we can target it directly. - _handleTerminalElementForExtractAllElementsOnBucketPath( - e, elements, expandArrayOnTrailingField, depth, arrayComponents); - } else { - // We'll need to inspect at least one more field, but we can take advantage of - // the bucket structure here to skip checking for any field at this depth with a - // dot in the name (e.g. '1.b') and instead just look at the next level down - // (e.g. look within '1' for a field named 'b'). - _handleIntermediateElementForExtractAllElementsOnBucketPath( - e, path, elements, expandArrayOnTrailingField, depth, arrayComponents); + std::string subPath = e.fieldName(); + if (!path.empty()) { + subPath.append("." + path); } + _handleElementForExtractAllElementsOnBucketPath(obj, + obj.getField(subPath), + subPath, + elements, + expandArrayOnTrailingField, + depth, + arrayComponents); } break; } @@ -270,42 +245,37 @@ boost::optional<BSONColumn> _extractAllElementsAlongBucketPath( bool _haveArrayAlongBucketDataPath(const BSONObj& obj, StringData path, BSONDepthIndex depth); bool _handleElementForHaveArrayAlongBucketDataPath(const BSONObj& obj, - StringData path, - BSONDepthIndex depth); - -bool _handleIntermediateElementForHaveArrayAlongBucketDataPath(BSONElement elem, - StringData path, - BSONDepthIndex depth) { - if (elem.type() == Object) { - auto embedded = elem.embeddedObject(); - return _handleElementForHaveArrayAlongBucketDataPath(embedded, path, depth + 1); - } else if (elem.type() == Array) { - return true; - } - // no match - return false; -} - -bool _handleTerminalElementForHaveArrayAlongBucketDataPath(BSONElement elem) { - return (elem.type() == Array); -} - - -bool _handleElementForHaveArrayAlongBucketDataPath(const BSONObj& obj, + BSONElement elem, StringData path, BSONDepthIndex depth) { - size_t idx = path.find('.'); - if (idx != std::string::npos) { - tassert( - 5930502, "BSON depth too great", depth != std::numeric_limits<BSONDepthIndex>::max()); - StringData left = path.substr(0, idx); - StringData next = path.substr(idx + 1, path.size()); - - BSONElement e = obj.getField(left); - - return _handleIntermediateElementForHaveArrayAlongBucketDataPath(e, next, depth); + if (elem.eoo()) { + size_t idx = path.find('.'); + if (idx != std::string::npos) { + tassert(5930502, + "BSON depth too great", + depth != std::numeric_limits<BSONDepthIndex>::max()); + StringData left = path.substr(0, idx); + StringData next = path.substr(idx + 1, path.size()); + + BSONElement e = obj.getField(left); + + if (e.type() == Object) { + auto embedded = e.embeddedObject(); + return _handleElementForHaveArrayAlongBucketDataPath( + embedded, embedded.getField(next), next, depth + 1); + } else if (e.type() == Array) { + return true; + } else { + // do nothing: no match + } + } + } else { + if (elem.type() == Array) { + return true; + } } - return _handleTerminalElementForHaveArrayAlongBucketDataPath(obj.getField(path)); + + return false; } bool _haveArrayAlongBucketDataPath(const BSONObj& obj, @@ -323,21 +293,13 @@ bool _haveArrayAlongBucketDataPath(const BSONObj& obj, return _haveArrayAlongBucketDataPath( e.embeddedObject(), next, isCompressed, depth + 1); } else if (isCompressed && BinData == e.type()) { - // Unbucketing happens here for nested measurement fields (i.e. data.a.b) in - // compressed buckets. We know that 'e' corresponds to the top-level - // measurement field (i.e. data.a) and we need to iterate over each of the - // numerically-indexed entries (i.e. data.a.1, data.a.5, etc.) and do a - // field lookup to find the actual field we want (i.e. data.a.1.b). - // Thanks to the bucket structure, we know there is no literal field with a - // dot at this depth (e.g. '1.b'), so we can skip that field lookup, but - // it's possible that the element e2 stores an object, array, or other type, - // and we need to figure out how to resolve the rest of the path based on - // the type. + // Unbucketing magic happens here for nested measurement fields (i.e. + // data.a.b) in compressed buckets. BSONColumn column{e}; for (const BSONElement& e2 : column) { - const bool foundArray = - _handleIntermediateElementForHaveArrayAlongBucketDataPath( - e2, next, depth); + BSONObj embedded = e2.isABSONObj() ? e2.embeddedObject() : BSONObj(); + const bool foundArray = _handleElementForHaveArrayAlongBucketDataPath( + embedded, embedded.getField(next), next, depth); if (foundArray) { return foundArray; } @@ -350,15 +312,12 @@ bool _haveArrayAlongBucketDataPath(const BSONObj& obj, return _haveArrayAlongBucketDataPath( e.embeddedObject(), StringData(), isCompressed, depth + 1); } else if (BinData == e.type()) { - // Unbucketing happens here for top-level measurement fields (i.e. data.a) in - // compressed buckets. We know that 'e' corresponds to the top-level - // measurement field (i.e. data.a) and we need to iterate over each of the - // numerically-indexed entries (i.e. data.a.1, data.a.5, etc.) to decide whether - // we have any arrays. + // Unbucketing magic happens here for top-level measurement fields (i.e. data.a) + // in compressed buckets. invariant(isCompressed && depth == 1); BSONColumn column{e}; for (const BSONElement& e2 : column) { - if (_handleTerminalElementForHaveArrayAlongBucketDataPath(e2)) { + if (e2.type() == Array) { return true; } } @@ -367,35 +326,26 @@ bool _haveArrayAlongBucketDataPath(const BSONObj& obj, return false; } case 2: { - // Unbucketing happens here for uncompressed buckets. We know that 'obj' corresponds to - // the top-level measurement field (i.e. data.a) and we need to iterate over each of the - // numerically-indexed entries (i.e. data.a.1, data.a.5, etc.) to extract the actual - // field we want. If we are after a top-level field, then we already have the element we - // want in 'e'. If we are after a nested field, then we need recurse. + // Unbucketing magic happens here for uncompressed buckets. invariant(!isCompressed); for (const BSONElement& e : obj) { - bool foundArray = false; - if (path.empty()) { - // The top-level measurement field (i.e. data.a) is the field we are trying to - // check, so we can target it directly. - if (_handleTerminalElementForHaveArrayAlongBucketDataPath(e)) { - return true; - } - } else { - // We'll need to inspect at least one more field, but we can take advantage of - // the bucket structure here to skip checking for any field at this depth with a - // dot in the name (e.g. '1.b') and instead just look at the next level down - // (e.g. look within '1' for a field named 'b'). - foundArray = - _handleIntermediateElementForHaveArrayAlongBucketDataPath(e, path, depth); + std::string subPath = e.fieldName(); + if (!path.empty()) { + subPath.append("." + path); } + BSONElement sub = obj.getField(subPath); + const bool foundArray = + _handleElementForHaveArrayAlongBucketDataPath(obj, sub, subPath, depth); if (foundArray) { return foundArray; } } return false; } - default: { return _handleElementForHaveArrayAlongBucketDataPath(obj, path, depth); } + default: { + BSONElement e = obj.getField(path); + return _handleElementForHaveArrayAlongBucketDataPath(obj, e, path, depth); + } } } @@ -435,8 +385,8 @@ Decision _fieldContainsArrayData(const BSONObj& maxObj, StringData field) { // When we get here, we know that some prefix value on the control.min path was a non-object // type < Object. We can also assume that our parent was an Object. - if (std::string::npos == field.find('.')) { - auto e = maxObj.getField(field); + auto e = maxObj.getField(field); + if (!e.eoo()) { if (e.type() == BSONType::Array) { return Decision::Yes; } else if (e.type() > BSONType::Array) { @@ -447,7 +397,7 @@ Decision _fieldContainsArrayData(const BSONObj& maxObj, StringData field) { if (auto res = _splitPath(field)) { auto& [left, next] = *res; - auto e = maxObj.getField(left); + e = maxObj.getField(left); if (e.type() >= BSONType::Array) { return e.type() == BSONType::Array ? Decision::Yes : Decision::Maybe; @@ -475,20 +425,23 @@ Decision _fieldContainsArrayData(const BSONObj& min, const BSONObj& max, StringD // Let's decide whether we are looking at the terminal field on the dotted path, or if we might // need to unpack sub-objects. - if (std::string::npos == field.find('.')) { - auto [minLit, maxLit] = _getLiteralFields(min, max, field); - tassert(5993302, "Malformed control summary for bucket", minLit.eoo() == maxLit.eoo()); - if (!minLit.eoo() /* => !maxLit.eoo()*/) { - return _controlTypesIndicateArrayData(minLit, maxLit, true); - } + const bool terminal = std::string::npos == field.find('.'); + + // First lets try to use the field name literally (i.e. treat it as terminal, even if it has an + // internal dot). + auto [minLit, maxLit] = _getLiteralFields(min, max, field); + tassert(5993302, "Malformed control summary for bucket", minLit.eoo() == maxLit.eoo()); + if (!minLit.eoo() /* => !maxLit.eoo()*/) { + return _controlTypesIndicateArrayData(minLit, maxLit, terminal); + } else if (terminal) { // Nothing further to evaluate, the field is missing from min and max, and thus from all // measurements in this bucket. return Decision::No; } auto [minEl, maxEl, nextField] = _getNextFields(min, max, field); - invariant(!nextField.empty()); - auto decision = _controlTypesIndicateArrayData(minEl, maxEl, false); + invariant(terminal == nextField.empty()); + auto decision = _controlTypesIndicateArrayData(minEl, maxEl, terminal); if (decision != Decision::Undecided) { return decision; } |
