diff options
author | Mathias Stearn <mathias@10gen.com> | 2012-12-05 18:55:25 -0500 |
---|---|---|
committer | Mathias Stearn <mathias@10gen.com> | 2012-12-10 18:54:04 -0500 |
commit | 459467c7cf47b57d8990eeb41c65b4e203e15b65 (patch) | |
tree | e1b969346a3c8f42ff60a078469b86316dd3c3c0 /src | |
parent | fefb4334afe40664438668a289c6daed6813b3c3 (diff) | |
download | mongo-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-x | src/mongo/db/pipeline/accumulator.h | 1 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/accumulator_add_to_set.cpp | 10 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/accumulator_first.cpp | 12 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/accumulator_min_max.cpp | 10 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/accumulator_push.cpp | 9 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/accumulator_sum.cpp | 2 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/document_source_group.cpp | 15 | ||||
-rwxr-xr-x | src/mongo/db/pipeline/document_source_unwind.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 47 | ||||
-rw-r--r-- | src/mongo/db/pipeline/value.cpp | 30 | ||||
-rw-r--r-- | src/mongo/db/pipeline/value.h | 11 | ||||
-rw-r--r-- | src/mongo/db/pipeline/value_internal.h | 1 | ||||
-rw-r--r-- | src/mongo/dbtests/accumulatortests.cpp | 18 | ||||
-rw-r--r-- | src/mongo/dbtests/documentsourcetests.cpp | 2 | ||||
-rw-r--r-- | src/mongo/dbtests/expressiontests.cpp | 35 |
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>(); |