|
37 | 37 | #include "mongo/db/instance.h" |
38 | 38 | #include "mongo/db/json.h" |
39 | 39 | #include "mongo/db/query/multi_plan_runner.h" |
| 40 | +#include "mongo/db/query/qlog.h" |
40 | 41 | #include "mongo/db/query/query_planner.h" |
41 | 42 | #include "mongo/db/query/query_planner_test_lib.h" |
42 | 43 | #include "mongo/db/query/stage_builder.h" |
@@ -129,9 +130,9 @@ namespace PlanRankingTests { |
129 | 130 |
|
130 | 131 | DBDirectClient PlanRankingTestBase::_client; |
131 | 132 |
|
132 | | - // |
133 | | - // Test that the "prefer ixisect" parameter works. |
134 | | - // |
| 133 | + /** |
| 134 | + * Test that the "prefer ixisect" parameter works. |
| 135 | + */ |
135 | 136 | class PlanRankingIntersectOverride : public PlanRankingTestBase { |
136 | 137 | public: |
137 | 138 | void run() { |
@@ -180,12 +181,165 @@ namespace PlanRankingTests { |
180 | 181 | } |
181 | 182 | }; |
182 | 183 |
|
| 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 | + |
183 | 333 | class All : public Suite { |
184 | 334 | public: |
185 | 335 | All() : Suite( "query_plan_ranking" ) {} |
186 | 336 |
|
187 | 337 | void setupTests() { |
188 | 338 | add<PlanRankingIntersectOverride>(); |
| 339 | + add<PlanRankingPreferCovered>(); |
| 340 | + add<PlanRankingAvoidIntersectIfNoResults>(); |
| 341 | + add<PlanRankingPreferCoveredEvenIfNoResults>(); |
| 342 | + add<PlanRankingPreferImmediateEOF>(); |
189 | 343 | } |
190 | 344 | } planRankingAll; |
191 | 345 |
|
|
0 commit comments