bool SubplanStage::canUseSubplanning(const CanonicalQuery& query) { const QueryRequest& qr = query.getQueryRequest(); const MatchExpression* expr = query.root(); // Hint provided if (!qr.getHint().isEmpty()) { return false; } // Min provided // Min queries are a special case of hinted queries. if (!qr.getMin().isEmpty()) { return false; } // Max provided // Similar to min, max queries are a special case of hinted queries. if (!qr.getMax().isEmpty()) { return false; } // Tailable cursors won't get cached, just turn into collscans. if (query.getQueryRequest().isTailable()) { return false; } // We can only subplan rooted $or queries, and only if they have at least one clause. return MatchExpression::OR == expr->matchType() && expr->numChildren() > 0; }
// static StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( const CanonicalQuery& baseQuery, MatchExpression* root, const MatchExpressionParser::WhereCallback& whereCallback) { // TODO: we should be passing the filter corresponding to 'root' to the LPQ rather than the base // query's filter, baseQuery.getParsed().getFilter(). BSONObj emptyObj; auto lpqStatus = LiteParsedQuery::makeAsOpQuery(baseQuery.nss(), 0, // ntoskip 0, // ntoreturn 0, // queryOptions baseQuery.getParsed().getFilter(), baseQuery.getParsed().getProj(), baseQuery.getParsed().getSort(), emptyObj, // hint emptyObj, // min emptyObj, // max false, // snapshot baseQuery.getParsed().isExplain()); if (!lpqStatus.isOK()) { return lpqStatus.getStatus(); } // Make the CQ we'll hopefully return. std::unique_ptr<CanonicalQuery> cq(new CanonicalQuery()); Status initStatus = cq->init(lpqStatus.getValue().release(), whereCallback, root->shallowClone().release()); if (!initStatus.isOK()) { return initStatus; } return std::move(cq); }
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; } return true; }
bool SubplanStage::canUseSubplanning(const CanonicalQuery& query) { const LiteParsedQuery& lpq = query.getParsed(); const MatchExpression* expr = query.root(); // 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; } // Tailable cursors won't get cached, just turn into collscans. if (query.getParsed().isTailable()) { return false; } // Snapshot is really a hint. if (query.getParsed().isSnapshot()) { return false; } // TODO: For now we only allow rooted OR. We should consider also allowing contained OR that // does not have a TEXT or GEO_NEAR node. return MatchExpression::OR == expr->matchType(); }
Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery& query, const vector<FieldRef*>* immutablePathsPtr, mutablebson::Document& doc) const { EqualityMatches equalities; Status status = Status::OK(); if (isDocReplacement()) { FieldRefSet pathsToExtract; // TODO: Refactor update logic, make _id just another immutable field static const FieldRef idPath("_id"); static const vector<FieldRef*> emptyImmutablePaths; const vector<FieldRef*>& immutablePaths = immutablePathsPtr ? *immutablePathsPtr : emptyImmutablePaths; pathsToExtract.fillFrom(immutablePaths); pathsToExtract.insert(&idPath); // Extract only immutable fields from replacement-style status = pathsupport::extractFullEqualityMatches(*query.root(), pathsToExtract, &equalities); } else { // Extract all fields from op-style status = pathsupport::extractEqualityMatches(*query.root(), &equalities); } if (!status.isOK()) return status; status = pathsupport::addEqualitiesToDoc(equalities, &doc); return status; }
Status SubplanRunner::planSubqueries() { MatchExpression* theOr = _query->root(); for (size_t i = 0; i < _plannerParams.indices.size(); ++i) { const IndexEntry& ie = _plannerParams.indices[i]; _indexMap[ie.keyPattern] = i; QLOG() << "Subplanner: index " << i << " is " << ie.toString() << endl; } const WhereCallbackReal whereCallback(_collection->ns().db()); for (size_t i = 0; i < theOr->numChildren(); ++i) { // Turn the i-th child into its own query. MatchExpression* orChild = theOr->getChild(i); CanonicalQuery* orChildCQ; Status childCQStatus = CanonicalQuery::canonicalize(*_query, orChild, &orChildCQ, whereCallback); if (!childCQStatus.isOK()) { mongoutils::str::stream ss; ss << "Subplanner: Can't canonicalize subchild " << orChild->toString() << " " << childCQStatus.reason(); return Status(ErrorCodes::BadValue, ss); } // Make sure it gets cleaned up. auto_ptr<CanonicalQuery> safeOrChildCQ(orChildCQ); // Plan the i-th child. vector<QuerySolution*> solutions; // We don't set NO_TABLE_SCAN because peeking at the cache data will keep us from // considering any plan that's a collscan. QLOG() << "Subplanner: planning child " << i << " of " << theOr->numChildren(); Status status = QueryPlanner::plan(*safeOrChildCQ, _plannerParams, &solutions); if (!status.isOK()) { mongoutils::str::stream ss; ss << "Subplanner: Can't plan for subchild " << orChildCQ->toString() << " " << status.reason(); return Status(ErrorCodes::BadValue, ss); } QLOG() << "Subplanner: got " << solutions.size() << " solutions"; if (0 == solutions.size()) { // If one child doesn't have an indexed solution, bail out. mongoutils::str::stream ss; ss << "Subplanner: No solutions for subchild " << orChildCQ->toString(); return Status(ErrorCodes::BadValue, ss); } // Hang onto the canonicalized subqueries and the corresponding query solutions // so that they can be used in subplan running later on. _cqs.push(safeOrChildCQ.release()); _solutions.push(solutions); } return Status::OK(); }
PlanCacheKey PlanCache::computeKey(const CanonicalQuery& cq) const { StringBuilder keyBuilder; encodeKeyForMatch(cq.root(), &keyBuilder); encodeKeyForSort(cq.getQueryRequest().getSort(), &keyBuilder); encodeKeyForProj(cq.getQueryRequest().getProj(), &keyBuilder); return keyBuilder.str(); }
// static bool IDHackStage::supportsQuery(const CanonicalQuery& query) { return !query.getParsed().showDiskLoc() && query.getParsed().getHint().isEmpty() && 0 == query.getParsed().getSkip() && CanonicalQuery::isSimpleIdQuery(query.getParsed().getFilter()) && !query.getParsed().getOptions().tailable; }
PlanCacheKey PlanCache::computeKey(const CanonicalQuery& cq) const { str::stream ss; encodePlanCacheKeyTree(cq.root(), &ss); encodePlanCacheKeySort(cq.getParsed().getSort(), &ss); encodePlanCacheKeyProj(cq.getParsed().getProj(), &ss); return ss; }
// static Status CanonicalQuery::canonicalize(const CanonicalQuery& baseQuery, MatchExpression* root, CanonicalQuery** out, const MatchExpressionParser::WhereCallback& whereCallback) { LiteParsedQuery* lpq; // Pass empty sort and projection. BSONObj emptyObj; // 0, 0, 0 is 'ntoskip', 'ntoreturn', and 'queryoptions' // false, false is 'snapshot' and 'explain' Status parseStatus = LiteParsedQuery::make(baseQuery.ns(), 0, 0, 0, baseQuery.getParsed().getFilter(), baseQuery.getParsed().getProj(), baseQuery.getParsed().getSort(), emptyObj, emptyObj, emptyObj, false, false, &lpq); if (!parseStatus.isOK()) { return parseStatus; } // Make the CQ we'll hopefully return. std::auto_ptr<CanonicalQuery> cq(new CanonicalQuery()); Status initStatus = cq->init(lpq, whereCallback, root->shallowClone()); if (!initStatus.isOK()) { return initStatus; } *out = cq.release(); return Status::OK(); }
/** * Cache key is a string-ified combination of the query and sort obfuscated * for minimal user comprehension. */ PlanCacheKey PlanCache::getPlanCacheKey(const CanonicalQuery& query) { stringstream ss; encodePlanCacheKeyTree(query.root(), &ss); encodePlanCacheKeySort(query.getParsed().getSort(), &ss); encodePlanCacheKeyProj(query.getParsed().getProj(), &ss); PlanCacheKey key(ss.str()); return key; }
// static bool IDHackRunner::supportsQuery(const CanonicalQuery& query) { return !query.getParsed().showDiskLoc() && query.getParsed().getHint().isEmpty() && 0 == query.getParsed().getSkip() && canUseProjection(query) && CanonicalQuery::isSimpleIdQuery(query.getParsed().getFilter()) && !query.getParsed().hasOption(QueryOption_CursorTailable); }
Status PlanCache::add(const CanonicalQuery& query, const std::vector<QuerySolution*>& solns, PlanRankingDecision* why, Date_t now) { invariant(why); if (solns.empty()) { return Status(ErrorCodes::BadValue, "no solutions provided"); } if (why->stats.size() != solns.size()) { return Status(ErrorCodes::BadValue, "number of stats in decision must match solutions"); } if (why->scores.size() != solns.size()) { return Status(ErrorCodes::BadValue, "number of scores in decision must match solutions"); } if (why->candidateOrder.size() != solns.size()) { return Status(ErrorCodes::BadValue, "candidate ordering entries in decision must match solutions"); } PlanCacheEntry* entry = new PlanCacheEntry(solns, why); const QueryRequest& qr = query.getQueryRequest(); entry->query = qr.getFilter().getOwned(); entry->sort = qr.getSort().getOwned(); if (query.getCollator()) { entry->collation = query.getCollator()->getSpec().toBSON(); } entry->timeOfCreation = now; // Strip projections on $-prefixed fields, as these are added by internal callers of the query // system and are not considered part of the user projection. BSONObjBuilder projBuilder; for (auto elem : qr.getProj()) { if (elem.fieldName()[0] == '$') { continue; } projBuilder.append(elem); } entry->projection = projBuilder.obj(); stdx::lock_guard<stdx::mutex> cacheLock(_cacheMutex); std::unique_ptr<PlanCacheEntry> evictedEntry = _cache.add(computeKey(query), entry); if (NULL != evictedEntry.get()) { LOG(1) << _ns << ": plan cache maximum size exceeded - " << "removed least recently used entry " << redact(evictedEntry->toString()); } return Status::OK(); }
StatusWith<CursorId> ClusterFind::runQuery(OperationContext* txn, const CanonicalQuery& query, const ReadPreferenceSetting& readPref, std::vector<BSONObj>* results) { invariant(results); // Projection on the reserved sort key field is illegal in mongos. if (query.getParsed().getProj().hasField(ClusterClientCursorParams::kSortKeyField)) { return {ErrorCodes::BadValue, str::stream() << "Projection contains illegal field '" << ClusterClientCursorParams::kSortKeyField << "': " << query.getParsed().getProj()}; } auto dbConfig = grid.catalogCache()->getDatabase(txn, query.nss().db().toString()); if (dbConfig.getStatus() == ErrorCodes::DatabaseNotFound) { // If the database doesn't exist, we successfully return an empty result set without // creating a cursor. return CursorId(0); } else if (!dbConfig.isOK()) { return dbConfig.getStatus(); } std::shared_ptr<ChunkManager> chunkManager; std::shared_ptr<Shard> primary; dbConfig.getValue()->getChunkManagerOrPrimary(txn, query.nss().ns(), chunkManager, primary); // Re-target and re-send the initial find command to the shards until we have established the // shard version. for (size_t retries = 1; retries <= kMaxStaleConfigRetries; ++retries) { auto cursorId = runQueryWithoutRetrying( txn, query, readPref, chunkManager.get(), std::move(primary), results); if (cursorId.isOK()) { return cursorId; } auto status = std::move(cursorId.getStatus()); if (status != ErrorCodes::SendStaleConfig && status != ErrorCodes::RecvStaleConfig) { // Errors other than receiving a stale config message from mongoD are fatal to the // operation. return status; } LOG(1) << "Received stale config for query " << query.toStringShort() << " on attempt " << retries << " of " << kMaxStaleConfigRetries << ": " << status.reason(); invariant(chunkManager); chunkManager = chunkManager->reload(txn); } return {ErrorCodes::StaleShardVersion, str::stream() << "Retried " << kMaxStaleConfigRetries << " times without establishing shard version."}; }
// static size_t MultiPlanStage::getTrialPeriodNumToReturn(const CanonicalQuery& query) { // Determine the number of results which we will produce during the plan // ranking phase before stopping. size_t numResults = static_cast<size_t>(internalQueryPlanEvaluationMaxResults); if (query.getParsed().getNToReturn()) { numResults = std::min(static_cast<size_t>(*query.getParsed().getNToReturn()), numResults); } else if (query.getParsed().getLimit()) { numResults = std::min(static_cast<size_t>(*query.getParsed().getLimit()), numResults); } return numResults; }
// static void Explain::generatePlannerInfo(PlanExecutor* exec, PlanStageStats* winnerStats, const vector<PlanStageStats*>& rejectedStats, BSONObjBuilder* out) { CanonicalQuery* query = exec->getCanonicalQuery(); BSONObjBuilder plannerBob(out->subobjStart("queryPlanner")); ; plannerBob.append("plannerVersion", QueryPlanner::kPlannerVersion); plannerBob.append("namespace", exec->ns()); // Find whether there is an index filter set for the query shape. The 'indexFilterSet' // field will always be false in the case of EOF or idhack plans. bool indexFilterSet = false; if (exec->collection() && exec->getCanonicalQuery()) { const CollectionInfoCache* infoCache = exec->collection()->infoCache(); const QuerySettings* querySettings = infoCache->getQuerySettings(); PlanCacheKey planCacheKey = infoCache->getPlanCache()->computeKey(*exec->getCanonicalQuery()); AllowedIndices* allowedIndicesRaw; if (querySettings->getAllowedIndices(planCacheKey, &allowedIndicesRaw)) { // Found an index filter set on the query shape. std::unique_ptr<AllowedIndices> allowedIndices(allowedIndicesRaw); indexFilterSet = true; } } plannerBob.append("indexFilterSet", indexFilterSet); // In general we should have a canonical query, but sometimes we may avoid // creating a canonical query as an optimization (specifically, the update system // does not canonicalize for idhack updates). In these cases, 'query' is NULL. if (NULL != query) { BSONObjBuilder parsedQueryBob(plannerBob.subobjStart("parsedQuery")); query->root()->toBSON(&parsedQueryBob); parsedQueryBob.doneFast(); } BSONObjBuilder winningPlanBob(plannerBob.subobjStart("winningPlan")); statsToBSON(*winnerStats, &winningPlanBob, ExplainCommon::QUERY_PLANNER); winningPlanBob.doneFast(); // Genenerate array of rejected plans. BSONArrayBuilder allPlansBob(plannerBob.subarrayStart("rejectedPlans")); for (size_t i = 0; i < rejectedStats.size(); i++) { BSONObjBuilder childBob(allPlansBob.subobjStart()); statsToBSON(*rejectedStats[i], &childBob, ExplainCommon::QUERY_PLANNER); } allPlansBob.doneFast(); plannerBob.doneFast(); }
Status PlanCache::add(const CanonicalQuery& query, const std::vector<QuerySolution*>& solns, PlanRankingDecision* why) { invariant(why); if (solns.empty()) { return Status(ErrorCodes::BadValue, "no solutions provided"); } if (why->stats.size() != solns.size()) { return Status(ErrorCodes::BadValue, "number of stats in decision must match solutions"); } if (why->scores.size() != solns.size()) { return Status(ErrorCodes::BadValue, "number of scores in decision must match solutions"); } if (why->candidateOrder.size() != solns.size()) { return Status(ErrorCodes::BadValue, "candidate ordering entries 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 the winning solution uses a blocking stage, then try and // find a fallback solution that has no blocking stage. if (solns[0]->hasBlockingStage) { for (size_t i = 1; i < solns.size(); ++i) { if (!solns[i]->hasBlockingStage) { entry->backupSoln.reset(i); break; } } } boost::lock_guard<boost::mutex> cacheLock(_cacheMutex); std::auto_ptr<PlanCacheEntry> evictedEntry = _cache.add(query.getPlanCacheKey(), entry); if (NULL != evictedEntry.get()) { LOG(1) << _ns << ": plan cache maximum size exceeded - " << "removed least recently used entry " << evictedEntry->toString(); } return Status::OK(); }
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; } // 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; } // Tailable cursors won't get cached, just turn into collscans. if (query.getParsed().isTailable()) { return false; } // Snapshot is really a hint. if (query.getParsed().isSnapshot()) { return false; } return true; }
void ChunkManager::getShardIdsForQuery(set<ShardId>& shardIds, const BSONObj& query) const { CanonicalQuery* canonicalQuery = NULL; Status status = CanonicalQuery::canonicalize( _ns, query, &canonicalQuery, WhereCallbackNoop()); boost::scoped_ptr<CanonicalQuery> canonicalQueryPtr(canonicalQuery); uassert(status.code(), status.reason(), status.isOK()); // Query validation if (QueryPlannerCommon::hasNode(canonicalQuery->root(), MatchExpression::GEO_NEAR)) { uassert(13501, "use geoNear command rather than $near query", false); } // Transforms query into bounds for each field in the shard key // for example : // Key { a: 1, b: 1 }, // Query { a : { $gte : 1, $lt : 2 }, // b : { $gte : 3, $lt : 4 } } // => Bounds { a : [1, 2), b : [3, 4) } IndexBounds bounds = getIndexBoundsForQuery(_keyPattern.toBSON(), canonicalQuery); // Transforms bounds for each shard key field into full shard key ranges // for example : // Key { a : 1, b : 1 } // Bounds { a : [1, 2), b : [3, 4) } // => Ranges { a : 1, b : 3 } => { a : 2, b : 4 } BoundList ranges = _keyPattern.flattenBounds(bounds); for (BoundList::const_iterator it = ranges.begin(); it != ranges.end(); ++it) { getShardIdsForRange(shardIds, it->first /*min*/, it->second /*max*/); // once we know we need to visit all shards no need to keep looping if(shardIds.size() == _shardIds.size()) break; } // SERVER-4914 Some clients of getShardIdsForQuery() assume at least one shard will be // returned. For now, we satisfy that assumption by adding a shard with no matches rather // than return an empty set of shards. if (shardIds.empty()) { massert( 16068, "no chunk ranges available", !_chunkRanges.ranges().empty() ); shardIds.insert(_chunkRanges.ranges().begin()->second->getShardId()); } }
bool PlanCache::shouldCacheQuery(const CanonicalQuery& query) { const QueryRequest& qr = query.getQueryRequest(); const MatchExpression* expr = query.root(); // Collection scan // No sort order requested if (qr.getSort().isEmpty() && expr->matchType() == MatchExpression::AND && expr->numChildren() == 0) { return false; } // Hint provided if (!qr.getHint().isEmpty()) { return false; } // Min provided // Min queries are a special case of hinted queries. if (!qr.getMin().isEmpty()) { return false; } // Max provided // Similar to min, max queries are a special case of hinted queries. if (!qr.getMax().isEmpty()) { return false; } // We don't read or write from the plan cache for explain. This ensures // that explain queries don't affect cache state, and it also makes // sure that we can always generate information regarding rejected plans // and/or trial period execution of candidate plans. if (qr.isExplain()) { return false; } // Tailable cursors won't get cached, just turn into collscans. if (query.getQueryRequest().isTailable()) { return false; } // Snapshot is really a hint. if (query.getQueryRequest().isSnapshot()) { return false; } return true; }
// static Status QueryPlanner::planFromCache(const CanonicalQuery& query, const QueryPlannerParams& params, CachedSolution* cachedSoln, QuerySolution** out) { // Create a copy of the expression tree. We use cachedSoln to annotate this with indices. MatchExpression* clone = query.root()->shallowClone(); // XXX: Use data in cachedSoln to tag 'clone' with the indices used. The tags use an index // ID which is an index into some vector of IndexEntry(s). How do we maintain this across // calls to plan? Do we want to store in the soln the keypatterns of the indices and just // map those to an index into params.indices? Might be easiest thing to do, and certainly // most intelligible for debugging. // Use the cached index assignments to build solnRoot. Takes ownership of clone. QuerySolutionNode* solnRoot = QueryPlannerAccess::buildIndexedDataAccess(query, clone, false, params.indices); // XXX: are the NULL cases an error/when does this happen / can this happen? if (NULL != solnRoot) { QuerySolution* soln = QueryPlannerAnalysis::analyzeDataAccess(query, params, solnRoot); if (NULL != soln) { QLOG() << "Planner: adding cached solution:\n" << soln->toString() << endl; *out = soln; } } // XXX: if any NULLs return error status? return Status::OK(); }
Status PlanCache::feedback(const CanonicalQuery& cq, PlanCacheEntryFeedback* feedback) { if (NULL == feedback) { return Status(ErrorCodes::BadValue, "feedback is NULL"); } std::auto_ptr<PlanCacheEntryFeedback> autoFeedback(feedback); const PlanCacheKey& ck = cq.getPlanCacheKey(); boost::lock_guard<boost::mutex> cacheLock(_cacheMutex); PlanCacheEntry* entry; Status cacheStatus = _cache.get(ck, &entry); if (!cacheStatus.isOK()) { return cacheStatus; } invariant(entry); if (entry->feedback.size() >= size_t(internalQueryCacheFeedbacksStored)) { // If we have enough feedback, then use it to determine whether // we should get rid of the cached solution. if (hasCachedPlanPerformanceDegraded(entry, autoFeedback.get())) { LOG(1) << _ns << ": removing plan cache entry " << entry->toString() << " - detected degradation in performance of cached solution."; _cache.remove(ck); } } else { // We don't have enough feedback yet---just store it and move on. entry->feedback.push_back(autoFeedback.release()); } return Status::OK(); }
Status PlanCache::add(const CanonicalQuery& query, const std::vector<QuerySolution*>& solns, PlanRankingDecision* why) { verify(why); if (solns.empty()) { return Status(ErrorCodes::BadValue, "no solutions provided"); } PlanCacheKey key = getPlanCacheKey(query); PlanCacheEntry* entry = new PlanCacheEntry(solns, why); const LiteParsedQuery& pq = query.getParsed(); entry->query = pq.getFilter().copy(); entry->sort = pq.getSort().copy(); entry->projection = pq.getProj().copy(); boost::lock_guard<boost::mutex> cacheLock(_cacheMutex); // XXX: Replacing existing entry - revisit when we have real cached solutions. // Delete previous entry typedef unordered_map<PlanCacheKey, PlanCacheEntry*>::const_iterator ConstIterator; ConstIterator i = _cache.find(key); if (i != _cache.end()) { PlanCacheEntry* previousEntry = i->second; delete previousEntry; } _cache[key] = entry; return Status::OK(); }
// static StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( OperationContext* opCtx, const CanonicalQuery& baseQuery, MatchExpression* root) { auto qr = stdx::make_unique<QueryRequest>(baseQuery.nss()); BSONObjBuilder builder; root->serialize(&builder); qr->setFilter(builder.obj()); qr->setProj(baseQuery.getQueryRequest().getProj()); qr->setSort(baseQuery.getQueryRequest().getSort()); qr->setCollation(baseQuery.getQueryRequest().getCollation()); qr->setExplain(baseQuery.getQueryRequest().isExplain()); auto qrStatus = qr->validate(); if (!qrStatus.isOK()) { return qrStatus; } std::unique_ptr<CollatorInterface> collator; if (baseQuery.getCollator()) { collator = baseQuery.getCollator()->clone(); } // Make the CQ we'll hopefully return. std::unique_ptr<CanonicalQuery> cq(new CanonicalQuery()); Status initStatus = cq->init(opCtx, std::move(qr), baseQuery.canHaveNoopMatchNodes(), root->shallowClone(), std::move(collator)); if (!initStatus.isOK()) { return initStatus; } return std::move(cq); }
// static bool SubplanRunner::canUseSubplanRunner(const CanonicalQuery& query) { const LiteParsedQuery& lpq = query.getParsed(); const MatchExpression* expr = query.root(); // Only rooted ORs work with the subplan scheme. if (MatchExpression::OR != expr->matchType()) { return false; } // 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; } // Tailable cursors won't get cached, just turn into collscans. if (query.getParsed().hasOption(QueryOption_CursorTailable)) { return false; } // Snapshot is really a hint. if (query.getParsed().isSnapshot()) { return false; } return true; }
// static StatusWith<std::unique_ptr<CanonicalQuery>> CanonicalQuery::canonicalize( OperationContext* opCtx, const CanonicalQuery& baseQuery, MatchExpression* root, const ExtensionsCallback& extensionsCallback) { // TODO: we should be passing the filter corresponding to 'root' to the QR rather than the base // query's filter, baseQuery.getQueryRequest().getFilter(). auto qr = stdx::make_unique<QueryRequest>(baseQuery.nss()); qr->setFilter(baseQuery.getQueryRequest().getFilter()); qr->setProj(baseQuery.getQueryRequest().getProj()); qr->setSort(baseQuery.getQueryRequest().getSort()); qr->setCollation(baseQuery.getQueryRequest().getCollation()); qr->setExplain(baseQuery.getQueryRequest().isExplain()); auto qrStatus = qr->validate(); if (!qrStatus.isOK()) { return qrStatus; } std::unique_ptr<CollatorInterface> collator; if (baseQuery.getCollator()) { collator = baseQuery.getCollator()->clone(); } // Make the CQ we'll hopefully return. std::unique_ptr<CanonicalQuery> cq(new CanonicalQuery()); Status initStatus = cq->init( std::move(qr), extensionsCallback, root->shallowClone().release(), std::move(collator)); if (!initStatus.isOK()) { return initStatus; } return std::move(cq); }
void QuerySettings::setAllowedIndices(const CanonicalQuery& canonicalQuery, const std::vector<BSONObj>& indexes) { const LiteParsedQuery& lpq = canonicalQuery.getParsed(); const BSONObj& query = lpq.getFilter(); const BSONObj& sort = lpq.getSort(); const BSONObj& projection = lpq.getProj(); AllowedIndexEntry* entry = new AllowedIndexEntry(query, sort, projection, indexes); const PlanCacheKey& key = canonicalQuery.getPlanCacheKey(); boost::lock_guard<boost::mutex> cacheLock(_mutex); AllowedIndexEntryMap::iterator i = _allowedIndexEntryMap.find(key); // Replace existing entry. if (i != _allowedIndexEntryMap.end()) { AllowedIndexEntry* entry = i->second; delete entry; } _allowedIndexEntryMap[key] = entry; }
void QuerySettings::setAllowedIndices(const CanonicalQuery& canonicalQuery, const PlanCacheKey& key, const BSONObjSet& indexKeyPatterns, const stdx::unordered_set<std::string>& indexNames) { const QueryRequest& qr = canonicalQuery.getQueryRequest(); const BSONObj& query = qr.getFilter(); const BSONObj& sort = qr.getSort(); const BSONObj& projection = qr.getProj(); const BSONObj collation = canonicalQuery.getCollator() ? canonicalQuery.getCollator()->getSpec().toBSON() : BSONObj(); stdx::lock_guard<stdx::mutex> cacheLock(_mutex); _allowedIndexEntryMap.erase(key); _allowedIndexEntryMap.emplace( std::piecewise_construct, std::forward_as_tuple(key), std::forward_as_tuple(query, sort, projection, collation, indexKeyPatterns, indexNames)); }
StatusWith<CursorId> ClusterFind::runQuery(OperationContext* txn, const CanonicalQuery& query, const ReadPreferenceSetting& readPref, std::vector<BSONObj>* results) { invariant(results); auto dbConfig = grid.catalogCache()->getDatabase(txn, query.nss().db().toString()); if (!dbConfig.isOK()) { return dbConfig.getStatus(); } std::shared_ptr<ChunkManager> chunkManager; std::shared_ptr<Shard> primary; dbConfig.getValue()->getChunkManagerOrPrimary(query.nss().ns(), chunkManager, primary); // Re-target and re-send the initial find command to the shards until we have established the // shard version. for (size_t retries = 1; retries <= kMaxStaleConfigRetries; ++retries) { auto cursorId = runQueryWithoutRetrying( txn, query, readPref, chunkManager.get(), std::move(primary), results); if (cursorId.isOK()) { return cursorId; } auto status = std::move(cursorId.getStatus()); if (status != ErrorCodes::RecvStaleConfig) { // Errors other than receiving a stale config message from mongoD are fatal to the // operation. return status; } LOG(1) << "Received stale config for query " << query.toStringShort() << " on attempt " << retries << " of " << kMaxStaleConfigRetries << ": " << status.reason(); invariant(chunkManager); chunkManager = chunkManager->reload(txn); } return {ErrorCodes::StaleShardVersion, str::stream() << "Retried " << kMaxStaleConfigRetries << " times without establishing shard version."}; }
Status UpdateDriver::populateDocumentWithQueryFields(const CanonicalQuery& query, const FieldRefSet& immutablePaths, mutablebson::Document& doc) const { EqualityMatches equalities; Status status = Status::OK(); if (_updateType == UpdateType::kReplacement) { // Extract only immutable fields. status = pathsupport::extractFullEqualityMatches(*query.root(), immutablePaths, &equalities); } else { // Extract all fields from op-style update. status = pathsupport::extractEqualityMatches(*query.root(), &equalities); } if (!status.isOK()) return status; status = pathsupport::addEqualitiesToDoc(equalities, &doc); return status; }