Skip to content

Commit fafbd01

Browse files
author
Hari Khalsa
committed
SERVER-12873 add more plan ranking dbtests
1 parent 7766137 commit fafbd01

File tree

1 file changed

+157
-3
lines changed

1 file changed

+157
-3
lines changed

src/mongo/dbtests/plan_ranking.cpp

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "mongo/db/instance.h"
3838
#include "mongo/db/json.h"
3939
#include "mongo/db/query/multi_plan_runner.h"
40+
#include "mongo/db/query/qlog.h"
4041
#include "mongo/db/query/query_planner.h"
4142
#include "mongo/db/query/query_planner_test_lib.h"
4243
#include "mongo/db/query/stage_builder.h"
@@ -129,9 +130,9 @@ namespace PlanRankingTests {
129130

130131
DBDirectClient PlanRankingTestBase::_client;
131132

132-
//
133-
// Test that the "prefer ixisect" parameter works.
134-
//
133+
/**
134+
* Test that the "prefer ixisect" parameter works.
135+
*/
135136
class PlanRankingIntersectOverride : public PlanRankingTestBase {
136137
public:
137138
void run() {
@@ -180,12 +181,165 @@ namespace PlanRankingTests {
180181
}
181182
};
182183

184+
/**
185+
* Two plans hit EOF at the same time, but one is covered. Make sure that we prefer the covered
186+
* plan.
187+
*/
188+
class PlanRankingPreferCovered : public PlanRankingTestBase {
189+
public:
190+
void run() {
191+
// Insert data {a:i, b:i}. Index {a:1} and {a:1, b:1}, query on 'a', projection on 'a'
192+
// and 'b'. Should prefer the second index as we can pull the 'b' data out.
193+
194+
static const int N = 10000;
195+
for (int i = 0; i < N; ++i) {
196+
insert(BSON("a" << i << "b" << i));
197+
}
198+
199+
addIndex(BSON("a" << 1));
200+
addIndex(BSON("a" << 1 << "b" << 1));
201+
202+
// Query for a==27 with projection that wants 'a' and 'b'. BSONObj() is for sort.
203+
CanonicalQuery* cq;
204+
ASSERT(CanonicalQuery::canonicalize(ns,
205+
BSON("a" << 27),
206+
BSONObj(),
207+
BSON("_id" << 0 << "a" << 1 << "b" << 1),
208+
&cq).isOK());
209+
ASSERT(NULL != cq);
210+
211+
// Takes ownership of cq.
212+
QuerySolution* soln = pickBestPlan(cq);
213+
214+
// Prefer the fully covered plan.
215+
ASSERT(QueryPlannerTestLib::solutionMatches(
216+
"{proj: {spec: {_id:0, a:1, b:1}, node: {ixscan: {pattern: {a: 1, b:1}}}}}",
217+
soln->root.get()));
218+
}
219+
};
220+
221+
/**
222+
* No plan produces any results or hits EOF. In this case we should never choose an index
223+
* intersection solution.
224+
*/
225+
class PlanRankingAvoidIntersectIfNoResults : public PlanRankingTestBase {
226+
public:
227+
void run() {
228+
// We insert lots of copies of {a:1, b:1, c: 20}. We have the indices {a:1} and {b:1},
229+
// and the query is {a:1, b:1, c: 999}. No data that matches the query but we won't
230+
// know that during plan ranking. We don't want to choose an intersection plan here.
231+
static const int N = 10000;
232+
233+
for (int i = 0; i < N; ++i) {
234+
insert(BSON("a" << 1 << "b" << 1 << "c" << 20));
235+
}
236+
237+
addIndex(BSON("a" << 1));
238+
addIndex(BSON("b" << 1));
239+
240+
// There is no data that matches this query but we don't know that until EOF.
241+
CanonicalQuery* cq;
242+
BSONObj queryObj = BSON("a" << 1 << "b" << 1 << "c" << 99);
243+
ASSERT(CanonicalQuery::canonicalize(ns, queryObj, &cq).isOK());
244+
ASSERT(NULL != cq);
245+
246+
// Takes ownership of cq.
247+
QuerySolution* soln = pickBestPlan(cq);
248+
249+
// Anti-prefer the intersection plan.
250+
bool bestIsScanOverA = QueryPlannerTestLib::solutionMatches(
251+
"{fetch: {node: {ixscan: {pattern: {a: 1}}}}}",
252+
soln->root.get());
253+
bool bestIsScanOverB = QueryPlannerTestLib::solutionMatches(
254+
"{fetch: {node: {ixscan: {pattern: {b: 1}}}}}",
255+
soln->root.get());
256+
ASSERT(bestIsScanOverA || bestIsScanOverB);
257+
}
258+
};
259+
260+
/**
261+
* No plan produces any results or hits EOF. In this case we should prefer covered solutions to
262+
* non-covered solutions.
263+
*/
264+
class PlanRankingPreferCoveredEvenIfNoResults : public PlanRankingTestBase {
265+
public:
266+
void run() {
267+
// We insert lots of copies of {a:1, b:1}. We have the indices {a:1} and {a:1, b:1},
268+
// the query is for a doc that doesn't exist, but there is a projection over 'a' and
269+
// 'b'. We should prefer the index that provides a covered query.
270+
static const int N = 10000;
271+
272+
for (int i = 0; i < N; ++i) {
273+
insert(BSON("a" << 1 << "b" << 1));
274+
}
275+
276+
addIndex(BSON("a" << 1));
277+
addIndex(BSON("a" << 1 << "b" << 1));
278+
279+
// There is no data that matches this query ({a:2}). Both scans will hit EOF before
280+
// returning any data.
281+
282+
CanonicalQuery* cq;
283+
ASSERT(CanonicalQuery::canonicalize(ns,
284+
BSON("a" << 2),
285+
BSONObj(),
286+
BSON("_id" << 0 << "a" << 1 << "b" << 1),
287+
&cq).isOK());
288+
ASSERT(NULL != cq);
289+
290+
// Takes ownership of cq.
291+
QuerySolution* soln = pickBestPlan(cq);
292+
// Prefer the fully covered plan.
293+
ASSERT(QueryPlannerTestLib::solutionMatches(
294+
"{proj: {spec: {_id:0, a:1, b:1}, node: {ixscan: {pattern: {a: 1, b:1}}}}}",
295+
soln->root.get()));
296+
}
297+
};
298+
299+
/**
300+
* We have an index on "a" which is somewhat selective and an index on "b" which is highly
301+
* selective (will cause an immediate EOF). Make sure that a query with predicates on both "a"
302+
* and "b" will use the index on "b".
303+
*/
304+
class PlanRankingPreferImmediateEOF : public PlanRankingTestBase {
305+
public:
306+
void run() {
307+
static const int N = 10000;
308+
309+
// 'a' is very selective, 'b' is not.
310+
for (int i = 0; i < N; ++i) {
311+
insert(BSON("a" << i << "b" << 1));
312+
}
313+
314+
// Add indices on 'a' and 'b'.
315+
addIndex(BSON("a" << 1));
316+
addIndex(BSON("b" << 1));
317+
318+
// Run the query {a:N+1, b:1}. (No such document.)
319+
CanonicalQuery* cq;
320+
verify(CanonicalQuery::canonicalize(ns, BSON("a" << N + 1 << "b" << 1), &cq).isOK());
321+
ASSERT(NULL != cq);
322+
323+
// {a: 100} is super selective so choose that.
324+
// Takes ownership of cq.
325+
QuerySolution* soln = pickBestPlan(cq);
326+
ASSERT(QueryPlannerTestLib::solutionMatches(
327+
"{fetch: {filter: {b:1}, node: {ixscan: {pattern: {a: 1}}}}}",
328+
soln->root.get()));
329+
}
330+
};
331+
332+
183333
class All : public Suite {
184334
public:
185335
All() : Suite( "query_plan_ranking" ) {}
186336

187337
void setupTests() {
188338
add<PlanRankingIntersectOverride>();
339+
add<PlanRankingPreferCovered>();
340+
add<PlanRankingAvoidIntersectIfNoResults>();
341+
add<PlanRankingPreferCoveredEvenIfNoResults>();
342+
add<PlanRankingPreferImmediateEOF>();
189343
}
190344
} planRankingAll;
191345

0 commit comments

Comments
 (0)