summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMathias Stearn <mathias@10gen.com>2012-12-05 18:55:25 -0500
committerMathias Stearn <mathias@10gen.com>2012-12-10 18:54:04 -0500
commit459467c7cf47b57d8990eeb41c65b4e203e15b65 (patch)
treee1b969346a3c8f42ff60a078469b86316dd3c3c0 /src
parentfefb4334afe40664438668a289c6daed6813b3c3 (diff)
downloadmongo-459467c7cf47b57d8990eeb41c65b4e203e15b65.tar.gz
Normalize handling of Undefined vs EOO/missing in agg
Related tickets: SERVER-6571 replace use of Undefined as missing with EOO SERVER-6471 ignore nullish values in $min and $max accumulators SERVER-6144 divide by zero makes field disappear (this solution isn't final)
Diffstat (limited to 'src')
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator.h1
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_add_to_set.cpp10
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_first.cpp12
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_min_max.cpp10
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_push.cpp9
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_sum.cpp2
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_group.cpp15
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_unwind.cpp22
-rw-r--r--src/mongo/db/pipeline/expression.cpp47
-rw-r--r--src/mongo/db/pipeline/value.cpp30
-rw-r--r--src/mongo/db/pipeline/value.h11
-rw-r--r--src/mongo/db/pipeline/value_internal.h1
-rw-r--r--src/mongo/dbtests/accumulatortests.cpp18
-rw-r--r--src/mongo/dbtests/documentsourcetests.cpp2
-rw-r--r--src/mongo/dbtests/expressiontests.cpp35
15 files changed, 117 insertions, 108 deletions
diff --git a/src/mongo/db/pipeline/accumulator.h b/src/mongo/db/pipeline/accumulator.h
index 16606a0dbbd..a4b52e90318 100755
--- a/src/mongo/db/pipeline/accumulator.h
+++ b/src/mongo/db/pipeline/accumulator.h
@@ -121,6 +121,7 @@ namespace mongo {
const intrusive_ptr<ExpressionContext> &pCtx);
private:
+ mutable bool _haveFirst;
AccumulatorFirst();
};
diff --git a/src/mongo/db/pipeline/accumulator_add_to_set.cpp b/src/mongo/db/pipeline/accumulator_add_to_set.cpp
index 81c96ff9091..d64faf48b8f 100755
--- a/src/mongo/db/pipeline/accumulator_add_to_set.cpp
+++ b/src/mongo/db/pipeline/accumulator_add_to_set.cpp
@@ -25,11 +25,11 @@ namespace mongo {
verify(vpOperand.size() == 1);
Value prhs(vpOperand[0]->evaluate(pDocument));
- if (prhs.getType() == Undefined)
- ; /* nothing to add to the array */
- else if (!pCtx->getDoingMerge())
- set.insert(prhs);
- else {
+ if (!pCtx->getDoingMerge()) {
+ if (!prhs.missing()) {
+ set.insert(prhs);
+ }
+ } else {
/*
If we're in the router, we need to take apart the arrays we
receive and put their elements into the array we are collecting.
diff --git a/src/mongo/db/pipeline/accumulator_first.cpp b/src/mongo/db/pipeline/accumulator_first.cpp
index b226fb552d6..ce53cccd365 100755
--- a/src/mongo/db/pipeline/accumulator_first.cpp
+++ b/src/mongo/db/pipeline/accumulator_first.cpp
@@ -25,15 +25,19 @@ namespace mongo {
verify(vpOperand.size() == 1);
/* only remember the first value seen */
- if (pValue.missing())
+ if (!_haveFirst) {
+ // can't use pValue.missing() since we want the first value even if missing
+ _haveFirst = true;
pValue = vpOperand[0]->evaluate(pDocument);
+ }
return pValue;
}
- AccumulatorFirst::AccumulatorFirst():
- AccumulatorSingleValue() {
- }
+ AccumulatorFirst::AccumulatorFirst()
+ : AccumulatorSingleValue()
+ , _haveFirst(false)
+ {}
intrusive_ptr<Accumulator> AccumulatorFirst::create(
const intrusive_ptr<ExpressionContext> &pCtx) {
diff --git a/src/mongo/db/pipeline/accumulator_min_max.cpp b/src/mongo/db/pipeline/accumulator_min_max.cpp
index bc48b6fb480..86172cb3338 100755
--- a/src/mongo/db/pipeline/accumulator_min_max.cpp
+++ b/src/mongo/db/pipeline/accumulator_min_max.cpp
@@ -25,17 +25,15 @@ namespace mongo {
verify(vpOperand.size() == 1);
Value prhs(vpOperand[0]->evaluate(pDocument));
- /* if this is the first value, just use it */
- if (pValue.missing())
- pValue = prhs;
- else {
+ // nullish values should have no impact on result
+ if (!prhs.nullish()) {
/* compare with the current value; swap if appropriate */
int cmp = Value::compare(pValue, prhs) * sense;
- if (cmp > 0)
+ if (cmp > 0 || pValue.missing()) // missing is lower than all other values
pValue = prhs;
}
- return pValue;
+ return Value();
}
AccumulatorMinMax::AccumulatorMinMax(int theSense):
diff --git a/src/mongo/db/pipeline/accumulator_push.cpp b/src/mongo/db/pipeline/accumulator_push.cpp
index 4530b798465..f5c76323602 100755
--- a/src/mongo/db/pipeline/accumulator_push.cpp
+++ b/src/mongo/db/pipeline/accumulator_push.cpp
@@ -25,11 +25,10 @@ namespace mongo {
verify(vpOperand.size() == 1);
Value prhs(vpOperand[0]->evaluate(pDocument));
- if (prhs.getType() == Undefined) {
- /* nothing to add to the array */
- }
- else if (!pCtx->getDoingMerge()) {
- vpValue.push_back(prhs);
+ if (!pCtx->getDoingMerge()) {
+ if (!prhs.missing()) {
+ vpValue.push_back(prhs);
+ }
}
else {
/*
diff --git a/src/mongo/db/pipeline/accumulator_sum.cpp b/src/mongo/db/pipeline/accumulator_sum.cpp
index 6fe091f3cf4..353b67f5b9f 100755
--- a/src/mongo/db/pipeline/accumulator_sum.cpp
+++ b/src/mongo/db/pipeline/accumulator_sum.cpp
@@ -29,7 +29,7 @@ namespace mongo {
// do nothing with non numeric types
if (!(rhsType == NumberInt || rhsType == NumberLong || rhsType == NumberDouble))
- return Value(0);
+ return Value();
// upgrade to the widest type required to hold the result
totalType = Value::getWidestNumeric(totalType, rhsType);
diff --git a/src/mongo/db/pipeline/document_source_group.cpp b/src/mongo/db/pipeline/document_source_group.cpp
index 5ac8d83345b..ff0752e3264 100755
--- a/src/mongo/db/pipeline/document_source_group.cpp
+++ b/src/mongo/db/pipeline/document_source_group.cpp
@@ -286,11 +286,11 @@ namespace mongo {
Document pDocument(pSource->getCurrent());
/* get the _id value */
- Value pId(pIdExpression->evaluate(pDocument));
+ Value pId = pIdExpression->evaluate(pDocument);
- /* treat Undefined the same as NULL SERVER-4674 */
- if (pId.getType() == Undefined)
- pId = Value(jstNULL);
+ /* treat missing values the same as NULL SERVER-4674 */
+ if (pId.missing())
+ pId = Value(BSONNULL);
/*
Look for the _id value in the map; if it's not there, add a
@@ -349,8 +349,13 @@ namespace mongo {
/* add the rest of the fields */
for(size_t i = 0; i < n; ++i) {
Value pValue((*pGroup)[i]->getValue());
- if (pValue.getType() != Undefined)
+ if (pValue.missing()) {
+ // we return undefined in this case so return objects are predictable
+ out.addField(vFieldName[i], Value(BSONUndefined));
+ }
+ else {
out.addField(vFieldName[i], pValue);
+ }
}
return out.freeze();
diff --git a/src/mongo/db/pipeline/document_source_unwind.cpp b/src/mongo/db/pipeline/document_source_unwind.cpp
index 4086372133c..249d3efb593 100755
--- a/src/mongo/db/pipeline/document_source_unwind.cpp
+++ b/src/mongo/db/pipeline/document_source_unwind.cpp
@@ -68,18 +68,8 @@ namespace mongo {
_index = 0;
Value pathValue = document.getNestedField(_unwindPath, &_unwindPathFieldIndexes);
- if (pathValue.missing()) {
- // The path does not exist.
- return;
- }
-
- bool nothingToEmit =
- (pathValue.getType() == jstNULL) ||
- (pathValue.getType() == Undefined) ||
- ((pathValue.getType() == Array) && (pathValue.getArrayLength() == 0));
-
- if (nothingToEmit) {
- // The target field exists, but there are no values to unwind.
+ if (pathValue.nullish()) {
+ // The path does not exist or is null.
return;
}
@@ -88,13 +78,17 @@ namespace mongo {
<< ": value at end of field path must be an array",
pathValue.getType() == Array);
+ if (pathValue.getArray().empty()) {
+ // there are no values to unwind.
+ return;
+ }
+
_inputArray = pathValue;
verify(!eof()); // Checked above that the array is nonempty.
}
bool DocumentSourceUnwind::Unwinder::eof() const {
- return (_inputArray.missing()) // getType asserts if missing
- || (_inputArray.getType() != Array)
+ return (_inputArray.getType() != Array)
|| (_index == _inputArray.getArrayLength());
}
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 2c64159af53..fcc049c9d47 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -946,7 +946,7 @@ namespace mongo {
double right = pRight.coerceToDouble();
if (right == 0)
- return Value(Undefined);
+ return Value(BSONUndefined);
double left = pLeft.coerceToDouble();
@@ -1075,15 +1075,13 @@ namespace mongo {
continue;
/*
- Don't add non-existent values (note: different from NULL);
+ Don't add non-existent values (note: different from NULL or Undefined);
this is consistent with existing selection syntax which doesn't
force the appearance of non-existent fields.
*/
- // TODO make missing distinct from Undefined
- if (pValue.getType() != Undefined)
+ if (!pValue.missing())
out.addField(field.first, pValue);
-
continue;
}
@@ -1141,11 +1139,11 @@ namespace mongo {
Value pValue(it->second->evaluate(rootDoc));
/*
- Don't add non-existent values (note: different from NULL);
+ Don't add non-existent values (note: different from NULL or Undefined);
this is consistent with existing selection syntax which doesn't
force the appearnance of non-existent fields.
*/
- if (pValue.getType() == Undefined)
+ if (pValue.missing())
continue;
// don't add field if nothing was found in the subobject
@@ -1310,7 +1308,7 @@ namespace mongo {
/* if the field doesn't exist, quit with an undefined value */
if (pValue.missing())
- return Value(Undefined);
+ return Value();
/* if we've hit the end of the path, stop */
++index;
@@ -1320,10 +1318,10 @@ namespace mongo {
/*
We're diving deeper. If the value was null, return null.
*/
- BSONType type = pValue.getType();
- if ((type == Undefined) || (type == jstNULL))
- return Value(Undefined);
+ if (pValue.nullish())
+ return Value();
+ BSONType type = pValue.getType();
if (type == Object) {
/* extract from the next level down */
return evaluatePath(index, pathLength, pValue.getDocument());
@@ -1338,8 +1336,7 @@ namespace mongo {
const vector<Value>& input = pValue.getArray();
for (size_t i=0; i < input.size(); i++) {
const Value& item = input[i];
- BSONType iType = item.getType();
- if ((iType == Undefined) || (iType == jstNULL)) {
+ if (item.nullish()) {
result.push_back(item);
continue;
}
@@ -1349,15 +1346,15 @@ namespace mongo {
"' along the dotted path '" <<
fieldPath.getPath(false) <<
"' is not an object, and cannot be navigated",
- iType == Object);
+ item.getType() == Object);
result.push_back(evaluatePath(index, pathLength, item.getDocument()));
}
return Value::createArray(result);
}
- // subdocument field does not exist, return undefined
- return Value(Undefined);
+ // subdocument field does not exist
+ return Value();
}
Value ExpressionFieldPath::evaluate(const Document& pDocument) const {
@@ -1745,20 +1742,21 @@ namespace mongo {
Value pLeft(vpOperand[0]->evaluate(pDocument));
Value pRight(vpOperand[1]->evaluate(pDocument));
+ // pass along nullish values
+ if (pLeft.nullish())
+ return pLeft;
+ if (pRight.nullish())
+ return pRight;
+
BSONType leftType = pLeft.getType();
BSONType rightType = pRight.getType();
uassert(16374, "$mod does not support dates", leftType != Date && rightType != Date);
- // pass along jstNULLs and Undefineds
- if (leftType == jstNULL || leftType == Undefined)
- return pLeft;
- if (rightType == jstNULL || rightType == Undefined)
- return pRight;
// ensure we aren't modding by 0
double right = pRight.coerceToDouble();
if (right == 0)
- return Value(Undefined);
+ return Value(BSONUndefined);
if (leftType == NumberDouble) {
// left is a double, return a double
@@ -1927,10 +1925,9 @@ namespace mongo {
Value ExpressionIfNull::evaluate(const Document& pDocument) const {
checkArgCount(2);
- Value pLeft(vpOperand[0]->evaluate(pDocument));
- BSONType leftType = pLeft.getType();
- if ((leftType != Undefined) && (leftType != jstNULL))
+ Value pLeft(vpOperand[0]->evaluate(pDocument));
+ if (!pLeft.nullish())
return pLeft;
Value pRight(vpOperand[1]->evaluate(pDocument));
diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp
index 8e4cb838e1c..1ea08160d30 100644
--- a/src/mongo/db/pipeline/value.cpp
+++ b/src/mongo/db/pipeline/value.cpp
@@ -246,23 +246,20 @@ namespace mongo {
}
Value Value::operator[] (size_t index) const {
- if (missing() || getType() != Array || index >= getArrayLength())
+ if (getType() != Array || index >= getArrayLength())
return Value();
return getArray()[index];
}
Value Value::operator[] (StringData name) const {
- if (missing() || getType() != Object)
+ if (getType() != Object)
return Value();
return getDocument()[name];
}
BSONObjBuilder& operator << (BSONObjBuilderValueStream& builder, const Value& val) {
- if (val.missing())
- return builder.builder();
-
switch(val.getType()) {
case EOO: return builder.builder(); // nothing appended
case MinKey: return builder << MINKEY;
@@ -319,9 +316,6 @@ namespace mongo {
}
bool Value::coerceToBool() const {
- if (missing())
- return false;
-
// TODO Unify the implementation with BSONElement::trueValue().
switch(getType()) {
case CodeWScope:
@@ -364,6 +358,7 @@ namespace mongo {
case NumberLong:
return static_cast<int>(_storage.longValue);
+ case EOO:
case jstNULL:
case Undefined:
return 0;
@@ -387,6 +382,7 @@ namespace mongo {
case NumberLong:
return _storage.longValue;
+ case EOO:
case jstNULL:
case Undefined:
return 0;
@@ -410,6 +406,7 @@ namespace mongo {
case NumberLong:
return static_cast<double>(_storage.longValue);
+ case EOO:
case jstNULL:
case Undefined:
return 0;
@@ -515,6 +512,7 @@ namespace mongo {
case Date:
return tmToISODateString(coerceToTm());
+ case EOO:
case jstNULL:
case Undefined:
return "";
@@ -572,10 +570,8 @@ namespace mongo {
int Value::compare(const Value& rL, const Value& rR) {
// Note, this function needs to behave identically to BSON's compareElementValues().
// Additionally, any changes here must be replicated in hash_combine().
-
- // TODO: remove conditional after SERVER-6571
- BSONType lType = rL.missing() ? EOO : rL.getType();
- BSONType rType = rR.missing() ? EOO : rR.getType();
+ BSONType lType = rL.getType();
+ BSONType rType = rR.getType();
int ret = lType == rType
? 0 // fast-path common case
@@ -694,8 +690,7 @@ namespace mongo {
}
void Value::hash_combine(size_t &seed) const {
- // TODO: remove conditional after SERVER-6571
- BSONType type = missing() ? EOO : getType();
+ BSONType type = getType();
boost::hash_combine(seed, canonicalizeBSONType(type));
@@ -802,6 +797,7 @@ namespace mongo {
case NumberDouble:
case NumberLong:
case NumberInt:
+ case EOO:
case jstNULL:
case Undefined:
return NumberDouble;
@@ -817,6 +813,7 @@ namespace mongo {
case NumberLong:
case NumberInt:
+ case EOO:
case jstNULL:
case Undefined:
return NumberLong;
@@ -834,6 +831,7 @@ namespace mongo {
return NumberLong;
case NumberInt:
+ case EOO:
case jstNULL:
case Undefined:
return NumberInt;
@@ -842,7 +840,7 @@ namespace mongo {
break;
}
}
- else if ((lType == jstNULL) || (lType == Undefined)) {
+ else if (lType == EOO || lType == jstNULL || lType == Undefined) {
switch(rType) {
case NumberDouble:
return NumberDouble;
@@ -919,8 +917,6 @@ namespace mongo {
}
ostream& operator << (ostream& out, const Value& val) {
- if (val.missing()) return out << "MISSING";
-
switch(val.getType()) {
case EOO: return out << "MISSING";
case MinKey: return out << "MinKey";
diff --git a/src/mongo/db/pipeline/value.h b/src/mongo/db/pipeline/value.h
index 95b90a354e5..7428cc87b5b 100644
--- a/src/mongo/db/pipeline/value.h
+++ b/src/mongo/db/pipeline/value.h
@@ -103,9 +103,14 @@ namespace mongo {
*/
bool missing() const { return _storage.type == EOO; }
- /** Get the BSON type of the field.
- * Warning: currently asserts if missing. This will probably change in the future.
- */
+ /// true if missing() or type is jstNULL or Undefined
+ bool nullish() const {
+ return missing()
+ || _storage.type == jstNULL
+ || _storage.type == Undefined;
+ }
+
+ /// Get the BSON type of the field.
BSONType getType() const { return _storage.bsonType(); }
/** Exact type getters.
diff --git a/src/mongo/db/pipeline/value_internal.h b/src/mongo/db/pipeline/value_internal.h
index e9176c8394e..4415dda136a 100644
--- a/src/mongo/db/pipeline/value_internal.h
+++ b/src/mongo/db/pipeline/value_internal.h
@@ -171,7 +171,6 @@ namespace mongo {
Document getDocument() const;
BSONType bsonType() const {
- verify(type != EOO);
return BSONType(type);
}
diff --git a/src/mongo/dbtests/accumulatortests.cpp b/src/mongo/dbtests/accumulatortests.cpp
index efec273c197..a4e29f107d2 100644
--- a/src/mongo/dbtests/accumulatortests.cpp
+++ b/src/mongo/dbtests/accumulatortests.cpp
@@ -359,13 +359,13 @@ namespace AccumulatorTests {
}
};
- /* The accumulator evaluates one document with the field missing retains undefined. */
+ /* The accumulator evaluates one document with the field missing, returns missing value. */
class Missing : public Base {
public:
void run() {
createAccumulator();
accumulator()->evaluate( fromjson( "{}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( EOO, accumulator()->getValue().getType() );
}
};
@@ -380,14 +380,14 @@ namespace AccumulatorTests {
}
};
- /* The accumulator evaluates two documents and retains the undefined value in the first. */
+ /* The accumulator evaluates two documents and retains the missing value in the first. */
class FirstMissing : public Base {
public:
void run() {
createAccumulator();
accumulator()->evaluate( fromjson( "{}" ) );
accumulator()->evaluate( fromjson( "{a:7}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( EOO, accumulator()->getValue().getType() );
}
};
@@ -433,7 +433,7 @@ namespace AccumulatorTests {
void run() {
createAccumulator();
accumulator()->evaluate( fromjson( "{}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( EOO , accumulator()->getValue().getType() );
}
};
@@ -455,7 +455,7 @@ namespace AccumulatorTests {
createAccumulator();
accumulator()->evaluate( fromjson( "{b:7}" ) );
accumulator()->evaluate( fromjson( "{}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( EOO , accumulator()->getValue().getType() );
}
};
@@ -501,7 +501,7 @@ namespace AccumulatorTests {
void run() {
createAccumulator();
accumulator()->evaluate( fromjson( "{}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( EOO , accumulator()->getValue().getType() );
}
};
@@ -523,7 +523,7 @@ namespace AccumulatorTests {
createAccumulator();
accumulator()->evaluate( fromjson( "{c:7}" ) );
accumulator()->evaluate( fromjson( "{}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( 7 , accumulator()->getValue().getInt() );
}
};
@@ -569,7 +569,7 @@ namespace AccumulatorTests {
void run() {
createAccumulator();
accumulator()->evaluate( fromjson( "{}" ) );
- ASSERT_EQUALS( Undefined, accumulator()->getValue().getType() );
+ ASSERT_EQUALS( EOO, accumulator()->getValue().getType() );
}
};
diff --git a/src/mongo/dbtests/documentsourcetests.cpp b/src/mongo/dbtests/documentsourcetests.cpp
index 67e0ddebfd3..e4dc5519f73 100644
--- a/src/mongo/dbtests/documentsourcetests.cpp
+++ b/src/mongo/dbtests/documentsourcetests.cpp
@@ -808,7 +808,7 @@ namespace DocumentSourceTests {
virtual BSONObj groupSpec() {
return BSON( "_id" << 0 << "first" << BSON( "$first" << "$missing" ) );
}
- virtual string expectedResultSetString() { return "[{_id:0}]"; }
+ virtual string expectedResultSetString() { return "[{_id:0, first:undefined}]"; }
};
/** Simulate merging sharded results in the router. */
diff --git a/src/mongo/dbtests/expressiontests.cpp b/src/mongo/dbtests/expressiontests.cpp
index 39a25447fe7..9f7ad2c0305 100644
--- a/src/mongo/dbtests/expressiontests.cpp
+++ b/src/mongo/dbtests/expressiontests.cpp
@@ -970,8 +970,7 @@ namespace ExpressionTests {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a" );
- // Result is undefined.
- assertBinaryEqual( fromjson( "{'':undefined}" ),
+ assertBinaryEqual( fromjson( "{}" ),
toBson( expression->evaluate( Document() ) ) );
}
};
@@ -992,7 +991,7 @@ namespace ExpressionTests {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
- assertBinaryEqual( fromjson( "{'':undefined}" ),
+ assertBinaryEqual( fromjson( "{}" ),
toBson( expression->evaluate
( fromBson( fromjson( "{a:null}" ) ) ) ) );
}
@@ -1003,18 +1002,29 @@ namespace ExpressionTests {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
- assertBinaryEqual( fromjson( "{'':undefined}" ),
+ assertBinaryEqual( fromjson( "{}" ),
toBson( expression->evaluate
( fromBson( fromjson( "{a:undefined}" ) ) ) ) );
}
};
+
+ /** Target field parent is missing. */
+ class NestedBelowMissing {
+ public:
+ void run() {
+ intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
+ assertBinaryEqual( fromjson( "{}" ),
+ toBson( expression->evaluate
+ ( fromBson( fromjson( "{z:1}" ) ) ) ) );
+ }
+ };
/** Target field parent is an integer. */
class NestedBelowInt {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
- assertBinaryEqual( fromjson( "{'':undefined}" ),
+ assertBinaryEqual( fromjson( "{}" ),
toBson( expression->evaluate
( fromBson( BSON( "a" << 2 ) ) ) ) );
}
@@ -1036,7 +1046,7 @@ namespace ExpressionTests {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
- assertBinaryEqual( fromjson( "{'':undefined}" ),
+ assertBinaryEqual( fromjson( "{}" ),
toBson( expression->evaluate
( fromBson( BSON( "a" << BSONObj() ) ) ) ) );
}
@@ -1101,7 +1111,7 @@ namespace ExpressionTests {
public:
void run() {
intrusive_ptr<Expression> expression = ExpressionFieldPath::create( "a.b" );
- assertBinaryEqual( fromjson( "{'':[9,null,undefined,undefined,20,undefined]}" ),
+ assertBinaryEqual( fromjson( "{'':[9,null,undefined,20]}" ),
toBson( expression->evaluate
( fromBson( fromjson
( "{a:[{b:9},null,undefined,{g:4},{b:20},{}]}"
@@ -1889,7 +1899,7 @@ namespace ExpressionTests {
}
};
- /** An undefined value is not projected.. */
+ /** An undefined value is passed through */
class ComputedUndefined : public ExpectedResultBase {
public:
virtual BSONObj source() {
@@ -1899,7 +1909,7 @@ namespace ExpressionTests {
expression()->addField( mongo::FieldPath( "a" ),
ExpressionConstant::create( Value(mongo::Undefined) ) );
}
- BSONObj expected() { return BSON( "_id" << 0 ); }
+ BSONObj expected() { return BSON( "_id" << 0 << "a" << BSONUndefined); }
BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); }
BSONObj expectedBsonRepresentation() {
return fromjson( "{a:{$const:undefined}}" );
@@ -1986,13 +1996,13 @@ namespace ExpressionTests {
// Create a sub expression returning an empty object.
intrusive_ptr<ExpressionObject> subExpression = ExpressionObject::create();
subExpression->addField( mongo::FieldPath( "b" ),
- ExpressionConstant::create( Value(mongo::Undefined) ) );
+ ExpressionFieldPath::create( "a.b" ) );
expression()->addField( mongo::FieldPath( "a" ), subExpression );
}
BSONObj expected() { return BSON( "_id" << 0 ); }
- BSONArray expectedDependencies() { return BSON_ARRAY( "_id" ); }
+ BSONArray expectedDependencies() { return BSON_ARRAY( "_id" << "a.b"); }
BSONObj expectedBsonRepresentation() {
- return fromjson( "{a:{b:{$const:undefined}}}" );
+ return fromjson( "{a:{b:'$a.b'}}" );
}
bool expectedIsSimple() { return false; }
};
@@ -3269,6 +3279,7 @@ namespace ExpressionTests {
add<FieldPath::Present>();
add<FieldPath::NestedBelowNull>();
add<FieldPath::NestedBelowUndefined>();
+ add<FieldPath::NestedBelowMissing>();
add<FieldPath::NestedBelowInt>();
add<FieldPath::NestedValue>();
add<FieldPath::NestedBelowEmptyObject>();