summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWan Bachtiar <sindbach@gmail.com>2017-01-19 11:00:30 +1100
committerTess Avitabile <tess.avitabile@mongodb.com>2017-03-03 13:48:52 -0500
commit6a6bffee00e95776f7dd50e96aa0b8874ca7a01d (patch)
treed6659b5edaecb8a357878006968fbcd61ad3cb6a
parentb549d892f16ca434927258ac300cd42c3d061af7 (diff)
downloadmongo-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.js95
-rw-r--r--src/mongo/db/pipeline/expression.cpp33
-rw-r--r--src/mongo/db/pipeline/expression.h8
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: