summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec/projection_exec_test.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/exec/projection_exec_test.cpp')
-rw-r--r--src/mongo/db/exec/projection_exec_test.cpp219
1 files changed, 219 insertions, 0 deletions
diff --git a/src/mongo/db/exec/projection_exec_test.cpp b/src/mongo/db/exec/projection_exec_test.cpp
new file mode 100644
index 00000000000..59af37dd6d4
--- /dev/null
+++ b/src/mongo/db/exec/projection_exec_test.cpp
@@ -0,0 +1,219 @@
+/**
+ * Copyright (C) 2013 mongoDB Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the GNU Affero General Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+/**
+ * This file contains tests for mongo/db/exec/projection_exec.cpp
+ */
+
+#include "mongo/db/exec/projection_exec.h"
+
+#include <memory>
+#include "mongo/db/json.h"
+#include "mongo/db/exec/working_set_computed_data.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/unittest/unittest.h"
+
+using namespace mongo;
+
+namespace {
+
+ using std::auto_ptr;
+
+ /**
+ * Utility function to create MatchExpression
+ */
+ MatchExpression* parseMatchExpression(const BSONObj& obj) {
+ StatusWithMatchExpression status = MatchExpressionParser::parse(obj);
+ ASSERT_TRUE(status.isOK());
+ MatchExpression* expr(status.getValue());
+ return expr;
+ }
+
+ //
+ // transform tests
+ //
+
+ /**
+ * test function to verify results of transform()
+ * on a working set member.
+ *
+ * specStr - projection specification
+ * queryStr - query
+ * objStr - object to run projection on
+ * data - computed data. Owned by working set member created in this function if not null.
+ * expectedStatusOK - expected status of transformation
+ * expectedObjStr - expected object after successful projection.
+ * Ignored if expectedStatusOK is false.
+ */
+
+ void testTransform(const char* specStr, const char* queryStr, const char* objStr,
+ WorkingSetComputedData* data,
+ bool expectedStatusOK, const char* expectedObjStr) {
+ // Create projection exec object.
+ BSONObj spec = fromjson(specStr);
+ BSONObj query = fromjson(queryStr);
+ auto_ptr<MatchExpression> queryExpression(parseMatchExpression(query));
+ ProjectionExec exec(spec, queryExpression.get());
+
+ // Create working set member.
+ WorkingSetMember wsm;
+ wsm.state = WorkingSetMember::OWNED_OBJ;
+ wsm.obj = fromjson(objStr);
+ if (data) {
+ wsm.addComputed(data);
+ }
+
+ // Transform object
+ Status status = exec.transform(&wsm);
+
+ // There are fewer checks to perform if we are expected a failed status.
+ if (!expectedStatusOK) {
+ if (status.isOK()) {
+ mongoutils::str::stream ss;
+ ss << "expected transform() to fail but got success instead."
+ << "\nprojection spec: " << specStr
+ << "\nquery: " << queryStr
+ << "\nobject before projection: " << objStr;
+ FAIL(ss);
+ }
+ return;
+ }
+
+ // If we are expecting a successful transformation but got a failed status instead,
+ // print out status message in assertion message.
+ if (!status.isOK()) {
+ mongoutils::str::stream ss;
+ ss << "transform() test failed: unexpected failed status: " << status.toString()
+ << "\nprojection spec: " << specStr
+ << "\nquery: " << queryStr
+ << "\nobject before projection: " << objStr
+ << "\nexpected object after projection: " << expectedObjStr;
+ FAIL(ss);
+ }
+
+ // Finally, we compare the projected object.
+ const BSONObj& obj = wsm.obj;
+ BSONObj expectedObj = fromjson(expectedObjStr);
+ if (obj != expectedObj) {
+ mongoutils::str::stream ss;
+ ss << "transform() test failed: unexpected projected object."
+ << "\nprojection spec: " << specStr
+ << "\nquery: " << queryStr
+ << "\nobject before projection: " << objStr
+ << "\nexpected object after projection: " << expectedObjStr
+ << "\nactual object after projection: " << obj.toString();
+ FAIL(ss);
+ }
+ }
+
+ /**
+ * testTransform without computed data argument.
+ */
+ void testTransform(const char* specStr, const char* queryStr, const char* objStr,
+ bool expectedStatusOK, const char* expectedObjStr) {
+ testTransform(specStr, queryStr, objStr, NULL, expectedStatusOK, expectedObjStr);
+ }
+
+ //
+ // position $
+ //
+
+ TEST(ProjectionExecTest, TransformPositionalDollar) {
+ // Valid position $ projections.
+ testTransform("{'a.$': 1}", "{a: 10}", "{a: [10, 20, 30]}", true, "{a: [10]}");
+ testTransform("{'a.$': 1}", "{a: 20}", "{a: [10, 20, 30]}", true, "{a: [20]}");
+ testTransform("{'a.$': 1}", "{a: 30}", "{a: [10, 20, 30]}", true, "{a: [30]}");
+ testTransform("{'a.$': 1}", "{a: {$gt: 4}}", "{a: [5]}", true, "{a: [5]}");
+
+ // Invalid position $ projections.
+ testTransform("{'a.$': 1}", "{a: {$size: 1}}", "{a: [5]}", false, "");
+ }
+
+ //
+ // $elemMatch
+ //
+
+ TEST(ProjectionExecTest, TransformElemMatch) {
+ const char* s = "{a: [{x: 1, y: 10}, {x: 1, y: 20}, {x: 2, y: 10}]}";
+
+ // Valid $elemMatch projections.
+ testTransform("{a: {$elemMatch: {x: 1}}}", "{}", s, true, "{a: [{x: 1, y: 10}]}");
+ testTransform("{a: {$elemMatch: {x: 1, y: 20}}}", "{}", s, true, "{a: [{x: 1, y: 20}]}");
+ testTransform("{a: {$elemMatch: {x: 2}}}", "{}", s, true, "{a: [{x: 2, y: 10}]}");
+ testTransform("{a: {$elemMatch: {x: 3}}}", "{}", s, true, "{}");
+
+ // $elemMatch on unknown field z
+ testTransform("{a: {$elemMatch: {z: 1}}}", "{}", s, true, "{}");
+ }
+
+ //
+ // $slice
+ //
+
+ TEST(ProjectionExecTest, TransformSliceCount) {
+ // Valid $slice projections using format {$slice: count}.
+ testTransform("{a: {$slice: -10}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ testTransform("{a: {$slice: -3}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ testTransform("{a: {$slice: -1}}", "{}", "{a: [4, 6, 8]}", true, "{a: [8]}");
+ testTransform("{a: {$slice: 0}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}");
+ testTransform("{a: {$slice: 1}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4]}");
+ testTransform("{a: {$slice: 3}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ testTransform("{a: {$slice: 10}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ }
+
+ TEST(ProjectionExecTest, TransformSliceSkipLimit) {
+ // Valid $slice projections using format {$slice: [skip, limit]}.
+ // Non-positive limits are rejected at the query parser and therefore not handled by
+ // the projection execution stage. In fact, it will abort on an invalid limit.
+ testTransform("{a: {$slice: [-10, 10]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ testTransform("{a: {$slice: [-3, 5]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6, 8]}");
+ testTransform("{a: {$slice: [-1, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [8]}");
+ testTransform("{a: {$slice: [0, 2]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4, 6]}");
+ testTransform("{a: {$slice: [0, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [4]}");
+ testTransform("{a: {$slice: [1, 1]}}", "{}", "{a: [4, 6, 8]}", true, "{a: [6]}");
+ testTransform("{a: {$slice: [3, 5]}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}");
+ testTransform("{a: {$slice: [10, 10]}}", "{}", "{a: [4, 6, 8]}", true, "{a: []}");
+ }
+
+ //
+ // $meta
+ // $meta projections add computed values to the projected object.
+ //
+
+ TEST(ProjectionExecTest, TransformMetaTextScore) {
+ // Query {} is ignored.
+ testTransform("{b: {$meta: 'textScore'}}", "{}", "{a: 'hello'}",
+ new mongo::TextScoreComputedData(100),
+ true, "{a: 'hello', b: 100}");
+ // Projected meta field should overwrite existing field.
+ testTransform("{b: {$meta: 'textScore'}}", "{}", "{a: 'hello', b: -1}",
+ new mongo::TextScoreComputedData(100),
+ true, "{a: 'hello', b: 100}");
+ }
+
+} // namespace