summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops/modifier_pull.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops/modifier_pull.cpp')
-rw-r--r--src/mongo/db/ops/modifier_pull.cpp399
1 files changed, 186 insertions, 213 deletions
diff --git a/src/mongo/db/ops/modifier_pull.cpp b/src/mongo/db/ops/modifier_pull.cpp
index 5fe94e878ea..3b45064afc7 100644
--- a/src/mongo/db/ops/modifier_pull.cpp
+++ b/src/mongo/db/ops/modifier_pull.cpp
@@ -38,265 +38,238 @@
namespace mongo {
- namespace mb = mutablebson;
- namespace str = mongoutils::str;
+namespace mb = mutablebson;
+namespace str = mongoutils::str;
+
+struct ModifierPull::PreparedState {
+ PreparedState(mb::Document& doc)
+ : doc(doc), idxFound(0), elemFound(doc.end()), elementsToRemove(), noOp(false) {}
+
+ // Document that is going to be changed.
+ mb::Document& doc;
+
+ // Index in _fieldRef for which an Element exist in the document.
+ size_t idxFound;
+
+ // Element corresponding to _fieldRef[0.._idxFound].
+ mb::Element elemFound;
+
+ // Values to be removed.
+ std::vector<mb::Element> elementsToRemove;
+
+ // True if this update is a no-op
+ bool noOp;
+};
+
+ModifierPull::ModifierPull()
+ : ModifierInterface(),
+ _fieldRef(),
+ _posDollar(0),
+ _exprElt(),
+ _exprObj(),
+ _matchExpr(),
+ _matcherOnPrimitive(false),
+ _preparedState() {}
+
+ModifierPull::~ModifierPull() {}
+
+Status ModifierPull::init(const BSONElement& modExpr, const Options& opts, bool* positional) {
+ // Perform standard field name and updateable checks.
+ _fieldRef.parse(modExpr.fieldName());
+ Status status = fieldchecker::isUpdatable(_fieldRef);
+ if (!status.isOK()) {
+ return status;
+ }
- struct ModifierPull::PreparedState {
+ // If a $-positional operator was used, get the index in which it occurred
+ // and ensure only one occurrence.
+ size_t foundCount;
+ bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
- PreparedState(mb::Document& doc)
- : doc(doc)
- , idxFound(0)
- , elemFound(doc.end())
- , elementsToRemove()
- , noOp(false) {
- }
+ if (positional)
+ *positional = foundDollar;
- // Document that is going to be changed.
- mb::Document& doc;
+ if (foundDollar && foundCount > 1) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Too many positional (i.e. '$') elements found in path '"
+ << _fieldRef.dottedField() << "'");
+ }
- // Index in _fieldRef for which an Element exist in the document.
- size_t idxFound;
+ _exprElt = modExpr;
- // Element corresponding to _fieldRef[0.._idxFound].
- mb::Element elemFound;
+ // If the element in the mod is actually an object or a regular expression, we need to
+ // build a matcher, instead of just doing an equality comparision.
+ if ((_exprElt.type() == mongo::Object) || (_exprElt.type() == mongo::RegEx)) {
+ if (_exprElt.type() == Object) {
+ _exprObj = _exprElt.embeddedObject();
- // Values to be removed.
- std::vector<mb::Element> elementsToRemove;
+ // If not is not a query operator, then it is a primitive.
+ _matcherOnPrimitive = (_exprObj.firstElement().getGtLtOp() != 0);
- // True if this update is a no-op
- bool noOp;
- };
+ // If the object is primitive then wrap it up into an object.
+ if (_matcherOnPrimitive)
+ _exprObj = BSON("" << _exprObj);
+ } else {
+ // For a regex, we also need to wrap and treat like a primitive.
+ _matcherOnPrimitive = true;
+ _exprObj = _exprElt.wrap("");
+ }
- ModifierPull::ModifierPull()
- : ModifierInterface()
- , _fieldRef()
- , _posDollar(0)
- , _exprElt()
- , _exprObj()
- , _matchExpr()
- , _matcherOnPrimitive(false)
- , _preparedState() {
- }
+ // Build the matcher around the object we built above. Currently, we do not allow
+ // $pull operations to contain $where clauses, so preserving this behaviour.
+ StatusWithMatchExpression parseResult =
+ MatchExpressionParser::parse(_exprObj, MatchExpressionParser::WhereCallback());
+ if (!parseResult.isOK())
+ return parseResult.getStatus();
- ModifierPull::~ModifierPull() {
+ _matchExpr.reset(parseResult.getValue());
}
- Status ModifierPull::init(const BSONElement& modExpr, const Options& opts,
- bool* positional) {
- // Perform standard field name and updateable checks.
- _fieldRef.parse(modExpr.fieldName());
- Status status = fieldchecker::isUpdatable(_fieldRef);
- if (! status.isOK()) {
- return status;
- }
-
- // If a $-positional operator was used, get the index in which it occurred
- // and ensure only one occurrence.
- size_t foundCount;
- bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount);
+ return Status::OK();
+}
- if (positional)
- *positional = foundDollar;
+Status ModifierPull::prepare(mb::Element root, StringData matchedField, ExecInfo* execInfo) {
+ _preparedState.reset(new PreparedState(root.getDocument()));
- if (foundDollar && foundCount > 1) {
+ // If we have a $-positional field, it is time to bind it to an actual field part.
+ if (_posDollar) {
+ if (matchedField.empty()) {
return Status(ErrorCodes::BadValue,
- str::stream() << "Too many positional (i.e. '$') elements found in path '"
- << _fieldRef.dottedField() << "'");
+ str::stream() << "The positional operator did not find the match "
+ "needed from the query. Unexpanded update: "
+ << _fieldRef.dottedField());
}
+ _fieldRef.setPart(_posDollar, matchedField);
+ }
- _exprElt = modExpr;
-
- // If the element in the mod is actually an object or a regular expression, we need to
- // build a matcher, instead of just doing an equality comparision.
- if ((_exprElt.type() == mongo::Object) || (_exprElt.type() == mongo::RegEx)) {
- if (_exprElt.type() == Object) {
- _exprObj = _exprElt.embeddedObject();
-
- // If not is not a query operator, then it is a primitive.
- _matcherOnPrimitive = (_exprObj.firstElement().getGtLtOp() != 0);
-
- // If the object is primitive then wrap it up into an object.
- if (_matcherOnPrimitive)
- _exprObj = BSON( "" << _exprObj );
- }
- else {
- // For a regex, we also need to wrap and treat like a primitive.
- _matcherOnPrimitive = true;
- _exprObj = _exprElt.wrap("");
- }
-
- // Build the matcher around the object we built above. Currently, we do not allow
- // $pull operations to contain $where clauses, so preserving this behaviour.
- StatusWithMatchExpression parseResult =
- MatchExpressionParser::parse(_exprObj,
- MatchExpressionParser::WhereCallback());
- if (!parseResult.isOK())
- return parseResult.getStatus();
+ // Locate the field name in 'root'.
+ Status status = pathsupport::findLongestPrefix(
+ _fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound);
+
+ // FindLongestPrefix may say the path does not exist at all, which is fine here, or
+ // that the path was not viable or otherwise wrong, in which case, the mod cannot
+ // proceed.
+ if (status.code() == ErrorCodes::NonExistentPath) {
+ _preparedState->elemFound = root.getDocument().end();
+ } else if (!status.isOK()) {
+ return status;
+ }
- _matchExpr.reset(parseResult.getValue());
- }
+ // We register interest in the field name. The driver needs this info to sort out if
+ // there is any conflict among mods.
+ execInfo->fieldRef[0] = &_fieldRef;
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // If no target element exists, then there is nothing to do here.
+ _preparedState->noOp = execInfo->noOp = true;
return Status::OK();
}
- Status ModifierPull::prepare(mb::Element root,
- StringData matchedField,
- ExecInfo* execInfo) {
-
- _preparedState.reset(new PreparedState(root.getDocument()));
-
- // If we have a $-positional field, it is time to bind it to an actual field part.
- if (_posDollar) {
- if (matchedField.empty()) {
- return Status(ErrorCodes::BadValue,
- str::stream() << "The positional operator did not find the match "
- "needed from the query. Unexpanded update: "
- << _fieldRef.dottedField());
- }
- _fieldRef.setPart(_posDollar, matchedField);
- }
-
- // Locate the field name in 'root'.
- Status status = pathsupport::findLongestPrefix(_fieldRef,
- root,
- &_preparedState->idxFound,
- &_preparedState->elemFound);
-
- // FindLongestPrefix may say the path does not exist at all, which is fine here, or
- // that the path was not viable or otherwise wrong, in which case, the mod cannot
- // proceed.
- if (status.code() == ErrorCodes::NonExistentPath) {
- _preparedState->elemFound = root.getDocument().end();
- } else if (!status.isOK()) {
- return status;
- }
-
- // We register interest in the field name. The driver needs this info to sort out if
- // there is any conflict among mods.
- execInfo->fieldRef[0] = &_fieldRef;
-
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
- // If no target element exists, then there is nothing to do here.
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
-
- // This operation only applies to arrays
- if (_preparedState->elemFound.getType() != mongo::Array)
- return Status(
- ErrorCodes::BadValue,
- "Cannot apply $pull to a non-array value");
-
- // If the array is empty, there is nothing to pull, so this is a noop.
- if (!_preparedState->elemFound.hasChildren()) {
- _preparedState->noOp = execInfo->noOp = true;
- return Status::OK();
- }
+ // This operation only applies to arrays
+ if (_preparedState->elemFound.getType() != mongo::Array)
+ return Status(ErrorCodes::BadValue, "Cannot apply $pull to a non-array value");
- // Walk the values in the array
- mb::Element cursor = _preparedState->elemFound.leftChild();
- while (cursor.ok()) {
- if (isMatch(cursor))
- _preparedState->elementsToRemove.push_back(cursor);
- cursor = cursor.rightSibling();
- }
+ // If the array is empty, there is nothing to pull, so this is a noop.
+ if (!_preparedState->elemFound.hasChildren()) {
+ _preparedState->noOp = execInfo->noOp = true;
+ return Status::OK();
+ }
- // If we didn't find any elements to add, then this is a no-op, and therefore in place.
- if (_preparedState->elementsToRemove.empty()) {
- _preparedState->noOp = execInfo->noOp = true;
- }
+ // Walk the values in the array
+ mb::Element cursor = _preparedState->elemFound.leftChild();
+ while (cursor.ok()) {
+ if (isMatch(cursor))
+ _preparedState->elementsToRemove.push_back(cursor);
+ cursor = cursor.rightSibling();
+ }
- return Status::OK();
+ // If we didn't find any elements to add, then this is a no-op, and therefore in place.
+ if (_preparedState->elementsToRemove.empty()) {
+ _preparedState->noOp = execInfo->noOp = true;
}
- Status ModifierPull::apply() const {
- dassert(_preparedState->noOp == false);
+ return Status::OK();
+}
- dassert(_preparedState->elemFound.ok() &&
- _preparedState->idxFound == (_fieldRef.numParts() - 1));
+Status ModifierPull::apply() const {
+ dassert(_preparedState->noOp == false);
- std::vector<mb::Element>::const_iterator where = _preparedState->elementsToRemove.begin();
- const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToRemove.end();
- for ( ; where != end; ++where)
- const_cast<mb::Element&>(*where).remove();
+ dassert(_preparedState->elemFound.ok() &&
+ _preparedState->idxFound == (_fieldRef.numParts() - 1));
- return Status::OK();
- }
+ std::vector<mb::Element>::const_iterator where = _preparedState->elementsToRemove.begin();
+ const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToRemove.end();
+ for (; where != end; ++where)
+ const_cast<mb::Element&>(*where).remove();
- Status ModifierPull::log(LogBuilder* logBuilder) const {
+ return Status::OK();
+}
- mb::Document& doc = logBuilder->getDocument();
+Status ModifierPull::log(LogBuilder* logBuilder) const {
+ mb::Document& doc = logBuilder->getDocument();
- if (!_preparedState->elemFound.ok() ||
- _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) {
+ // If we didn't find the element that we wanted to pull from, we log an unset for
+ // that element.
+ return logBuilder->addToUnsets(_fieldRef.dottedField());
- // If we didn't find the element that we wanted to pull from, we log an unset for
- // that element.
- return logBuilder->addToUnsets(_fieldRef.dottedField());
+ } else {
+ // TODO: This is copied more or less identically from $push. As a result, it copies the
+ // behavior in $push that relies on 'apply' having been called unless this is a no-op.
- } else {
+ // TODO We can log just a positional unset in several cases. For now, let's just log
+ // the full resulting array.
- // TODO: This is copied more or less identically from $push. As a result, it copies the
- // behavior in $push that relies on 'apply' having been called unless this is a no-op.
+ // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
+ // 'logRoot'. We start by creating the {$set: ...} Element.
- // TODO We can log just a positional unset in several cases. For now, let's just log
- // the full resulting array.
+ // Then we create the {<fieldname>:[]} Element, that is, an empty array.
+ mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
+ if (!logElement.ok()) {
+ return Status(ErrorCodes::InternalError, "cannot create details for $pull mod");
+ }
- // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under
- // 'logRoot'. We start by creating the {$set: ...} Element.
+ mb::Element curr = _preparedState->elemFound.leftChild();
+ while (curr.ok()) {
+ dassert(curr.hasValue());
- // Then we create the {<fieldname>:[]} Element, that is, an empty array.
- mb::Element logElement = doc.makeElementArray(_fieldRef.dottedField());
- if (!logElement.ok()) {
- return Status(ErrorCodes::InternalError, "cannot create details for $pull mod");
+ // We need to copy each array entry from the resulting document to the log
+ // document.
+ mb::Element currCopy = doc.makeElementWithNewFieldName(StringData(), curr.getValue());
+ if (!currCopy.ok()) {
+ return Status(ErrorCodes::InternalError, "could create copy element");
}
-
- mb::Element curr = _preparedState->elemFound.leftChild();
- while (curr.ok()) {
-
- dassert(curr.hasValue());
-
- // We need to copy each array entry from the resulting document to the log
- // document.
- mb::Element currCopy = doc.makeElementWithNewFieldName(
- StringData(),
- curr.getValue());
- if (!currCopy.ok()) {
- return Status(ErrorCodes::InternalError, "could create copy element");
- }
- Status status = logElement.pushBack(currCopy);
- if (!status.isOK()) {
- return Status(ErrorCodes::BadValue, "could not append entry for $pull log");
- }
- curr = curr.rightSibling();
+ Status status = logElement.pushBack(currCopy);
+ if (!status.isOK()) {
+ return Status(ErrorCodes::BadValue, "could not append entry for $pull log");
}
-
- return logBuilder->addToSets(logElement);
+ curr = curr.rightSibling();
}
- }
- bool ModifierPull::isMatch(mutablebson::ConstElement element) {
+ return logBuilder->addToSets(logElement);
+ }
+}
- // TODO: We are assuming that 'element' hasValue is true. That might be OK if the
- // conflict detection logic will prevent us from ever seeing a deserialized element,
- // but are we sure about that?
+bool ModifierPull::isMatch(mutablebson::ConstElement element) {
+ // TODO: We are assuming that 'element' hasValue is true. That might be OK if the
+ // conflict detection logic will prevent us from ever seeing a deserialized element,
+ // but are we sure about that?
- dassert(element.hasValue());
+ dassert(element.hasValue());
- if (!_matchExpr)
- return (element.compareWithBSONElement(_exprElt, false) == 0);
+ if (!_matchExpr)
+ return (element.compareWithBSONElement(_exprElt, false) == 0);
- if (_matcherOnPrimitive) {
- // TODO: This is kinda slow.
- BSONObj candidate = element.getValue().wrap("");
- return _matchExpr->matchesBSON(candidate);
- }
+ if (_matcherOnPrimitive) {
+ // TODO: This is kinda slow.
+ BSONObj candidate = element.getValue().wrap("");
+ return _matchExpr->matchesBSON(candidate);
+ }
- if (element.getType() != Object)
- return false;
+ if (element.getType() != Object)
+ return false;
- return _matchExpr->matchesBSON(element.getValueObject());
- }
+ return _matchExpr->matchesBSON(element.getValueObject());
+}
-} // namespace mongo
+} // namespace mongo