summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/bson/mutable/element.cpp3
-rw-r--r--src/mongo/bson/mutable/element.h3
-rw-r--r--src/mongo/db/jsobj.cpp2
-rw-r--r--src/mongo/db/ops/field_checker.cpp24
-rw-r--r--src/mongo/db/ops/modifier_add_to_set.cpp51
-rw-r--r--src/mongo/db/ops/modifier_bit.cpp53
-rw-r--r--src/mongo/db/ops/modifier_compare.cpp12
-rw-r--r--src/mongo/db/ops/modifier_current_date.cpp9
-rw-r--r--src/mongo/db/ops/modifier_inc.cpp43
-rw-r--r--src/mongo/db/ops/modifier_object_replace.cpp33
-rw-r--r--src/mongo/db/ops/modifier_pop.cpp28
-rw-r--r--src/mongo/db/ops/modifier_pull.cpp6
-rw-r--r--src/mongo/db/ops/modifier_pull_all.cpp27
-rw-r--r--src/mongo/db/ops/modifier_push.cpp75
-rw-r--r--src/mongo/db/ops/modifier_rename.cpp34
-rw-r--r--src/mongo/db/ops/modifier_set.cpp12
-rw-r--r--src/mongo/db/ops/modifier_unset.cpp17
-rw-r--r--src/mongo/db/ops/update_driver.cpp35
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;
}
}