diff options
Diffstat (limited to 'src/mongo/db/ops/modifier_pull.cpp')
-rw-r--r-- | src/mongo/db/ops/modifier_pull.cpp | 399 |
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 |