diff options
Diffstat (limited to 'src/mongo/db/ops/modifier_add_to_set.cpp')
-rw-r--r-- | src/mongo/db/ops/modifier_add_to_set.cpp | 572 |
1 files changed, 269 insertions, 303 deletions
diff --git a/src/mongo/db/ops/modifier_add_to_set.cpp b/src/mongo/db/ops/modifier_add_to_set.cpp index 381d06632e3..383991c2b1f 100644 --- a/src/mongo/db/ops/modifier_add_to_set.cpp +++ b/src/mongo/db/ops/modifier_add_to_set.cpp @@ -37,155 +37,145 @@ namespace mongo { - namespace mb = mutablebson; - namespace str = mongoutils::str; - - namespace { - - template<typename Ordering, typename Equality> - void deduplicate(mb::Element parent, Ordering comp, Equality equal) { - - // First, build a vector of the children. - std::vector<mb::Element> children; - mb::Element current = parent.leftChild(); - while (current.ok()) { - children.push_back(current); - current = current.rightSibling(); - } +namespace mb = mutablebson; +namespace str = mongoutils::str; + +namespace { + +template <typename Ordering, typename Equality> +void deduplicate(mb::Element parent, Ordering comp, Equality equal) { + // First, build a vector of the children. + std::vector<mb::Element> children; + mb::Element current = parent.leftChild(); + while (current.ok()) { + children.push_back(current); + current = current.rightSibling(); + } - // Then, sort the child vector with our comparator. - std::sort(children.begin(), children.end(), comp); + // Then, sort the child vector with our comparator. + std::sort(children.begin(), children.end(), comp); - // Next, remove duplicates by walking the vector. - std::vector<mb::Element>::iterator where = children.begin(); - const std::vector<mb::Element>::iterator end = children.end(); + // Next, remove duplicates by walking the vector. + std::vector<mb::Element>::iterator where = children.begin(); + const std::vector<mb::Element>::iterator end = children.end(); - while( where != end ) { - std::vector<mb::Element>::iterator next = where; ++next; - while (next != end && equal(*where, *next)) { - next->remove(); - ++next; - } - where = next; - } + while (where != end) { + std::vector<mb::Element>::iterator next = where; + ++next; + while (next != end && equal(*where, *next)) { + next->remove(); + ++next; } + where = next; + } +} - } // namespace +} // namespace - struct ModifierAddToSet::PreparedState { +struct ModifierAddToSet::PreparedState { + PreparedState(mb::Document& doc) + : doc(doc), + idxFound(0), + elemFound(doc.end()), + addAll(false), + elementsToAdd(), + noOp(false) {} - PreparedState(mb::Document& doc) - : doc(doc) - , idxFound(0) - , elemFound(doc.end()) - , addAll(false) - , elementsToAdd() - , noOp(false) { - } + // Document that is going to be changed. + mb::Document& doc; - // Document that is going to be changed. - mb::Document& doc; + // Index in _fieldRef for which an Element exist in the document. + size_t idxFound; - // Index in _fieldRef for which an Element exist in the document. - size_t idxFound; + // Element corresponding to _fieldRef[0.._idxFound]. + mb::Element elemFound; - // Element corresponding to _fieldRef[0.._idxFound]. - mb::Element elemFound; + // Are we adding all of the $each elements, or just a subset? + bool addAll; - // Are we adding all of the $each elements, or just a subset? - bool addAll; + // Values to be applied. + std::vector<mb::Element> elementsToAdd; - // Values to be applied. - std::vector<mb::Element> elementsToAdd; + // True if this update is a no-op + bool noOp; +}; - // True if this update is a no-op - bool noOp; - }; +ModifierAddToSet::ModifierAddToSet() + : ModifierInterface(), _fieldRef(), _posDollar(0), _valDoc(), _val(_valDoc.end()) {} - ModifierAddToSet::ModifierAddToSet() - : ModifierInterface () - , _fieldRef() - , _posDollar(0) - , _valDoc() - , _val(_valDoc.end()) { - } +ModifierAddToSet::~ModifierAddToSet() {} - ModifierAddToSet::~ModifierAddToSet() { +Status ModifierAddToSet::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; } - Status ModifierAddToSet::init(const BSONElement& modExpr, const Options& opts, - bool* positional) { + // 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); - // Perform standard field name and updateable checks. - _fieldRef.parse(modExpr.fieldName()); - Status status = fieldchecker::isUpdatable(_fieldRef); - if (! status.isOK()) { - return status; - } + if (positional) + *positional = foundDollar; - // 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); + if (foundDollar && foundCount > 1) { + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); + } - if (positional) - *positional = foundDollar; + // TODO: The driver could potentially do this re-writing. - if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, - str::stream() << "Too many positional (i.e. '$') elements found in path '" - << _fieldRef.dottedField() << "'"); - } - - // TODO: The driver could potentially do this re-writing. - - // If the type of the value is 'Object', we might be dealing with a $each. See if that - // is the case. - if (modExpr.type() == mongo::Object) { - BSONElement modExprObjPayload = modExpr.embeddedObject().firstElement(); - if (!modExprObjPayload.eoo() && StringData(modExprObjPayload.fieldName()) == "$each") { - // It is a $each. Verify that the payload is an array as is required for $each, - // set our flag, and store the array as our value. - if (modExprObjPayload.type() != mongo::Array) { - return Status(ErrorCodes::BadValue, - str::stream() << "The argument to $each in $addToSet must " - "be an array but it was of type " - << typeName(modExprObjPayload.type())); - } - - status = _valDoc.root().appendElement(modExprObjPayload); - if (!status.isOK()) - return status; - - _val = _valDoc.root().leftChild(); - - deduplicate(_val, mb::woLess(false), mb::woEqual(false)); + // If the type of the value is 'Object', we might be dealing with a $each. See if that + // is the case. + if (modExpr.type() == mongo::Object) { + BSONElement modExprObjPayload = modExpr.embeddedObject().firstElement(); + if (!modExprObjPayload.eoo() && StringData(modExprObjPayload.fieldName()) == "$each") { + // It is a $each. Verify that the payload is an array as is required for $each, + // set our flag, and store the array as our value. + if (modExprObjPayload.type() != mongo::Array) { + return Status(ErrorCodes::BadValue, + str::stream() << "The argument to $each in $addToSet must " + "be an array but it was of type " + << typeName(modExprObjPayload.type())); } - } - - // If this wasn't an 'each', turn it into one. No need to sort or de-dup since we only - // have one element. - if (_val == _valDoc.end()) { - mb::Element each = _valDoc.makeElementArray("$each"); - status = each.appendElement(modExpr); + status = _valDoc.root().appendElement(modExprObjPayload); if (!status.isOK()) return status; - status = _valDoc.root().pushBack(each); - if (!status.isOK()) - return status; + _val = _valDoc.root().leftChild(); - _val = each; + deduplicate(_val, mb::woLess(false), mb::woEqual(false)); } + } - // Check if no invalid data (such as fields with '$'s) are being used in the $each - // clause. - mb::ConstElement valCursor = _val.leftChild(); - while (valCursor.ok()) { - const BSONType type = valCursor.getType(); - dassert(valCursor.hasValue()); - switch(type) { + // If this wasn't an 'each', turn it into one. No need to sort or de-dup since we only + // have one element. + if (_val == _valDoc.end()) { + mb::Element each = _valDoc.makeElementArray("$each"); + + status = each.appendElement(modExpr); + if (!status.isOK()) + return status; + + status = _valDoc.root().pushBack(each); + if (!status.isOK()) + return status; + + _val = each; + } + + // Check if no invalid data (such as fields with '$'s) are being used in the $each + // clause. + mb::ConstElement valCursor = _val.leftChild(); + while (valCursor.ok()) { + const BSONType type = valCursor.getType(); + dassert(valCursor.hasValue()); + switch (type) { case mongo::Object: { Status s = valCursor.getValueObject().storageValidEmbedded(); if (!s.isOK()) @@ -202,231 +192,207 @@ namespace mongo { } default: break; - } - - valCursor = valCursor.rightSibling(); } - return Status::OK(); + valCursor = valCursor.rightSibling(); } - Status ModifierAddToSet::prepare(mb::Element root, - StringData matchedField, - ExecInfo* execInfo) { + return Status::OK(); +} - _preparedState.reset(new PreparedState(root.getDocument())); +Status ModifierAddToSet::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; + // 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); + } - // 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; - - // - // in-place and no-op logic - // - - // If the field path is not fully present, then this mod cannot be in place, nor is a - // noOp. - if (!_preparedState->elemFound.ok() || - _preparedState->idxFound < (_fieldRef.numParts() - 1)) { - // If no target element exists, we will simply be creating a new array. - _preparedState->addAll = true; - return Status::OK(); - } + // 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; + } - // This operation only applies to arrays - if (_preparedState->elemFound.getType() != mongo::Array) { - mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); - return Status( - ErrorCodes::BadValue, - str::stream() << "Cannot apply $addToSet to a non-array field. Field named '" - << _preparedState->elemFound.getFieldName() - << "' has a non-array type " - << typeName(_preparedState->elemFound.getType()) - << " in the document " - << idElem.toString()); - } + // 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 the array is empty, then we don't need to check anything: all of the values are - // going to be added. - if (!_preparedState->elemFound.hasChildren()) { - _preparedState->addAll = true; - return Status::OK(); - } + // + // in-place and no-op logic + // - // For each value in the $each clause, compare it against the values in the array. If - // the element is not present, record it as one to add. - mb::Element eachIter = _val.leftChild(); - while (eachIter.ok()) { - mb::Element where = mb::findElement( - _preparedState->elemFound.leftChild(), - mb::woEqualTo(eachIter, false)); - if (!where.ok()) { - // The element was not found. Record the element from $each as one to be added. - _preparedState->elementsToAdd.push_back(eachIter); - } - eachIter = eachIter.rightSibling(); - } + // If the field path is not fully present, then this mod cannot be in place, nor is a + // noOp. + if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { + // If no target element exists, we will simply be creating a new array. + _preparedState->addAll = true; + return Status::OK(); + } - // If we didn't find any elements to add, then this is a no-op. - if (_preparedState->elementsToAdd.empty()) { - _preparedState->noOp = execInfo->noOp = true; - } + // This operation only applies to arrays + if (_preparedState->elemFound.getType() != mongo::Array) { + mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); + return Status(ErrorCodes::BadValue, + str::stream() + << "Cannot apply $addToSet to a non-array field. Field named '" + << _preparedState->elemFound.getFieldName() << "' has a non-array type " + << typeName(_preparedState->elemFound.getType()) << " in the document " + << idElem.toString()); + } + // If the array is empty, then we don't need to check anything: all of the values are + // going to be added. + if (!_preparedState->elemFound.hasChildren()) { + _preparedState->addAll = true; return Status::OK(); } - Status ModifierAddToSet::apply() const { - dassert(_preparedState->noOp == false); + // For each value in the $each clause, compare it against the values in the array. If + // the element is not present, record it as one to add. + mb::Element eachIter = _val.leftChild(); + while (eachIter.ok()) { + mb::Element where = + mb::findElement(_preparedState->elemFound.leftChild(), mb::woEqualTo(eachIter, false)); + if (!where.ok()) { + // The element was not found. Record the element from $each as one to be added. + _preparedState->elementsToAdd.push_back(eachIter); + } + eachIter = eachIter.rightSibling(); + } - // TODO: The contents of this block are lifted directly from $push. + // If we didn't find any elements to add, then this is a no-op. + if (_preparedState->elementsToAdd.empty()) { + _preparedState->noOp = execInfo->noOp = true; + } - // If the array field is not there, create it as an array and attach it to the - // document. - if (!_preparedState->elemFound.ok() || - _preparedState->idxFound < (_fieldRef.numParts() - 1)) { - - // Creates the array element - mb::Document& doc = _preparedState->doc; - StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1); - mb::Element baseArray = doc.makeElementArray(lastPart); - if (!baseArray.ok()) { - return Status(ErrorCodes::InternalError, "can't create new base array"); - } + return Status::OK(); +} - // Now, we can be in two cases here, as far as attaching the element being set - // goes: (a) none of the parts in the element's path exist, or (b) some parts of - // the path exist but not all. - if (!_preparedState->elemFound.ok()) { - _preparedState->elemFound = doc.root(); - _preparedState->idxFound = 0; - } - else { - _preparedState->idxFound++; - } +Status ModifierAddToSet::apply() const { + dassert(_preparedState->noOp == false); - // createPathAt() will complete the path and attach 'elemToSet' at the end of it. - Status status = pathsupport::createPathAt(_fieldRef, - _preparedState->idxFound, - _preparedState->elemFound, - baseArray); - if (!status.isOK()) { - return status; - } + // TODO: The contents of this block are lifted directly from $push. - // Point to the base array just created. The subsequent code expects it to exist - // already. - _preparedState->elemFound = baseArray; + // If the array field is not there, create it as an array and attach it to the + // document. + if (!_preparedState->elemFound.ok() || _preparedState->idxFound < (_fieldRef.numParts() - 1)) { + // Creates the array element + mb::Document& doc = _preparedState->doc; + StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1); + mb::Element baseArray = doc.makeElementArray(lastPart); + if (!baseArray.ok()) { + return Status(ErrorCodes::InternalError, "can't create new base array"); } - if (_preparedState->addAll) { - - // If we are adding all the values, we can just walk over _val; + // Now, we can be in two cases here, as far as attaching the element being set + // goes: (a) none of the parts in the element's path exist, or (b) some parts of + // the path exist but not all. + if (!_preparedState->elemFound.ok()) { + _preparedState->elemFound = doc.root(); + _preparedState->idxFound = 0; + } else { + _preparedState->idxFound++; + } - mb::Element where = _val.leftChild(); - while (where.ok()) { + // createPathAt() will complete the path and attach 'elemToSet' at the end of it. + Status status = pathsupport::createPathAt( + _fieldRef, _preparedState->idxFound, _preparedState->elemFound, baseArray); + if (!status.isOK()) { + return status; + } - dassert(where.hasValue()); + // Point to the base array just created. The subsequent code expects it to exist + // already. + _preparedState->elemFound = baseArray; + } - mb::Element toAdd = _preparedState->doc.makeElement(where.getValue()); - Status status = _preparedState->elemFound.pushBack(toAdd); - if (!status.isOK()) - return status; + if (_preparedState->addAll) { + // If we are adding all the values, we can just walk over _val; - where = where.rightSibling(); - } + mb::Element where = _val.leftChild(); + while (where.ok()) { + dassert(where.hasValue()); - } else { + mb::Element toAdd = _preparedState->doc.makeElement(where.getValue()); + Status status = _preparedState->elemFound.pushBack(toAdd); + if (!status.isOK()) + return status; - // Otherwise, we aren't adding all the values, and we need to add exactly those - // elements that were found to be missing during our scan in prepare. - std::vector<mb::Element>::const_iterator where = - _preparedState->elementsToAdd.begin(); + where = where.rightSibling(); + } - const std::vector<mb::Element>::const_iterator end = - _preparedState->elementsToAdd.end(); + } else { + // Otherwise, we aren't adding all the values, and we need to add exactly those + // elements that were found to be missing during our scan in prepare. + std::vector<mb::Element>::const_iterator where = _preparedState->elementsToAdd.begin(); - for ( ; where != end; ++where) { + const std::vector<mb::Element>::const_iterator end = _preparedState->elementsToAdd.end(); - dassert(where->hasValue()); + for (; where != end; ++where) { + dassert(where->hasValue()); - mb::Element toAdd = _preparedState->doc.makeElement(where->getValue()); - Status status = _preparedState->elemFound.pushBack(toAdd); - if (!status.isOK()) - return status; - } + mb::Element toAdd = _preparedState->doc.makeElement(where->getValue()); + Status status = _preparedState->elemFound.pushBack(toAdd); + if (!status.isOK()) + return status; } - - return Status::OK(); } - Status ModifierAddToSet::log(LogBuilder* logBuilder) const { + return Status::OK(); +} - // 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. +Status ModifierAddToSet::log(LogBuilder* logBuilder) const { + // 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. - // TODO We can log just a positional set in several cases. For now, let's just log the - // full resulting array. + // TODO We can log just a positional set in several cases. For now, let's just log the + // full resulting array. - // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under - // 'logRoot'. We start by creating the {$set: ...} Element. - mb::Document& doc = logBuilder->getDocument(); + // We'd like to create an entry such as {$set: {<fieldname>: [<resulting aray>]}} under + // 'logRoot'. We start by creating the {$set: ...} Element. + mb::Document& doc = logBuilder->getDocument(); - // 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 $addToSet mod"); - } - - // Fill up the empty array. - mb::Element curr = _preparedState->elemFound.leftChild(); - while (curr.ok()) { + // 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 $addToSet mod"); + } - dassert(curr.hasValue()); + // Fill up the empty array. + 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, - str::stream() << "Could not append entry for $addToSet oplog entry." - << "Underlying cause: " << status.toString()); - } - curr = curr.rightSibling(); + // 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"); } - - return logBuilder->addToSets(logElement); + Status status = logElement.pushBack(currCopy); + if (!status.isOK()) { + return Status(ErrorCodes::BadValue, + str::stream() << "Could not append entry for $addToSet oplog entry." + << "Underlying cause: " << status.toString()); + } + curr = curr.rightSibling(); } -} // namespace mongo + return logBuilder->addToSets(logElement); +} + +} // namespace mongo |