diff options
Diffstat (limited to 'src/mongo/db/ops/modifier_set.cpp')
-rw-r--r-- | src/mongo/db/ops/modifier_set.cpp | 389 |
1 files changed, 180 insertions, 209 deletions
diff --git a/src/mongo/db/ops/modifier_set.cpp b/src/mongo/db/ops/modifier_set.cpp index fe808138c68..93abc04f892 100644 --- a/src/mongo/db/ops/modifier_set.cpp +++ b/src/mongo/db/ops/modifier_set.cpp @@ -37,253 +37,224 @@ namespace mongo { - namespace str = mongoutils::str; +namespace str = mongoutils::str; - struct ModifierSet::PreparedState { +struct ModifierSet::PreparedState { + PreparedState(mutablebson::Document* targetDoc) + : doc(*targetDoc), idxFound(0), elemFound(doc.end()), noOp(false), elemIsBlocking(false) {} - PreparedState(mutablebson::Document* targetDoc) - : doc(*targetDoc) - , idxFound(0) - , elemFound(doc.end()) - , noOp(false) - , elemIsBlocking(false) { - } + // Document that is going to be changed. + mutablebson::Document& doc; - // Document that is going to be changed. - mutablebson::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]. + mutablebson::Element elemFound; - // Element corresponding to _fieldRef[0.._idxFound]. - mutablebson::Element elemFound; + // This $set is a no-op? + bool noOp; - // This $set is a no-op? - bool noOp; + // The element we find during a replication operation that blocks our update path + bool elemIsBlocking; +}; - // The element we find during a replication operation that blocks our update path - bool elemIsBlocking; +ModifierSet::ModifierSet(ModifierSet::ModifierSetMode mode) + : _fieldRef(), _posDollar(0), _setMode(mode), _val(), _modOptions() {} - }; +ModifierSet::~ModifierSet() {} - ModifierSet::ModifierSet(ModifierSet::ModifierSetMode mode) - : _fieldRef() - , _posDollar(0) - , _setMode(mode) - , _val() - , _modOptions() { - } +Status ModifierSet::init(const BSONElement& modExpr, const Options& opts, bool* positional) { + // + // field name analysis + // - ModifierSet::~ModifierSet() { + // Break down the field name into its 'dotted' components (aka parts) and check that + // the field is fit for updates + _fieldRef.parse(modExpr.fieldName()); + Status status = fieldchecker::isUpdatable(_fieldRef); + if (!status.isOK()) { + return status; } - Status ModifierSet::init(const BSONElement& modExpr, const Options& opts, - bool* positional) { - - // - // field name analysis - // - - // Break down the field name into its 'dotted' components (aka parts) and check that - // the field is fit for updates - _fieldRef.parse(modExpr.fieldName()); - Status status = fieldchecker::isUpdatable(_fieldRef); - if (! status.isOK()) { - return status; - } - - // If a $-positional operator was used, get the index in which it occurred - // and ensure only one occurrence. - size_t foundCount; - bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); - - 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; - // - // value analysis - // - - if (!modExpr.ok()) - return Status(ErrorCodes::BadValue, "cannot $set an empty value"); - - _val = modExpr; - _modOptions = opts; - - return Status::OK(); + if (foundDollar && foundCount > 1) { + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } - Status ModifierSet::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); - } + // + // value analysis + // - // 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 (!modExpr.ok()) + return Status(ErrorCodes::BadValue, "cannot $set an empty value"); - if (_setMode == SET_ON_INSERT) { - execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT; - } + _val = modExpr; + _modOptions = opts; - // 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(); +} - // - // in-place and no-op logic - // +Status ModifierSet::prepare(mutablebson::Element root, + StringData matchedField, + ExecInfo* execInfo) { + _preparedState.reset(new PreparedState(&root.getDocument())); - // 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 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); + } - // 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; - } + // 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; + } - return Status::OK(); + if (_setMode == SET_ON_INSERT) { + execInfo->context = ModifierInterface::ExecInfo::INSERT_CONTEXT; } - Status ModifierSet::apply() const { - dassert(!_preparedState->noOp); + // 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; - const bool destExists = _preparedState->elemFound.ok() && - _preparedState->idxFound == (_fieldRef.numParts()-1); - // If there's no need to create any further field part, the $set is simply a value - // assignment. - if (destExists) { - return _preparedState->elemFound.setValueBSONElement(_val); - } + // + // in-place and no-op logic + // - // - // Complete document path 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(); + } - // Creates the final element that's going to be $set in 'doc'. - mutablebson::Document& doc = _preparedState->doc; - StringData lastPart = _fieldRef.getPart(_fieldRef.numParts()-1); - mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val); - if (!elemToSet.ok()) { - return Status(ErrorCodes::InternalError, "can't create new element"); - } + // 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; + } - // 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++; - } + return Status::OK(); +} - // Remove the blocking element, if we are from replication applier. See comment below. - if (_modOptions.fromReplication && !destExists && _preparedState->elemFound.ok() && - _preparedState->elemIsBlocking && - (!(_preparedState->elemFound.isType(Array)) || - !(_preparedState->elemFound.isType(Object))) - ) { - - /** - * With replication we want to be able to remove blocking elements for $set (only). - * The reason they are blocking elements is that they are not embedded documents - * (objects) nor an array (a special type of an embedded doc) and we cannot - * add children to them (because the $set path requires adding children past - * the blocking element). - * - * Imagine that we started with this: - * {_id:1, a:1} + {$set : {"a.b.c" : 1}} -> {_id:1, a: {b: {c:1}}} - * Above we found that element (a:1) is blocking at position 1. We now will replace - * it with an empty object so the normal logic below can be - * applied from the root (in this example case). - * - * Here is an array example: - * {_id:1, a:[1, 2]} + {$set : {"a.0.c" : 1}} -> {_id:1, a: [ {c:1}, 2]} - * The blocking element is "a.0" since it is a number, non-object, and we must - * then replace it with an empty object so we can add c:1 to that empty object - */ - - mutablebson::Element blockingElem = _preparedState->elemFound; - BSONObj newObj; - // Replace blocking non-object with an empty object - Status status = blockingElem.setValueObject(newObj); - if (!status.isOK()) { - return status; - } - } +Status ModifierSet::apply() const { + dassert(!_preparedState->noOp); - // createPathAt() will complete the path and attach 'elemToSet' at the end of it. - return pathsupport::createPathAt(_fieldRef, - _preparedState->idxFound, - _preparedState->elemFound, - elemToSet); + const bool destExists = + _preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1); + // If there's no need to create any further field part, the $set is simply a value + // assignment. + if (destExists) { + return _preparedState->elemFound.setValueBSONElement(_val); } - Status ModifierSet::log(LogBuilder* logBuilder) const { + // + // Complete document path logic + // - // We'd like to create an entry such as {$set: {<fieldname>: <value>}} under 'logRoot'. - // We start by creating the {$set: ...} Element. - mutablebson::Document& doc = logBuilder->getDocument(); + // Creates the final element that's going to be $set in 'doc'. + mutablebson::Document& doc = _preparedState->doc; + StringData lastPart = _fieldRef.getPart(_fieldRef.numParts() - 1); + mutablebson::Element elemToSet = doc.makeElementWithNewFieldName(lastPart, _val); + if (!elemToSet.ok()) { + return Status(ErrorCodes::InternalError, "can't create new element"); + } - // 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); + // 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++; + } - if (!logElement.ok()) { - return Status(ErrorCodes::InternalError, "cannot create details for $set mod"); + // Remove the blocking element, if we are from replication applier. See comment below. + if (_modOptions.fromReplication && !destExists && _preparedState->elemFound.ok() && + _preparedState->elemIsBlocking && (!(_preparedState->elemFound.isType(Array)) || + !(_preparedState->elemFound.isType(Object)))) { + /** + * With replication we want to be able to remove blocking elements for $set (only). + * The reason they are blocking elements is that they are not embedded documents + * (objects) nor an array (a special type of an embedded doc) and we cannot + * add children to them (because the $set path requires adding children past + * the blocking element). + * + * Imagine that we started with this: + * {_id:1, a:1} + {$set : {"a.b.c" : 1}} -> {_id:1, a: {b: {c:1}}} + * Above we found that element (a:1) is blocking at position 1. We now will replace + * it with an empty object so the normal logic below can be + * applied from the root (in this example case). + * + * Here is an array example: + * {_id:1, a:[1, 2]} + {$set : {"a.0.c" : 1}} -> {_id:1, a: [ {c:1}, 2]} + * The blocking element is "a.0" since it is a number, non-object, and we must + * then replace it with an empty object so we can add c:1 to that empty object + */ + + mutablebson::Element blockingElem = _preparedState->elemFound; + BSONObj newObj; + // Replace blocking non-object with an empty object + Status status = blockingElem.setValueObject(newObj); + if (!status.isOK()) { + return status; } + } - return logBuilder->addToSets(logElement); + // createPathAt() will complete the path and attach 'elemToSet' at the end of it. + return pathsupport::createPathAt( + _fieldRef, _preparedState->idxFound, _preparedState->elemFound, elemToSet); +} + +Status ModifierSet::log(LogBuilder* logBuilder) 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 = logBuilder->getDocument(); + + // 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"); } -} // namespace mongo + return logBuilder->addToSets(logElement); +} + +} // namespace mongo |