Status ModifierUnset::log(mutablebson::Element logRoot) const { // We'd like to create an entry such as {$unset: {<fieldname>: 1}} under 'logRoot'. // We start by creating the {$unset: ...} Element. mutablebson::Document& doc = logRoot.getDocument(); mutablebson::Element unsetElement = doc.makeElementObject("$unset"); if (!unsetElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create log entry for $unset mod"); } // Then we create the {<fieldname>: <value>} Element. Note that <fieldname> must be a // dotted field, and not only the last part of that field. The rationale here is that // somoene picking up this log entry -- e.g., a secondary -- must be capable of doing // the same path find/creation that was done in the previous calls here. mutablebson::Element logElement = doc.makeElementInt(_fieldRef.dottedField(), 1); if (!logElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create log details for $unset mod"); } // Now, we attach the {<fieldname>: `} Element under the {$unset: ...} one. Status status = unsetElement.pushBack(logElement); if (!status.isOK()) { return status; } // And attach the result under the 'logRoot' Element provided. return logRoot.pushBack(unsetElement); }
Status ModifierInc::log(mutablebson::Element logRoot) const { // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'. // We start by creating the {$set: ...} Element. mutablebson::Document& doc = logRoot.getDocument(); mutablebson::Element setElement = doc.makeElementObject("$set"); if (!setElement.ok()) { return Status(ErrorCodes::InternalError, "cannot append log entry for $set mod"); } // Then we create the {<fieldname>: <value>} Element. mutablebson::Element logElement = doc.makeElementSafeNum( _fieldRef.dottedField(), _preparedState->newValue); if (!logElement.ok()) { return Status(ErrorCodes::InternalError, "cannot append details for $set mod"); } // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} one. Status status = setElement.pushBack(logElement); if (!status.isOK()) { return status; } // And attach the result under the 'logRoot' Element provided. return logRoot.pushBack(setElement); }
Status ModifierSet::log(mutablebson::Element logRoot) const { // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'. // We start by creating the {$set: ...} Element. mutablebson::Document& doc = logRoot.getDocument(); mutablebson::Element setElement = doc.makeElementObject("$set"); if (!setElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create log entry for $set mod"); } // Then we create the {<fieldname>: <value>} Element. Note that we log the mod with a // dotted field, if it was applied over a dotted field. The rationale is that the // secondary may be in a different state than the primary and thus make different // decisions about creating the intermediate path in _fieldRef or not. mutablebson::Element logElement = doc.makeElementWithNewFieldName(_fieldRef.dottedField(), _val); if (!logElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create details for $set mod"); } // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} one. Status status = setElement.pushBack(logElement); if (!status.isOK()) { return status; } // And attach the result under the 'logRoot' Element provided. return logRoot.pushBack(setElement); }
Status ModifierPush::prepare(mutablebson::Element root, const 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'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remainin path, if missing, will // be created during the apply. 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()) { const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts()-1)); // If the path exists, we require the target field to be already an // array. if (destExists && _preparedState->elemFound.getType() != Array) { mb::Element idElem = mb::findFirstChildNamed(root, "_id"); return Status(ErrorCodes::BadValue, str::stream() << "The field '" << _fieldRef.dottedField() << "'" << " must be an array but is of type " << typeName(_preparedState->elemFound.getType()) << " in document {" << idElem.toString() << "}"); } } else { 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; return Status::OK(); }
Status LogBuilder::addToSetsWithNewFieldName(StringData name, const mutablebson::Element val) { mutablebson::Element elemToSet = _logRoot.getDocument().makeElementWithNewFieldName(name, val); if (!elemToSet.ok()) return Status(ErrorCodes::InternalError, str::stream() << "Could not create new '" << name << "' element from existing element '" << val.getFieldName() << "' of type " << typeName(val.getType())); return addToSets(elemToSet); }
Status ModifierAddToSet::log(mb::Element logRoot) 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. // 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 = logRoot.getDocument(); mb::Element setElement = doc.makeElementObject("$set"); if (!setElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create log entry for $addToSet mod"); } // 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()) { 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 $addToSet log"); } curr = curr.rightSibling(); } // Now, we attach the {<fieldname>: [<filled array>]} Element under the {$set: ...} // one. Status status = setElement.pushBack(logElement); if (!status.isOK()) { return status; } // And attach the result under the 'logRoot' Element provided. return logRoot.pushBack(setElement); }
Status ModifierObjectReplace::prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(&root.getDocument())); // objectSize checked by binaryEqual (optimization) BSONObj objOld = root.getDocument().getObject(); if (objOld.binaryEqual(_val)) { _preparedState->noOp = true; execInfo->noOp = true; } return Status::OK(); }
Status ModifierObjectReplace::prepare(mutablebson::Element root, const StringData& matchedField, ExecInfo* execInfo) { _preparedState.reset(new PreparedState(&root.getDocument())); return Status::OK(); }
PreparedState(mutablebson::Element root) : doc(root.getDocument()) , fromElemFound(doc.end()) , toIdxFound(0) , toElemFound(doc.end()) , applyCalled(false){ }
Status ModifierCurrentDate::prepare(mutablebson::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 (_pathReplacementPosition) { if (matchedField.empty()) { return Status(ErrorCodes::BadValue, str::stream() << "The positional operator did not find the match " "needed from the query. Unexpanded update: " << _updatePath.dottedField()); } _updatePath.setPart(_pathReplacementPosition, matchedField); } // Locate the field name in 'root'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remaining path, if missing, will // be created during the apply. Status status = pathsupport::findLongestPrefix(_updatePath, 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] = &_updatePath; return Status::OK(); }
Status ModifierUnset::prepare(mutablebson::Element root, const 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()); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // Locate the field name in 'root'. Note that if we don't have the full path in the // doc, there isn't anything to unset, really. Status status = pathsupport::findLongestPrefix(_fieldRef, root, &_preparedState->idxFound, &_preparedState->elemFound); if (!status.isOK() || _preparedState->idxFound != (_fieldRef.numParts() -1)) { execInfo->noOp = _preparedState->noOp = true; execInfo->fieldRef[0] = &_fieldRef; return Status::OK(); } // If there is indeed something to unset, we register so, along with the interest in // the field name. The driver needs this info to sort out if there is any conflict // among mods. execInfo->fieldRef[0] = &_fieldRef; // The only way for an $unset to be inplace is for its target field to be the last one // of the object. That is, it is always the right child on its paths. The current // rationale is that there should be no holes in a BSONObj and, to be in place, no // field boundaries must change. // // TODO: // mutablebson::Element curr = _preparedState->elemFound; // while (curr.ok()) { // if (curr.rightSibling().ok()) { // } // curr = curr.parent(); // } return Status::OK(); }
Status AuthorizationManager::getBSONForPrivileges(const PrivilegeVector& privileges, mutablebson::Element resultArray) { for (PrivilegeVector::const_iterator it = privileges.begin(); it != privileges.end(); ++it) { std::string errmsg; ParsedPrivilege privilege; if (!ParsedPrivilege::privilegeToParsedPrivilege(*it, &privilege, &errmsg)) { return Status(ErrorCodes::BadValue, errmsg); } resultArray.appendObject("privileges", privilege.toBSON()); } return Status::OK(); }
Status ModifierObjectReplace::log(mutablebson::Element logRoot) const { // We'd like to create an entry such as {<object replacement>} under 'logRoot'. mutablebson::Document& doc = logRoot.getDocument(); BSONObjIterator it(_val); while (it.more()) { BSONElement elem = it.next(); Status status = doc.root().appendElement(elem); if (!status.isOK()) { return status; } } return Status::OK(); }
Status AuthorizationManager::getBSONForRole(RoleGraph* graph, const RoleName& roleName, mutablebson::Element result) { if (!graph->roleExists(roleName)) { return Status(ErrorCodes::RoleNotFound, mongoutils::str::stream() << roleName.getFullName() << "does not name an existing role"); } std::string id = mongoutils::str::stream() << roleName.getDB() << "." << roleName.getRole(); result.appendString("_id", id); result.appendString(ROLE_NAME_FIELD_NAME, roleName.getRole()); result.appendString(ROLE_DB_FIELD_NAME, roleName.getDB()); // Build privileges array mutablebson::Element privilegesArrayElement = result.getDocument().makeElementArray("privileges"); result.pushBack(privilegesArrayElement); const PrivilegeVector& privileges = graph->getDirectPrivileges(roleName); Status status = getBSONForPrivileges(privileges, privilegesArrayElement); if (!status.isOK()) { return status; } // Build roles array mutablebson::Element rolesArrayElement = result.getDocument().makeElementArray("roles"); result.pushBack(rolesArrayElement); for (RoleNameIterator roles = graph->getDirectSubordinates(roleName); roles.more(); roles.next()) { const RoleName& subRole = roles.get(); mutablebson::Element roleObj = result.getDocument().makeElementObject(""); roleObj.appendString(ROLE_NAME_FIELD_NAME, subRole.getRole()); roleObj.appendString(ROLE_DB_FIELD_NAME, subRole.getDB()); rolesArrayElement.pushBack(roleObj); } return Status::OK(); }
Status ModifierAddToSet::prepare(mb::Element root, const 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, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // 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; // // 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 < static_cast<int32_t>(_fieldRef.numParts() - 1)) { // If no target element exists, we will simply be creating a new array. _preparedState->addAll = true; return Status::OK(); } // This operation only applies to arrays if (_preparedState->elemFound.getType() != mongo::Array) return Status( ErrorCodes::BadValue, "Cannot apply $addToSet to a non-array value"); // 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(); } // 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 we didn't find any elements to add, then this is a no-op, and therefore in place. if (_preparedState->elementsToAdd.empty()) { _preparedState->noOp = execInfo->noOp = true; execInfo->inPlace = true; } return Status::OK(); }
Status ModifierInc::prepare(mutablebson::Element root, const 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, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // Locate the field name in 'root'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remaining path, if missing, will // be created during the apply. 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; // Capture the value we are going to write. At this point, there may not be a value // against which to operate, so the result will be simply _val. _preparedState->newValue = _val; // // 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 < static_cast<int32_t>(_fieldRef.numParts() - 1)) { return Status::OK(); } // If the value being $inc'ed is the same as the one already in the doc, than this is a // noOp. if (!_preparedState->elemFound.isNumeric()) return Status(ErrorCodes::BadValue, "invalid attempt to increment a non-numeric field"); const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Update newValue w.r.t to the current value of the found element. _preparedState->newValue += currentValue; // If the result of the addition is invalid, we must return an error. if (!_preparedState->newValue.isValid()) return Status(ErrorCodes::BadValue, "Failed to increment current value"); // If the values are identical (same type, same value), then this is a no-op, and // therefore in-place as well. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; _preparedState->inPlace = execInfo->inPlace = true; return Status::OK(); } // If the types are the same, this can be done in place. // // TODO: Potentially, cases where $inc results in a mixed type of the same size could // be in-place as well, but we don't currently handle them. if (_preparedState->newValue.type() == currentValue.type()) { _preparedState->inPlace = execInfo->inPlace = true; } return Status::OK(); }
Status ModifierInc::prepare(mutablebson::Element root, const 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'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remaining path, if missing, will // be created during the apply. 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; // Capture the value we are going to write. At this point, there may not be a value // against which to operate, so the result will be simply _val. _preparedState->newValue = _val; // // 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)) { // For multiplication, we treat ops against missing as yielding zero. We take // advantage here of the promotion rules for SafeNum; the expression below will // always yield a zero of the same type of operand that the user provided // (e.g. double). if (_mode == MODE_MUL) _preparedState->newValue *= SafeNum(static_cast<int>(0)); return Status::OK(); } // If the value being $inc'ed is the same as the one already in the doc, than this is a // noOp. if (!_preparedState->elemFound.isNumeric()) { mb::Element idElem = mb::findFirstChildNamed(root, "_id"); return Status( ErrorCodes::BadValue, str::stream() << "Cannot apply " << (_mode == MODE_INC ? "$inc" : "$mul") << " to a value of non-numeric type. {" << idElem.toString() << "} has the field '" << _preparedState->elemFound.getFieldName() << "' of non-numeric type " << typeName(_preparedState->elemFound.getType())); } const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Update newValue w.r.t to the current value of the found element. if (_mode == MODE_INC) _preparedState->newValue += currentValue; else _preparedState->newValue *= currentValue; // If the result of the addition is invalid, we must return an error. if (!_preparedState->newValue.isValid()) { mb::Element idElem = mb::findFirstChildNamed(root, "_id"); return Status(ErrorCodes::BadValue, str::stream() << "Failed to apply $inc operations to current value (" << currentValue.debugString() << ") for document {" << idElem.toString() << "}"); } // If the values are identical (same type, same value), then this is a no-op. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } return Status::OK(); }
Status ModifierPush::prepare(mutablebson::Element root, const 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, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // Locate the field name in 'root'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remainin path, if missing, will // be created during the apply. 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()) { const bool destExists = (_preparedState->idxFound == (_fieldRef.numParts()-1)); // If the path exists, we require the target field to be already an // array. if (destExists && _preparedState->elemFound.getType() != Array) { return Status(ErrorCodes::BadValue, "can only $push into arrays"); } // If the $sort clause is being used, we require all the items in the array to be // objects themselves (as opposed to base types). This is a temporary restriction // that can be lifted once we support full sort semantics in $push. if (_sortPresent && destExists) { mutablebson::Element curr = _preparedState->elemFound.leftChild(); while (curr.ok()) { if (curr.getType() != Object) { return Status(ErrorCodes::BadValue, "$push with sort requires object arrays"); } curr = curr.rightSibling(); } } } else { 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; return Status::OK(); }
Status ModifierBit::prepare(mutablebson::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; // // 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, the value we will write is the result of applying // the operation to a zero-initialized integer element. _preparedState->newValue = apply(SafeNum(static_cast<int>(0))); return Status::OK(); } if (!_preparedState->elemFound.isIntegral()) { mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status(ErrorCodes::BadValue, str::stream() << "Cannot apply $bit to a value of non-integral type." << idElem.toString() << " has the field " << _preparedState->elemFound.getFieldName() << " of non-integer type " << typeName(_preparedState->elemFound.getType())); } const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Apply the op over the existing value and the mod value, and capture the result. _preparedState->newValue = apply(currentValue); if (!_preparedState->newValue.isValid()) { // TODO: Include list of ops, if that is easy, at some future point. return Status(ErrorCodes::BadValue, str::stream() << "Failed to apply $bit operations to current value: " << currentValue.debugString()); } // If the values are identical (same type, same value), then this is a no-op. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } return Status::OK(); }
Status ModifierPull::log(mb::Element logRoot) const { mb::Document& doc = logRoot.getDocument(); mb::Element opElement = doc.end(); mb::Element logElement = doc.end(); if (!_preparedState->elemFound.ok() || _preparedState->idxFound < static_cast<int32_t>(_fieldRef.numParts() - 1)) { // If we didn't find the element that we wanted to pull from, we log an unset for // that element. opElement = doc.makeElementObject("$unset"); if (!opElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create log entry for $pull mod"); } logElement = doc.makeElementInt(_fieldRef.dottedField(), 1); } 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. // TODO We can log just a positional unset 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. opElement = doc.makeElementObject("$set"); if (!opElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create log entry for $pull mod"); } // Then we create the {<fieldname>:[]} Element, that is, an empty array. logElement = doc.makeElementArray(_fieldRef.dottedField()); if (!logElement.ok()) { return Status(ErrorCodes::InternalError, "cannot create details for $pull mod"); } 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(); } } // Now, we attach log element under the op element. Status status = opElement.pushBack(logElement); if (!status.isOK()) { return status; } // And attach the result under the 'logRoot' Element provided by the caller. return logRoot.pushBack(opElement); }
Status ModifierPull::prepare(mb::Element root, const 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, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // 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(); } // 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 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; } return Status::OK(); }
Status ModifierBit::prepare(mutablebson::Element root, const 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, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // 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; // // 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, the value we will write is the result of applying // the operation to a zero-initialized integer element. _preparedState->newValue = apply(SafeNum(static_cast<int>(0))); return Status::OK(); } if (!_preparedState->elemFound.isIntegral()) return Status( ErrorCodes::BadValue, "Cannot apply $bit to a value of non-integral type"); const SafeNum currentValue = _preparedState->elemFound.getValueSafeNum(); // Apply the op over the existing value and the mod value, and capture the result. _preparedState->newValue = apply(currentValue); if (!_preparedState->newValue.isValid()) return Status(ErrorCodes::BadValue, "Failed to apply $bit to current value"); // If the values are identical (same type, same value), then this is a no-op. if (_preparedState->newValue.isIdentical(currentValue)) { _preparedState->noOp = execInfo->noOp = true; return Status::OK(); } return Status::OK(); }
Status findLongestPrefix(const FieldRef& prefix, mutablebson::Element root, size_t* idxFound, mutablebson::Element* elemFound) { // If root is empty or the prefix is so, there's no point in looking for a prefix. const size_t prefixSize = prefix.numParts(); if (!root.hasChildren() || prefixSize == 0) { return Status(ErrorCodes::NonExistentPath, "either the document or the path are empty"); } // Loop through prefix's parts. At each iteration, check that the part ('curr') exists // in 'root' and that the type of the previous part ('prev') allows for children. mutablebson::Element curr = root; mutablebson::Element prev = root; size_t i = 0; size_t numericPart = 0; bool viable = true; for (; i < prefixSize; i++) { // If prefix wants to reach 'curr' by applying a non-numeric index to an array // 'prev', or if 'curr' wants to traverse a leaf 'prev', then we'd be in a // non-viable path (see definition on the header file). StringData prefixPart = prefix.getPart(i); prev = curr; switch (curr.getType()) { case Object: curr = prev[prefixPart]; break; case Array: if (!isNumeric(prefixPart, &numericPart)) { viable = false; } else { curr = prev[numericPart]; } break; default: viable = false; } // If we couldn't find the next field part of the prefix in the document or if the // field part we're in constitutes a non-viable path, we can stop looking. if (!curr.ok() || !viable) { break; } } // We broke out of the loop because one of four things happened. (a) 'prefix' and // 'root' have nothing in common, (b) 'prefix' is not viable in 'root', (c) not all the // parts in 'prefix' exist in 'root', or (d) all parts do. In each case, we need to // figure out what index and Element pointer to return. if (i == 0) { return Status(ErrorCodes::NonExistentPath, "cannot find path in the document"); } else if (!viable) { *idxFound = i - 1; *elemFound = prev; return Status(ErrorCodes::PathNotViable, mongolutils::str::stream() << "cannot use the part (" << prefix.getPart(i - 1) << " of " << prefix.dottedField() << ") to traverse the element ({" << curr.toString() << "})"); } else if (curr.ok()) { *idxFound = i - 1; *elemFound = curr; return Status::OK(); } else { *idxFound = i - 1; *elemFound = prev; return Status::OK(); } }
Status createPathAt(const FieldRef& prefix, size_t idxFound, mutablebson::Element elemFound, mutablebson::Element newElem) { Status status = Status::OK(); // Sanity check that 'idxField' is an actual part. const size_t size = prefix.numParts(); if (idxFound >= size) { return Status(ErrorCodes::BadValue, "index larger than path size"); } mutablebson::Document& doc = elemFound.getDocument(); // If we are creating children under an array and a numeric index is next, then perhaps // we need padding. size_t i = idxFound; bool inArray = false; if (elemFound.getType() == mongol::Array) { size_t newIdx = 0; if (!isNumeric(prefix.getPart(idxFound), &newIdx)) { return Status(ErrorCodes::InvalidPath, "Array require numeric fields"); } status = maybePadTo(&elemFound, newIdx); if (!status.isOK()) { return status; } // If there is a next field, that would be an array element. We'd like to mark that // field because we create array elements differently than we do regular objects. if (++i < size) { inArray = true; } } // Create all the remaining parts but the last one. for (; i < size - 1; i++) { mutablebson::Element elem = doc.makeElementObject(prefix.getPart(i)); if (!elem.ok()) { return Status(ErrorCodes::InternalError, "cannot create path"); } // If this field is an array element, we wrap it in an object (because array // elements are wraped in { "N": <element> } objects. if (inArray) { // TODO pass empty StringData to makeElementObject, when that's supported. mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */); if (!arrayObj.ok()) { return Status(ErrorCodes::InternalError, "cannot create item on array"); } status = arrayObj.pushBack(elem); if (!status.isOK()) { return status; } status = elemFound.pushBack(arrayObj); if (!status.isOK()) { return status; } inArray = false; } else { status = elemFound.pushBack(elem); if (!status.isOK()) { return status; } } elemFound = elem; } // Attach the last element. Here again, if we're in a field that is an array element, // we wrap it in an object first. if (inArray) { // TODO pass empty StringData to makeElementObject, when that's supported. mutablebson::Element arrayObj = doc.makeElementObject("" /* it's an array */); if (!arrayObj.ok()) { return Status(ErrorCodes::InternalError, "cannot create item on array"); } status = arrayObj.pushBack(newElem); if (!status.isOK()) { return status; } status = elemFound.pushBack(arrayObj); if (!status.isOK()) { return status; } } else { status = elemFound.pushBack(newElem); if (!status.isOK()) { return status; } } return Status::OK(); }
Status ModifierPullAll::prepare(mutablebson::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 (_positionalPathIndex) { 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(_positionalPathIndex, matchedField); } // Locate the field name in 'root'. Note that if we don't have the full path in the // doc, there isn't anything to unset, really. Status status = pathsupport::findLongestPrefix( _fieldRef, root, &_preparedState->pathFoundIndex, &_preparedState->pathFoundElement); // Check if we didn't find the full path if (status.isOK()) { const bool destExists = (_preparedState->pathFoundIndex == (_fieldRef.numParts() - 1)); if (!destExists) { execInfo->noOp = true; } else { // If the path exists, we require the target field to be already an // array. if (_preparedState->pathFoundElement.getType() != Array) { mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status( ErrorCodes::BadValue, str::stream() << "Can only apply $pullAll to an array. " << idElem.toString() << " has the field " << _preparedState->pathFoundElement.getFieldName() << " of non-array type " << typeName(_preparedState->pathFoundElement.getType())); } // No children, nothing to do -- not an error state if (!_preparedState->pathFoundElement.hasChildren()) { execInfo->noOp = true; } else { mutablebson::Element elem = _preparedState->pathFoundElement.leftChild(); while (elem.ok()) { if (std::find_if(_elementsToFind.begin(), _elementsToFind.end(), mutableElementEqualsBSONElement(elem, _collator)) != _elementsToFind.end()) { _preparedState->elementsToRemove.push_back(elem); } elem = elem.rightSibling(); } // Nothing to remove so it is a noOp. if (_preparedState->elementsToRemove.empty()) execInfo->noOp = true; } } } else { // Let the caller know we can't do anything given the mod, _fieldRef, and doc. execInfo->noOp = true; // okay if path not found if (status.code() == ErrorCodes::NonExistentPath) status = Status::OK(); } // Let the caller know what field we care about execInfo->fieldRef[0] = &_fieldRef; return status; }
Status ModifierRename::prepare(mutablebson::Element root, StringData matchedField, ExecInfo* execInfo) { // Rename doesn't work with positional fields ($) dassert(matchedField.empty()); _preparedState.reset(new PreparedState(root)); // Locate the to field name in 'root', which must exist. size_t fromIdxFound; Status status = pathsupport::findLongestPrefix( _fromFieldRef, root, &fromIdxFound, &_preparedState->fromElemFound); const bool sourceExists = (_preparedState->fromElemFound.ok() && fromIdxFound == (_fromFieldRef.numParts() - 1)); // If we can't find the full element in the from field then we can't do anything. if (!status.isOK() || !sourceExists) { execInfo->noOp = true; _preparedState->fromElemFound = root.getDocument().end(); // TODO: remove this special case from existing behavior if (status.code() == ErrorCodes::PathNotViable) { return status; } return Status::OK(); } // Ensure no array in ancestry if what we found is not at the root mutablebson::Element curr = _preparedState->fromElemFound.parent(); if (curr != curr.getDocument().root()) while (curr.ok() && (curr != curr.getDocument().root())) { if (curr.getType() == Array) return Status(ErrorCodes::BadValue, str::stream() << "The source field cannot be an array element, '" << _fromFieldRef.dottedField() << "' in doc with " << findElementNamed(root.leftChild(), "_id").toString() << " has an array field called '" << curr.getFieldName() << "'"); curr = curr.parent(); } // "To" side validation below status = pathsupport::findLongestPrefix( _toFieldRef, root, &_preparedState->toIdxFound, &_preparedState->toElemFound); // FindLongestPrefix may return not viable or any other error and then we cannot proceed. if (status.code() == ErrorCodes::NonExistentPath) { // Not an error condition as we will create the "to" path as needed. } else if (!status.isOK()) { return status; } const bool destExists = _preparedState->toElemFound.ok() && (_preparedState->toIdxFound == (_toFieldRef.numParts() - 1)); // Ensure no array in ancestry of "to" Element // Set to either parent, or node depending on if the full path element was found curr = (destExists ? _preparedState->toElemFound.parent() : _preparedState->toElemFound); if (curr != curr.getDocument().root()) { while (curr.ok()) { if (curr.getType() == Array) return Status(ErrorCodes::BadValue, str::stream() << "The destination field cannot be an array element, '" << _fromFieldRef.dottedField() << "' in doc with " << findElementNamed(root.leftChild(), "_id").toString() << " has an array field called '" << curr.getFieldName() << "'"); curr = curr.parent(); } } // 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] = &_fromFieldRef; execInfo->fieldRef[1] = &_toFieldRef; execInfo->noOp = false; return Status::OK(); }
Status ModifierSet::prepare(mutablebson::Element root, const 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'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remainin path, if missing, will // be created during the apply. 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 (_modOptions.fromReplication && status.code() == ErrorCodes::PathNotViable) { // If we are coming from replication and it is an invalid path, // then push on indicating that we had a blocking element, which we stopped at _preparedState->elemIsBlocking = true; } else if (!status.isOK()) { return status; } if (_setMode == SET_ON_INSERT) { execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT; } // 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)) { return Status::OK(); } // If the value being $set is the same as the one already in the doc, than this is a // noOp. if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts()-1) && _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) { execInfo->noOp = _preparedState->noOp = true; } return Status::OK(); }
Status ModifierSet::prepare(mutablebson::Element root, const 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, "matched field not provided"); } _preparedState->boundDollar = matchedField.toString(); _fieldRef.setPart(_posDollar, _preparedState->boundDollar); } // Locate the field name in 'root'. Note that we may not have all the parts in the path // in the doc -- which is fine. Our goal now is merely to reason about whether this mod // apply is a noOp or whether is can be in place. The remainin path, if missing, will // be created during the apply. 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 (_setMode == SET_ON_INSERT) { execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT; } // 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 < static_cast<int32_t>(_fieldRef.numParts()-1)) { return Status::OK(); } // We may allow this $set to be in place if the value being set and the existing one // have the same size. if (_val.isNumber() && (_preparedState->elemFound != root.getDocument().end()) && (_val.type() == _preparedState->elemFound.getType())) { execInfo->inPlace = _preparedState->inPlace = true; } // If the value being $set is the same as the one already in the doc, than this is a // noOp. if (_preparedState->elemFound.ok() && _preparedState->idxFound == static_cast<int32_t>(_fieldRef.numParts()-1) && _preparedState->elemFound.compareWithBSONElement(_val, false /*ignore field*/) == 0) { execInfo->noOp = _preparedState->noOp = true; } return Status::OK(); }