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, 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'. 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, 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) { 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(); } // 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. if (_preparedState->elementsToAdd.empty()) { _preparedState->noOp = execInfo->noOp = true; } 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 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 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; }