diff options
author | Wan Bachtiar <sindbach@gmail.com> | 2017-01-19 11:00:30 +1100 |
---|---|---|
committer | Tess Avitabile <tess.avitabile@mongodb.com> | 2017-03-03 13:48:52 -0500 |
commit | 6a6bffee00e95776f7dd50e96aa0b8874ca7a01d (patch) | |
tree | d6659b5edaecb8a357878006968fbcd61ad3cb6a | |
parent | b549d892f16ca434927258ac300cd42c3d061af7 (diff) | |
download | mongo-6a6bffee00e95776f7dd50e96aa0b8874ca7a01d.tar.gz |
SERVER-18794 Add aggregation pipeline expression $objectToArray
Closes #1
Signed-off-by: Tess Avitabile <tess.avitabile@mongodb.com>
-rw-r--r-- | jstests/aggregation/expressions/objectToArray.js | 95 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 33 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 8 |
3 files changed, 136 insertions, 0 deletions
diff --git a/jstests/aggregation/expressions/objectToArray.js b/jstests/aggregation/expressions/objectToArray.js new file mode 100644 index 00000000000..5f92b0ae1a9 --- /dev/null +++ b/jstests/aggregation/expressions/objectToArray.js @@ -0,0 +1,95 @@ +// Tests for the $objectToArray aggregation expression. +(function() { + "use strict"; + + // For assertErrorCode(). + load("jstests/aggregation/extras/utils.js"); + + let coll = db.object_to_array_expr; + coll.drop(); + + let object_to_array_expr = {$project: {expanded: {$objectToArray: "$subDoc"}}}; + + // $objectToArray correctly converts a document to an array of key-value pairs. + assert.writeOK(coll.insert({_id: 0, subDoc: {"a": 1, "b": 2, "c": "foo"}})); + let result = coll.aggregate([{$match: {_id: 0}}, object_to_array_expr]).toArray(); + assert.eq( + result, + [{_id: 0, expanded: [{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "c", "v": "foo"}]}]); + + assert.writeOK(coll.insert({_id: 1, subDoc: {"y": []}})); + result = coll.aggregate([{$match: {_id: 1}}, object_to_array_expr]).toArray(); + assert.eq(result, [{_id: 1, expanded: [{"k": "y", "v": []}]}]); + + assert.writeOK(coll.insert({_id: 2, subDoc: {"a": 1, "b": {"d": "string"}, "c": [1, 2]}})); + result = coll.aggregate([{$match: {_id: 2}}, object_to_array_expr]).toArray(); + assert.eq( + result, [{ + _id: 2, + expanded: + [{"k": "a", "v": 1}, {"k": "b", "v": {"d": "string"}}, {"k": "c", "v": [1, 2]}] + }]); + + assert.writeOK(coll.insert({_id: 3, subDoc: {}})); + result = coll.aggregate([{$match: {_id: 3}}, object_to_array_expr]).toArray(); + assert.eq(result, [{_id: 3, expanded: []}]); + + // Turns to array from the root of the document. + assert.writeOK(coll.insert({_id: 4, "a": 1, "b": 2, "c": 3})); + result = + coll.aggregate([{$match: {_id: 4}}, {$project: {document: {$objectToArray: "$$ROOT"}}}]) + .toArray(); + assert.eq(result, [ + { + _id: 4, + document: + [{"k": "_id", "v": 4}, {"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "c", "v": 3}] + } + ]); + + assert.writeOK(coll.insert({_id: 5, "date": ISODate("2017-01-24T00:00:00")})); + result = coll.aggregate([ + {$match: {_id: 5}}, + {$project: {document: {$objectToArray: {dayOfWeek: {$dayOfWeek: "$date"}}}}} + ]) + .toArray(); + assert.eq(result, [{_id: 5, document: [{"k": "dayOfWeek", "v": 3}]}]); + + // $objectToArray errors on non-document types. + assert.writeOK(coll.insert({_id: 6, subDoc: "string"})); + assertErrorCode(coll, [{$match: {_id: 6}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 7, subDoc: ObjectId()})); + assertErrorCode(coll, [{$match: {_id: 7}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 8, subDoc: NumberLong(0)})); + assertErrorCode(coll, [{$match: {_id: 8}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 9, subDoc: []})); + assertErrorCode(coll, [{$match: {_id: 9}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 10, subDoc: [0]})); + assertErrorCode(coll, [{$match: {_id: 10}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 11, subDoc: ["string"]})); + assertErrorCode(coll, [{$match: {_id: 11}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 12, subDoc: [{"a": "b"}]})); + assertErrorCode(coll, [{$match: {_id: 12}}, object_to_array_expr], 40390); + + assert.writeOK(coll.insert({_id: 13, subDoc: NaN})); + assertErrorCode(coll, [{$match: {_id: 13}}, object_to_array_expr], 40390); + + // $objectToArray outputs null on null-ish types. + assert.writeOK(coll.insert({_id: 14, subDoc: null})); + result = coll.aggregate([{$match: {_id: 14}}, object_to_array_expr]).toArray(); + assert.eq(result, [{_id: 14, expanded: null}]); + + assert.writeOK(coll.insert({_id: 15, subDoc: undefined})); + result = coll.aggregate([{$match: {_id: 15}}, object_to_array_expr]).toArray(); + assert.eq(result, [{_id: 15, expanded: null}]); + + assert.writeOK(coll.insert({_id: 16})); + result = coll.aggregate([{$match: {_id: 16}}, object_to_array_expr]).toArray(); + assert.eq(result, [{_id: 16, expanded: null}]); +}()); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index d9a087e08c1..e320d4a9529 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -600,6 +600,39 @@ const char* ExpressionArrayElemAt::getOpName() const { return "$arrayElemAt"; } +/* ------------------------- ExpressionObjectToArray -------------------------- */ + +Value ExpressionObjectToArray::evaluateInternal(Variables* vars) const { + const Value targetVal = vpOperand[0]->evaluateInternal(vars); + + if (targetVal.nullish()) { + return Value(BSONNULL); + } + + uassert(40390, + str::stream() << "$objectToArray requires a document input, found: " + << typeName(targetVal.getType()), + (targetVal.getType() == BSONType::Object)); + + vector<Value> output; + + FieldIterator iter = targetVal.getDocument().fieldIterator(); + while (iter.more()) { + Document::FieldPair pair = iter.next(); + MutableDocument keyvalue; + keyvalue.addField("k", Value(pair.first)); + keyvalue.addField("v", pair.second); + output.push_back(keyvalue.freezeToValue()); + } + + return Value(output); +} + +REGISTER_EXPRESSION(objectToArray, ExpressionObjectToArray::parse); +const char* ExpressionObjectToArray::getOpName() const { + return "$objectToArray"; +} + /* ------------------------- ExpressionCeil -------------------------- */ Value ExpressionCeil::evaluateNumericArg(const Value& numericArg) const { diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index ef9a3ae3f19..f1507643609 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -584,6 +584,14 @@ public: const char* getOpName() const final; }; +class ExpressionObjectToArray final : public ExpressionFixedArity<ExpressionObjectToArray, 1> { +public: + explicit ExpressionObjectToArray(const boost::intrusive_ptr<ExpressionContext>& expCtx) + : ExpressionFixedArity<ExpressionObjectToArray, 1>(expCtx) {} + + Value evaluateInternal(Variables* vars) const final; + const char* getOpName() const final; +}; class ExpressionCeil final : public ExpressionSingleNumericArg<ExpressionCeil> { public: |