summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mongo/db/query/SConscript29
-rw-r--r--src/mongo/db/query/cached_plan_runner.h6
-rw-r--r--src/mongo/db/query/canonical_query.cpp8
-rw-r--r--src/mongo/db/query/canonical_query.h8
-rw-r--r--src/mongo/db/query/index_bounds.cpp40
-rw-r--r--src/mongo/db/query/index_bounds.h2
-rw-r--r--src/mongo/db/query/lite_parsed_query.cpp207
-rw-r--r--src/mongo/db/query/lite_parsed_query.h116
-rw-r--r--src/mongo/db/query/multi_plan_runner.cpp7
-rw-r--r--src/mongo/db/query/multi_plan_runner.h2
-rw-r--r--src/mongo/db/query/new_find.cpp10
-rw-r--r--src/mongo/db/query/plan_executor.h7
-rw-r--r--src/mongo/db/query/query_planner.cpp120
-rw-r--r--src/mongo/db/query/query_planner.h10
-rw-r--r--src/mongo/db/query/query_planner_test.cpp78
-rw-r--r--src/mongo/db/query/query_solution.h31
-rw-r--r--src/mongo/db/query/runner.h3
-rw-r--r--src/mongo/db/query/simple_plan_runner.h111
-rw-r--r--src/mongo/db/query/single_solution_runner.h7
19 files changed, 665 insertions, 137 deletions
diff --git a/src/mongo/db/query/SConscript b/src/mongo/db/query/SConscript
index 3b48d11ba5f..1617cefad32 100644
--- a/src/mongo/db/query/SConscript
+++ b/src/mongo/db/query/SConscript
@@ -3,16 +3,41 @@
Import("env")
env.StaticLibrary(
- target = 'query',
+ target = 'query_planner',
source = [
"canonical_query.cpp",
+ "lite_parsed_query.cpp",
+ "query_planner.cpp",
+ ],
+ LIBDEPS = [
+ "index_bounds",
+ "$BUILD_DIR/mongo/bson",
+ "$BUILD_DIR/mongo/expressions",
+ "$BUILD_DIR/mongo/expressions_geo",
+ "$BUILD_DIR/mongo/query_parsing",
+ ],
+)
+
+env.CppUnitTest(
+ target = "query_planner_test",
+ source = [
+ "query_planner_test.cpp"
+ ],
+ LIBDEPS = [
+ "query_planner",
+ ],
+)
+
+env.StaticLibrary(
+ target = 'query',
+ source = [
"multi_plan_runner.cpp",
"new_find.cpp",
"plan_ranker.cpp",
- "query_planner.cpp",
"stage_builder.cpp",
],
LIBDEPS = [
+ "query_planner",
"$BUILD_DIR/mongo/db/exec/exec"
],
)
diff --git a/src/mongo/db/query/cached_plan_runner.h b/src/mongo/db/query/cached_plan_runner.h
index ef6c1c5882b..7770b8913d0 100644
--- a/src/mongo/db/query/cached_plan_runner.h
+++ b/src/mongo/db/query/cached_plan_runner.h
@@ -16,8 +16,8 @@
#pragma once
-#include "mongo/db/parsed_query.h"
#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/lite_parsed_query.h"
#include "mongo/db/query/plan_cache.h"
#include "mongo/db/query/plan_executor.h"
#include "mongo/db/query/runner.h"
@@ -49,6 +49,8 @@ namespace mongo {
Runner::RunnerState state = _exec->getNext(objOut);
if (Runner::RUNNER_EOF == state && !_updatedCache) {
+ _updatedCache = true;
+
// We're done. Update the cache.
PlanCache* cache = PlanCache::get(_canonicalQuery->ns());
@@ -62,8 +64,8 @@ namespace mongo {
if (!cache->remove(*_canonicalQuery, *_cachedQuery->solution)) {
warning() << "Cached plan runner couldn't remove plan from cache. Maybe"
" somebody else did already?";
+ return Runner::RUNNER_EOF;
}
- return Runner::RUNNER_EOF;
}
// We're done running. Update cache.
diff --git a/src/mongo/db/query/canonical_query.cpp b/src/mongo/db/query/canonical_query.cpp
index ee546150adc..4e4aec737ee 100644
--- a/src/mongo/db/query/canonical_query.cpp
+++ b/src/mongo/db/query/canonical_query.cpp
@@ -25,8 +25,8 @@ namespace mongo {
Status CanonicalQuery::canonicalize(QueryMessage& qm, CanonicalQuery** out) {
auto_ptr<CanonicalQuery> cq(new CanonicalQuery());
- // TODO: ParsedQuery throws. Fix it to return error.
- cq->_pq.reset(new ParsedQuery(qm));
+ // TODO: LiteParsedQuery throws. Fix it to return error.
+ cq->_pq.reset(new LiteParsedQuery(qm));
// TODO: If pq.hasOption(QueryOption_CursorTailable) make sure it's a capped collection and
// make sure the order(??) is $natural: 1.
@@ -49,11 +49,11 @@ namespace mongo {
CanonicalQuery** out) {
auto_ptr<CanonicalQuery> cq(new CanonicalQuery());
- // ParsedQuery saves the pointer to the NS that we provide it. It's not going to remain
+ // LiteParsedQuery saves the pointer to the NS that we provide it. It's not going to remain
// valid unless we cache it ourselves.
cq->_ns = ns;
- cq->_pq.reset(new ParsedQuery(cq->_ns.c_str(), 0, 0, 0, query, BSONObj()));
+ cq->_pq.reset(new LiteParsedQuery(cq->_ns.c_str(), 0, 0, 0, query));
StatusWithMatchExpression swme = MatchExpressionParser::parse(cq->_pq->getFilter());
if (!swme.isOK()) { return swme.getStatus(); }
diff --git a/src/mongo/db/query/canonical_query.h b/src/mongo/db/query/canonical_query.h
index 219e527e4db..5631c0a2f8e 100644
--- a/src/mongo/db/query/canonical_query.h
+++ b/src/mongo/db/query/canonical_query.h
@@ -20,13 +20,13 @@
#include "mongo/db/dbmessage.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
-#include "mongo/db/parsed_query.h"
+#include "mongo/db/query/lite_parsed_query.h"
namespace mongo {
class CanonicalQuery {
public:
- // TODO: qm is mutable because ParsedQuery wants it mutable. FIX.
+ // TODO: qm is mutable because LiteParsedQuery wants it mutable. FIX.
static Status canonicalize(QueryMessage& qm, CanonicalQuery** out);
// This is for testing, when we don't have a QueryMessage.
@@ -40,7 +40,7 @@ namespace mongo {
//
MatchExpression* root() const { return _root.get(); }
BSONObj getQueryObj() const { return _pq->getFilter(); }
- const ParsedQuery& getParsed() const { return *_pq; }
+ const LiteParsedQuery& getParsed() const { return *_pq; }
string toString() const;
@@ -48,7 +48,7 @@ namespace mongo {
// You must go through canonicalize to create a CanonicalQuery.
CanonicalQuery() { }
- scoped_ptr<ParsedQuery> _pq;
+ scoped_ptr<LiteParsedQuery> _pq;
// _root points into _pq->getFilter()
scoped_ptr<MatchExpression> _root;
diff --git a/src/mongo/db/query/index_bounds.cpp b/src/mongo/db/query/index_bounds.cpp
index b6e953f4d7a..e72e51ccf1f 100644
--- a/src/mongo/db/query/index_bounds.cpp
+++ b/src/mongo/db/query/index_bounds.cpp
@@ -29,6 +29,39 @@ namespace {
namespace mongo {
+ // For debugging.
+ string IndexBounds::toString() const {
+ stringstream ss;
+ for (size_t i = 0; i < fields.size(); ++i) {
+ if (i > 0) {
+ ss << ", ";
+ }
+ const OrderedIntervalList& oil = fields[i];
+ ss << "field #" << i << "['" << oil.name << "']: ";
+ for (size_t j = 0; j < oil.intervals.size(); ++j) {
+ const Interval& iv = oil.intervals[j];
+ if (iv.startInclusive) {
+ ss << "[";
+ }
+ else {
+ ss << "(";
+ }
+ // false means omit the field name
+ ss << iv.start.toString(false);
+ ss << ", ";
+ ss << iv.end.toString(false);
+ if (iv.endInclusive) {
+ ss << "]";
+ }
+ else {
+ ss << ")";
+ }
+ }
+ }
+
+ return ss.str();
+ }
+
//
// Validity checking for bounds
//
@@ -54,12 +87,17 @@ namespace mongo {
for (size_t j = 0; j < field.intervals.size(); ++j) {
// false means don't consider field name.
int cmp = sgn(field.intervals[j].end.woCompare(field.intervals[j].start, false));
+
+ if (cmp == 0 && field.intervals[j].startInclusive
+ && field.intervals[j].endInclusive) { continue; }
+
if (cmp != expectedOrientation) { return false; }
}
// Make sure each interval is oriented correctly with respect to its neighbors.
for (size_t j = 1; j < field.intervals.size(); ++j) {
- int cmp = sgn(field.intervals[j].start.woCompare(field.intervals[j - 1].end, false));
+ int cmp = sgn(field.intervals[j].start.woCompare(field.intervals[j - 1].end,
+ false));
if (cmp == 0) {
// The end of one interval is the start of another. This is only valid if
diff --git a/src/mongo/db/query/index_bounds.h b/src/mongo/db/query/index_bounds.h
index c8d43ed6f85..fd0ce078a81 100644
--- a/src/mongo/db/query/index_bounds.h
+++ b/src/mongo/db/query/index_bounds.h
@@ -59,6 +59,8 @@ namespace mongo {
// For each indexed field, the values that the field is allowed to take on.
vector<OrderedIntervalList> fields;
+ string toString() const;
+
// Debugging check.
// We must have as many fields the key pattern does.
// The fields must be oriented in the direction we'd encounter them given the indexing
diff --git a/src/mongo/db/query/lite_parsed_query.cpp b/src/mongo/db/query/lite_parsed_query.cpp
new file mode 100644
index 00000000000..ff2d27e2792
--- /dev/null
+++ b/src/mongo/db/query/lite_parsed_query.cpp
@@ -0,0 +1,207 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "mongo/db/query/lite_parsed_query.h"
+
+#include "mongo/db/dbmessage.h"
+#include "mongo/util/assert_util.h"
+
+namespace mongo {
+
+ // XXX: THIS DOESNT BELONG HERE
+ /* We cut off further objects once we cross this threshold; thus, you might get
+ a little bit more than this, it is a threshold rather than a limit.
+ */
+ static const int32_t MaxBytesToReturnToClientAtOnce = 4 * 1024 * 1024;
+
+ LiteParsedQuery::LiteParsedQuery( QueryMessage& qm ) :
+ _ns( qm.ns ),
+ _ntoskip( qm.ntoskip ),
+ _ntoreturn( qm.ntoreturn ),
+ _options( qm.queryOptions ) {
+ init( qm.query );
+ }
+
+ LiteParsedQuery::LiteParsedQuery( const char* ns,
+ int ntoskip,
+ int ntoreturn,
+ int queryoptions,
+ const BSONObj& query) :
+ _ns( ns ),
+ _ntoskip( ntoskip ),
+ _ntoreturn( ntoreturn ),
+ _options( queryoptions ) {
+ init( query );
+ }
+
+ bool LiteParsedQuery::hasIndexSpecifier() const {
+ return ! _hint.isEmpty() || ! _min.isEmpty() || ! _max.isEmpty();
+ }
+
+ // XXX THIS DOESNT BELONG HERE
+ bool LiteParsedQuery::enoughForFirstBatch( int n , int len ) const {
+ if ( _ntoreturn == 0 )
+ return ( len > 1024 * 1024 ) || n >= 101;
+ return n >= _ntoreturn || len > MaxBytesToReturnToClientAtOnce;
+ }
+
+ bool LiteParsedQuery::enough( int n ) const {
+ if ( _ntoreturn == 0 )
+ return false;
+ return n >= _ntoreturn;
+ }
+
+ bool LiteParsedQuery::enoughForExplain( long long n ) const {
+ if ( _wantMore || _ntoreturn == 0 ) {
+ return false;
+ }
+ return n >= _ntoreturn;
+ }
+
+ void LiteParsedQuery::init( const BSONObj& q ) {
+ _reset();
+ uassert(17032, "bad skip value in query", _ntoskip >= 0);
+
+ if ( _ntoreturn < 0 ) {
+ /* _ntoreturn greater than zero is simply a hint on how many objects to send back per
+ "cursor batch".
+ A negative number indicates a hard limit.
+ */
+ _wantMore = false;
+ _ntoreturn = -_ntoreturn;
+ }
+
+
+ BSONElement e = q["query"];
+ if ( ! e.isABSONObj() )
+ e = q["$query"];
+
+ if ( e.isABSONObj() ) {
+ _filter = e.embeddedObject().getOwned();
+ _initTop( q );
+ }
+ else {
+ _filter = q.getOwned();
+ }
+
+ //
+ // Parse options that are valid for both queries and commands
+ //
+
+ // $readPreference
+ _hasReadPref = q.hasField("$readPreference");
+
+ // $maxTimeMS
+ BSONElement maxTimeMSElt = q.getField("$maxTimeMS");
+ if (!maxTimeMSElt.eoo()) {
+ uassert(17033,
+ mongoutils::str::stream() <<
+ "$maxTimeMS must be a number type, instead found type: " <<
+ maxTimeMSElt.type(),
+ maxTimeMSElt.isNumber());
+ }
+ // If $maxTimeMS was not specified, _maxTimeMS is set to 0 (special value for "allow to
+ // run indefinitely").
+ long long maxTimeMSLongLong = maxTimeMSElt.safeNumberLong();
+ uassert(17034,
+ "$maxTimeMS out of range [0,2147483647]",
+ maxTimeMSLongLong >= 0 && maxTimeMSLongLong <= INT_MAX);
+ _maxTimeMS = static_cast<int>(maxTimeMSLongLong);
+ }
+
+ void LiteParsedQuery::_reset() {
+ _wantMore = true;
+ _explain = false;
+ _snapshot = false;
+ _returnKey = false;
+ _showDiskLoc = false;
+ _maxScan = 0;
+ }
+
+ /* This is for languages whose "objects" are not well ordered (JSON is well ordered).
+ [ { a : ... } , { b : ... } ] -> { a : ..., b : ... }
+ */
+ static BSONObj transformOrderFromArrayFormat(BSONObj order) {
+ /* note: this is slow, but that is ok as order will have very few pieces */
+ BSONObjBuilder b;
+ char p[2] = "0";
+
+ while ( 1 ) {
+ BSONObj j = order.getObjectField(p);
+ if ( j.isEmpty() )
+ break;
+ BSONElement e = j.firstElement();
+ uassert(17035, "bad order array", !e.eoo());
+ uassert(17036, "bad order array [2]", e.isNumber());
+ b.append(e);
+ (*p)++;
+ uassert(17037, "too many ordering elements", *p <= '9');
+ }
+
+ return b.obj();
+ }
+
+ void LiteParsedQuery::_initTop( const BSONObj& top ) {
+ BSONObjIterator i( top );
+ while ( i.more() ) {
+ BSONElement e = i.next();
+ const char * name = e.fieldName();
+
+ if ( strcmp( "$orderby" , name ) == 0 ||
+ strcmp( "orderby" , name ) == 0 ) {
+ if ( e.type() == Object ) {
+ _order = e.embeddedObject();
+ }
+ else if ( e.type() == Array ) {
+ _order = transformOrderFromArrayFormat( _order );
+ }
+ else {
+ uasserted(17038, "sort must be an object or array");
+ }
+ continue;
+ }
+
+ if( *name == '$' ) {
+ name++;
+ if ( strcmp( "explain" , name ) == 0 )
+ _explain = e.trueValue();
+ else if ( strcmp( "snapshot" , name ) == 0 )
+ _snapshot = e.trueValue();
+ else if ( strcmp( "min" , name ) == 0 )
+ _min = e.embeddedObject();
+ else if ( strcmp( "max" , name ) == 0 )
+ _max = e.embeddedObject();
+ else if ( strcmp( "hint" , name ) == 0 )
+ _hint = e.wrap();
+ else if ( strcmp( "returnKey" , name ) == 0 )
+ _returnKey = e.trueValue();
+ else if ( strcmp( "maxScan" , name ) == 0 )
+ _maxScan = e.numberInt();
+ else if ( strcmp( "showDiskLoc" , name ) == 0 )
+ _showDiskLoc = e.trueValue();
+ else if ( strcmp( "comment" , name ) == 0 ) {
+ ; // no-op
+ }
+ }
+ }
+
+ if ( _snapshot ) {
+ uassert(17039, "E12001 can't sort with $snapshot", _order.isEmpty() );
+ uassert(17050, "E12002 can't use hint with $snapshot", _hint.isEmpty() );
+ }
+
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/query/lite_parsed_query.h b/src/mongo/db/query/lite_parsed_query.h
new file mode 100644
index 00000000000..e84112872a8
--- /dev/null
+++ b/src/mongo/db/query/lite_parsed_query.h
@@ -0,0 +1,116 @@
+/* Copyright 2013 10gen Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "mongo/db/jsobj.h"
+
+namespace mongo {
+
+ class QueryMessage;
+
+ /**
+ * Parses the QueryMessage received from the user and makes the various fields more easily
+ * accessible.
+ *
+ * TODO: This is ported from mongo/db/parsed_query.cpp. It needs some clean-up. In particular
+ * it should be parse-only. No query logic.
+ *
+ * Notably missing is a projection.
+ */
+
+ /**
+ * this represents a total user query
+ * includes fields from the query message, both possible query levels
+ * parses everything up front
+ */
+ class LiteParsedQuery {
+ public:
+ LiteParsedQuery(QueryMessage& qm);
+
+ LiteParsedQuery(const char* ns,
+ int ntoskip,
+ int ntoreturn,
+ int queryoptions,
+ const BSONObj& query);
+
+ const char* ns() const { return _ns; }
+ bool isLocalDB() const { return strncmp( _ns, "local.", 6 ) == 0; }
+
+ const BSONObj& getFilter() const { return _filter; }
+
+ int getSkip() const { return _ntoskip; }
+ int getNumToReturn() const { return _ntoreturn; }
+ bool wantMore() const { return _wantMore; }
+ int getOptions() const { return _options; }
+ bool hasOption(int x) const { return ( x & _options ) != 0; }
+ bool hasReadPref() const { return _hasReadPref; }
+
+ bool isExplain() const { return _explain; }
+ bool isSnapshot() const { return _snapshot; }
+ bool returnKey() const { return _returnKey; }
+ bool showDiskLoc() const { return _showDiskLoc; }
+
+ const BSONObj& getMin() const { return _min; }
+ const BSONObj& getMax() const { return _max; }
+ const BSONObj& getOrder() const { return _order; }
+ const BSONObj& getHint() const { return _hint; }
+ int getMaxScan() const { return _maxScan; }
+ int getMaxTimeMS() const { return _maxTimeMS; }
+
+ bool hasIndexSpecifier() const;
+
+ /* if ntoreturn is zero, we return up to 101 objects. on the subsequent getmore, there
+ is only a size limit. The idea is that on a find() where one doesn't use much results,
+ we don't return much, but once getmore kicks in, we start pushing significant quantities.
+
+ The n limit (vs. size) is important when someone fetches only one small field from big
+ objects, which causes massive scanning server-side.
+ */
+ bool enoughForFirstBatch( int n, int len ) const;
+
+ bool enough( int n ) const;
+
+ bool enoughForExplain( long long n ) const;
+
+ private:
+ void init( const BSONObj& q );
+
+ void _reset();
+
+ void _initTop( const BSONObj& top );
+
+ void initFields( const BSONObj& fields );
+
+ const char* const _ns;
+ const int _ntoskip;
+ int _ntoreturn;
+ BSONObj _filter;
+ BSONObj _order;
+ const int _options;
+ bool _wantMore;
+ bool _explain;
+ bool _snapshot;
+ bool _returnKey;
+ bool _showDiskLoc;
+ bool _hasReadPref;
+ BSONObj _min;
+ BSONObj _max;
+ BSONObj _hint;
+ int _maxScan;
+ int _maxTimeMS;
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/query/multi_plan_runner.cpp b/src/mongo/db/query/multi_plan_runner.cpp
index 34882ca5338..a982a35a356 100644
--- a/src/mongo/db/query/multi_plan_runner.cpp
+++ b/src/mongo/db/query/multi_plan_runner.cpp
@@ -79,7 +79,12 @@ namespace mongo {
bool MultiPlanRunner::forceYield() {
saveState();
ClientCursor::registerRunner(this);
- ClientCursor::staticYield(ClientCursor::suggestYieldMicros(), getQuery().getParsed().ns(), NULL);
+ ClientCursor::staticYield(ClientCursor::suggestYieldMicros(),
+ getQuery().getParsed().ns(),
+ NULL);
+ // During the yield, the database we're operating over or any collection we're relying on
+ // may be dropped. When this happens all cursors and runners on that database and
+ // collection are killed or deleted in some fashion. (This is how the _killed gets set.)
ClientCursor::deregisterRunner(this);
if (!_killed) { restoreState(); }
return !_killed;
diff --git a/src/mongo/db/query/multi_plan_runner.h b/src/mongo/db/query/multi_plan_runner.h
index 248713e3109..ec35d8ffde5 100644
--- a/src/mongo/db/query/multi_plan_runner.h
+++ b/src/mongo/db/query/multi_plan_runner.h
@@ -23,8 +23,8 @@
#include "mongo/db/exec/working_set.h"
#include "mongo/db/exec/plan_stage.h"
#include "mongo/db/jsobj.h"
-#include "mongo/db/parsed_query.h"
#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/lite_parsed_query.h"
#include "mongo/db/query/plan_ranker.h"
#include "mongo/db/query/plan_executor.h"
#include "mongo/db/query/runner.h"
diff --git a/src/mongo/db/query/new_find.cpp b/src/mongo/db/query/new_find.cpp
index cb9b0b3be13..4c5d050c6b4 100644
--- a/src/mongo/db/query/new_find.cpp
+++ b/src/mongo/db/query/new_find.cpp
@@ -121,7 +121,8 @@ namespace mongo {
Client::ReadContext ctx(ns);
// TODO: Document.
- replVerifyReadsOk();
+ // TODO: do this when we can pass in our own parsed query
+ //replVerifyReadsOk();
ClientCursorPin ccPin(cursorid);
ClientCursor* cc = ccPin.c();
@@ -162,7 +163,7 @@ namespace mongo {
startingResult = cc->pos();
Runner* runner = cc->getRunner();
- const ParsedQuery& pq = runner->getQuery().getParsed();
+ const LiteParsedQuery& pq = runner->getQuery().getParsed();
// Get results out of the runner.
// TODO: There may be special handling required for tailable cursors?
@@ -244,10 +245,11 @@ namespace mongo {
const ChunkVersion shardingVersionAtStart = shardingState.getVersion(q.ns);
// We use this a lot below.
- const ParsedQuery& pq = runner->getQuery().getParsed();
+ const LiteParsedQuery& pq = runner->getQuery().getParsed();
// TODO: Document why we do this.
- replVerifyReadsOk(&pq);
+ // TODO: do this when we can pass in our own parsed query
+ //replVerifyReadsOk(&pq);
// If this exists, the collection is sharded.
// If it doesn't exist, we can assume we're not sharded.
diff --git a/src/mongo/db/query/plan_executor.h b/src/mongo/db/query/plan_executor.h
index e4e3bf47997..f140fac7e12 100644
--- a/src/mongo/db/query/plan_executor.h
+++ b/src/mongo/db/query/plan_executor.h
@@ -25,6 +25,11 @@
namespace mongo {
/**
+ *
+ * A PlanExecutor is the abstraction that knows how to crank a tree of stages into execution.
+ * The executor is usually part of a larger abstraction that is interacting with the cache
+ * and/or the query optimizer.
+ *
* Executes a plan. Used by a runner. Calls work() on a plan until a result is produced.
* Stops when the plan is EOF or if the plan errors.
*
@@ -40,7 +45,7 @@ namespace mongo {
WorkingSet* getWorkingSet() { return _workingSet.get(); }
/**
- * Takes ownership of root.
+ * Takes ownership of root (and therefore the entire tree).
*/
void setRoot(PlanStage* root) {
verify(root);
diff --git a/src/mongo/db/query/query_planner.cpp b/src/mongo/db/query/query_planner.cpp
index e09749e59aa..3e26e0d1b51 100644
--- a/src/mongo/db/query/query_planner.cpp
+++ b/src/mongo/db/query/query_planner.cpp
@@ -46,9 +46,125 @@ namespace mongo {
// Add this solution to the list of solutions.
soln->root.reset(csn);
out->push_back(soln.release());
+ }
+ }
+
+ void getIndicesStartingWith(const StringData& field, const BSONObjSet& kps, BSONObjSet* out) {
+ for (BSONObjSet::const_iterator i = kps.begin(); i != kps.end(); ++i) {
+ const BSONObj& obj = *i;
+ if (field == obj.firstElement().fieldName()) {
+ out->insert(obj);
+ }
+ }
+ }
+
+ BSONObj objFromElement(const BSONElement& elt) {
+ BSONObjBuilder bob;
+ bob.append(elt);
+ return bob.obj();
+ }
+
+ /**
+ * Make a point interval from the provided object.
+ * The object must have exactly one field which is the value of the point interval.
+ */
+ Interval makePointInterval(const BSONObj& obj) {
+ Interval ret;
+ ret._intervalData = obj;
+ ret.startInclusive = ret.endInclusive = true;
+ ret.start = ret.end = obj.firstElement();
+ return ret;
+ }
+
+ /**
+ * Make a range interval from the provided object.
+ * The object must have exactly two fields. The first field is the start, the second the end.
+ * The two inclusive flags indicate whether or not the start/end fields are included in the
+ * interval (closed interval if included, open if not).
+ */
+ Interval makeRangeInterval(const BSONObj& obj, bool startInclusive, bool endInclusive) {
+ Interval ret;
+ ret._intervalData = obj;
+ ret.startInclusive = startInclusive;
+ ret.endInclusive = endInclusive;
+ BSONObjIterator it(obj);
+ verify(it.more());
+ ret.start = it.next();
+ verify(it.more());
+ ret.end = it.next();
+ return ret;
+ }
- // TODO: limit and skip
- // TODO: sort.
+ void QueryPlanner::planWithIndices(const CanonicalQuery& query,
+ const BSONObjSet& indexKeyPatterns,
+ vector<QuerySolution*>* out) {
+ const MatchExpression* root = query.root();
+
+ if (MatchExpression::EQ == root->matchType()) {
+ const EqualityMatchExpression* node = static_cast<const EqualityMatchExpression*>(root);
+
+ // TODO: this is going to be cached/precomputed in a smarter way.
+ BSONObjSet overField;
+ getIndicesStartingWith(node->path(), indexKeyPatterns, &overField);
+
+ // No indices over the field, can't do anything here.
+ if (overField.empty()) { return; }
+
+ // We have to copy the data out of the parse tree and stuff it into the index bounds.
+ // BSONValue will be useful here.
+ BSONObj dataObj = objFromElement(node->getData());
+ verify(dataObj.isOwned());
+
+ // Every index that we're using to satisfy this query has these bounds. It may also
+ // have other fields
+ OrderedIntervalList oil(node->path().toString());
+ oil.intervals.push_back(makePointInterval(dataObj));
+ IndexBounds commonBounds;
+ commonBounds.fields.push_back(oil);
+
+ // For every index that we can use...
+ for (BSONObjSet::iterator it = overField.begin(); it != overField.end(); ++it) {
+ const BSONObj& indexKeyPattern = *it;
+
+ // Create an index scan solution node.
+ IndexScanNode* ixscan = new IndexScanNode();
+ ixscan->indexKeyPattern = indexKeyPattern;
+ // Copy the common bounds.
+ ixscan->bounds = commonBounds;
+ // And add min/max key bounds to any trailing fields.
+ BSONObjIterator it(indexKeyPattern);
+ // Skip the field we're indexed on.
+ it.next();
+ // And for every other field.
+ while (it.more()) {
+ BSONElement elt = it.next();
+
+ // ARGH, BSONValue would make this shorter.
+ BSONObjBuilder bob;
+ if (-1 == elt.number()) {
+ // Index should go from MaxKey to MinKey
+ bob.appendMaxKey("");
+ bob.appendMinKey("");
+ }
+ else {
+ // Index goes from MinKey to MaxKey
+ bob.appendMinKey("");
+ bob.appendMaxKey("");
+ }
+ OrderedIntervalList oil(elt.fieldName());
+ oil.intervals.push_back(makeRangeInterval(bob.obj(), true, true));
+ ixscan->bounds.fields.push_back(oil);
+ }
+
+ // TODO: This can be a debug check eventually.
+ // TODO: We might change the direction depending on the requested sort.
+ verify(ixscan->bounds.isValidFor(indexKeyPattern, 1));
+
+ // Wrap the ixscan in a solution node.
+ QuerySolution* soln = new QuerySolution();
+ soln->root.reset(ixscan);
+ out->push_back(soln);
+ }
}
}
diff --git a/src/mongo/db/query/query_planner.h b/src/mongo/db/query/query_planner.h
index dcfc5ecf9d2..1210365c9b7 100644
--- a/src/mongo/db/query/query_planner.h
+++ b/src/mongo/db/query/query_planner.h
@@ -35,6 +35,16 @@ namespace mongo {
*/
static void plan(const CanonicalQuery& query, vector<QuerySolution*>* out);
+ /**
+ * Outputs a series of possible solutions for the provided 'query' into 'out'. Uses the
+ * provided indices to generate a solution.
+ *
+ * Caller owns pointers in *out.
+ */
+ static void planWithIndices(const CanonicalQuery& query,
+ const BSONObjSet& indexKeyPatterns,
+ vector<QuerySolution*>* out);
+
private:
/**
* Returns true if the tree rooted at 'node' requires an index to answer the query. There
diff --git a/src/mongo/db/query/query_planner_test.cpp b/src/mongo/db/query/query_planner_test.cpp
new file mode 100644
index 00000000000..5ae32ac0a4d
--- /dev/null
+++ b/src/mongo/db/query/query_planner_test.cpp
@@ -0,0 +1,78 @@
+/**
+ * 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/>.
+ */
+
+/**
+ * This file contains tests for mongo/db/query/query_planner.cpp
+ */
+
+#include "mongo/db/query/query_planner.h"
+#include "mongo/db/query/query_solution.h"
+#include "mongo/db/matcher/expression_parser.h"
+#include "mongo/db/json.h"
+#include "mongo/db/jsobj.h"
+#include "mongo/unittest/unittest.h"
+#include "mongo/util/assert_util.h"
+
+using namespace mongo;
+
+namespace {
+
+ static const char* ns = "somebogusns";
+
+ // Test the most basic index-using functionality.
+ TEST(QueryPlannerTest, EqualityIndexScan) {
+ // Get the canonical query.
+ BSONObj queryObj = BSON("x" << 5);
+ CanonicalQuery* cq;
+ ASSERT(CanonicalQuery::canonicalize(ns, queryObj, &cq).isOK());
+
+ // This is our index. It's the simplest index we can use.
+ BSONObjSet indices;
+ indices.insert(BSON("x" << 1));
+
+ // If we can't get this right...
+ vector<QuerySolution*> solns;
+ QueryPlanner::planWithIndices(*cq, indices, &solns);
+ ASSERT_EQUALS(size_t(1), solns.size());
+
+ QuerySolutionNode* node = solns[0]->root.get();
+ ASSERT_EQUALS(STAGE_IXSCAN, node->getType());
+
+ // TODO: Check bounds.
+ }
+
+ TEST(QueryPlannerTest, EqualityIndexScanWithTrailingFields) {
+ BSONObj queryObj = BSON("x" << 5);
+
+ CanonicalQuery* cq;
+ ASSERT(CanonicalQuery::canonicalize(ns, queryObj, &cq).isOK());
+
+ BSONObjSet indices;
+ indices.insert(BSON("x" << 1 << "y" << 1));
+
+ vector<QuerySolution*> solns;
+ QueryPlanner::planWithIndices(*cq, indices, &solns);
+
+ // Available index is prefixed by our equality, use it.
+ ASSERT_EQUALS(size_t(1), solns.size());
+
+ QuerySolutionNode* node = solns[0]->root.get();
+ ASSERT_EQUALS(STAGE_IXSCAN, node->getType());
+
+ // TODO: Check bounds.
+ }
+
+} // namespace
diff --git a/src/mongo/db/query/query_solution.h b/src/mongo/db/query/query_solution.h
index bc57a3c969f..82ee2d5295e 100644
--- a/src/mongo/db/query/query_solution.h
+++ b/src/mongo/db/query/query_solution.h
@@ -17,6 +17,7 @@
#pragma once
#include "mongo/db/matcher/expression.h"
+#include "mongo/db/query/index_bounds.h"
#include "mongo/db/query/stage_types.h"
namespace mongo {
@@ -89,7 +90,37 @@ namespace mongo {
// Not owned.
// This is a sub-tree of the filter in the QuerySolution that owns us.
+ // TODO: This may change in the future.
MatchExpression* filter;
};
+ struct IndexScanNode : public QuerySolutionNode {
+ IndexScanNode() : filter(NULL), limit(0), direction(1) { }
+
+ virtual StageType getType() const { return STAGE_IXSCAN; }
+
+ virtual void appendToString(stringstream* ss) const {
+ *ss << "IXSCAN kp=" << indexKeyPattern;
+ if (NULL != filter) {
+ *ss << " filter= " << filter->toString();
+ }
+ *ss << " dir = " << direction;
+ *ss << " bounds = " << bounds.toString();
+ }
+
+ BSONObj indexKeyPattern;
+
+ // Not owned.
+ // This is a sub-tree of the filter in the QuerySolution that owns us.
+ // TODO: This may change in the future.
+ MatchExpression* filter;
+
+ // Only set for 2d.
+ int limit;
+
+ int direction;
+
+ IndexBounds bounds;
+ };
+
} // namespace mongo
diff --git a/src/mongo/db/query/runner.h b/src/mongo/db/query/runner.h
index 66fff915c8d..33b54e2875b 100644
--- a/src/mongo/db/query/runner.h
+++ b/src/mongo/db/query/runner.h
@@ -69,7 +69,8 @@ namespace mongo {
/**
* Mark the Runner as no longer valid. Can happen when a runner yields and the underlying
- * database is dropped/indexes removed/etc.
+ * database is dropped/indexes removed/etc. All future to calls to getNext return
+ * RUNNER_DEAD. Every other call is a NOOP.
*/
virtual void kill() = 0;
diff --git a/src/mongo/db/query/simple_plan_runner.h b/src/mongo/db/query/simple_plan_runner.h
deleted file mode 100644
index 92d2645777d..00000000000
--- a/src/mongo/db/query/simple_plan_runner.h
+++ /dev/null
@@ -1,111 +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/>.
- */
-
-#include "mongo/db/exec/plan_stage.h"
-#include "mongo/db/exec/working_set.h"
-#include "mongo/db/exec/working_set_common.h"
-#include "mongo/db/query/runner.h"
-#include "mongo/db/pdfile.h"
-
-#pragma once
-
-namespace mongo {
-
- /**
- * A placeholder for a full-featured plan runner. Calls work() on a plan until a result is
- * produced. Stops when the plan is EOF or if the plan errors.
- *
- * TODO: Yielding policy
- * TODO: Graceful error handling
- * TODO: Stats, diagnostics, instrumentation, etc.
- * TODO: Rename. It's not a full runner. It just holds the stage/WS and handles yielding.
- */
- class SimplePlanRunner {
- public:
- SimplePlanRunner() : _workingSet(new WorkingSet()) { }
- SimplePlanRunner(WorkingSet* ws, PlanStage* rt) : _workingSet(ws), _root(rt) { }
-
- WorkingSet* getWorkingSet() { return _workingSet.get(); }
-
- /**
- * Takes ownership of root.
- */
- void setRoot(PlanStage* root) {
- verify(root);
- _root.reset(root);
- }
-
- void saveState() { _root->prepareToYield(); }
- void restoreState() { _root->recoverFromYield(); }
- void invalidate(const DiskLoc& dl) { _root->invalidate(dl); }
-
- PlanStageStats* getStats() { return _root->getStats(); }
-
- bool getNext(BSONObj* objOut) {
- for (;;) {
- WorkingSetID id;
- PlanStage::StageState code = _root->work(&id);
-
- if (PlanStage::ADVANCED == code) {
- WorkingSetMember* member = _workingSet->get(id);
- uassert(16912, "Couldn't fetch obj from query plan",
- WorkingSetCommon::fetch(member));
- *objOut = member->obj;
- _workingSet->free(id);
- return true;
- }
- else if (code == PlanStage::NEED_TIME) {
- // TODO: Runners can't yield themselves until we rework ClientCursor to not
- // delete itself.
- }
- else if (PlanStage::NEED_FETCH == code) {
- // id has a loc and refers to an obj we need to fetch.
- WorkingSetMember* member = _workingSet->get(id);
-
- // This must be true for somebody to request a fetch and can only change when an
- // invalidation happens, which is when we give up a lock. Don't give up the
- // lock between receiving the NEED_FETCH and actually fetching(?).
- verify(member->hasLoc());
-
- // Actually bring record into memory.
- Record* record = member->loc.rec();
- // TODO: We would yield ourselves here and call ClientCursor::staticYield once
- // we rework ClientCursor to not delete itself.
- record->touch();
-
- // Record should be in memory now. Log if it's not.
- if (!Record::likelyInPhysicalMemory(record->dataNoThrowing())) {
- OCCASIONALLY {
- warning() << "Record wasn't in memory immediately after fetch: "
- << member->loc.toString() << endl;
- }
- }
-
- // Note that we're not freeing id. Fetch semantics say that we shouldn't.
- }
- else {
- // IS_EOF, FAILURE. We just stop here.
- return false;
- }
- }
- }
-
- private:
- scoped_ptr<WorkingSet> _workingSet;
- scoped_ptr<PlanStage> _root;
- };
-
-} // namespace mongo
diff --git a/src/mongo/db/query/single_solution_runner.h b/src/mongo/db/query/single_solution_runner.h
index f1e43db92ee..246ef12d4e4 100644
--- a/src/mongo/db/query/single_solution_runner.h
+++ b/src/mongo/db/query/single_solution_runner.h
@@ -17,8 +17,8 @@
#pragma once
#include "mongo/db/clientcursor.h"
-#include "mongo/db/parsed_query.h"
#include "mongo/db/query/canonical_query.h"
+#include "mongo/db/query/lite_parsed_query.h"
#include "mongo/db/query/plan_cache.h"
#include "mongo/db/query/plan_executor.h"
#include "mongo/db/query/runner.h"
@@ -63,7 +63,6 @@ namespace mongo {
virtual void invalidate(const DiskLoc& dl) {
if (!_killed) {
- cout << "Sending invalidate to exec\n";
_exec->invalidate(dl);
}
}
@@ -75,7 +74,9 @@ namespace mongo {
virtual bool forceYield() {
saveState();
ClientCursor::registerRunner(this);
- ClientCursor::staticYield(ClientCursor::suggestYieldMicros(), getQuery().getParsed().ns(), NULL);
+ ClientCursor::staticYield(ClientCursor::suggestYieldMicros(),
+ getQuery().getParsed().ns(),
+ NULL);
ClientCursor::deregisterRunner(this);
if (!_killed) { restoreState(); }
return !_killed;