diff options
Diffstat (limited to 'src/mongo/db/exec/projection_exec_test.cpp')
| -rw-r--r-- | src/mongo/db/exec/projection_exec_test.cpp | 219 |
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 |
