diff options
-rw-r--r-- | src/mongo/bson/mutable/element.cpp | 3 | ||||
-rw-r--r-- | src/mongo/bson/mutable/element.h | 3 | ||||
-rw-r--r-- | src/mongo/db/jsobj.cpp | 2 | ||||
-rw-r--r-- | src/mongo/db/ops/field_checker.cpp | 24 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_add_to_set.cpp | 51 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_bit.cpp | 53 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_compare.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_current_date.cpp | 9 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_inc.cpp | 43 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_object_replace.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_pop.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_pull.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_pull_all.cpp | 27 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_push.cpp | 75 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_rename.cpp | 34 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_set.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/ops/modifier_unset.cpp | 17 | ||||
-rw-r--r-- | src/mongo/db/ops/update_driver.cpp | 35 |
18 files changed, 345 insertions, 122 deletions
diff --git a/src/mongo/bson/mutable/element.cpp b/src/mongo/bson/mutable/element.cpp index bb5504f63b5..e2e02d61fe4 100644 --- a/src/mongo/bson/mutable/element.cpp +++ b/src/mongo/bson/mutable/element.cpp @@ -150,6 +150,9 @@ namespace mutablebson { } std::string Element::toString() const { + if (!ok()) + return "INVALID-MUTABLE-ELEMENT"; + if (hasValue()) return getValue().toString(); diff --git a/src/mongo/bson/mutable/element.h b/src/mongo/bson/mutable/element.h index fc3c67bdee4..e78933773d9 100644 --- a/src/mongo/bson/mutable/element.h +++ b/src/mongo/bson/mutable/element.h @@ -548,7 +548,8 @@ namespace mutablebson { */ Status appendSafeNum(const StringData& fieldName, SafeNum value); - /** Convert this element to its JSON representation */ + /** Convert this element to its JSON representation if ok(), + * otherwise return !ok() message */ std::string toString() const; private: diff --git a/src/mongo/db/jsobj.cpp b/src/mongo/db/jsobj.cpp index 3245c5e655e..864377cdcab 100644 --- a/src/mongo/db/jsobj.cpp +++ b/src/mongo/db/jsobj.cpp @@ -932,7 +932,7 @@ namespace mongo { && str::equals(name,"_id")) { return Status(ErrorCodes::InvalidIdField, str::stream() << name - << " is not valid for storage because of the type: " + << " is not valid for storage because it is of type " << typeName(e.type())); } diff --git a/src/mongo/db/ops/field_checker.cpp b/src/mongo/db/ops/field_checker.cpp index 86d7981b24b..e4611727832 100644 --- a/src/mongo/db/ops/field_checker.cpp +++ b/src/mongo/db/ops/field_checker.cpp @@ -44,21 +44,25 @@ namespace fieldchecker { const size_t numParts = field.numParts(); if (numParts == 0) { - return Status(ErrorCodes::BadValue, "cannot update an empty field name"); + return Status(ErrorCodes::EmptyFieldName, + "An empty update path is not valid."); } for (size_t i = 0; i != numParts; ++i) { const StringData part = field.getPart(i); if ((i == 0) && part.compare("_id") == 0) { - return Status(ErrorCodes::BadValue, - "update cannot affect the _id"); + return Status(ErrorCodes::ImmutableIdField, + mongoutils::str::stream() << "The update path '" + << field.dottedField() + << "' contains the '_id' field, which cannot be updated."); } if (part.empty()) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << field.dottedField() - << " contains empty fields"); + return Status(ErrorCodes::EmptyFieldName, + mongoutils::str::stream() << "The update path '" + << field.dottedField() + << "' contains an empty field, which is not allowed."); } if (!legacy && (part[0] == '$')) { @@ -88,9 +92,11 @@ namespace fieldchecker { part.startsWith("$ref"); if (!mightBePartOfDbRef) - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << field.dottedField() - << " contains field names with leading '$' character"); + return Status(ErrorCodes::DollarPrefixedFieldName, + mongoutils::str::stream() << "The update path '" + << field.dottedField() + << "' contains an illegal field name " + "(field name starts with '$')."); } diff --git a/src/mongo/db/ops/modifier_add_to_set.cpp b/src/mongo/db/ops/modifier_add_to_set.cpp index 6b85b45a6c5..af0bcdde57d 100644 --- a/src/mongo/db/ops/modifier_add_to_set.cpp +++ b/src/mongo/db/ops/modifier_add_to_set.cpp @@ -33,10 +33,12 @@ #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 mb = mutablebson; + namespace str = mongoutils::str; namespace { @@ -129,7 +131,9 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } // TODO: The driver could potentially do this re-writing. @@ -143,7 +147,9 @@ namespace mongo { // set our flag, and store the array as our value. if (modExprObjPayload.type() != mongo::Array) { return Status(ErrorCodes::BadValue, - "Argument to $each operator in $addToSet must be an array"); + str::stream() << "The argument to $each in $addToSet must " + "be an array but it was type of: " + << typeName(modExprObjPayload.type())); } status = _valDoc.root().appendElement(modExprObjPayload); @@ -178,22 +184,25 @@ namespace mongo { while (valCursor.ok()) { const BSONType type = valCursor.getType(); dassert(valCursor.hasValue()); - bool okForStorage = true; switch(type) { - case mongo::Object: - okForStorage = valCursor.getValueObject().okForStorage(); + case mongo::Object: { + Status s = valCursor.getValueObject().storageValidEmbedded(); + if (!s.isOK()) + return s; + break; - case mongo::Array: - okForStorage = valCursor.getValueArray().okForStorage(); + } + case mongo::Array: { + Status s = valCursor.getValueArray().storageValidEmbedded(); + if (!s.isOK()) + return s; + break; + } default: break; } - if (!okForStorage) { - return Status(ErrorCodes::BadValue, "Field name not OK for storage"); - } - valCursor = valCursor.rightSibling(); } @@ -209,7 +218,10 @@ namespace mongo { // 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"); + 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); @@ -248,10 +260,17 @@ namespace mongo { } // This operation only applies to arrays - if (_preparedState->elemFound.getType() != mongo::Array) + if (_preparedState->elemFound.getType() != mongo::Array) { + mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status( ErrorCodes::BadValue, - "Cannot apply $addToSet to a non-array value"); + 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. @@ -400,7 +419,9 @@ namespace mongo { } Status status = logElement.pushBack(currCopy); if (!status.isOK()) { - return Status(ErrorCodes::BadValue, "could not append entry for $addToSet log"); + return Status(ErrorCodes::BadValue, + str::stream() << "Could not append entry for $addToSet oplog entry." + << "Underlying cause: " << status.toString()); } curr = curr.rightSibling(); } diff --git a/src/mongo/db/ops/modifier_bit.cpp b/src/mongo/db/ops/modifier_bit.cpp index 3f285bbc7f1..cdf3951989f 100644 --- a/src/mongo/db/ops/modifier_bit.cpp +++ b/src/mongo/db/ops/modifier_bit.cpp @@ -29,6 +29,7 @@ #include "mongo/db/ops/modifier_bit.h" #include "mongo/base/error_codes.h" +#include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/document.h" #include "mongo/db/ops/field_checker.h" #include "mongo/db/ops/log_builder.h" @@ -37,7 +38,8 @@ namespace mongo { - using mongoutils::str::stream; + namespace mb = mutablebson; + namespace str = mongoutils::str; struct ModifierBit::PreparedState { @@ -92,12 +94,17 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } if (modExpr.type() != mongo::Object) return Status(ErrorCodes::BadValue, - "Value following $bit must be an Object"); + str::stream() << "The $bit modifier is not compatible with " + << typeName(modExpr.type()) + << ". You must pass in an embedded document: " + "{$bit: {field: {and/or/xor: #}}"); BSONObjIterator opsIterator(modExpr.embeddedObject()); @@ -110,7 +117,9 @@ namespace mongo { (curOp.type() != mongo::NumberLong)) return Status( ErrorCodes::BadValue, - "Argument to $bit operation must be a NumberInt or NumberLong"); + str::stream() << "The $bit modifier field must be an Integer(32/64 bit); a '" + << typeName(curOp.type()) + << "' is not supported here: " << curOp); SafeNumOp op = NULL; @@ -126,7 +135,9 @@ namespace mongo { else { return Status( ErrorCodes::BadValue, - "Only 'and', 'or', and 'xor' are supported $bit sub-operators"); + str::stream() << "The $bit modifier only supports 'and', 'or', and 'xor', not '" + << payloadFieldName + << "' which is an unknown operator: " << curOp); } const OpEntry entry = {SafeNum(curOp), op}; @@ -147,7 +158,10 @@ namespace mongo { // 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"); + 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); @@ -188,20 +202,28 @@ namespace mongo { return Status::OK(); } - if (!_preparedState->elemFound.isIntegral()) + if (!_preparedState->elemFound.isIntegral()) { + mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); return Status( ErrorCodes::BadValue, - "Cannot apply $bit to a value of non-integral type"); + 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()) + if (!_preparedState->newValue.isValid()) { + // TODO: Include list of ops, if that is easy, at some future point. return Status(ErrorCodes::BadValue, - "Failed to apply $bit to current value"); - + 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; @@ -257,9 +279,12 @@ namespace mongo { _fieldRef.dottedField(), _preparedState->newValue); - if (!logElement.ok()) - return Status(ErrorCodes::InternalError, "cannot append details for $bit mod"); - + if (!logElement.ok()) { + return Status(ErrorCodes::InternalError, + str::stream() << "Could not append entry to $bit oplog entry: " + << "set '" << _fieldRef.dottedField() << "' -> " + << _preparedState->newValue.debugString() ); + } return logBuilder->addToSets(logElement); } diff --git a/src/mongo/db/ops/modifier_compare.cpp b/src/mongo/db/ops/modifier_compare.cpp index 4942e8f6b1b..0d1a9185c8c 100644 --- a/src/mongo/db/ops/modifier_compare.cpp +++ b/src/mongo/db/ops/modifier_compare.cpp @@ -21,9 +21,11 @@ #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 ModifierCompare::PreparedState { @@ -55,6 +57,7 @@ namespace mongo { } Status ModifierCompare::init(const BSONElement& modExpr, const Options& opts) { + _updatePath.parse(modExpr.fieldName()); Status status = fieldchecker::isUpdatable(_updatePath); if (!status.isOK()) { @@ -66,7 +69,9 @@ namespace mongo { size_t foundCount; fieldchecker::isPositional(_updatePath, &_pathReplacementPosition, &foundCount); if (_pathReplacementPosition && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _updatePath.dottedField() << "'"); } // Store value for later. @@ -83,7 +88,10 @@ namespace mongo { // 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, "matched field not provided"); + return Status(ErrorCodes::BadValue, + str::stream() << "The positional operator did not find the match " + "needed from the query. Unexpanded update: " + << _updatePath.dottedField()); } _preparedState->pathReplacementString = matchedField.toString(); _updatePath.setPart(_pathReplacementPosition, _preparedState->pathReplacementString); diff --git a/src/mongo/db/ops/modifier_current_date.cpp b/src/mongo/db/ops/modifier_current_date.cpp index a506a4546ba..ba4cd384038 100644 --- a/src/mongo/db/ops/modifier_current_date.cpp +++ b/src/mongo/db/ops/modifier_current_date.cpp @@ -89,7 +89,9 @@ namespace mongo { &_pathReplacementPosition, &foundCount); if (_pathReplacementPosition && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _updatePath.dottedField() << "'"); } // Validate and store the type to produce @@ -153,7 +155,10 @@ namespace mongo { // 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, "matched field not provided"); + return Status(ErrorCodes::BadValue, + str::stream() << "The positional operator did not find the match " + "needed from the query. Unexpanded update: " + << _updatePath.dottedField()); } _preparedState->pathReplacementString = matchedField.toString(); _updatePath.setPart(_pathReplacementPosition, _preparedState->pathReplacementString); diff --git a/src/mongo/db/ops/modifier_inc.cpp b/src/mongo/db/ops/modifier_inc.cpp index 919df507576..0237262ad62 100644 --- a/src/mongo/db/ops/modifier_inc.cpp +++ b/src/mongo/db/ops/modifier_inc.cpp @@ -29,6 +29,7 @@ #include "mongo/db/ops/modifier_inc.h" #include "mongo/base/error_codes.h" +#include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/document.h" #include "mongo/db/ops/field_checker.h" #include "mongo/db/ops/log_builder.h" @@ -37,7 +38,8 @@ namespace mongo { - using mongoutils::str::stream; + namespace mb = mutablebson; + namespace str = mongoutils::str; struct ModifierInc::PreparedState { @@ -98,7 +100,9 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } // @@ -109,7 +113,8 @@ namespace mongo { // TODO: Context for mod error messages would be helpful // include mod code, etc. return Status(ErrorCodes::BadValue, - "Cannot increment with non-numeric argument"); + str::stream() << "Cannot increment with non-numeric argument: " + << modExpr); } _val = modExpr; @@ -127,7 +132,10 @@ namespace mongo { // 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"); + 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); @@ -180,10 +188,16 @@ namespace mongo { // 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"); - + if (!_preparedState->elemFound.isNumeric()) { + mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); + return Status( + ErrorCodes::BadValue, + str::stream() << "Cannot apply $inc 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. @@ -193,9 +207,11 @@ namespace mongo { _preparedState->newValue *= currentValue; // If the result of the addition is invalid, we must return an error. - if (!_preparedState->newValue.isValid()) + if (!_preparedState->newValue.isValid()) { return Status(ErrorCodes::BadValue, - "Failed to increment current value"); + str::stream() << "Failed to apply $inc 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)) { @@ -260,7 +276,12 @@ namespace mongo { _preparedState->newValue); if (!logElement.ok()) { - return Status(ErrorCodes::InternalError, "cannot append details for $inc/$mul mod"); + return Status(ErrorCodes::InternalError, + str::stream() << "Could not append entry to " + << (_mode == MODE_INC ? "$inc" : "$mul") + << " oplog entry: " + << "set '" << _fieldRef.dottedField() << "' -> " + << _preparedState->newValue.debugString() ); } // Now, we attach the {<fieldname>: <value>} Element under the {$set: ...} segment. diff --git a/src/mongo/db/ops/modifier_object_replace.cpp b/src/mongo/db/ops/modifier_object_replace.cpp index 073b56d3abe..7b312a34a99 100644 --- a/src/mongo/db/ops/modifier_object_replace.cpp +++ b/src/mongo/db/ops/modifier_object_replace.cpp @@ -35,6 +35,8 @@ namespace mongo { + namespace str = mongoutils::str; + namespace { // TODO: Egregiously stolen from jsobjmanipulator.h and instance.cpp to break a link // dependency cycle. We should sort this out, but we don't need to do it right now. @@ -83,20 +85,27 @@ namespace mongo { Status ModifierObjectReplace::init(const BSONElement& modExpr, const Options& opts) { if (modExpr.type() != Object) { - return Status(ErrorCodes::BadValue, mongoutils::str::stream() << - "object replace expects full object but type was " << modExpr.type()); - } - - if (opts.enforceOkForStorage && !modExpr.embeddedObject().okForStorageAsRoot()) { - return Status(ErrorCodes::BadValue, "not okForStorage: " - " the document has invalid fields"); + // Impossible, really since the caller check this already... + return Status(ErrorCodes::BadValue, + str::stream() << "object replace expects full object but type was " + << modExpr.type()); } - BSONObjIterator it(modExpr.embeddedObject()); - while (it.more()) { - BSONElement elem = it.next(); - if (*elem.fieldName() == '$') { - return Status(ErrorCodes::BadValue, "can't mix modifiers and non-modifiers"); + if (opts.enforceOkForStorage) { + Status s = modExpr.embeddedObject().storageValid(); + if (!s.isOK()) + return s; + } else { + // storageValid checks for $-prefixed field names, so only run if we didn't do that. + BSONObjIterator it(modExpr.embeddedObject()); + while (it.more()) { + BSONElement elem = it.next(); + if (*elem.fieldName() == '$') { + return Status(ErrorCodes::BadValue, + str::stream() << "A replace document can't" + " contain update modifiers: " + << elem.fieldNameStringData()); + } } } diff --git a/src/mongo/db/ops/modifier_pop.cpp b/src/mongo/db/ops/modifier_pop.cpp index 0c0d6ae78bb..2adb3075ca6 100644 --- a/src/mongo/db/ops/modifier_pop.cpp +++ b/src/mongo/db/ops/modifier_pop.cpp @@ -34,9 +34,13 @@ #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 mb = mutablebson; + namespace str = mongoutils::str; + struct ModifierPop::PreparedState { PreparedState(mutablebson::Document* targetDoc) @@ -92,7 +96,9 @@ namespace mongo { &_positionalPathIndex, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional($) elements found in path '" + << _fieldRef.dottedField() << "'"); } // @@ -118,7 +124,10 @@ namespace mongo { // 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, "matched field not provided"); + return Status(ErrorCodes::BadValue, + str::stream() << "The positional operator did not find the match " + "needed from the query. Unexpanded update: " + << _fieldRef.dottedField()); } _preparedState->pathPositionalPart = matchedField.toString(); _fieldRef.setPart(_positionalPathIndex, _preparedState->pathPositionalPart); @@ -135,7 +144,15 @@ namespace mongo { // If the path exists, we require the target field to be already an // array. if (_preparedState->pathFoundElement.getType() != Array) { - return Status(ErrorCodes::BadValue, "can only $pop from arrays"); + mb::Element idElem = mb::findElementNamed(root, "_id"); + return Status( + ErrorCodes::BadValue, + str::stream() << "Can only $pop from arrays. " + << 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 @@ -180,7 +197,10 @@ namespace mongo { doc.makeElementBool(_fieldRef.dottedField(), true); if (!logElement.ok()) { - return Status(ErrorCodes::InternalError, "cannot create details"); + return Status(ErrorCodes::InternalError, + str::stream() << "Could not append entry to $pop oplog entry: " + << "set '" << _fieldRef.dottedField() << "' -> " + << _preparedState->pathFoundElement.toString() ); } // Now, we attach the {<fieldname>: <value>} Element under the {$op: ...} one. diff --git a/src/mongo/db/ops/modifier_pull.cpp b/src/mongo/db/ops/modifier_pull.cpp index cd508b96022..84cd1fddfeb 100644 --- a/src/mongo/db/ops/modifier_pull.cpp +++ b/src/mongo/db/ops/modifier_pull.cpp @@ -34,10 +34,12 @@ #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 mb = mutablebson; + namespace str = mongoutils::str; struct ModifierPull::PreparedState { @@ -96,7 +98,9 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } _exprElt = modExpr; diff --git a/src/mongo/db/ops/modifier_pull_all.cpp b/src/mongo/db/ops/modifier_pull_all.cpp index 4c3b09c790e..eb03c1bc314 100644 --- a/src/mongo/db/ops/modifier_pull_all.cpp +++ b/src/mongo/db/ops/modifier_pull_all.cpp @@ -34,9 +34,13 @@ #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 mb = mutablebson; + namespace str = mongoutils::str; + struct ModifierPullAll::PreparedState { PreparedState(mutablebson::Document* targetDoc) @@ -108,7 +112,9 @@ namespace mongo { &_positionalPathIndex, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } // @@ -116,7 +122,9 @@ namespace mongo { // if (modExpr.type() != Array) { - return Status(ErrorCodes::BadValue, "$pullAll requires an array argument"); + return Status(ErrorCodes::BadValue, + str::stream() << "$pullAll requires an array argument but was given a " + << typeName(modExpr.type())); } // store the stuff to remove later @@ -134,7 +142,10 @@ namespace mongo { // 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, "matched field not provided"); + return Status(ErrorCodes::BadValue, + str::stream() << "The positional operator did not find the match " + "needed from the query. Unexpanded update: " + << _fieldRef.dottedField()); } _preparedState->pathPositionalPart = matchedField.toString(); _fieldRef.setPart(_positionalPathIndex, _preparedState->pathPositionalPart); @@ -151,7 +162,15 @@ namespace mongo { // If the path exists, we require the target field to be already an // array. if (_preparedState->pathFoundElement.getType() != Array) { - return Status(ErrorCodes::BadValue, "can only $pull* from arrays"); + 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 diff --git a/src/mongo/db/ops/modifier_push.cpp b/src/mongo/db/ops/modifier_push.cpp index 4da720fc066..2b207257f56 100644 --- a/src/mongo/db/ops/modifier_push.cpp +++ b/src/mongo/db/ops/modifier_push.cpp @@ -41,6 +41,9 @@ namespace mongo { + namespace mb = mutablebson; + namespace str = mongoutils::str; + namespace { bool isPatternElement(const BSONElement& pattern) { @@ -110,9 +113,9 @@ namespace mongo { while (itEach.more()) { BSONElement eachItem = itEach.next(); if (eachItem.type() == Object || eachItem.type() == Array) { - if (! eachItem.Obj().okForStorage()) { - return Status(ErrorCodes::BadValue, "cannot use '$' in $each entries"); - } + Status s = eachItem.Obj().storageValidEmbedded(); + if (!s.isOK()) + return s; } } @@ -207,7 +210,9 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } // @@ -220,8 +225,10 @@ namespace mongo { switch (modExpr.type()) { case Array: - if (! modExpr.Obj().okForStorage()) { - return Status(ErrorCodes::BadValue, "cannot use '$' or '.' as values"); + { + Status s = modExpr.Obj().storageValidEmbedded(); + if (!s.isOK()) + return s; } if (_pushMode == PUSH_ALL) { @@ -242,7 +249,9 @@ namespace mongo { case Object: if (_pushMode == PUSH_ALL) { - return Status(ErrorCodes::BadValue, "$pushAll requires an array of values"); + return Status(ErrorCodes::BadValue, + str::stream() << "$pushAll requires an array of values " + "but was given an embedded document, not an array."); } // If any known clause ($each, $slice, or $sort) is present, we'd assume @@ -259,16 +268,20 @@ namespace mongo { } } else { - if (! modExpr.Obj().okForStorage()) { - return Status(ErrorCodes::BadValue, "cannot use '$' as values"); - } + Status s = modExpr.Obj().storageValidEmbedded(); + if (!s.isOK()) + return s; + _val = modExpr; } break; default: if (_pushMode == PUSH_ALL) { - return Status(ErrorCodes::BadValue, "$pushAll requires an array of values"); + return Status(ErrorCodes::BadValue, + str::stream() << "$pushAll requires an array of values " + "but was given an " + << typeName(modExpr.type())); } _val = modExpr; @@ -282,14 +295,18 @@ namespace mongo { } if (!sliceElem.isNumber()) { - return Status(ErrorCodes::BadValue, "$slice must be a numeric value"); + return Status(ErrorCodes::BadValue, + str::stream() << "The value for $slice must " + "a be a numeric value not " + << typeName(sliceElem.type())); } // If the value of slice is not fraction, even if it's a double, we allow it. The // reason here is that the shell will use doubles by default unless told otherwise. double fractional = sliceElem.numberDouble(); if (fractional - static_cast<int64_t>(fractional) != 0) { - return Status(ErrorCodes::BadValue, "$slice in $push cannot be fractional"); + return Status(ErrorCodes::BadValue, + "The $slice value in $push cannot be fractional"); } _slice = sliceElem.numberLong(); @@ -299,17 +316,21 @@ namespace mongo { // Is sort present and correct? if (sortElem.type() != EOO) { if (_pushMode == PUSH_ALL) { - return Status(ErrorCodes::BadValue, "cannot use $sort in $pushAll"); + return Status(ErrorCodes::BadValue, + "cannot use $sort in $pushAll"); } if (sortElem.type() != Object && !sortElem.isNumber()) { - return Status(ErrorCodes::BadValue, "invalid $sort clause"); + return Status(ErrorCodes::BadValue, + "The $sort is invalid: use 1/-1 to sort the whole element, " + "or {field:1/-1} to sort embedded fields"); } if (sortElem.isABSONObj()) { BSONObj sortObj = sortElem.embeddedObject(); if (sortObj.isEmpty()) { - return Status(ErrorCodes::BadValue, "sort pattern is empty"); + return Status(ErrorCodes::BadValue, + "The $sort pattern is empty when it should be a set of fields."); } // Check if the sort pattern is sound. @@ -321,7 +342,7 @@ namespace mongo { // We require either <field>: 1 or -1 for asc and desc. if (!isPatternElement(sortPatternElem)) { return Status(ErrorCodes::BadValue, - "$sort elements' must be either 1 or -1"); + "The $sort element value must be either 1 or -1"); } // All fields parts must be valid. @@ -329,13 +350,15 @@ namespace mongo { sortField.parse(sortPatternElem.fieldName()); if (sortField.numParts() == 0) { return Status(ErrorCodes::BadValue, - "$sort field cannot be empty"); + "The $sort field cannot be empty"); } for (size_t i = 0; i < sortField.numParts(); i++) { if (sortField.getPart(i).size() == 0) { return Status(ErrorCodes::BadValue, - "empty field in dotted sort pattern"); + str::stream() << "The $sort field is a dotted field " + "but has an empty part: " + << sortField.dottedField()); } } } @@ -346,7 +369,7 @@ namespace mongo { // Ensure the sortElem number is valid. if (!isPatternElement(sortElem)) { return Status(ErrorCodes::BadValue, - "$sort elements' must be either 1 or -1"); + "The $sort element value must be either 1 or -1"); } _sort = PatternElementCmp(BSON("" << sortElem.number())); @@ -367,7 +390,10 @@ namespace mongo { // 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"); + 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); @@ -395,7 +421,12 @@ namespace mongo { // 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"); + mb::Element idElem = mb::findElementNamed(root.leftChild(), "_id"); + return Status(ErrorCodes::BadValue, + str::stream() << "The field '" << _fieldRef.dottedField() << "'" + << " must be and array but is of type " + << typeName(_preparedState->elemFound.getType()) + << " in document " << idElem.toString() ); } } else { diff --git a/src/mongo/db/ops/modifier_rename.cpp b/src/mongo/db/ops/modifier_rename.cpp index cb7d2875afe..d2114e598b1 100644 --- a/src/mongo/db/ops/modifier_rename.cpp +++ b/src/mongo/db/ops/modifier_rename.cpp @@ -30,6 +30,7 @@ #include "mongo/base/error_codes.h" #include "mongo/bson/mutable/document.h" +#include "mongo/bson/mutable/algorithm.h" #include "mongo/db/ops/field_checker.h" #include "mongo/db/ops/log_builder.h" #include "mongo/db/ops/path_support.h" @@ -37,7 +38,7 @@ namespace mongo { - using mongoutils::str::stream; + namespace str = mongoutils::str; struct ModifierRename::PreparedState { @@ -77,7 +78,9 @@ namespace mongo { Status ModifierRename::init(const BSONElement& modExpr, const Options& opts) { if (modExpr.type() != String) { - return Status(ErrorCodes::BadValue, "rename 'to' field must be a string"); + return Status(ErrorCodes::BadValue, + str::stream() << "The 'to' field for $rename must be a string: " + << modExpr); } // Extract the field names from the mod expression @@ -97,13 +100,17 @@ namespace mongo { // TODO: Remove this restriction and make a noOp to lift restriction // Old restriction is that if the fields are the same then it is not allowed. if (_fromFieldRef == _toFieldRef) - return Status(ErrorCodes::BadValue, "$rename source must differ from target"); + return Status(ErrorCodes::BadValue, + str::stream() << "The source and target field for $rename must differ: " + << modExpr); // TODO: Remove this restriction by allowing moving deeping from the 'from' path // Old restriction is that if the to/from is on the same path it fails if (_fromFieldRef.isPrefixOf(_toFieldRef) || _toFieldRef.isPrefixOf(_fromFieldRef)){ return Status(ErrorCodes::BadValue, - "$rename source/destination cannot be on the same path"); + str::stream() << "The source and target field for $rename must " + "not be on the same path: " + << modExpr); } // TODO: We can remove this restriction as long as there is only one, // or it is the same array -- should think on this a bit. @@ -111,9 +118,13 @@ namespace mongo { // If a $-positional operator was used it is an error size_t dummyPos; if (fieldchecker::isPositional(_fromFieldRef, &dummyPos)) - return Status(ErrorCodes::BadValue, "$rename source may not be dynamic array"); + return Status(ErrorCodes::BadValue, + str::stream() << "The source field for $rename may not be dynamic: " + << _fromFieldRef.dottedField()); else if (fieldchecker::isPositional(_toFieldRef, &dummyPos)) - return Status(ErrorCodes::BadValue, "$rename target may not be dynamic array"); + return Status(ErrorCodes::BadValue, + str::stream() << "The destination field for $rename may not be dynamic: " + << _toFieldRef.dottedField()); return Status::OK(); } @@ -150,7 +161,11 @@ namespace mongo { mutablebson::Element curr = _preparedState->fromElemFound.parent(); while (curr.ok()) { if (curr.getType() == Array) - return Status(ErrorCodes::BadValue, "source field cannot come from an 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(); } @@ -179,7 +194,10 @@ namespace mongo { while (curr.ok()) { if (curr.getType() == Array) return Status(ErrorCodes::BadValue, - "destination field cannot have an array ancestor"); + 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(); } diff --git a/src/mongo/db/ops/modifier_set.cpp b/src/mongo/db/ops/modifier_set.cpp index a3e05ef6f19..65033091e45 100644 --- a/src/mongo/db/ops/modifier_set.cpp +++ b/src/mongo/db/ops/modifier_set.cpp @@ -33,9 +33,11 @@ #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 { @@ -98,7 +100,9 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } // @@ -113,8 +117,10 @@ namespace mongo { case Object: case Array: - if (opts.enforceOkForStorage && !modExpr.Obj().okForStorage()) { - return Status(ErrorCodes::BadValue, "cannot use '$' as values"); + if (opts.enforceOkForStorage) { + Status s = modExpr.Obj().storageValidEmbedded(); + if (!s.isOK()) + return s; } break; diff --git a/src/mongo/db/ops/modifier_unset.cpp b/src/mongo/db/ops/modifier_unset.cpp index 4ab5516b393..48516f825a2 100644 --- a/src/mongo/db/ops/modifier_unset.cpp +++ b/src/mongo/db/ops/modifier_unset.cpp @@ -33,9 +33,12 @@ #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 ModifierUnset::PreparedState { PreparedState(mutablebson::Document* targetDoc) @@ -78,8 +81,7 @@ namespace mongo { // field name analysis // - // Break down the field name into its 'dotted' components (aka parts) and check that - // there are no empty parts. + // Perform standard field name and updateable checks. _fieldRef.parse(modExpr.fieldName()); Status status = fieldchecker::isUpdatableLegacy(_fieldRef); if (! status.isOK()) { @@ -91,9 +93,12 @@ namespace mongo { size_t foundCount; bool foundDollar = fieldchecker::isPositional(_fieldRef, &_posDollar, &foundCount); if (foundDollar && foundCount > 1) { - return Status(ErrorCodes::BadValue, "too many positional($) elements found."); + return Status(ErrorCodes::BadValue, + str::stream() << "Too many positional (i.e. '$') elements found in path '" + << _fieldRef.dottedField() << "'"); } + // // value analysis // @@ -113,12 +118,16 @@ namespace mongo { // 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"); + 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, diff --git a/src/mongo/db/ops/update_driver.cpp b/src/mongo/db/ops/update_driver.cpp index b92bf310170..0133730847b 100644 --- a/src/mongo/db/ops/update_driver.cpp +++ b/src/mongo/db/ops/update_driver.cpp @@ -42,6 +42,8 @@ namespace mongo { + namespace str = mongoutils::str; + UpdateDriver::UpdateDriver(const Options& opts) : _multi(opts.multi) , _upsert(opts.upsert) @@ -91,17 +93,25 @@ namespace mongo { // Check whether this is a valid mod type. modifiertable::ModifierType modType = modifiertable::getType(outerModElem.fieldName()); if (modType == modifiertable::MOD_UNKNOWN) { - return Status(ErrorCodes::FailedToParse, "unknown modifier type"); + return Status(ErrorCodes::FailedToParse, + str::stream() << "Unknown modifier: " << outerModElem.fieldName()); } // Check whether there is indeed a list of mods under this modifier. if (outerModElem.type() != Object) { - return Status(ErrorCodes::FailedToParse, "List of mods must be an object"); + return Status(ErrorCodes::FailedToParse, + str::stream() << "List of $mods must be an embedded document" + " but it is a " + << typeName(outerModElem.type()) + << " instead."); } // Check whether there are indeed mods under this modifier. if (outerModElem.embeddedObject().isEmpty()) { - return Status(ErrorCodes::FailedToParse, "Empty expression after update $mod"); + return Status(ErrorCodes::FailedToParse, + str::stream() << outerModElem.fieldName() + << " is empty. You must specify a field like so: " + "{$mod: {<field>: ...}}"); } BSONObjIterator innerIter(outerModElem.embeddedObject()); @@ -110,7 +120,10 @@ namespace mongo { if (innerModElem.eoo()) { return Status(ErrorCodes::FailedToParse, - "empty entry in $mod expression list"); + str::stream() << outerModElem.fieldName() + << "." << innerModElem.fieldName() + << " has no value: " << innerModElem + << " which is not allowed for any $<mod>."); } auto_ptr<ModifierInterface> mod(modifiertable::makeUpdateMod(modType)); @@ -230,10 +243,11 @@ namespace mongo { const FieldRef* other; if (!targetFields.insert(execInfo.fieldRef[i], &other)) { return Status(ErrorCodes::ConflictingUpdateOperators, - mongoutils::str::stream() - << "Cannot update '" << other->dottedField() - << "' and '" << execInfo.fieldRef[i]->dottedField() - << "' at the same time"); + str::stream() << "Cannot update '" + << other->dottedField() + << "' and '" + << execInfo.fieldRef[i]->dottedField() + << "' at the same time"); } } @@ -342,7 +356,10 @@ namespace mongo { return idPattern.obj(); } else { - uassert( 16980, "multi-update requires all modified objects to have an _id" , ! multi ); + uassert( 16980, + str::stream() << "Multi-update operations require all documents to " + "have an '_id' field. " << doc.toString(false, false), + ! multi ); return doc; } } |