summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHari Khalsa <hkhalsa@10gen.com>2013-09-23 10:42:41 -0400
committerHari Khalsa <hkhalsa@10gen.com>2013-09-23 19:28:10 -0400
commit44ae87438efc76040d93b039233d7f2ff4f46e3d (patch)
tree594ebd1e16c59e7a462cab61d0eab14046d9e181 /src
parent221b1d1c23cf66a6609e0e0c9ab63f23c0142b3a (diff)
downloadmongo-44ae87438efc76040d93b039233d7f2ff4f46e3d.tar.gz
SERVER-10471 covering and sort analysis. split projection parse from exec
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/exec/SConscript4
-rw-r--r--src/mongo/db/exec/projection.cpp10
-rw-r--r--src/mongo/db/exec/projection.h7
-rw-r--r--src/mongo/db/exec/projection_executor.cpp96
-rw-r--r--src/mongo/db/exec/projection_executor.h53
-rw-r--r--src/mongo/db/exec/query_projection.cpp148
-rw-r--r--src/mongo/db/query/SConscript1
-rw-r--r--src/mongo/db/query/canonical_query.cpp28
-rw-r--r--src/mongo/db/query/canonical_query.h8
-rw-r--r--src/mongo/db/query/parsed_projection.h118
-rw-r--r--src/mongo/db/query/projection_parser.cpp76
-rw-r--r--src/mongo/db/query/projection_parser.h (renamed from src/mongo/db/exec/query_projection.h)16
-rw-r--r--src/mongo/db/query/query_planner.cpp107
-rw-r--r--src/mongo/db/query/query_planner_test.cpp62
-rw-r--r--src/mongo/db/query/query_solution.cpp9
-rw-r--r--src/mongo/db/query/query_solution.h8
-rw-r--r--src/mongo/db/query/stage_builder.cpp13
17 files changed, 556 insertions, 208 deletions
diff --git a/src/mongo/db/exec/SConscript b/src/mongo/db/exec/SConscript
index 9b974356f90..96ab04eccea 100644
--- a/src/mongo/db/exec/SConscript
+++ b/src/mongo/db/exec/SConscript
@@ -44,13 +44,13 @@ env.StaticLibrary(
"merge_sort.cpp",
"or.cpp",
"projection.cpp",
- "query_projection.cpp",
+ "projection_executor.cpp",
"skip.cpp",
"sort.cpp",
"stagedebug_cmd.cpp",
"working_set_common.cpp",
],
LIBDEPS = [
- "$BUILD_DIR/mongo/bson"
+ "$BUILD_DIR/mongo/bson",
],
)
diff --git a/src/mongo/db/exec/projection.cpp b/src/mongo/db/exec/projection.cpp
index f7e0edde052..fc2b81f6604 100644
--- a/src/mongo/db/exec/projection.cpp
+++ b/src/mongo/db/exec/projection.cpp
@@ -26,16 +26,18 @@
* it in the license file.
*/
+#include "mongo/db/exec/projection.h"
+
#include "mongo/db/diskloc.h"
#include "mongo/db/exec/plan_stage.h"
-#include "mongo/db/exec/projection.h"
+#include "mongo/db/exec/projection_executor.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
namespace mongo {
- ProjectionStage::ProjectionStage(QueryProjection* projection, WorkingSet* ws, PlanStage* child,
- const MatchExpression* filter)
+ ProjectionStage::ProjectionStage(ParsedProjection* projection, WorkingSet* ws, PlanStage* child,
+ const MatchExpression* filter)
: _projection(projection), _ws(ws), _child(child), _filter(filter) { }
ProjectionStage::~ProjectionStage() { }
@@ -51,7 +53,7 @@ namespace mongo {
if (PlanStage::ADVANCED == status) {
WorkingSetMember* member = _ws->get(id);
- Status status = _projection->project(member);
+ Status status = ProjectionExecutor::apply(_projection, member);
if (!status.isOK()) { return PlanStage::FAILURE; }
*out = id;
}
diff --git a/src/mongo/db/exec/projection.h b/src/mongo/db/exec/projection.h
index ade2bbfab84..284c7e05956 100644
--- a/src/mongo/db/exec/projection.h
+++ b/src/mongo/db/exec/projection.h
@@ -30,9 +30,9 @@
#include "mongo/db/diskloc.h"
#include "mongo/db/exec/plan_stage.h"
-#include "mongo/db/exec/query_projection.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
+#include "mongo/db/query/parsed_projection.h"
namespace mongo {
@@ -41,7 +41,7 @@ namespace mongo {
*/
class ProjectionStage : public PlanStage {
public:
- ProjectionStage(QueryProjection* projection, WorkingSet* ws, PlanStage* child,
+ ProjectionStage(ParsedProjection* projection, WorkingSet* ws, PlanStage* child,
const MatchExpression* filter);
virtual ~ProjectionStage();
@@ -55,7 +55,8 @@ namespace mongo {
PlanStageStats* getStats();
private:
- scoped_ptr<QueryProjection> _projection;
+ // Not owned by us.
+ ParsedProjection* _projection;
// _ws is not owned by us.
WorkingSet* _ws;
diff --git a/src/mongo/db/exec/projection_executor.cpp b/src/mongo/db/exec/projection_executor.cpp
new file mode 100644
index 00000000000..f6d1bc707d2
--- /dev/null
+++ b/src/mongo/db/exec/projection_executor.cpp
@@ -0,0 +1,96 @@
+/**
+ * Copyright (C) 2013 10gen 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.
+ */
+
+#include "mongo/db/exec/projection_executor.h"
+
+#include "mongo/db/exec/working_set.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/query/parsed_projection.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+
+ // static
+ Status ProjectionExecutor::apply(const ParsedProjection* proj, WorkingSetMember* wsm) {
+ if (ParsedProjection::FIND_SYNTAX == proj->getType()) {
+ return applyFindSyntax(static_cast<const FindProjection*>(proj), wsm);
+ }
+ else {
+ return Status(ErrorCodes::BadValue, "trying to apply unknown projection type");
+ }
+ }
+
+ // static
+ Status ProjectionExecutor::applyFindSyntax(const FindProjection* proj, WorkingSetMember* wsm) {
+ BSONObjBuilder bob;
+ if (proj->_includeID) {
+ BSONElement elt;
+ if (!wsm->getFieldDotted("_id", &elt)) {
+ return Status(ErrorCodes::BadValue, "Couldn't get _id field in proj");
+ }
+ bob.append(elt);
+ }
+
+ if (proj->_includedFields.size() > 0) {
+ // We only want stuff in _fields.
+ const vector<string>& fields = proj->_includedFields;
+ for (size_t i = 0; i < fields.size(); ++i) {
+ BSONElement elt;
+ // We can project a field that doesn't exist. We just ignore it.
+ // UNITTEST 11738048
+ if (wsm->getFieldDotted(fields[i], &elt) && !elt.eoo()) {
+ bob.append(elt);
+ }
+ }
+ }
+ else {
+ // We want stuff NOT in _fields. This can't be covered, so we expect an obj.
+ if (!wsm->hasObj()) {
+ return Status(ErrorCodes::BadValue,
+ "exclusion specified for projection but no obj to iter over");
+ }
+ const unordered_set<string>& fields = proj->_excludedFields;
+ BSONObjIterator it(wsm->obj);
+ while (it.more()) {
+ BSONElement elt = it.next();
+ if (!mongoutils::str::equals("_id", elt.fieldName())) {
+ if (fields.end() == fields.find(elt.fieldName())) {
+ bob.append(elt);
+ }
+ }
+ }
+ }
+
+ wsm->state = WorkingSetMember::OWNED_OBJ;
+ wsm->obj = bob.obj();
+ wsm->keyData.clear();
+ wsm->loc = DiskLoc();
+ return Status::OK();
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/exec/projection_executor.h b/src/mongo/db/exec/projection_executor.h
new file mode 100644
index 00000000000..ab1fb40fae0
--- /dev/null
+++ b/src/mongo/db/exec/projection_executor.h
@@ -0,0 +1,53 @@
+/**
+ * Copyright (C) 2013 10gen 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.
+ */
+
+#pragma once
+
+#include "mongo/db/exec/working_set.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/db/query/parsed_projection.h"
+
+namespace mongo {
+
+ /**
+ * Executes a pre-parsed projection.
+ *
+ * TODO: Add unit test.
+ */
+ class ProjectionExecutor {
+ public:
+ /**
+ * Compute the projection over the WSM. Place the output in the provided WSM.
+ */
+ static Status apply(const ParsedProjection* proj, WorkingSetMember* wsm);
+
+ private:
+ static Status applyFindSyntax(const FindProjection* proj, WorkingSetMember* wsm);
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/exec/query_projection.cpp b/src/mongo/db/exec/query_projection.cpp
deleted file mode 100644
index 0bb06ca1f4b..00000000000
--- a/src/mongo/db/exec/query_projection.cpp
+++ /dev/null
@@ -1,148 +0,0 @@
-/**
- * Copyright (C) 2013 10gen 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.
- */
-
-#include "mongo/db/exec/query_projection.h"
-
-#include "mongo/db/exec/working_set.h"
-#include "mongo/db/jsobj.h"
-#include "mongo/util/mongoutils/str.h"
-
-namespace mongo {
-
- //
- // .find() syntax incl/excl projection
- // TODO: Test.
- //
-
- class InclExclProjection : public QueryProjection {
- public:
- virtual ~InclExclProjection() { }
-
- Status project(WorkingSetMember* wsm) {
- BSONObjBuilder bob;
- if (_includeID) {
- BSONElement elt;
- if (!wsm->getFieldDotted("_id", &elt)) {
- return Status(ErrorCodes::BadValue, "Couldn't get _id field in proj");
- }
- bob.append(elt);
- }
-
- if (_fieldsInclusive) {
- // We only want stuff in _fields.
- for (vector<string>::const_iterator it = _includedFields.begin();
- it != _includedFields.end(); ++it) {
- BSONElement elt;
- // We can project a field that doesn't exist. We just ignore it.
- // UNITTEST 11738048
- if (wsm->getFieldDotted(*it, &elt) && !elt.eoo()) {
- bob.append(elt);
- }
- }
- }
- else {
- // We want stuff NOT in _fields. This can't be covered, so we expect an obj.
- if (!wsm->hasObj()) {
- return Status(ErrorCodes::BadValue,
- "exclusion specified for projection but no obj to iter over");
- }
- BSONObjIterator it(wsm->obj);
- while (it.more()) {
- BSONElement elt = it.next();
- if (!mongoutils::str::equals("_id", elt.fieldName())) {
- if (_excludedFields.end() == _excludedFields.find(elt.fieldName())) {
- bob.append(elt);
- }
- }
- }
- }
-
- wsm->state = WorkingSetMember::OWNED_OBJ;
- wsm->obj = bob.obj();
- wsm->keyData.clear();
- wsm->loc = DiskLoc();
- return Status::OK();
- }
-
- private:
- friend class QueryProjection;
-
- // _id can be included/excluded separately and is by default included.
- bool _includeID;
-
- // Either we include all of _includedFields or we exclude all of _excludedFields.
- bool _fieldsInclusive;
- unordered_set<string> _excludedFields;
-
- // Fields can be ordered if they're included.
- // UNITTEST 11738048
- vector<string> _includedFields;
- };
-
- // static
- Status QueryProjection::newInclusionExclusion(const BSONObj& obj, QueryProjection** out) {
- auto_ptr<InclExclProjection> qp(new InclExclProjection());
-
- // Include _id by default.
- qp->_includeID = true;
-
- // By default include everything.
- bool lastNonIDValue = false;
-
- BSONObjIterator it(obj);
- while (it.more()) {
- BSONElement elt = it.next();
- if (mongoutils::str::equals("_id", elt.fieldName())) {
- qp->_includeID = elt.trueValue();
- }
- else {
- bool newFieldValue = elt.trueValue();
- if (qp->_includedFields.size() > 0 || qp->_excludedFields.size() > 0) {
- // make sure we're all true or all false otherwise error
- if (newFieldValue != lastNonIDValue) {
- return Status(ErrorCodes::BadValue, "Inconsistent projection specs");
- }
- }
- lastNonIDValue = newFieldValue;
- if (lastNonIDValue) {
- // inclusive
- qp->_includedFields.push_back(elt.fieldName());
- }
- else {
- // exclusive
- qp->_excludedFields.insert(elt.fieldName());
- }
- }
- }
-
- qp->_fieldsInclusive = lastNonIDValue;
- *out = qp.release();
- return Status::OK();
- }
-
-} // namespace mongo
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index 5f8ac2712d5..2847f8ef042 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -10,6 +10,7 @@ env.StaticLibrary(
"index_tag.cpp",
"lite_parsed_query.cpp",
"plan_enumerator.cpp",
+ "projection_parser.cpp",
"query_planner.cpp",
"query_solution.cpp",
],
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index beca421af84..5367816747d 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -30,6 +30,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/query/projection_parser.h"
namespace mongo {
@@ -64,6 +65,24 @@ namespace mongo {
return Status::OK();
}
+ // static
+ Status CanonicalQuery::canonicalize(const string& ns, const BSONObj& query,
+ const BSONObj& sort, const BSONObj& proj,
+ CanonicalQuery** out) {
+ LiteParsedQuery* lpq;
+ // Pass empty sort and projection.
+ BSONObj emptyObj;
+ Status parseStatus = LiteParsedQuery::make(ns, 0, 0, 0, query, proj, sort, &lpq);
+ if (!parseStatus.isOK()) { return parseStatus; }
+
+ auto_ptr<CanonicalQuery> cq(new CanonicalQuery());
+ Status initStatus = cq->init(lpq);
+ if (!initStatus.isOK()) { return initStatus; }
+
+ *out = cq.release();
+ return Status::OK();
+ }
+
void normalizeTree(MatchExpression* root) {
// root->isLogical() is true now. We care about AND and OR. Negations currently scare us.
if (MatchExpression::AND == root->matchType() || MatchExpression::OR == root->matchType()) {
@@ -113,6 +132,15 @@ namespace mongo {
normalizeTree(root);
_root.reset(root);
+ if (!_pq->getProj().isEmpty()) {
+ ParsedProjection* proj;
+ Status projStatus = ProjectionParser::parseFindSyntax(_pq->getProj(), &proj);
+ if (!projStatus.isOK()) {
+ return projStatus;
+ }
+ _proj.reset(proj);
+ }
+
return Status::OK();
}
diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h
index be51fde183a..3f7c15bb6ff 100644
--- a/src/mongo/db/query/canonical_query.h
+++ b/src/mongo/db/query/canonical_query.h
@@ -33,6 +33,7 @@
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/query/lite_parsed_query.h"
+#include "mongo/db/query/parsed_projection.h"
namespace mongo {
@@ -40,8 +41,10 @@ namespace mongo {
public:
static Status canonicalize(const QueryMessage& qm, CanonicalQuery** out);
- // This is for testing, when we don't have a QueryMessage.
+ // These are for testing, when we don't have a QueryMessage.
static Status canonicalize(const string& ns, const BSONObj& query, CanonicalQuery** out);
+ static Status canonicalize(const string& ns, const BSONObj& query, const BSONObj& sort,
+ const BSONObj& proj, CanonicalQuery** out);
// What namespace is this query over?
const string& ns() const { return _pq->ns(); }
@@ -52,6 +55,7 @@ namespace mongo {
MatchExpression* root() const { return _root.get(); }
BSONObj getQueryObj() const { return _pq->getFilter(); }
const LiteParsedQuery& getParsed() const { return *_pq; }
+ ParsedProjection* getProj() const { return _proj.get(); }
string toString() const;
@@ -64,6 +68,8 @@ namespace mongo {
scoped_ptr<LiteParsedQuery> _pq;
+ scoped_ptr<ParsedProjection> _proj;
+
// _root points into _pq->getFilter()
scoped_ptr<MatchExpression> _root;
};
diff --git a/src/mongo/db/query/parsed_projection.h b/src/mongo/db/query/parsed_projection.h
new file mode 100644
index 00000000000..6f16cfe3697
--- /dev/null
+++ b/src/mongo/db/query/parsed_projection.h
@@ -0,0 +1,118 @@
+/**
+ * Copyright (C) 2013 10gen 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.
+ */
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "mongo/platform/unordered_set.h"
+
+namespace mongo {
+
+ /**
+ * Superclass for all projections. Projections are downcased to their specific implementation
+ * and executed.
+ */
+ class ParsedProjection {
+ public:
+ virtual ~ParsedProjection() { }
+
+ enum Type {
+ FIND_SYNTAX,
+ };
+
+ virtual Type getType() const = 0;
+
+ virtual string toString() const = 0;
+
+ //
+ // Properties of a projection. These allow us to determine if the projection is covered or
+ // not.
+ //
+
+ /**
+ * Does the projection require the entire document? If so, there must be a fetch before the
+ * projection.
+ */
+ virtual bool requiresDocument() const = 0;
+
+ /**
+ * What fields does the projection require?
+ */
+ virtual const vector<string>& requiredFields() const = 0;
+ };
+
+ //
+ // ParsedProjection implementations
+ //
+ class FindProjection : public ParsedProjection {
+ public:
+ Type getType() const {
+ return ParsedProjection::FIND_SYNTAX;
+ }
+
+ string toString() const {
+ // XXX FIXME
+ return "";
+ }
+
+ bool requiresDocument() const {
+ // If you're excluding fields, you must have something to exclude them from.
+ if (_excludedFields.size() > 0) {
+ verify(0 == _includedFields.size());
+ return true;
+ }
+
+ // If you're including fields, they could come from an index.
+ return false;
+ }
+
+ virtual const vector<string>& requiredFields() const {
+ verify(0 == _excludedFields.size());
+ return _includedFields;
+ }
+
+ private:
+ // ProjectionParser constructs us.
+ friend class ProjectionParser;
+
+ // ProjectionExecutor reads the fields below.
+ friend class ProjectionExecutor;
+
+ // _id can be included/excluded separately and is by default included.
+ bool _includeID;
+
+ // Either you exclude certain fields...
+ unordered_set<string> _excludedFields;
+
+ // ...or you include other fields, which can be ordered.
+ // UNITTEST 11738048
+ vector<string> _includedFields;
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/query/projection_parser.cpp b/src/mongo/db/query/projection_parser.cpp
new file mode 100644
index 00000000000..37d6804de21
--- /dev/null
+++ b/src/mongo/db/query/projection_parser.cpp
@@ -0,0 +1,76 @@
+/**
+ * Copyright (C) 2013 10gen 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.
+ */
+
+#include "mongo/db/query/projection_parser.h"
+
+#include "mongo/db/jsobj.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+
+ // static
+ Status ProjectionParser::parseFindSyntax(const BSONObj& obj, ParsedProjection** out) {
+ auto_ptr<FindProjection> qp(new FindProjection());
+
+ // Include _id by default.
+ qp->_includeID = true;
+
+ // By default include everything.
+ bool lastNonIDValue = false;
+
+ BSONObjIterator it(obj);
+ while (it.more()) {
+ BSONElement elt = it.next();
+ if (mongoutils::str::equals("_id", elt.fieldName())) {
+ qp->_includeID = elt.trueValue();
+ }
+ else {
+ bool newFieldValue = elt.trueValue();
+ if (qp->_excludedFields.size() + qp->_includedFields.size() > 0) {
+ // make sure we're all true or all false otherwise error
+ if (newFieldValue != lastNonIDValue) {
+ return Status(ErrorCodes::BadValue, "Inconsistent projection specs");
+ }
+ }
+ lastNonIDValue = newFieldValue;
+ if (lastNonIDValue) {
+ // inclusive
+ qp->_includedFields.push_back(elt.fieldName());
+ }
+ else {
+ // exclusive
+ qp->_excludedFields.insert(elt.fieldName());
+ }
+ }
+ }
+
+ *out = qp.release();
+ return Status::OK();
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/exec/query_projection.h b/src/mongo/db/query/projection_parser.h
index 5a7dcc83e3d..ab24ca750af 100644
--- a/src/mongo/db/exec/query_projection.h
+++ b/src/mongo/db/query/projection_parser.h
@@ -28,29 +28,21 @@
#pragma once
-#include "mongo/db/exec/working_set.h"
#include "mongo/db/jsobj.h"
+#include "mongo/db/query/parsed_projection.h"
namespace mongo {
/**
- * An interface for projecting (modifying) a WSM.
- * TODO: Add unit test.
+ * Parses each of the various projection syntaxes that exist.
*/
- class QueryProjection {
+ class ProjectionParser {
public:
- virtual ~QueryProjection() { }
-
- /**
- * Compute the projection over the WSM. Place the output in the provided WSM.
- */
- virtual Status project(WorkingSetMember* wsm) = 0;
-
/**
* This projection handles the inclusion/exclusion syntax of the .find() command.
* For details, see http://docs.mongodb.org/manual/reference/method/db.collection.find/
*/
- static Status newInclusionExclusion(const BSONObj& inclExcl, QueryProjection** out);
+ static Status parseFindSyntax(const BSONObj& inclExcl, ParsedProjection** out);
};
} // namespace mongo
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index 0026509400c..819462f102b 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -165,9 +165,9 @@ namespace mongo {
}
}
- if (!query.getParsed().getProj().isEmpty()) {
+ if (NULL != query.getProj()) {
ProjectionNode* proj = new ProjectionNode();
- proj->projection = query.getParsed().getProj();
+ proj->projection = query.getProj();
proj->child.reset(solnRoot);
solnRoot = proj;
}
@@ -583,35 +583,93 @@ namespace mongo {
return NULL;
}
+ /**
+ * Order of what happens:
+ * find results
+ * sort results
+ * project results
+ */
+
QuerySolution* makeSolution(const CanonicalQuery& query, MatchExpression* taggedRoot,
const vector<BSONObj>& indexKeyPatterns) {
cout << "about to build solntree from tagged tree:\n" << taggedRoot->toString() << endl;
+
QuerySolutionNode* solnRoot = buildSolutionTree(taggedRoot, indexKeyPatterns);
if (NULL == solnRoot) { return NULL; }
- // TODO XXX: Solutions need properties, need to use those properties to see when things
- // covered, sorts provided, etc.
+ // solnRoot finds all our results.
- // Fetch before proj
- bool addFetch = (STAGE_FETCH != solnRoot->getType());
- if (addFetch) {
- FetchNode* fetch = new FetchNode();
- fetch->child.reset(solnRoot);
- solnRoot = fetch;
- }
+ // Sort the results.
+ if (!query.getParsed().getSort().isEmpty()) {
+ // See if solnRoot gives us the sort. If so, we're done.
+ if (0 == query.getParsed().getSort().woCompare(solnRoot->getSort())) {
+ // Sort is already provided!
+ }
+ else {
+ // If solnRoot isn't already sorted, let's see if it has the fields we're sorting
+ // on. If it's fetched, it has all the fields by definition. If it's not, we check
+ // sort field by sort field.
+ if (!solnRoot->fetched()) {
+ bool sortCovered = true;
+ BSONObjIterator it(query.getParsed().getSort());
+ while (it.more()) {
+ if (!solnRoot->hasField(it.next().fieldName())) {
+ sortCovered = false;
+ break;
+ }
+ }
- if (!query.getParsed().getProj().isEmpty()) {
- ProjectionNode* proj = new ProjectionNode();
- proj->projection = query.getParsed().getProj();
- proj->child.reset(solnRoot);
- solnRoot = proj;
+ if (!sortCovered) {
+ FetchNode* fetch = new FetchNode();
+ fetch->child.reset(solnRoot);
+ solnRoot = fetch;
+ }
+ }
+
+ SortNode* sort = new SortNode();
+ sort->pattern = query.getParsed().getSort();
+ sort->child.reset(solnRoot);
+ solnRoot = sort;
+ }
}
- if (!query.getParsed().getSort().isEmpty()) {
- SortNode* sort = new SortNode();
- sort->pattern = query.getParsed().getSort();
- sort->child.reset(solnRoot);
- solnRoot = sort;
+ // Project the results.
+ if (NULL != query.getProj()) {
+ if (query.getProj()->requiresDocument()) {
+ if (!solnRoot->fetched()) {
+ FetchNode* fetch = new FetchNode();
+ fetch->child.reset(solnRoot);
+ solnRoot = fetch;
+ }
+ }
+ else {
+ const vector<string>& fields = query.getProj()->requiredFields();
+ bool covered = true;
+ for (size_t i = 0; i < fields.size(); ++i) {
+ if (!solnRoot->hasField(fields[i])) {
+ covered = false;
+ break;
+ }
+ }
+ if (!covered) {
+ FetchNode* fetch = new FetchNode();
+ fetch->child.reset(solnRoot);
+ solnRoot = fetch;
+ }
+ }
+
+ // We now know we have whatever data is required for the projection.
+ ProjectionNode* projNode = new ProjectionNode();
+ projNode->projection = query.getProj();
+ projNode->child.reset(solnRoot);
+ solnRoot = projNode;
+ }
+ else {
+ if (!solnRoot->fetched()) {
+ FetchNode* fetch = new FetchNode();
+ fetch->child.reset(solnRoot);
+ solnRoot = fetch;
+ }
}
if (0 != query.getParsed().getSkip()) {
@@ -732,7 +790,6 @@ namespace mongo {
MatchExpression* rawTree;
while (isp.getNext(&rawTree)) {
-
//
// Planner Section 3: Logical Rewrite. Use the index selection and the tree structure
// to try to rewrite the tree. TODO: Do this for real. We treat the tree as static.
@@ -762,6 +819,12 @@ namespace mongo {
}
}
+ if (0 == out->size() && !query.getParsed().getSort().isEmpty()) {
+ // There are no indexed plans outputted, but there's a requested sort. See if we have
+ // an index that is the same as the sort pattern.
+ // XXX: we may want to do this even if an indexed plan is outputted...detect that.
+ }
+
// TODO: Do we always want to offer a collscan solution?
if (!hasPredicate(predicates, MatchExpression::GEO_NEAR)) {
QuerySolution* collscan = makeCollectionScan(query, false);
diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp
index bdc865a6ca8..60595622ee6 100644
--- a/src/mongo/db/query/query_planner_test.cpp
+++ b/src/mongo/db/query/query_planner_test.cpp
@@ -73,6 +73,12 @@ namespace {
ASSERT_GREATER_THAN(solns.size(), 0U);;
}
+ void runDetailedQuery(const BSONObj& query, const BSONObj& sort, const BSONObj& proj) {
+ ASSERT_OK(CanonicalQuery::canonicalize(ns, query, sort, proj, &cq));
+ QueryPlanner::plan(*cq, keyPatterns, &solns);
+ ASSERT_GREATER_THAN(solns.size(), 0U);;
+ }
+
//
// Introspect solutions.
//
@@ -91,9 +97,28 @@ namespace {
found++;
}
}
+ if (1 != found) {
+ cout << "Can't find requested stage type " << stageType
+ << ", dump of all solutions:\n";
+ for (vector<QuerySolution*>::const_iterator it = solns.begin();
+ it != solns.end();
+ ++it) {
+ cout << (*it)->toString() << endl;
+ }
+ }
ASSERT_EQUALS(found, 1U);
}
+ void getAllPlans(StageType stageType, vector<QuerySolution*>* out) const {
+ for (vector<QuerySolution*>::const_iterator it = solns.begin();
+ it != solns.end();
+ ++it) {
+ if ((*it)->root->getType() == stageType) {
+ out->push_back(*it);
+ }
+ }
+ }
+
// { 'field': [ [min, max, startInclusive, endInclusive], ... ], 'field': ... }
void boundsEqual(BSONObj boundsObj, IndexBounds bounds) const {
ASSERT_EQUALS(static_cast<int>(bounds.size()), boundsObj.nFields());
@@ -338,6 +363,43 @@ namespace {
// TODO check filter
}
+ //
+ // Basic covering
+ //
+
+ TEST_F(SingleIndexTest, BasicCovering) {
+ setIndex(BSON("x" << 1));
+ // query, sort, proj
+ runDetailedQuery(fromjson("{ x : {$gt: 1}}"), BSONObj(), fromjson("{x: 1}"));
+ ASSERT_EQUALS(getNumSolutions(), 2U);
+
+ vector<QuerySolution*> solns;
+ getAllPlans(STAGE_PROJECTION, &solns);
+ ASSERT_EQUALS(solns.size(), 2U);
+
+ for (size_t i = 0; i < solns.size(); ++i) {
+ // cout << solns[i]->toString();
+ ProjectionNode* pn = static_cast<ProjectionNode*>(solns[i]->root.get());
+ ASSERT(STAGE_COLLSCAN == pn->child->getType() || STAGE_IXSCAN == pn->child->getType());
+ }
+ }
+
+ //
+ // Basic sort elimination
+ //
+
+ TEST_F(SingleIndexTest, BasicSortElim) {
+ setIndex(BSON("x" << 1));
+ // query, sort, proj
+ runDetailedQuery(fromjson("{ x : {$gt: 1}}"), fromjson("{x: 1}"), BSONObj());
+ ASSERT_EQUALS(getNumSolutions(), 2U);
+
+ QuerySolution* collScanSolution;
+ getPlanByType(STAGE_SORT, &collScanSolution);
+
+ QuerySolution* indexedSolution;
+ getPlanByType(STAGE_FETCH, &indexedSolution);
+ }
// STOPPED HERE - need to hook up machinery for multiple indexed predicates
// second is not working (until the machinery is in place)
diff --git a/src/mongo/db/query/query_solution.cpp b/src/mongo/db/query/query_solution.cpp
index bb11e3b181b..79bf04c45b7 100644
--- a/src/mongo/db/query/query_solution.cpp
+++ b/src/mongo/db/query/query_solution.cpp
@@ -38,12 +38,12 @@ namespace mongo {
void CollectionScanNode::appendToString(stringstream* ss, int indent) const {
addIndent(ss, indent);
- *ss << "COLLSCAN";
+ *ss << "COLLSCAN\n";
addIndent(ss, indent + 1);
- *ss << "ns = " << name;
+ *ss << "ns = " << name << endl;
if (NULL != filter) {
addIndent(ss, indent + 1);
- *ss << " filter = " << filter->toString() << endl;
+ *ss << " filter = " << filter->toString();
}
addIndent(ss, indent + 1);
*ss << "fetched = " << fetched() << endl;
@@ -262,8 +262,9 @@ namespace mongo {
void ProjectionNode::appendToString(stringstream* ss, int indent) const {
addIndent(ss, indent);
*ss << "PROJ\n";
+ verify(NULL != projection);
addIndent(ss, indent + 1);
- *ss << "proj = " << projection.toString() << endl;
+ *ss << "proj = " << projection->toString() << endl;
addIndent(ss, indent + 1);
*ss << "fetched = " << fetched() << endl;
addIndent(ss, indent + 1);
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index e931775f6c9..5ea9b4fcf8d 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -30,6 +30,7 @@
#include "mongo/db/matcher/expression.h"
#include "mongo/db/query/index_bounds.h"
+#include "mongo/db/query/projection_parser.h"
#include "mongo/db/query/stage_types.h"
namespace mongo {
@@ -261,7 +262,7 @@ namespace mongo {
};
struct ProjectionNode : public QuerySolutionNode {
- ProjectionNode() { }
+ ProjectionNode() : projection(NULL) { }
virtual ~ProjectionNode() { }
virtual StageType getType() const { return STAGE_PROJECTION; }
@@ -294,8 +295,11 @@ namespace mongo {
return BSONObj();
}
- BSONObj projection;
+ // Points into the CanonicalQuery.
+ ParsedProjection* projection;
+
scoped_ptr<QuerySolutionNode> child;
+
// TODO: Filter
};
diff --git a/src/mongo/db/query/stage_builder.cpp b/src/mongo/db/query/stage_builder.cpp
index dc2d59f1ceb..da4c60fadb8 100644
--- a/src/mongo/db/query/stage_builder.cpp
+++ b/src/mongo/db/query/stage_builder.cpp
@@ -95,16 +95,9 @@ namespace mongo {
}
else if (STAGE_PROJECTION == root->getType()) {
const ProjectionNode* pn = static_cast<const ProjectionNode*>(root);
- QueryProjection* proj;
- if (!QueryProjection::newInclusionExclusion(pn->projection, &proj).isOK()) {
- return NULL;
- }
PlanStage* childStage = buildStages(ns, pn->child.get(), ws);
- if (NULL == childStage) {
- delete proj;
- return NULL;
- }
- return new ProjectionStage(proj, ws, childStage, NULL);
+ if (NULL == childStage) { return NULL; }
+ return new ProjectionStage(pn->projection, ws, childStage, NULL);
}
else if (STAGE_LIMIT == root->getType()) {
const LimitNode* ln = static_cast<const LimitNode*>(root);
@@ -146,7 +139,7 @@ namespace mongo {
}
}
- //static
+ // static
bool StageBuilder::build(const QuerySolution& solution, PlanStage** rootOut,
WorkingSet** wsOut) {
QuerySolutionNode* root = solution.root.get();