/**
* Copyright (C) 2015 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 .
*
* 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.
*/
#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery
#include "mongo/platform/basic.h"
#include "mongo/db/query/query_planner_test_fixture.h"
#include "mongo/db/namespace_string.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/db/query/query_knobs.h"
#include "mongo/db/query/query_planner.h"
#include "mongo/db/query/query_planner_test_lib.h"
#include "mongo/util/log.h"
namespace mongo {
using unittest::assertGet;
const char* QueryPlannerTest::ns = "somebogus.ns";
void QueryPlannerTest::setUp() {
internalQueryPlannerEnableHashIntersection = true;
params.options = QueryPlannerParams::INCLUDE_COLLSCAN;
addIndex(BSON("_id" << 1));
}
void QueryPlannerTest::addIndex(BSONObj keyPattern, bool multikey) {
params.indices.push_back(IndexEntry(keyPattern,
multikey,
false, // sparse
false, // unique
"hari_king_of_the_stove",
NULL, // filterExpr
BSONObj()));
}
void QueryPlannerTest::addIndex(BSONObj keyPattern, bool multikey, bool sparse) {
params.indices.push_back(IndexEntry(keyPattern,
multikey,
sparse,
false, // unique
"note_to_self_dont_break_build",
NULL, // filterExpr
BSONObj()));
}
void QueryPlannerTest::addIndex(BSONObj keyPattern, bool multikey, bool sparse, bool unique) {
params.indices.push_back(IndexEntry(keyPattern,
multikey,
sparse,
unique,
"sql_query_walks_into_bar_and_says_can_i_join_you?",
NULL, // filterExpr
BSONObj()));
}
void QueryPlannerTest::addIndex(BSONObj keyPattern, BSONObj infoObj) {
params.indices.push_back(IndexEntry(keyPattern,
false, // multikey
false, // sparse
false, // unique
"foo",
NULL, // filterExpr
infoObj));
}
void QueryPlannerTest::runQuery(BSONObj query) {
runQuerySortProjSkipLimit(query, BSONObj(), BSONObj(), 0, 0);
}
void QueryPlannerTest::runQuerySortProj(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj) {
runQuerySortProjSkipLimit(query, sort, proj, 0, 0);
}
void QueryPlannerTest::runQuerySkipLimit(const BSONObj& query, long long skip, long long limit) {
runQuerySortProjSkipLimit(query, BSONObj(), BSONObj(), skip, limit);
}
void QueryPlannerTest::runQueryHint(const BSONObj& query, const BSONObj& hint) {
runQuerySortProjSkipLimitHint(query, BSONObj(), BSONObj(), 0, 0, hint);
}
void QueryPlannerTest::runQuerySortProjSkipLimit(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj,
long long skip,
long long limit) {
runQuerySortProjSkipLimitHint(query, sort, proj, skip, limit, BSONObj());
}
void QueryPlannerTest::runQuerySortHint(const BSONObj& query,
const BSONObj& sort,
const BSONObj& hint) {
runQuerySortProjSkipLimitHint(query, sort, BSONObj(), 0, 0, hint);
}
void QueryPlannerTest::runQueryHintMinMax(const BSONObj& query,
const BSONObj& hint,
const BSONObj& minObj,
const BSONObj& maxObj) {
runQueryFull(query, BSONObj(), BSONObj(), 0, 0, hint, minObj, maxObj, false);
}
void QueryPlannerTest::runQuerySortProjSkipLimitHint(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj,
long long skip,
long long limit,
const BSONObj& hint) {
runQueryFull(query, sort, proj, skip, limit, hint, BSONObj(), BSONObj(), false);
}
void QueryPlannerTest::runQuerySnapshot(const BSONObj& query) {
runQueryFull(query, BSONObj(), BSONObj(), 0, 0, BSONObj(), BSONObj(), BSONObj(), true);
}
void QueryPlannerTest::runQueryFull(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj,
long long skip,
long long limit,
const BSONObj& hint,
const BSONObj& minObj,
const BSONObj& maxObj,
bool snapshot) {
// Clean up any previous state from a call to runQueryFull
solns.clear();
{
CanonicalQuery* rawCq;
Status s = CanonicalQuery::canonicalize(ns,
query,
sort,
proj,
skip,
limit,
hint,
minObj,
maxObj,
snapshot,
false, // explain
&rawCq);
ASSERT_OK(s);
cq.reset(rawCq);
}
ASSERT_OK(QueryPlanner::plan(*cq, params, &solns.mutableVector()));
}
void QueryPlannerTest::runInvalidQuery(const BSONObj& query) {
runInvalidQuerySortProjSkipLimit(query, BSONObj(), BSONObj(), 0, 0);
}
void QueryPlannerTest::runInvalidQuerySortProj(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj) {
runInvalidQuerySortProjSkipLimit(query, sort, proj, 0, 0);
}
void QueryPlannerTest::runInvalidQuerySortProjSkipLimit(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj,
long long skip,
long long limit) {
runInvalidQuerySortProjSkipLimitHint(query, sort, proj, skip, limit, BSONObj());
}
void QueryPlannerTest::runInvalidQueryHint(const BSONObj& query, const BSONObj& hint) {
runInvalidQuerySortProjSkipLimitHint(query, BSONObj(), BSONObj(), 0, 0, hint);
}
void QueryPlannerTest::runInvalidQueryHintMinMax(const BSONObj& query,
const BSONObj& hint,
const BSONObj& minObj,
const BSONObj& maxObj) {
runInvalidQueryFull(query, BSONObj(), BSONObj(), 0, 0, hint, minObj, maxObj, false);
}
void QueryPlannerTest::runInvalidQuerySortProjSkipLimitHint(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj,
long long skip,
long long limit,
const BSONObj& hint) {
runInvalidQueryFull(query, sort, proj, skip, limit, hint, BSONObj(), BSONObj(), false);
}
void QueryPlannerTest::runInvalidQueryFull(const BSONObj& query,
const BSONObj& sort,
const BSONObj& proj,
long long skip,
long long limit,
const BSONObj& hint,
const BSONObj& minObj,
const BSONObj& maxObj,
bool snapshot) {
solns.clear();
{
CanonicalQuery* rawCq;
Status s = CanonicalQuery::canonicalize(ns,
query,
sort,
proj,
skip,
limit,
hint,
minObj,
maxObj,
snapshot,
false, // explain
&rawCq);
ASSERT_OK(s);
cq.reset(rawCq);
}
Status s = QueryPlanner::plan(*cq, params, &solns.mutableVector());
ASSERT_NOT_OK(s);
}
void QueryPlannerTest::runQueryAsCommand(const BSONObj& cmdObj) {
solns.clear();
const NamespaceString nss(ns);
invariant(nss.isValid());
const bool isExplain = false;
std::unique_ptr lpq(
assertGet(LiteParsedQuery::makeFromFindCommand(nss, cmdObj, isExplain)));
CanonicalQuery* rawCq;
WhereCallbackNoop whereCallback;
Status canonStatus = CanonicalQuery::canonicalize(lpq.release(), &rawCq, whereCallback);
ASSERT_OK(canonStatus);
cq.reset(rawCq);
Status s = QueryPlanner::plan(*cq, params, &solns.mutableVector());
ASSERT_OK(s);
}
size_t QueryPlannerTest::getNumSolutions() const {
return solns.size();
}
void QueryPlannerTest::dumpSolutions() const {
mongoutils::str::stream ost;
dumpSolutions(ost);
log() << std::string(ost);
}
void QueryPlannerTest::dumpSolutions(mongoutils::str::stream& ost) const {
for (auto&& soln : solns) {
ost << soln->toString() << '\n';
}
}
void QueryPlannerTest::assertNumSolutions(size_t expectSolutions) const {
if (getNumSolutions() == expectSolutions) {
return;
}
mongoutils::str::stream ss;
ss << "expected " << expectSolutions << " solutions but got " << getNumSolutions()
<< " instead. solutions generated: " << '\n';
dumpSolutions(ss);
FAIL(ss);
}
size_t QueryPlannerTest::numSolutionMatches(const std::string& solnJson) const {
BSONObj testSoln = fromjson(solnJson);
size_t matches = 0;
for (auto&& soln : solns) {
QuerySolutionNode* root = soln->root.get();
if (QueryPlannerTestLib::solutionMatches(testSoln, root)) {
++matches;
}
}
return matches;
}
void QueryPlannerTest::assertSolutionExists(const std::string& solnJson, size_t numMatches) const {
size_t matches = numSolutionMatches(solnJson);
if (numMatches == matches) {
return;
}
mongoutils::str::stream ss;
ss << "expected " << numMatches << " matches for solution " << solnJson << " but got "
<< matches << " instead. all solutions generated: " << '\n';
dumpSolutions(ss);
FAIL(ss);
}
void QueryPlannerTest::assertHasOneSolutionOf(const std::vector& solnStrs) const {
size_t matches = 0;
for (std::vector::const_iterator it = solnStrs.begin(); it != solnStrs.end();
++it) {
if (1U == numSolutionMatches(*it)) {
++matches;
}
}
if (1U == matches) {
return;
}
mongoutils::str::stream ss;
ss << "assertHasOneSolutionOf expected one matching solution"
<< " but got " << matches << " instead. all solutions generated: " << '\n';
dumpSolutions(ss);
FAIL(ss);
}
} // namespace mongo