diff options
Diffstat (limited to 'src/mongo/db/query/plan_cache.cpp')
-rw-r--r-- | src/mongo/db/query/plan_cache.cpp | 1099 |
1 files changed, 571 insertions, 528 deletions
diff --git a/src/mongo/db/query/plan_cache.cpp b/src/mongo/db/query/plan_cache.cpp index b79c2e8f659..f97a81c0f01 100644 --- a/src/mongo/db/query/plan_cache.cpp +++ b/src/mongo/db/query/plan_cache.cpp @@ -37,7 +37,7 @@ #include <memory> #include "boost/thread/locks.hpp" #include "mongo/base/owned_pointer_vector.h" -#include "mongo/client/dbclientinterface.h" // For QueryOption_foobar +#include "mongo/client/dbclientinterface.h" // For QueryOption_foobar #include "mongo/db/matcher/expression_array.h" #include "mongo/db/matcher/expression_geo.h" #include "mongo/db/query/plan_ranker.h" @@ -50,23 +50,23 @@ namespace mongo { namespace { - // Delimiters for cache key encoding. - const char kEncodeDiscriminatorsBegin = '<'; - const char kEncodeDiscriminatorsEnd = '>'; - const char kEncodeChildrenBegin = '['; - const char kEncodeChildrenEnd = ']'; - const char kEncodeChildrenSeparator = ','; - const char kEncodeSortSection = '~'; - const char kEncodeProjectionSection = '|'; - - /** - * Encode user-provided string. Cache key delimiters seen in the - * user string are escaped with a backslash. - */ - void encodeUserString(StringData s, StringBuilder* keyBuilder) { - for (size_t i = 0; i < s.size(); ++i) { - char c = s[i]; - switch (c) { +// Delimiters for cache key encoding. +const char kEncodeDiscriminatorsBegin = '<'; +const char kEncodeDiscriminatorsEnd = '>'; +const char kEncodeChildrenBegin = '['; +const char kEncodeChildrenEnd = ']'; +const char kEncodeChildrenSeparator = ','; +const char kEncodeSortSection = '~'; +const char kEncodeProjectionSection = '|'; + +/** + * Encode user-provided string. Cache key delimiters seen in the + * user string are escaped with a backslash. + */ +void encodeUserString(StringData s, StringBuilder* keyBuilder) { + for (size_t i = 0; i < s.size(); ++i) { + char c = s[i]; + switch (c) { case kEncodeDiscriminatorsBegin: case kEncodeDiscriminatorsEnd: case kEncodeChildrenBegin: @@ -75,637 +75,680 @@ namespace { case kEncodeSortSection: case kEncodeProjectionSection: case '\\': - *keyBuilder << '\\'; - // Fall through to default case. + *keyBuilder << '\\'; + // Fall through to default case. default: *keyBuilder << c; - } } } +} - /** - * 2-character encoding of MatchExpression::MatchType. - */ - const char* encodeMatchType(MatchExpression::MatchType mt) { - switch(mt) { - case MatchExpression::AND: return "an"; break; - case MatchExpression::OR: return "or"; break; - case MatchExpression::NOR: return "nr"; break; - case MatchExpression::NOT: return "nt"; break; - case MatchExpression::ELEM_MATCH_OBJECT: return "eo"; break; - case MatchExpression::ELEM_MATCH_VALUE: return "ev"; break; - case MatchExpression::SIZE: return "sz"; break; - case MatchExpression::LTE: return "le"; break; - case MatchExpression::LT: return "lt"; break; - case MatchExpression::EQ: return "eq"; break; - case MatchExpression::GT: return "gt"; break; - case MatchExpression::GTE: return "ge"; break; - case MatchExpression::REGEX: return "re"; break; - case MatchExpression::MOD: return "mo"; break; - case MatchExpression::EXISTS: return "ex"; break; - case MatchExpression::MATCH_IN: return "in"; break; - case MatchExpression::TYPE_OPERATOR: return "ty"; break; - case MatchExpression::GEO: return "go"; break; - case MatchExpression::WHERE: return "wh"; break; - case MatchExpression::ATOMIC: return "at"; break; - case MatchExpression::ALWAYS_FALSE: return "af"; break; - case MatchExpression::GEO_NEAR: return "gn"; break; - case MatchExpression::TEXT: return "te"; break; - default: verify(0); return ""; - } +/** + * 2-character encoding of MatchExpression::MatchType. + */ +const char* encodeMatchType(MatchExpression::MatchType mt) { + switch (mt) { + case MatchExpression::AND: + return "an"; + break; + case MatchExpression::OR: + return "or"; + break; + case MatchExpression::NOR: + return "nr"; + break; + case MatchExpression::NOT: + return "nt"; + break; + case MatchExpression::ELEM_MATCH_OBJECT: + return "eo"; + break; + case MatchExpression::ELEM_MATCH_VALUE: + return "ev"; + break; + case MatchExpression::SIZE: + return "sz"; + break; + case MatchExpression::LTE: + return "le"; + break; + case MatchExpression::LT: + return "lt"; + break; + case MatchExpression::EQ: + return "eq"; + break; + case MatchExpression::GT: + return "gt"; + break; + case MatchExpression::GTE: + return "ge"; + break; + case MatchExpression::REGEX: + return "re"; + break; + case MatchExpression::MOD: + return "mo"; + break; + case MatchExpression::EXISTS: + return "ex"; + break; + case MatchExpression::MATCH_IN: + return "in"; + break; + case MatchExpression::TYPE_OPERATOR: + return "ty"; + break; + case MatchExpression::GEO: + return "go"; + break; + case MatchExpression::WHERE: + return "wh"; + break; + case MatchExpression::ATOMIC: + return "at"; + break; + case MatchExpression::ALWAYS_FALSE: + return "af"; + break; + case MatchExpression::GEO_NEAR: + return "gn"; + break; + case MatchExpression::TEXT: + return "te"; + break; + default: + verify(0); + return ""; } +} - /** - * Encodes GEO match expression. - * Encoding includes: - * - type of geo query (within/intersect/near) - * - geometry type - * - CRS (flat or spherical) - */ - void encodeGeoMatchExpression(const GeoMatchExpression* tree, StringBuilder* keyBuilder) { - const GeoExpression& geoQuery = tree->getGeoExpression(); - - // Type of geo query. - switch (geoQuery.getPred()) { - case GeoExpression::WITHIN: *keyBuilder << "wi"; break; - case GeoExpression::INTERSECT: *keyBuilder << "in"; break; - case GeoExpression::INVALID: *keyBuilder << "id"; break; - } +/** + * Encodes GEO match expression. + * Encoding includes: + * - type of geo query (within/intersect/near) + * - geometry type + * - CRS (flat or spherical) + */ +void encodeGeoMatchExpression(const GeoMatchExpression* tree, StringBuilder* keyBuilder) { + const GeoExpression& geoQuery = tree->getGeoExpression(); + + // Type of geo query. + switch (geoQuery.getPred()) { + case GeoExpression::WITHIN: + *keyBuilder << "wi"; + break; + case GeoExpression::INTERSECT: + *keyBuilder << "in"; + break; + case GeoExpression::INVALID: + *keyBuilder << "id"; + break; + } - // Geometry type. - // Only one of the shared_ptrs in GeoContainer may be non-NULL. - *keyBuilder << geoQuery.getGeometry().getDebugType(); + // Geometry type. + // Only one of the shared_ptrs in GeoContainer may be non-NULL. + *keyBuilder << geoQuery.getGeometry().getDebugType(); - // CRS (flat or spherical) - if (FLAT == geoQuery.getGeometry().getNativeCRS()) { + // CRS (flat or spherical) + if (FLAT == geoQuery.getGeometry().getNativeCRS()) { + *keyBuilder << "fl"; + } else if (SPHERE == geoQuery.getGeometry().getNativeCRS()) { + *keyBuilder << "sp"; + } else if (STRICT_SPHERE == geoQuery.getGeometry().getNativeCRS()) { + *keyBuilder << "ss"; + } else { + error() << "unknown CRS type " << (int)geoQuery.getGeometry().getNativeCRS() + << " in geometry of type " << geoQuery.getGeometry().getDebugType(); + invariant(false); + } +} + +/** + * Encodes GEO_NEAR match expression. + * Encode: + * - isNearSphere + * - CRS (flat or spherical) + */ +void encodeGeoNearMatchExpression(const GeoNearMatchExpression* tree, StringBuilder* keyBuilder) { + const GeoNearExpression& nearQuery = tree->getData(); + + // isNearSphere + *keyBuilder << (nearQuery.isNearSphere ? "ns" : "nr"); + + // CRS (flat or spherical or strict-winding spherical) + switch (nearQuery.centroid->crs) { + case FLAT: *keyBuilder << "fl"; - } - else if (SPHERE == geoQuery.getGeometry().getNativeCRS()) { + break; + case SPHERE: *keyBuilder << "sp"; - } - else if (STRICT_SPHERE == geoQuery.getGeometry().getNativeCRS()) { + break; + case STRICT_SPHERE: *keyBuilder << "ss"; - } - else { - error() << "unknown CRS type " << (int)geoQuery.getGeometry().getNativeCRS() - << " in geometry of type " << geoQuery.getGeometry().getDebugType(); - invariant(false); - } - } - - /** - * Encodes GEO_NEAR match expression. - * Encode: - * - isNearSphere - * - CRS (flat or spherical) - */ - void encodeGeoNearMatchExpression(const GeoNearMatchExpression* tree, - StringBuilder* keyBuilder) { - const GeoNearExpression& nearQuery = tree->getData(); - - // isNearSphere - *keyBuilder << (nearQuery.isNearSphere ? "ns" : "nr"); - - // CRS (flat or spherical or strict-winding spherical) - switch (nearQuery.centroid->crs) { - case FLAT: *keyBuilder << "fl"; break; - case SPHERE: *keyBuilder << "sp"; break; - case STRICT_SPHERE: *keyBuilder << "ss"; break; + break; case UNSET: error() << "unknown CRS type " << (int)nearQuery.centroid->crs << " in point geometry for near query"; invariant(false); break; - } } +} } // namespace - // - // Cache-related functions for CanonicalQuery - // +// +// Cache-related functions for CanonicalQuery +// - bool PlanCache::shouldCacheQuery(const CanonicalQuery& query) { - const LiteParsedQuery& lpq = query.getParsed(); - const MatchExpression* expr = query.root(); +bool PlanCache::shouldCacheQuery(const CanonicalQuery& query) { + const LiteParsedQuery& lpq = query.getParsed(); + const MatchExpression* expr = query.root(); - // Collection scan - // No sort order requested - if (lpq.getSort().isEmpty() && - expr->matchType() == MatchExpression::AND && expr->numChildren() == 0) { - return false; - } - - // Hint provided - if (!lpq.getHint().isEmpty()) { - return false; - } - - // Min provided - // Min queries are a special case of hinted queries. - if (!lpq.getMin().isEmpty()) { - return false; - } - - // Max provided - // Similar to min, max queries are a special case of hinted queries. - if (!lpq.getMax().isEmpty()) { - return false; - } + // Collection scan + // No sort order requested + if (lpq.getSort().isEmpty() && expr->matchType() == MatchExpression::AND && + expr->numChildren() == 0) { + return false; + } - // Explain queries are not-cacheable. This is primarily because of - // the need to generate current and accurate information in allPlans. - // If the explain report is generated by the cached plan runner using - // stale information from the cache for the losing plans, allPlans would - // simply be wrong. - if (lpq.isExplain()) { - return false; - } + // Hint provided + if (!lpq.getHint().isEmpty()) { + return false; + } - // Tailable cursors won't get cached, just turn into collscans. - if (query.getParsed().isTailable()) { - return false; - } + // Min provided + // Min queries are a special case of hinted queries. + if (!lpq.getMin().isEmpty()) { + return false; + } - // Snapshot is really a hint. - if (query.getParsed().isSnapshot()) { - return false; - } + // Max provided + // Similar to min, max queries are a special case of hinted queries. + if (!lpq.getMax().isEmpty()) { + return false; + } - return true; - } - - // - // CachedSolution - // - - CachedSolution::CachedSolution(const PlanCacheKey& key, const PlanCacheEntry& entry) - : plannerData(entry.plannerData.size()), - key(key), - query(entry.query.getOwned()), - sort(entry.sort.getOwned()), - projection(entry.projection.getOwned()), - decisionWorks(entry.decision->stats[0]->common.works) { - // CachedSolution should not having any references into - // cache entry. All relevant data should be cloned/copied. - for (size_t i = 0; i < entry.plannerData.size(); ++i) { - verify(entry.plannerData[i]); - plannerData[i] = entry.plannerData[i]->clone(); - } + // Explain queries are not-cacheable. This is primarily because of + // the need to generate current and accurate information in allPlans. + // If the explain report is generated by the cached plan runner using + // stale information from the cache for the losing plans, allPlans would + // simply be wrong. + if (lpq.isExplain()) { + return false; } - CachedSolution::~CachedSolution() { - for (std::vector<SolutionCacheData*>::const_iterator i = plannerData.begin(); - i != plannerData.end(); ++i) { - SolutionCacheData* scd = *i; - delete scd; - } + // Tailable cursors won't get cached, just turn into collscans. + if (query.getParsed().isTailable()) { + return false; } - // - // PlanCacheEntry - // + // Snapshot is really a hint. + if (query.getParsed().isSnapshot()) { + return false; + } - PlanCacheEntry::PlanCacheEntry(const std::vector<QuerySolution*>& solutions, - PlanRankingDecision* why) - : plannerData(solutions.size()), - decision(why) { - invariant(why); + return true; +} - // The caller of this constructor is responsible for ensuring - // that the QuerySolution 's' has valid cacheData. If there's no - // data to cache you shouldn't be trying to construct a PlanCacheEntry. +// +// CachedSolution +// - // Copy the solution's cache data into the plan cache entry. - for (size_t i = 0; i < solutions.size(); ++i) { - invariant(solutions[i]->cacheData.get()); - plannerData[i] = solutions[i]->cacheData->clone(); - } +CachedSolution::CachedSolution(const PlanCacheKey& key, const PlanCacheEntry& entry) + : plannerData(entry.plannerData.size()), + key(key), + query(entry.query.getOwned()), + sort(entry.sort.getOwned()), + projection(entry.projection.getOwned()), + decisionWorks(entry.decision->stats[0]->common.works) { + // CachedSolution should not having any references into + // cache entry. All relevant data should be cloned/copied. + for (size_t i = 0; i < entry.plannerData.size(); ++i) { + verify(entry.plannerData[i]); + plannerData[i] = entry.plannerData[i]->clone(); } +} - PlanCacheEntry::~PlanCacheEntry() { - for (size_t i = 0; i < feedback.size(); ++i) { - delete feedback[i]; - } - for (size_t i = 0; i < plannerData.size(); ++i) { - delete plannerData[i]; - } +CachedSolution::~CachedSolution() { + for (std::vector<SolutionCacheData*>::const_iterator i = plannerData.begin(); + i != plannerData.end(); + ++i) { + SolutionCacheData* scd = *i; + delete scd; } +} - PlanCacheEntry* PlanCacheEntry::clone() const { - OwnedPointerVector<QuerySolution> solutions; - for (size_t i = 0; i < plannerData.size(); ++i) { - QuerySolution* qs = new QuerySolution(); - qs->cacheData.reset(plannerData[i]->clone()); - solutions.mutableVector().push_back(qs); - } - PlanCacheEntry* entry = new PlanCacheEntry(solutions.vector(), decision->clone()); - - // Copy query shape. - entry->query = query.getOwned(); - entry->sort = sort.getOwned(); - entry->projection = projection.getOwned(); - - // Copy performance stats. - for (size_t i = 0; i < feedback.size(); ++i) { - PlanCacheEntryFeedback* fb = new PlanCacheEntryFeedback(); - fb->stats.reset(feedback[i]->stats->clone()); - fb->score = feedback[i]->score; - entry->feedback.push_back(fb); - } - return entry; - } +// +// PlanCacheEntry +// - std::string PlanCacheEntry::toString() const { - return str::stream() - << "(query: " << query.toString() - << ";sort: " << sort.toString() - << ";projection: " << projection.toString() - << ";solutions: " << plannerData.size() - << ")"; - } +PlanCacheEntry::PlanCacheEntry(const std::vector<QuerySolution*>& solutions, + PlanRankingDecision* why) + : plannerData(solutions.size()), decision(why) { + invariant(why); - std::string CachedSolution::toString() const { - return str::stream() << "key: " << key << '\n'; + // The caller of this constructor is responsible for ensuring + // that the QuerySolution 's' has valid cacheData. If there's no + // data to cache you shouldn't be trying to construct a PlanCacheEntry. + + // Copy the solution's cache data into the plan cache entry. + for (size_t i = 0; i < solutions.size(); ++i) { + invariant(solutions[i]->cacheData.get()); + plannerData[i] = solutions[i]->cacheData->clone(); } +} - // - // PlanCacheIndexTree - // +PlanCacheEntry::~PlanCacheEntry() { + for (size_t i = 0; i < feedback.size(); ++i) { + delete feedback[i]; + } + for (size_t i = 0; i < plannerData.size(); ++i) { + delete plannerData[i]; + } +} - void PlanCacheIndexTree::setIndexEntry(const IndexEntry& ie) { - entry.reset(new IndexEntry(ie)); +PlanCacheEntry* PlanCacheEntry::clone() const { + OwnedPointerVector<QuerySolution> solutions; + for (size_t i = 0; i < plannerData.size(); ++i) { + QuerySolution* qs = new QuerySolution(); + qs->cacheData.reset(plannerData[i]->clone()); + solutions.mutableVector().push_back(qs); } + PlanCacheEntry* entry = new PlanCacheEntry(solutions.vector(), decision->clone()); - PlanCacheIndexTree* PlanCacheIndexTree::clone() const { - PlanCacheIndexTree* root = new PlanCacheIndexTree(); - if (NULL != entry.get()) { - root->index_pos = index_pos; - root->setIndexEntry(*entry.get()); - } + // Copy query shape. + entry->query = query.getOwned(); + entry->sort = sort.getOwned(); + entry->projection = projection.getOwned(); + // Copy performance stats. + for (size_t i = 0; i < feedback.size(); ++i) { + PlanCacheEntryFeedback* fb = new PlanCacheEntryFeedback(); + fb->stats.reset(feedback[i]->stats->clone()); + fb->score = feedback[i]->score; + entry->feedback.push_back(fb); + } + return entry; +} + +std::string PlanCacheEntry::toString() const { + return str::stream() << "(query: " << query.toString() << ";sort: " << sort.toString() + << ";projection: " << projection.toString() + << ";solutions: " << plannerData.size() << ")"; +} + +std::string CachedSolution::toString() const { + return str::stream() << "key: " << key << '\n'; +} + +// +// PlanCacheIndexTree +// + +void PlanCacheIndexTree::setIndexEntry(const IndexEntry& ie) { + entry.reset(new IndexEntry(ie)); +} + +PlanCacheIndexTree* PlanCacheIndexTree::clone() const { + PlanCacheIndexTree* root = new PlanCacheIndexTree(); + if (NULL != entry.get()) { + root->index_pos = index_pos; + root->setIndexEntry(*entry.get()); + } + + for (std::vector<PlanCacheIndexTree*>::const_iterator it = children.begin(); + it != children.end(); + ++it) { + PlanCacheIndexTree* clonedChild = (*it)->clone(); + root->children.push_back(clonedChild); + } + return root; +} + +std::string PlanCacheIndexTree::toString(int indents) const { + StringBuilder result; + if (!children.empty()) { + result << std::string(3 * indents, '-') << "Node\n"; + int newIndent = indents + 1; for (std::vector<PlanCacheIndexTree*>::const_iterator it = children.begin(); - it != children.end(); ++it) { - PlanCacheIndexTree* clonedChild = (*it)->clone(); - root->children.push_back(clonedChild); - } - return root; - } - - std::string PlanCacheIndexTree::toString(int indents) const { - StringBuilder result; - if (!children.empty()) { - result << std::string(3 * indents, '-') << "Node\n"; - int newIndent = indents + 1; - for (std::vector<PlanCacheIndexTree*>::const_iterator it = children.begin(); - it != children.end(); ++it) { - result << (*it)->toString(newIndent); - } - return result.str(); - } - else { - result << std::string(3 * indents, '-') << "Leaf "; - if (NULL != entry.get()) { - result << entry->keyPattern.toString() << ", pos: " << index_pos; - } - result << '\n'; + it != children.end(); + ++it) { + result << (*it)->toString(newIndent); } return result.str(); + } else { + result << std::string(3 * indents, '-') << "Leaf "; + if (NULL != entry.get()) { + result << entry->keyPattern.toString() << ", pos: " << index_pos; + } + result << '\n'; } + return result.str(); +} - // - // SolutionCacheData - // +// +// SolutionCacheData +// - SolutionCacheData* SolutionCacheData::clone() const { - SolutionCacheData* other = new SolutionCacheData(); - if (NULL != this->tree.get()) { - // 'tree' could be NULL if the cached solution - // is a collection scan. - other->tree.reset(this->tree->clone()); - } - other->solnType = this->solnType; - other->wholeIXSolnDir = this->wholeIXSolnDir; - other->indexFilterApplied = this->indexFilterApplied; - return other; +SolutionCacheData* SolutionCacheData::clone() const { + SolutionCacheData* other = new SolutionCacheData(); + if (NULL != this->tree.get()) { + // 'tree' could be NULL if the cached solution + // is a collection scan. + other->tree.reset(this->tree->clone()); } + other->solnType = this->solnType; + other->wholeIXSolnDir = this->wholeIXSolnDir; + other->indexFilterApplied = this->indexFilterApplied; + return other; +} - std::string SolutionCacheData::toString() const { - switch (this->solnType) { +std::string SolutionCacheData::toString() const { + switch (this->solnType) { case WHOLE_IXSCAN_SOLN: verify(this->tree.get()); - return str::stream() - << "(whole index scan solution: " - << "dir=" << this->wholeIXSolnDir << "; " - << "tree=" << this->tree->toString() - << ")"; + return str::stream() << "(whole index scan solution: " + << "dir=" << this->wholeIXSolnDir << "; " + << "tree=" << this->tree->toString() << ")"; case COLLSCAN_SOLN: return "(collection scan)"; case USE_INDEX_TAGS_SOLN: verify(this->tree.get()); - return str::stream() - << "(index-tagged expression tree: " - << "tree=" << this->tree->toString() - << ")"; - } - MONGO_UNREACHABLE; + return str::stream() << "(index-tagged expression tree: " + << "tree=" << this->tree->toString() << ")"; } + MONGO_UNREACHABLE; +} - // - // PlanCache - // +// +// PlanCache +// - PlanCache::PlanCache() : _cache(internalQueryCacheSize) { } +PlanCache::PlanCache() : _cache(internalQueryCacheSize) {} - PlanCache::PlanCache(const std::string& ns) : _cache(internalQueryCacheSize), _ns(ns) { } +PlanCache::PlanCache(const std::string& ns) : _cache(internalQueryCacheSize), _ns(ns) {} - PlanCache::~PlanCache() { } +PlanCache::~PlanCache() {} - /** - * Traverses expression tree pre-order. - * Appends an encoding of each node's match type and path name - * to the output stream. - */ - void PlanCache::encodeKeyForMatch(const MatchExpression* tree, - StringBuilder* keyBuilder) const { - // Encode match type and path. - *keyBuilder << encodeMatchType(tree->matchType()); +/** + * Traverses expression tree pre-order. + * Appends an encoding of each node's match type and path name + * to the output stream. + */ +void PlanCache::encodeKeyForMatch(const MatchExpression* tree, StringBuilder* keyBuilder) const { + // Encode match type and path. + *keyBuilder << encodeMatchType(tree->matchType()); - encodeUserString(tree->path(), keyBuilder); + encodeUserString(tree->path(), keyBuilder); - // GEO and GEO_NEAR require additional encoding. - if (MatchExpression::GEO == tree->matchType()) { - encodeGeoMatchExpression(static_cast<const GeoMatchExpression*>(tree), keyBuilder); - } - else if (MatchExpression::GEO_NEAR == tree->matchType()) { - encodeGeoNearMatchExpression(static_cast<const GeoNearMatchExpression*>(tree), - keyBuilder); + // GEO and GEO_NEAR require additional encoding. + if (MatchExpression::GEO == tree->matchType()) { + encodeGeoMatchExpression(static_cast<const GeoMatchExpression*>(tree), keyBuilder); + } else if (MatchExpression::GEO_NEAR == tree->matchType()) { + encodeGeoNearMatchExpression(static_cast<const GeoNearMatchExpression*>(tree), keyBuilder); + } + + // Encode indexability. + const IndexabilityDiscriminators& discriminators = + _indexabilityState.getDiscriminators(tree->path()); + if (!discriminators.empty()) { + *keyBuilder << kEncodeDiscriminatorsBegin; + // For each discriminator on this path, append the character '0' or '1'. + for (const IndexabilityDiscriminator& discriminator : discriminators) { + *keyBuilder << discriminator(tree); } + *keyBuilder << kEncodeDiscriminatorsEnd; + } - // Encode indexability. - const IndexabilityDiscriminators& discriminators = - _indexabilityState.getDiscriminators(tree->path()); - if (!discriminators.empty()) { - *keyBuilder << kEncodeDiscriminatorsBegin; - // For each discriminator on this path, append the character '0' or '1'. - for (const IndexabilityDiscriminator& discriminator : discriminators) { - *keyBuilder << discriminator(tree); - } - *keyBuilder << kEncodeDiscriminatorsEnd; + // Traverse child nodes. + // Enclose children in []. + if (tree->numChildren() > 0) { + *keyBuilder << kEncodeChildrenBegin; + } + // Use comma to separate children encoding. + for (size_t i = 0; i < tree->numChildren(); ++i) { + if (i > 0) { + *keyBuilder << kEncodeChildrenSeparator; } + encodeKeyForMatch(tree->getChild(i), keyBuilder); + } + if (tree->numChildren() > 0) { + *keyBuilder << kEncodeChildrenEnd; + } +} + +/** + * Encodes sort order into cache key. + * Sort order is normalized because it provided by + * LiteParsedQuery. + */ +void PlanCache::encodeKeyForSort(const BSONObj& sortObj, StringBuilder* keyBuilder) const { + if (sortObj.isEmpty()) { + return; + } + + *keyBuilder << kEncodeSortSection; - // Traverse child nodes. - // Enclose children in []. - if (tree->numChildren() > 0) { - *keyBuilder << kEncodeChildrenBegin; + BSONObjIterator it(sortObj); + while (it.more()) { + BSONElement elt = it.next(); + // $meta text score + if (LiteParsedQuery::isTextScoreMeta(elt)) { + *keyBuilder << "t"; } - // Use comma to separate children encoding. - for (size_t i = 0; i < tree->numChildren(); ++i) { - if (i > 0) { - *keyBuilder << kEncodeChildrenSeparator; - } - encodeKeyForMatch(tree->getChild(i), keyBuilder); + // Ascending + else if (elt.numberInt() == 1) { + *keyBuilder << "a"; } - if (tree->numChildren() > 0) { - *keyBuilder << kEncodeChildrenEnd; + // Descending + else { + *keyBuilder << "d"; } - } + encodeUserString(elt.fieldName(), keyBuilder); - /** - * Encodes sort order into cache key. - * Sort order is normalized because it provided by - * LiteParsedQuery. - */ - void PlanCache::encodeKeyForSort(const BSONObj& sortObj, StringBuilder* keyBuilder) const { - if (sortObj.isEmpty()) { - return; + // Sort argument separator + if (it.more()) { + *keyBuilder << ","; } + } +} - *keyBuilder << kEncodeSortSection; - - BSONObjIterator it(sortObj); - while (it.more()) { - BSONElement elt = it.next(); - // $meta text score - if (LiteParsedQuery::isTextScoreMeta(elt)) { - *keyBuilder << "t"; - } - // Ascending - else if (elt.numberInt() == 1) { - *keyBuilder << "a"; - } - // Descending - else { - *keyBuilder << "d"; - } - encodeUserString(elt.fieldName(), keyBuilder); - - // Sort argument separator - if (it.more()) { - *keyBuilder << ","; - } - } +/** + * Encodes parsed projection into cache key. + * Does a simple toString() on each projected field + * in the BSON object. + * Orders the encoded elements in the projection by field name. + * This handles all the special projection types ($meta, $elemMatch, etc.) + */ +void PlanCache::encodeKeyForProj(const BSONObj& projObj, StringBuilder* keyBuilder) const { + if (projObj.isEmpty()) { + return; } - /** - * Encodes parsed projection into cache key. - * Does a simple toString() on each projected field - * in the BSON object. - * Orders the encoded elements in the projection by field name. - * This handles all the special projection types ($meta, $elemMatch, etc.) - */ - void PlanCache::encodeKeyForProj(const BSONObj& projObj, StringBuilder* keyBuilder) const { - if (projObj.isEmpty()) { - return; - } + *keyBuilder << kEncodeProjectionSection; - *keyBuilder << kEncodeProjectionSection; + // Sorts the BSON elements by field name using a map. + std::map<StringData, BSONElement> elements; - // Sorts the BSON elements by field name using a map. - std::map<StringData, BSONElement> elements; + BSONObjIterator it(projObj); + while (it.more()) { + BSONElement elt = it.next(); + StringData fieldName = elt.fieldNameStringData(); + elements[fieldName] = elt; + } - BSONObjIterator it(projObj); - while (it.more()) { - BSONElement elt = it.next(); - StringData fieldName = elt.fieldNameStringData(); - elements[fieldName] = elt; - } + // Read elements in order of field name + for (std::map<StringData, BSONElement>::const_iterator i = elements.begin(); + i != elements.end(); + ++i) { + const BSONElement& elt = (*i).second; - // Read elements in order of field name - for (std::map<StringData, BSONElement>::const_iterator i = elements.begin(); - i != elements.end(); ++i) { - const BSONElement& elt = (*i).second; - - if (elt.isSimpleType()) { - // For inclusion/exclusion projections, we encode as "i" or "e". - *keyBuilder << (elt.trueValue() ? "i" : "e"); - } - else { - // For projection operators, we use the verbatim string encoding of the element. - encodeUserString(elt.toString(false, // includeFieldName - false), // full - keyBuilder); - } - - encodeUserString(elt.fieldName(), keyBuilder); + if (elt.isSimpleType()) { + // For inclusion/exclusion projections, we encode as "i" or "e". + *keyBuilder << (elt.trueValue() ? "i" : "e"); + } else { + // For projection operators, we use the verbatim string encoding of the element. + encodeUserString(elt.toString(false, // includeFieldName + false), // full + keyBuilder); } - } - Status PlanCache::add(const CanonicalQuery& query, - const std::vector<QuerySolution*>& solns, - PlanRankingDecision* why) { - invariant(why); + encodeUserString(elt.fieldName(), keyBuilder); + } +} - if (solns.empty()) { - return Status(ErrorCodes::BadValue, "no solutions provided"); - } +Status PlanCache::add(const CanonicalQuery& query, + const std::vector<QuerySolution*>& solns, + PlanRankingDecision* why) { + invariant(why); - if (why->stats.size() != solns.size()) { - return Status(ErrorCodes::BadValue, - "number of stats in decision must match solutions"); - } + if (solns.empty()) { + return Status(ErrorCodes::BadValue, "no solutions provided"); + } - if (why->scores.size() != solns.size()) { - return Status(ErrorCodes::BadValue, - "number of scores in decision must match solutions"); - } + if (why->stats.size() != solns.size()) { + return Status(ErrorCodes::BadValue, "number of stats in decision must match solutions"); + } - if (why->candidateOrder.size() != solns.size()) { - return Status(ErrorCodes::BadValue, - "candidate ordering entries in decision must match solutions"); - } + if (why->scores.size() != solns.size()) { + return Status(ErrorCodes::BadValue, "number of scores in decision must match solutions"); + } - PlanCacheEntry* entry = new PlanCacheEntry(solns, why); - const LiteParsedQuery& pq = query.getParsed(); - entry->query = pq.getFilter().getOwned(); - entry->sort = pq.getSort().getOwned(); - entry->projection = pq.getProj().getOwned(); + if (why->candidateOrder.size() != solns.size()) { + return Status(ErrorCodes::BadValue, + "candidate ordering entries in decision must match solutions"); + } - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - std::unique_ptr<PlanCacheEntry> evictedEntry = _cache.add(computeKey(query), entry); + PlanCacheEntry* entry = new PlanCacheEntry(solns, why); + const LiteParsedQuery& pq = query.getParsed(); + entry->query = pq.getFilter().getOwned(); + entry->sort = pq.getSort().getOwned(); + entry->projection = pq.getProj().getOwned(); - if (NULL != evictedEntry.get()) { - LOG(1) << _ns << ": plan cache maximum size exceeded - " - << "removed least recently used entry " - << evictedEntry->toString(); - } + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + std::unique_ptr<PlanCacheEntry> evictedEntry = _cache.add(computeKey(query), entry); - return Status::OK(); + if (NULL != evictedEntry.get()) { + LOG(1) << _ns << ": plan cache maximum size exceeded - " + << "removed least recently used entry " << evictedEntry->toString(); } - Status PlanCache::get(const CanonicalQuery& query, CachedSolution** crOut) const { - PlanCacheKey key = computeKey(query); - verify(crOut); + return Status::OK(); +} - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - PlanCacheEntry* entry; - Status cacheStatus = _cache.get(key, &entry); - if (!cacheStatus.isOK()) { - return cacheStatus; - } - invariant(entry); +Status PlanCache::get(const CanonicalQuery& query, CachedSolution** crOut) const { + PlanCacheKey key = computeKey(query); + verify(crOut); - *crOut = new CachedSolution(key, *entry); - - return Status::OK(); + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + PlanCacheEntry* entry; + Status cacheStatus = _cache.get(key, &entry); + if (!cacheStatus.isOK()) { + return cacheStatus; } + invariant(entry); - Status PlanCache::feedback(const CanonicalQuery& cq, PlanCacheEntryFeedback* feedback) { - if (NULL == feedback) { - return Status(ErrorCodes::BadValue, "feedback is NULL"); - } - std::unique_ptr<PlanCacheEntryFeedback> autoFeedback(feedback); - PlanCacheKey ck = computeKey(cq); - - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - PlanCacheEntry* entry; - Status cacheStatus = _cache.get(ck, &entry); - if (!cacheStatus.isOK()) { - return cacheStatus; - } - invariant(entry); + *crOut = new CachedSolution(key, *entry); - // We store up to a constant number of feedback entries. - if (entry->feedback.size() < size_t(internalQueryCacheFeedbacksStored)) { - entry->feedback.push_back(autoFeedback.release()); - } + return Status::OK(); +} - return Status::OK(); +Status PlanCache::feedback(const CanonicalQuery& cq, PlanCacheEntryFeedback* feedback) { + if (NULL == feedback) { + return Status(ErrorCodes::BadValue, "feedback is NULL"); } + std::unique_ptr<PlanCacheEntryFeedback> autoFeedback(feedback); + PlanCacheKey ck = computeKey(cq); - Status PlanCache::remove(const CanonicalQuery& canonicalQuery) { - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - return _cache.remove(computeKey(canonicalQuery)); + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + PlanCacheEntry* entry; + Status cacheStatus = _cache.get(ck, &entry); + if (!cacheStatus.isOK()) { + return cacheStatus; } + invariant(entry); - void PlanCache::clear() { - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - _cache.clear(); - _writeOperations.store(0); + // We store up to a constant number of feedback entries. + if (entry->feedback.size() < size_t(internalQueryCacheFeedbacksStored)) { + entry->feedback.push_back(autoFeedback.release()); } - PlanCacheKey PlanCache::computeKey(const CanonicalQuery& cq) const { - StringBuilder keyBuilder; - encodeKeyForMatch(cq.root(), &keyBuilder); - encodeKeyForSort(cq.getParsed().getSort(), &keyBuilder); - encodeKeyForProj(cq.getParsed().getProj(), &keyBuilder); - return keyBuilder.str(); - } + return Status::OK(); +} - Status PlanCache::getEntry(const CanonicalQuery& query, PlanCacheEntry** entryOut) const { - PlanCacheKey key = computeKey(query); - verify(entryOut); +Status PlanCache::remove(const CanonicalQuery& canonicalQuery) { + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + return _cache.remove(computeKey(canonicalQuery)); +} - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - PlanCacheEntry* entry; - Status cacheStatus = _cache.get(key, &entry); - if (!cacheStatus.isOK()) { - return cacheStatus; - } - invariant(entry); +void PlanCache::clear() { + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + _cache.clear(); + _writeOperations.store(0); +} - *entryOut = entry->clone(); +PlanCacheKey PlanCache::computeKey(const CanonicalQuery& cq) const { + StringBuilder keyBuilder; + encodeKeyForMatch(cq.root(), &keyBuilder); + encodeKeyForSort(cq.getParsed().getSort(), &keyBuilder); + encodeKeyForProj(cq.getParsed().getProj(), &keyBuilder); + return keyBuilder.str(); +} - return Status::OK(); +Status PlanCache::getEntry(const CanonicalQuery& query, PlanCacheEntry** entryOut) const { + PlanCacheKey key = computeKey(query); + verify(entryOut); + + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + PlanCacheEntry* entry; + Status cacheStatus = _cache.get(key, &entry); + if (!cacheStatus.isOK()) { + return cacheStatus; } + invariant(entry); - std::vector<PlanCacheEntry*> PlanCache::getAllEntries() const { - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - std::vector<PlanCacheEntry*> entries; - typedef std::list< std::pair<PlanCacheKey, PlanCacheEntry*> >::const_iterator ConstIterator; - for (ConstIterator i = _cache.begin(); i != _cache.end(); i++) { - PlanCacheEntry* entry = i->second; - entries.push_back(entry->clone()); - } + *entryOut = entry->clone(); - return entries; - } + return Status::OK(); +} - bool PlanCache::contains(const CanonicalQuery& cq) const { - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - return _cache.hasKey(computeKey(cq)); +std::vector<PlanCacheEntry*> PlanCache::getAllEntries() const { + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + std::vector<PlanCacheEntry*> entries; + typedef std::list<std::pair<PlanCacheKey, PlanCacheEntry*>>::const_iterator ConstIterator; + for (ConstIterator i = _cache.begin(); i != _cache.end(); i++) { + PlanCacheEntry* entry = i->second; + entries.push_back(entry->clone()); } - size_t PlanCache::size() const { - stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); - return _cache.size(); - } + return entries; +} - void PlanCache::notifyOfWriteOp() { - // It's fine to clear the cache multiple times if multiple threads - // increment the counter to kPlanCacheMaxWriteOperations or greater. - if (_writeOperations.addAndFetch(1) < internalQueryCacheWriteOpsBetweenFlush) { - return; - } +bool PlanCache::contains(const CanonicalQuery& cq) const { + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + return _cache.hasKey(computeKey(cq)); +} - LOG(1) << _ns << ": clearing collection plan cache - " - << internalQueryCacheWriteOpsBetweenFlush - << " write operations detected since last refresh."; - clear(); - } +size_t PlanCache::size() const { + stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); + return _cache.size(); +} - void PlanCache::notifyOfIndexEntries(const std::vector<IndexEntry>& indexEntries) { - _indexabilityState.updateDiscriminators(indexEntries); +void PlanCache::notifyOfWriteOp() { + // It's fine to clear the cache multiple times if multiple threads + // increment the counter to kPlanCacheMaxWriteOperations or greater. + if (_writeOperations.addAndFetch(1) < internalQueryCacheWriteOpsBetweenFlush) { + return; } + LOG(1) << _ns << ": clearing collection plan cache - " << internalQueryCacheWriteOpsBetweenFlush + << " write operations detected since last refresh."; + clear(); +} + +void PlanCache::notifyOfIndexEntries(const std::vector<IndexEntry>& indexEntries) { + _indexabilityState.updateDiscriminators(indexEntries); +} + } // namespace mongo |