/**
* Copyright (C) 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "mongo/db/ops/modifier_set.h"
#include "mongo/base/error_codes.h"
#include "mongo/bson/mutable/document.h"
#include "mongo/db/ops/field_checker.h"
#include "mongo/db/ops/log_builder.h"
#include "mongo/db/ops/path_support.h"
#include "mongo/util/mongoutils/str.h"
namespace mongo {
namespace str = mongoutils::str;
struct ModifierSet::PreparedState {
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;
// Index in _fieldRef for which an Element exist in the document.
size_t idxFound;
// Element corresponding to _fieldRef[0.._idxFound].
mutablebson::Element elemFound;
// This $set is a no-op?
bool noOp;
// 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() {}
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 (foundDollar && foundCount > 1) {
return Status(ErrorCodes::BadValue,
str::stream() << "Too many positional (i.e. '$') elements found in path '"
<< _fieldRef.dottedField()
<< "'");
}
//
// value analysis
//
if (!modExpr.ok())
return Status(ErrorCodes::BadValue, "cannot $set an empty value");
_val = modExpr;
_modOptions = opts;
return Status::OK();
}
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);
}
// 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 it 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. We
// use binary equality to compare so that any change to the document is considered, unlike using
// a comparison that winds up in woCompare (see SERVER-16801). In the case where elemFound
// doesn't have a serialized representation, we just declare the operation to not be a
// no-op. This is potentially a missed optimization, but is unlikely to cause much pain since in
// the normal update workflow we only admit one modification on any path from a leaf to the
// document root. In that domain, hasValue will always be true. We may encounter a
// non-serialized elemFound in the case where our base document is the result of calling
// populateDocumentWithQueryFields, so this could cause us to do slightly more work than
// strictly necessary in the case where an update (w upsert:true) becomes an insert.
if (_preparedState->elemFound.ok() && _preparedState->idxFound == (_fieldRef.numParts() - 1) &&
_preparedState->elemFound.hasValue() &&
_preparedState->elemFound.getValue().binaryEqualValues(_val)) {
execInfo->noOp = _preparedState->noOp = true;
}
return Status::OK();
}
Status ModifierSet::apply() const {
dassert(!_preparedState->noOp);
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);
}
//
// Complete document path logic
//
// 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");
}
// 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++;
}
// 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;
}
}
// 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: {: }} under 'logRoot'.
// We start by creating the {$set: ...} Element.
mutablebson::Document& doc = logBuilder->getDocument();
// Create the {: } 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");
}
return logBuilder->addToSets(logElement);
}
} // namespace mongo