summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops/update_driver.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops/update_driver.cpp')
-rw-r--r--src/mongo/db/ops/update_driver.cpp576
1 files changed, 282 insertions, 294 deletions
diff --git a/src/mongo/db/ops/update_driver.cpp b/src/mongo/db/ops/update_driver.cpp
index 28ae06215ae..ccd966dbb81 100644
--- a/src/mongo/db/ops/update_driver.cpp
+++ b/src/mongo/db/ops/update_driver.cpp
@@ -44,353 +44,341 @@
namespace mongo {
- namespace str = mongoutils::str;
- namespace mb = mongo::mutablebson;
+namespace str = mongoutils::str;
+namespace mb = mongo::mutablebson;
- using std::unique_ptr;
- using std::vector;
+using std::unique_ptr;
+using std::vector;
- using pathsupport::EqualityMatches;
+using pathsupport::EqualityMatches;
- UpdateDriver::UpdateDriver(const Options& opts)
- : _replacementMode(false)
- , _indexedFields(NULL)
- , _logOp(opts.logOp)
- , _modOptions(opts.modOptions)
- , _affectIndices(false)
- , _positional(false) {
- }
-
- UpdateDriver::~UpdateDriver() {
- clear();
- }
+UpdateDriver::UpdateDriver(const Options& opts)
+ : _replacementMode(false),
+ _indexedFields(NULL),
+ _logOp(opts.logOp),
+ _modOptions(opts.modOptions),
+ _affectIndices(false),
+ _positional(false) {}
- Status UpdateDriver::parse(const BSONObj& updateExpr, const bool multi) {
- clear();
+UpdateDriver::~UpdateDriver() {
+ clear();
+}
- // Check if the update expression is a full object replacement.
- if (*updateExpr.firstElementFieldName() != '$') {
- if (multi) {
- return Status(ErrorCodes::FailedToParse,
- "multi update only works with $ operators");
- }
+Status UpdateDriver::parse(const BSONObj& updateExpr, const bool multi) {
+ clear();
- // Modifiers expect BSONElements as input. But the input to object replace is, by
- // definition, an object. We wrap the 'updateExpr' as the mod is expecting. Note
- // that the wrapper is temporary so the object replace mod should make a copy of
- // the object.
- unique_ptr<ModifierObjectReplace> mod(new ModifierObjectReplace);
- BSONObj wrapper = BSON( "dummy" << updateExpr );
- Status status = mod->init(wrapper.firstElement(), _modOptions);
- if (!status.isOK()) {
- return status;
- }
-
- _mods.push_back(mod.release());
-
- // Register the fact that this driver will only do full object replacements.
- _replacementMode = true;
+ // Check if the update expression is a full object replacement.
+ if (*updateExpr.firstElementFieldName() != '$') {
+ if (multi) {
+ return Status(ErrorCodes::FailedToParse, "multi update only works with $ operators");
+ }
- return Status::OK();
+ // Modifiers expect BSONElements as input. But the input to object replace is, by
+ // definition, an object. We wrap the 'updateExpr' as the mod is expecting. Note
+ // that the wrapper is temporary so the object replace mod should make a copy of
+ // the object.
+ unique_ptr<ModifierObjectReplace> mod(new ModifierObjectReplace);
+ BSONObj wrapper = BSON("dummy" << updateExpr);
+ Status status = mod->init(wrapper.firstElement(), _modOptions);
+ if (!status.isOK()) {
+ return status;
}
- // The update expression is made of mod operators, that is
- // { <$mod>: {...}, <$mod>: {...}, ... }
- BSONObjIterator outerIter(updateExpr);
- while (outerIter.more()) {
- BSONElement outerModElem = outerIter.next();
-
- // Check whether this is a valid mod type.
- modifiertable::ModifierType modType = modifiertable::getType(outerModElem.fieldName());
- if (modType == modifiertable::MOD_UNKNOWN) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Unknown modifier: " << outerModElem.fieldName());
- }
+ _mods.push_back(mod.release());
- // Check whether there is indeed a list of mods under this modifier.
- if (outerModElem.type() != Object) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "Modifiers operate on fields but we found a "
- << typeName(outerModElem.type())
- << " instead. For example: {$mod: {<field>: ...}}"
- << " not {" << outerModElem.toString() << "}");
- }
+ // Register the fact that this driver will only do full object replacements.
+ _replacementMode = true;
- // Check whether there are indeed mods under this modifier.
- if (outerModElem.embeddedObject().isEmpty()) {
- return Status(ErrorCodes::FailedToParse,
- str::stream() << "'" << outerModElem.fieldName()
- << "' is empty. You must specify a field like so: "
- "{" << outerModElem.fieldName() << ": {<field>: ...}}");
- }
+ return Status::OK();
+ }
- BSONObjIterator innerIter(outerModElem.embeddedObject());
- while (innerIter.more()) {
- BSONElement innerModElem = innerIter.next();
+ // The update expression is made of mod operators, that is
+ // { <$mod>: {...}, <$mod>: {...}, ... }
+ BSONObjIterator outerIter(updateExpr);
+ while (outerIter.more()) {
+ BSONElement outerModElem = outerIter.next();
- Status status = addAndParse(modType, innerModElem);
- if (!status.isOK()) {
- return status;
- }
- }
+ // Check whether this is a valid mod type.
+ modifiertable::ModifierType modType = modifiertable::getType(outerModElem.fieldName());
+ if (modType == modifiertable::MOD_UNKNOWN) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Unknown modifier: " << outerModElem.fieldName());
}
- // Register the fact that there will be only $mod's in this driver -- no object
- // replacement.
- _replacementMode = false;
-
- return Status::OK();
- }
+ // Check whether there is indeed a list of mods under this modifier.
+ if (outerModElem.type() != Object) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "Modifiers operate on fields but we found a "
+ << typeName(outerModElem.type())
+ << " instead. For example: {$mod: {<field>: ...}}"
+ << " not {" << outerModElem.toString() << "}");
+ }
- inline Status UpdateDriver::addAndParse(const modifiertable::ModifierType type,
- const BSONElement& elem) {
- if (elem.eoo()) {
+ // Check whether there are indeed mods under this modifier.
+ if (outerModElem.embeddedObject().isEmpty()) {
return Status(ErrorCodes::FailedToParse,
- str::stream() << "'" << elem.fieldName()
- << "' has no value in : " << elem
- << " which is not allowed for any $" << type << " mod.");
+ str::stream() << "'" << outerModElem.fieldName()
+ << "' is empty. You must specify a field like so: "
+ "{" << outerModElem.fieldName() << ": {<field>: ...}}");
}
- unique_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(type));
- dassert(mod.get());
+ BSONObjIterator innerIter(outerModElem.embeddedObject());
+ while (innerIter.more()) {
+ BSONElement innerModElem = innerIter.next();
- bool positional = false;
- Status status = mod->init(elem, _modOptions, &positional);
- if (!status.isOK()) {
- return status;
+ Status status = addAndParse(modType, innerModElem);
+ if (!status.isOK()) {
+ return status;
+ }
}
+ }
- // If any modifier indicates that it requires a positional match, toggle the
- // _positional flag to true.
- _positional = _positional || positional;
+ // Register the fact that there will be only $mod's in this driver -- no object
+ // replacement.
+ _replacementMode = false;
- _mods.push_back(mod.release());
+ return Status::OK();
+}
- return Status::OK();
+inline Status UpdateDriver::addAndParse(const modifiertable::ModifierType type,
+ const BSONElement& elem) {
+ if (elem.eoo()) {
+ return Status(ErrorCodes::FailedToParse,
+ str::stream() << "'" << elem.fieldName() << "' has no value in : " << elem
+ << " which is not allowed for any $" << type << " mod.");
}
- Status UpdateDriver::populateDocumentWithQueryFields(const BSONObj& query,
- const vector<FieldRef*>* immutablePaths,
- mutablebson::Document& doc) const {
- CanonicalQuery* rawCG;
- // We canonicalize the query to collapse $and/$or, and the first arg (ns) is not needed
- // Also, because this is for the upsert case, where we insert a new document if one was
- // not found, the $where clause does not make sense, hence empty WhereCallback.
- Status s = CanonicalQuery::canonicalize("", query, &rawCG, WhereCallbackNoop());
- if (!s.isOK())
- return s;
- unique_ptr<CanonicalQuery> cq(rawCG);
- return populateDocumentWithQueryFields(rawCG, immutablePaths, doc);
- }
+ unique_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(type));
+ dassert(mod.get());
- Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery* query,
- const vector<FieldRef*>* immutablePathsPtr,
- mutablebson::Document& doc) const {
- EqualityMatches equalities;
- Status status = Status::OK();
+ bool positional = false;
+ Status status = mod->init(elem, _modOptions, &positional);
+ if (!status.isOK()) {
+ return status;
+ }
- if (isDocReplacement()) {
+ // If any modifier indicates that it requires a positional match, toggle the
+ // _positional flag to true.
+ _positional = _positional || positional;
+
+ _mods.push_back(mod.release());
+
+ return Status::OK();
+}
+
+Status UpdateDriver::populateDocumentWithQueryFields(const BSONObj& query,
+ const vector<FieldRef*>* immutablePaths,
+ mutablebson::Document& doc) const {
+ CanonicalQuery* rawCG;
+ // We canonicalize the query to collapse $and/$or, and the first arg (ns) is not needed
+ // Also, because this is for the upsert case, where we insert a new document if one was
+ // not found, the $where clause does not make sense, hence empty WhereCallback.
+ Status s = CanonicalQuery::canonicalize("", query, &rawCG, WhereCallbackNoop());
+ if (!s.isOK())
+ return s;
+ unique_ptr<CanonicalQuery> cq(rawCG);
+ return populateDocumentWithQueryFields(rawCG, immutablePaths, doc);
+}
+
+Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery* query,
+ const vector<FieldRef*>* immutablePathsPtr,
+ mutablebson::Document& doc) const {
+ EqualityMatches equalities;
+ Status status = Status::OK();
+
+ if (isDocReplacement()) {
+ FieldRefSet pathsToExtract;
+
+ // TODO: Refactor update logic, make _id just another immutable field
+ static const FieldRef idPath("_id");
+ static const vector<FieldRef*> emptyImmutablePaths;
+ const vector<FieldRef*>& immutablePaths =
+ immutablePathsPtr ? *immutablePathsPtr : emptyImmutablePaths;
+
+ pathsToExtract.fillFrom(immutablePaths);
+ pathsToExtract.insert(&idPath);
+
+ // Extract only immutable fields from replacement-style
+ status =
+ pathsupport::extractFullEqualityMatches(*query->root(), pathsToExtract, &equalities);
+ } else {
+ // Extract all fields from op-style
+ status = pathsupport::extractEqualityMatches(*query->root(), &equalities);
+ }
- FieldRefSet pathsToExtract;
+ if (!status.isOK())
+ return status;
- // TODO: Refactor update logic, make _id just another immutable field
- static const FieldRef idPath("_id");
- static const vector<FieldRef*> emptyImmutablePaths;
- const vector<FieldRef*>& immutablePaths =
- immutablePathsPtr ? *immutablePathsPtr : emptyImmutablePaths;
+ status = pathsupport::addEqualitiesToDoc(equalities, &doc);
+ return status;
+}
+
+Status UpdateDriver::update(StringData matchedField,
+ mutablebson::Document* doc,
+ BSONObj* logOpRec,
+ FieldRefSet* updatedFields,
+ bool* docWasModified) {
+ // TODO: assert that update() is called at most once in a !_multi case.
+
+ // Use the passed in FieldRefSet
+ FieldRefSet* targetFields = updatedFields;
+
+ // If we didn't get a FieldRefSet* from the caller, allocate storage and use
+ // the unique_ptr for lifecycle management
+ unique_ptr<FieldRefSet> targetFieldScopedPtr;
+ if (!targetFields) {
+ targetFieldScopedPtr.reset(new FieldRefSet());
+ targetFields = targetFieldScopedPtr.get();
+ }
- pathsToExtract.fillFrom(immutablePaths);
- pathsToExtract.insert(&idPath);
+ _affectIndices = (isDocReplacement() && (_indexedFields != NULL));
- // Extract only immutable fields from replacement-style
- status = pathsupport::extractFullEqualityMatches(*query->root(),
- pathsToExtract,
- &equalities);
- }
- else {
- // Extract all fields from op-style
- status = pathsupport::extractEqualityMatches(*query->root(), &equalities);
- }
+ _logDoc.reset();
+ LogBuilder logBuilder(_logDoc.root());
- if (!status.isOK())
+ // Ask each of the mods to type check whether they can operate over the current document
+ // and, if so, to change that document accordingly.
+ for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
+ ModifierInterface::ExecInfo execInfo;
+ Status status = (*it)->prepare(doc->root(), matchedField, &execInfo);
+ if (!status.isOK()) {
return status;
+ }
- status = pathsupport::addEqualitiesToDoc(equalities, &doc);
- return status;
- }
+ // If a mod wants to be applied only if this is an upsert (or only if this is a
+ // strict update), we should respect that. If a mod doesn't care, it would state
+ // it is fine with ANY update context.
+ const bool validContext = (execInfo.context == ModifierInterface::ExecInfo::ANY_CONTEXT ||
+ execInfo.context == _context);
- Status UpdateDriver::update(StringData matchedField,
- mutablebson::Document* doc,
- BSONObj* logOpRec,
- FieldRefSet* updatedFields,
- bool* docWasModified) {
- // TODO: assert that update() is called at most once in a !_multi case.
-
- // Use the passed in FieldRefSet
- FieldRefSet* targetFields = updatedFields;
-
- // If we didn't get a FieldRefSet* from the caller, allocate storage and use
- // the unique_ptr for lifecycle management
- unique_ptr<FieldRefSet> targetFieldScopedPtr;
- if (!targetFields) {
- targetFieldScopedPtr.reset(new FieldRefSet());
- targetFields = targetFieldScopedPtr.get();
+ // Nothing to do if not in a valid context.
+ if (!validContext) {
+ continue;
}
- _affectIndices = (isDocReplacement() && (_indexedFields != NULL));
- _logDoc.reset();
- LogBuilder logBuilder(_logDoc.root());
-
- // Ask each of the mods to type check whether they can operate over the current document
- // and, if so, to change that document accordingly.
- for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
- ModifierInterface::ExecInfo execInfo;
- Status status = (*it)->prepare(doc->root(), matchedField, &execInfo);
- if (!status.isOK()) {
- return status;
+ // Gather which fields this mod is interested on and whether these fields were
+ // "taken" by previous mods. Note that not all mods are multi-field mods. When we
+ // see an empty field, we may stop looking for others.
+ for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) {
+ if (execInfo.fieldRef[i] == 0) {
+ break;
}
- // If a mod wants to be applied only if this is an upsert (or only if this is a
- // strict update), we should respect that. If a mod doesn't care, it would state
- // it is fine with ANY update context.
- const bool validContext = (execInfo.context == ModifierInterface::ExecInfo::ANY_CONTEXT ||
- execInfo.context == _context);
-
- // Nothing to do if not in a valid context.
- if (!validContext) {
- continue;
+ // Record each field being updated but check for conflicts first
+ const FieldRef* other;
+ if (!targetFields->insert(execInfo.fieldRef[i], &other)) {
+ return Status(ErrorCodes::ConflictingUpdateOperators,
+ str::stream() << "Cannot update '" << other->dottedField()
+ << "' and '" << execInfo.fieldRef[i]->dottedField()
+ << "' at the same time");
}
-
- // Gather which fields this mod is interested on and whether these fields were
- // "taken" by previous mods. Note that not all mods are multi-field mods. When we
- // see an empty field, we may stop looking for others.
- for (int i = 0; i < ModifierInterface::ExecInfo::MAX_NUM_FIELDS; i++) {
- if (execInfo.fieldRef[i] == 0) {
- break;
- }
-
- // Record each field being updated but check for conflicts first
- const FieldRef* other;
- if (!targetFields->insert(execInfo.fieldRef[i], &other)) {
- return Status(ErrorCodes::ConflictingUpdateOperators,
- str::stream() << "Cannot update '"
- << other->dottedField()
- << "' and '"
- << execInfo.fieldRef[i]->dottedField()
- << "' at the same time");
- }
-
- // We start with the expectation that a mod will be in-place. But if the mod
- // touched an indexed field and the mod will indeed be executed -- that is, it
- // is not a no-op and it is in a valid context -- then we switch back to a
- // non-in-place mode.
- //
- // TODO: make mightBeIndexed and fieldRef like each other.
- if (!_affectIndices &&
- !execInfo.noOp &&
- _indexedFields &&
- _indexedFields->mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
- _affectIndices = true;
- doc->disableInPlaceUpdates();
- }
+ // We start with the expectation that a mod will be in-place. But if the mod
+ // touched an indexed field and the mod will indeed be executed -- that is, it
+ // is not a no-op and it is in a valid context -- then we switch back to a
+ // non-in-place mode.
+ //
+ // TODO: make mightBeIndexed and fieldRef like each other.
+ if (!_affectIndices && !execInfo.noOp && _indexedFields &&
+ _indexedFields->mightBeIndexed(execInfo.fieldRef[i]->dottedField())) {
+ _affectIndices = true;
+ doc->disableInPlaceUpdates();
}
+ }
- if (!execInfo.noOp) {
- status = (*it)->apply();
+ if (!execInfo.noOp) {
+ status = (*it)->apply();
- if (docWasModified)
- *docWasModified = true;
+ if (docWasModified)
+ *docWasModified = true;
- if (!status.isOK()) {
- return status;
- }
+ if (!status.isOK()) {
+ return status;
}
+ }
- // If we require a replication oplog entry for this update, go ahead and generate one.
- if (!execInfo.noOp && _logOp && logOpRec) {
- status = (*it)->log(&logBuilder);
- if (!status.isOK()) {
- return status;
- }
+ // If we require a replication oplog entry for this update, go ahead and generate one.
+ if (!execInfo.noOp && _logOp && logOpRec) {
+ status = (*it)->log(&logBuilder);
+ if (!status.isOK()) {
+ return status;
}
-
}
-
- if (_logOp && logOpRec)
- *logOpRec = _logDoc.getObject();
-
- return Status::OK();
}
- size_t UpdateDriver::numMods() const {
- return _mods.size();
+ if (_logOp && logOpRec)
+ *logOpRec = _logDoc.getObject();
+
+ return Status::OK();
+}
+
+size_t UpdateDriver::numMods() const {
+ return _mods.size();
+}
+
+bool UpdateDriver::isDocReplacement() const {
+ return _replacementMode;
+}
+
+bool UpdateDriver::modsAffectIndices() const {
+ return _affectIndices;
+}
+
+void UpdateDriver::refreshIndexKeys(const UpdateIndexData* indexedFields) {
+ _indexedFields = indexedFields;
+}
+
+bool UpdateDriver::logOp() const {
+ return _logOp;
+}
+
+void UpdateDriver::setLogOp(bool logOp) {
+ _logOp = logOp;
+}
+
+ModifierInterface::Options UpdateDriver::modOptions() const {
+ return _modOptions;
+}
+
+void UpdateDriver::setModOptions(ModifierInterface::Options modOpts) {
+ _modOptions = modOpts;
+}
+
+ModifierInterface::ExecInfo::UpdateContext UpdateDriver::context() const {
+ return _context;
+}
+
+void UpdateDriver::setContext(ModifierInterface::ExecInfo::UpdateContext context) {
+ _context = context;
+}
+
+BSONObj UpdateDriver::makeOplogEntryQuery(const BSONObj& doc, bool multi) const {
+ BSONObjBuilder idPattern;
+ BSONElement id;
+ // NOTE: If the matching object lacks an id, we'll log
+ // with the original pattern. This isn't replay-safe.
+ // It might make sense to suppress the log instead
+ // if there's no id.
+ if (doc.getObjectID(id)) {
+ idPattern.append(id);
+ return idPattern.obj();
+ } else {
+ uassert(16980,
+ str::stream() << "Multi-update operations require all documents to "
+ "have an '_id' field. " << doc.toString(false, false),
+ !multi);
+ return doc;
}
-
- bool UpdateDriver::isDocReplacement() const {
- return _replacementMode;
- }
-
- bool UpdateDriver::modsAffectIndices() const {
- return _affectIndices;
- }
-
- void UpdateDriver::refreshIndexKeys(const UpdateIndexData* indexedFields) {
- _indexedFields = indexedFields;
- }
-
- bool UpdateDriver::logOp() const {
- return _logOp;
- }
-
- void UpdateDriver::setLogOp(bool logOp) {
- _logOp = logOp;
- }
-
- ModifierInterface::Options UpdateDriver::modOptions() const {
- return _modOptions;
- }
-
- void UpdateDriver::setModOptions(ModifierInterface::Options modOpts) {
- _modOptions = modOpts;
- }
-
- ModifierInterface::ExecInfo::UpdateContext UpdateDriver::context() const {
- return _context;
- }
-
- void UpdateDriver::setContext(ModifierInterface::ExecInfo::UpdateContext context) {
- _context = context;
- }
-
- BSONObj UpdateDriver::makeOplogEntryQuery(const BSONObj& doc, bool multi) const {
- BSONObjBuilder idPattern;
- BSONElement id;
- // NOTE: If the matching object lacks an id, we'll log
- // with the original pattern. This isn't replay-safe.
- // It might make sense to suppress the log instead
- // if there's no id.
- if ( doc.getObjectID( id ) ) {
- idPattern.append( id );
- return idPattern.obj();
- }
- else {
- uassert( 16980,
- str::stream() << "Multi-update operations require all documents to "
- "have an '_id' field. " << doc.toString(false, false),
- ! multi );
- return doc;
- }
- }
- void UpdateDriver::clear() {
- for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
- delete *it;
- }
- _mods.clear();
- _indexedFields = NULL;
- _replacementMode = false;
- _positional = false;
+}
+void UpdateDriver::clear() {
+ for (vector<ModifierInterface*>::iterator it = _mods.begin(); it != _mods.end(); ++it) {
+ delete *it;
}
+ _mods.clear();
+ _indexedFields = NULL;
+ _replacementMode = false;
+ _positional = false;
+}
-} // namespace mongo
+} // namespace mongo