summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJames Wahlin <james.wahlin@10gen.com>2016-11-09 14:14:40 -0500
committerJames Wahlin <james.wahlin@10gen.com>2016-11-15 14:49:34 -0500
commite0b312bbe4f2c50470560b92fbcfbdd3e0471d2f (patch)
tree1c9a133f1f9c6f6f0cf61fee8af3eacff5ee0329 /src
parentc55f35b894f145d14e7f3b4b431f4139c80a3778 (diff)
downloadmongo-e0b312bbe4f2c50470560b92fbcfbdd3e0471d2f.tar.gz
SERVER-26964 Make FieldPath more efficient
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/pipeline/document.cpp2
-rw-r--r--src/mongo/db/pipeline/document_source.cpp3
-rw-r--r--src/mongo/db/pipeline/document_value_test.cpp4
-rw-r--r--src/mongo/db/pipeline/field_path.cpp87
-rw-r--r--src/mongo/db/pipeline/field_path.h64
-rw-r--r--src/mongo/db/pipeline/field_path_test.cpp68
-rw-r--r--src/mongo/db/pipeline/parsed_add_fields.cpp4
-rw-r--r--src/mongo/db/pipeline/parsed_inclusion_projection.cpp8
8 files changed, 61 insertions, 179 deletions
diff --git a/src/mongo/db/pipeline/document.cpp b/src/mongo/db/pipeline/document.cpp
index 51fd08d4d20..627bafdfd78 100644
--- a/src/mongo/db/pipeline/document.cpp
+++ b/src/mongo/db/pipeline/document.cpp
@@ -335,7 +335,7 @@ static Value getNestedFieldHelper(const Document& doc,
const FieldPath& fieldNames,
vector<Position>* positions,
size_t level) {
- const string& fieldName = fieldNames.getFieldName(level);
+ const auto fieldName = fieldNames.getFieldName(level);
const Position pos = doc.positionOf(fieldName);
if (!pos.found())
diff --git a/src/mongo/db/pipeline/document_source.cpp b/src/mongo/db/pipeline/document_source.cpp
index b5e5309d9e6..b83ec851ebe 100644
--- a/src/mongo/db/pipeline/document_source.cpp
+++ b/src/mongo/db/pipeline/document_source.cpp
@@ -110,8 +110,7 @@ std::set<std::string> extractModifiedDependencies(const std::set<std::string>& d
// should not be included in the modified dependencies.
for (auto&& dependency : dependencies) {
bool preserved = false;
- auto depAsPath = FieldPath(dependency);
- auto firstField = depAsPath.getFieldName(0);
+ auto firstField = FieldPath::extractFirstFieldFromDottedPath(dependency).toString();
// If even a prefix is preserved, the path is preserved, so search for any prefixes of
// 'dependency' as well. 'preservedPaths' is an *ordered* set, so we only have to search the
// range ['firstField', 'dependency'] to find any prefixes of 'dependency'.
diff --git a/src/mongo/db/pipeline/document_value_test.cpp b/src/mongo/db/pipeline/document_value_test.cpp
index f8f451bede1..c0182cb2fc4 100644
--- a/src/mongo/db/pipeline/document_value_test.cpp
+++ b/src/mongo/db/pipeline/document_value_test.cpp
@@ -206,14 +206,14 @@ public:
ASSERT_VALUE_EQ(md.peek()["x"]["y"]["z"], Value("nested"));
// Set a nested field using setNestedField
- FieldPath xxyyzz = string("xx.yy.zz");
+ FieldPath xxyyzz("xx.yy.zz");
md.setNestedField(xxyyzz, Value("nested"));
ASSERT_VALUE_EQ(md.peek().getNestedField(xxyyzz), Value("nested"));
// Set a nested fields through an existing empty document
md["xxx"] = Value(Document());
md["xxx"]["yyy"] = Value(Document());
- FieldPath xxxyyyzzz = string("xxx.yyy.zzz");
+ FieldPath xxxyyyzzz("xxx.yyy.zzz");
md.setNestedField(xxxyyyzzz, Value("nested"));
ASSERT_VALUE_EQ(md.peek().getNestedField(xxxyyyzzz), Value("nested"));
diff --git a/src/mongo/db/pipeline/field_path.cpp b/src/mongo/db/pipeline/field_path.cpp
index 4fa7d699b52..b54fbe88b2c 100644
--- a/src/mongo/db/pipeline/field_path.cpp
+++ b/src/mongo/db/pipeline/field_path.cpp
@@ -35,84 +35,36 @@
namespace mongo {
-using std::ostream;
using std::string;
-using std::stringstream;
using std::vector;
-using namespace mongoutils;
-
-const char FieldPath::prefix[] = "$";
-
-std::string FieldPath::getFullyQualifiedPath(StringData prefix, StringData suffix) {
+string FieldPath::getFullyQualifiedPath(StringData prefix, StringData suffix) {
if (prefix.empty()) {
return suffix.toString();
}
+
return str::stream() << prefix << "." << suffix;
}
-FieldPath::FieldPath(const vector<string>& fieldNames) {
- massert(16409, "FieldPath cannot be constructed from an empty vector.", !fieldNames.empty());
- _fieldNames.reserve(fieldNames.size());
- for (auto fieldName : fieldNames) {
- pushFieldName(fieldName);
+FieldPath::FieldPath(std::string inputPath)
+ : _fieldPath(std::move(inputPath)), _fieldPathDotPosition{string::npos} {
+ uassert(40352, "FieldPath cannot be constructed with empty string", !_fieldPath.empty());
+ uassert(40353, "FieldPath must not end with a '.'.", _fieldPath[_fieldPath.size() - 1] != '.');
+
+ // Store index delimiter position for use in field lookup.
+ size_t dotPos;
+ size_t startPos = 0;
+ while (string::npos != (dotPos = _fieldPath.find('.', startPos))) {
+ _fieldPathDotPosition.push_back(dotPos);
+ startPos = dotPos + 1;
}
-}
-
-FieldPath::FieldPath(const string& fieldPath) {
- // Split 'fieldPath' at the dots.
- size_t startpos = 0;
- while (true) {
- // Find the next dot.
- const size_t dotpos = fieldPath.find('.', startpos);
- // If there are no more dots, use the remainder of the string.
- if (dotpos == fieldPath.npos) {
- string lastFieldName = fieldPath.substr(startpos, dotpos);
- pushFieldName(lastFieldName);
- break;
- }
+ _fieldPathDotPosition.push_back(_fieldPath.size());
- // Use the string up to the dot.
- const size_t length = dotpos - startpos;
- string nextFieldName = fieldPath.substr(startpos, length);
- pushFieldName(nextFieldName);
-
- // Start the next search after the dot.
- startpos = dotpos + 1;
+ // Validate fields.
+ for (size_t i = 0; i < getPathLength(); ++i) {
+ uassertValidFieldName(getFieldName(i));
}
- verify(getPathLength() > 0);
-}
-
-string FieldPath::fullPath() const {
- stringstream ss;
- const bool includePrefix = false;
- writePath(ss, includePrefix);
- return ss.str();
-}
-
-string FieldPath::fullPathWithPrefix() const {
- stringstream ss;
- const bool includePrefix = true;
- writePath(ss, includePrefix);
- return ss.str();
-}
-
-void FieldPath::writePath(ostream& outStream, bool includePrefix) const {
- if (includePrefix)
- outStream << prefix;
-
- const size_t n = _fieldNames.size();
-
- verify(n > 0);
- outStream << _fieldNames[0];
- for (size_t i = 1; i < n; ++i)
- outStream << '.' << _fieldNames[i];
-}
-
-FieldPath FieldPath::tail() const {
- vector<string> allButFirst(_fieldNames.begin() + 1, _fieldNames.end());
- return FieldPath(allButFirst);
}
void FieldPath::uassertValidFieldName(StringData fieldName) {
@@ -123,9 +75,4 @@ void FieldPath::uassertValidFieldName(StringData fieldName) {
uassert(
16412, "FieldPath field names may not contain '.'.", fieldName.find('.') == string::npos);
}
-
-void FieldPath::pushFieldName(const string& fieldName) {
- uassertValidFieldName(fieldName);
- _fieldNames.push_back(fieldName);
-}
}
diff --git a/src/mongo/db/pipeline/field_path.h b/src/mongo/db/pipeline/field_path.h
index 8ac025f899e..2a93e607078 100644
--- a/src/mongo/db/pipeline/field_path.h
+++ b/src/mongo/db/pipeline/field_path.h
@@ -28,7 +28,6 @@
#pragma once
-#include <iosfwd>
#include <string>
#include <vector>
@@ -57,11 +56,7 @@ public:
* Returns the substring of 'path' until the first '.', or the entire string if there is no '.'.
*/
static StringData extractFirstFieldFromDottedPath(StringData path) {
- const auto firstDot = path.find('.');
- if (firstDot == std::string::npos) {
- return path;
- }
- return path.substr(0, firstDot);
+ return path.substr(0, path.find('.'));
}
/**
@@ -69,67 +64,58 @@ public:
*
* Field names are validated using uassertValidFieldName().
*/
- FieldPath(const std::string& fieldPath);
-
- /**
- * Throws a UserException if 'fieldNames' is empty or if any of the field names fail validation.
- *
- * Field names are validated using uassertValidFieldName().
- */
- FieldPath(const std::vector<std::string>& fieldNames);
+ /* implicit */ FieldPath(std::string inputPath);
+ /* implicit */ FieldPath(StringData inputPath) : FieldPath(inputPath.toString()) {}
+ /* implicit */ FieldPath(const char* inputPath) : FieldPath(std::string(inputPath)) {}
/**
* Returns the number of path elements in the field path.
*/
size_t getPathLength() const {
- return _fieldNames.size();
+ return _fieldPathDotPosition.size() - 1;
}
/**
* Return the ith field name from this path using zero-based indexes.
*/
- const std::string& getFieldName(size_t i) const {
+ StringData getFieldName(size_t i) const {
dassert(i < getPathLength());
- return _fieldNames[i];
+ const auto begin = _fieldPathDotPosition[i] + 1;
+ const auto end = _fieldPathDotPosition[i + 1];
+ return StringData(&_fieldPath[begin], end - begin);
}
/**
* Returns the full path, not including the prefix 'FieldPath::prefix'.
*/
- std::string fullPath() const;
+ const std::string& fullPath() const {
+ return _fieldPath;
+ }
/**
* Returns the full path, including the prefix 'FieldPath::prefix'.
*/
- std::string fullPathWithPrefix() const;
-
- /**
- * Write the full path to 'outStream', including the prefix 'FieldPath::prefix' if
- * 'includePrefix' is specified.
- */
- void writePath(std::ostream& outStream, bool includePrefix) const;
-
- static const char* getPrefix() {
- return prefix;
+ std::string fullPathWithPrefix() const {
+ return prefix + _fieldPath;
}
-
/**
* A FieldPath like this but missing the first element (useful for recursion).
* Precondition getPathLength() > 1.
*/
- FieldPath tail() const;
+ FieldPath tail() const {
+ massert(16409, "FieldPath::tail() called on single element path", getPathLength() > 1);
+ return {_fieldPath.substr(_fieldPathDotPosition[1] + 1)};
+ }
private:
- /**
- * Push a new field name to the back of the vector of names comprising the field path.
- *
- * Throws a UserException if 'fieldName' does not pass validation done by
- * uassertValidFieldName().
- */
- void pushFieldName(const std::string& fieldName);
+ static const char prefix = '$';
- static const char prefix[];
+ // Contains the full field path, with each field delimited by a '.' character.
+ std::string _fieldPath;
- std::vector<std::string> _fieldNames;
+ // Contains the position of field delimiter dots in '_fieldPath'. The first element contains
+ // string::npos (which evaluates to -1) and the last contains _fieldPath.size() to facilitate
+ // lookup.
+ std::vector<size_t> _fieldPathDotPosition;
};
}
diff --git a/src/mongo/db/pipeline/field_path_test.cpp b/src/mongo/db/pipeline/field_path_test.cpp
index 83df46410ae..6886e0116a7 100644
--- a/src/mongo/db/pipeline/field_path_test.cpp
+++ b/src/mongo/db/pipeline/field_path_test.cpp
@@ -45,15 +45,6 @@ public:
}
};
-/** FieldPath constructed from empty vector. */
-class EmptyVector {
-public:
- void run() {
- vector<string> vec;
- ASSERT_THROWS(FieldPath path(vec), MsgAssertionException);
- }
-};
-
/** FieldPath constructed from a simple string (without dots). */
class Simple {
public:
@@ -66,18 +57,6 @@ public:
}
};
-/** FieldPath constructed from a single element vector. */
-class SimpleVector {
-public:
- void run() {
- vector<string> vec(1, "foo");
- FieldPath path(vec);
- ASSERT_EQUALS(1U, path.getPathLength());
- ASSERT_EQUALS("foo", path.getFieldName(0));
- ASSERT_EQUALS("foo", path.fullPath());
- }
-};
-
/** FieldPath consisting of a '$' character. */
class DollarSign {
public:
@@ -107,28 +86,6 @@ public:
}
};
-/** FieldPath constructed from a single element vector containing a dot. */
-class VectorWithDot {
-public:
- void run() {
- vector<string> vec(1, "fo.o");
- ASSERT_THROWS(FieldPath path(vec), UserException);
- }
-};
-
-/** FieldPath constructed from a two element vector. */
-class TwoFieldVector {
-public:
- void run() {
- vector<string> vec;
- vec.push_back("foo");
- vec.push_back("bar");
- FieldPath path(vec);
- ASSERT_EQUALS(2U, path.getPathLength());
- ASSERT_EQUALS("foo.bar", path.fullPath());
- }
-};
-
/** FieldPath with a '$' prefix in the second field. */
class DollarSignPrefixSecondField {
public:
@@ -174,6 +131,14 @@ public:
}
};
+/** FieldPath constructed with only dots. */
+class OnlyDots {
+public:
+ void run() {
+ ASSERT_THROWS(FieldPath path("..."), UserException);
+ }
+};
+
/** FieldPath constructed from a string with one letter between two dots. */
class LetterBetweenDots {
public:
@@ -192,17 +157,6 @@ public:
}
};
-/** FieldPath constructed with a vector containing a null character. */
-class VectorNullCharacter {
-public:
- void run() {
- vector<string> vec;
- vec.push_back("foo");
- vec.push_back(string("b\0r", 3));
- ASSERT_THROWS(FieldPath path(vec), UserException);
- }
-};
-
/** Tail of a FieldPath. */
class Tail {
public:
@@ -228,22 +182,18 @@ public:
All() : Suite("field_path") {}
void setupTests() {
add<Empty>();
- add<EmptyVector>();
add<Simple>();
- add<SimpleVector>();
add<DollarSign>();
add<DollarSignPrefix>();
add<Dotted>();
- add<VectorWithDot>();
- add<TwoFieldVector>();
add<DollarSignPrefixSecondField>();
add<TwoDotted>();
add<TerminalDot>();
add<PrefixDot>();
add<AdjacentDots>();
+ add<OnlyDots>();
add<LetterBetweenDots>();
add<NullCharacter>();
- add<VectorNullCharacter>();
add<Tail>();
add<TailThreeFields>();
}
diff --git a/src/mongo/db/pipeline/parsed_add_fields.cpp b/src/mongo/db/pipeline/parsed_add_fields.cpp
index 3f9b5427a32..2bca98f21bd 100644
--- a/src/mongo/db/pipeline/parsed_add_fields.cpp
+++ b/src/mongo/db/pipeline/parsed_add_fields.cpp
@@ -66,7 +66,7 @@ void ParsedAddFields::parse(const BSONObj& spec, const VariablesParseState& vari
auto remainingPath = FieldPath(elem.fieldName());
auto child = _root.get();
while (remainingPath.getPathLength() > 1) {
- child = child->addOrGetChild(remainingPath.getFieldName(0));
+ child = child->addOrGetChild(remainingPath.getFieldName(0).toString());
remainingPath = remainingPath.tail();
}
// It is illegal to construct an empty FieldPath, so the above loop ends one
@@ -103,7 +103,7 @@ bool ParsedAddFields::parseObjectAsExpression(StringData pathToObject,
// This is an expression like {$add: [...]}. We have already verified that it has only one
// field.
invariant(objSpec.nFields() == 1);
- _root->addComputedField(pathToObject.toString(),
+ _root->addComputedField(pathToObject,
Expression::parseExpression(objSpec, variablesParseState));
return true;
}
diff --git a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
index 77fccf8107f..abac372be7e 100644
--- a/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
+++ b/src/mongo/db/pipeline/parsed_inclusion_projection.cpp
@@ -206,7 +206,7 @@ void InclusionNode::addComputedField(const FieldPath& path, boost::intrusive_ptr
_orderToProcessAdditionsAndChildren.push_back(fieldName);
return;
}
- addOrGetChild(path.getFieldName(0))->addComputedField(path.tail(), expr);
+ addOrGetChild(path.getFieldName(0).toString())->addComputedField(path.tail(), expr);
}
void InclusionNode::addIncludedField(const FieldPath& path) {
@@ -214,7 +214,7 @@ void InclusionNode::addIncludedField(const FieldPath& path) {
_inclusions.insert(path.fullPath());
return;
}
- addOrGetChild(path.getFieldName(0))->addIncludedField(path.tail());
+ addOrGetChild(path.getFieldName(0).toString())->addIncludedField(path.tail());
}
InclusionNode* InclusionNode::addOrGetChild(std::string field) {
@@ -299,7 +299,7 @@ void ParsedInclusionProjection::parse(const BSONObj& spec,
auto remainingPath = FieldPath(elem.fieldName());
auto child = _root.get();
while (remainingPath.getPathLength() > 1) {
- child = child->addOrGetChild(remainingPath.getFieldName(0));
+ child = child->addOrGetChild(remainingPath.getFieldName(0).toString());
remainingPath = remainingPath.tail();
}
// It is illegal to construct an empty FieldPath, so the above loop ends one
@@ -350,7 +350,7 @@ bool ParsedInclusionProjection::parseObjectAsExpression(
// This is an expression like {$add: [...]}. We have already verified that it has only one
// field.
invariant(objSpec.nFields() == 1);
- _root->addComputedField(pathToObject.toString(),
+ _root->addComputedField(pathToObject,
Expression::parseExpression(objSpec, variablesParseState));
return true;
}