summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops/modifier_add_to_set.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops/modifier_add_to_set.cpp')
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.cpp572
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