/** * Parses the json string 'findCmd', specifying a find command, to a CanonicalQuery. */ std::unique_ptr<CanonicalQuery> cqFromFindCommand(const std::string& findCmd) { BSONObj cmdObj = fromjson(findCmd); bool isExplain = false; auto qr = unittest::assertGet(QueryRequest::makeFromFindCommand(nss, cmdObj, isExplain)); auto cq = unittest::assertGet( CanonicalQuery::canonicalize(txn(), std::move(qr), ExtensionsCallbackNoop())); return cq; }
TEST(MatchExpressionParserTest, TextParsesSuccessfullyWhenAllowed) { auto query = fromjson("{$text: {$search: 'str'}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_OK(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kText) .getStatus()); }
void SortKeyGenerator::getBoundsForSort(OperationContext* txn, const BSONObj& queryObj, const BSONObj& sortObj) { QueryPlannerParams params; params.options = QueryPlannerParams::NO_TABLE_SCAN; // We're creating a "virtual index" with key pattern equal to the sort order. IndexEntry sortOrder(sortObj, IndexNames::BTREE, true, MultikeyPaths{}, false, false, "doesnt_matter", NULL, BSONObj()); params.indices.push_back(sortOrder); auto statusWithQueryForSort = CanonicalQuery::canonicalize( txn, NamespaceString("fake.ns"), queryObj, ExtensionsCallbackNoop()); verify(statusWithQueryForSort.isOK()); std::unique_ptr<CanonicalQuery> queryForSort = std::move(statusWithQueryForSort.getValue()); std::vector<QuerySolution*> solns; LOG(5) << "Sort key generation: Planning to obtain bounds for sort."; QueryPlanner::plan(*queryForSort, params, &solns); // TODO: are there ever > 1 solns? If so, do we look for a specific soln? if (1 == solns.size()) { IndexScanNode* ixScan = NULL; QuerySolutionNode* rootNode = solns[0]->root.get(); if (rootNode->getType() == STAGE_FETCH) { FetchNode* fetchNode = static_cast<FetchNode*>(rootNode); if (fetchNode->children[0]->getType() != STAGE_IXSCAN) { delete solns[0]; // No bounds. return; } ixScan = static_cast<IndexScanNode*>(fetchNode->children[0]); } else if (rootNode->getType() == STAGE_IXSCAN) { ixScan = static_cast<IndexScanNode*>(rootNode); } if (ixScan) { _bounds.fields.swap(ixScan->bounds.fields); _hasBounds = true; } } for (size_t i = 0; i < solns.size(); ++i) { delete solns[i]; } }
TEST(MatchExpressionParserTest, ExprFailsToParseWithinTopLevelOr) { auto query = fromjson("{$or: [{x: 1}, {$expr: {$eq: ['$a', 5]}}]}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_NOT_OK(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr) .getStatus()); }
TEST(MatchExpressionParserTest, ExprParsesSuccessfullyWithAdditionalTopLevelPredicates) { auto query = fromjson("{x: 1, $expr: {$eq: ['$a', 5]}, y: 1}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_OK(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kExpr) .getStatus()); }
TEST(MatchExpressionParserTest, NearParsesSuccessfullyWhenAllowed) { auto query = fromjson("{a: {$near: {$geometry: {type: 'Point', coordinates: [0, 0]}}}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_OK(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kGeoNear) .getStatus()); }
TEST(MatchExpressionParserTest, WhereParsesSuccessfullyWhenAllowed) { auto query = fromjson("{$where: 'this.a == this.b'}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_OK(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::AllowedFeatures::kJavascript) .getStatus()); }
/** * Parses the json string 'findCmd', specifying a find command, to a CanonicalQuery. */ std::unique_ptr<CanonicalQuery> cqFromFindCommand(const std::string& findCmd) { BSONObj cmdObj = fromjson(findCmd); bool isExplain = false; auto lpq = unittest::assertGet(LiteParsedQuery::makeFromFindCommand(nss, cmdObj, isExplain)); auto cq = unittest::assertGet( CanonicalQuery::canonicalize(lpq.release(), ExtensionsCallbackNoop())); return cq; }
void ChunkManager::getShardIdsForQuery(OperationContext* txn, const BSONObj& query, set<ShardId>* shardIds) const { auto statusWithCQ = CanonicalQuery::canonicalize(NamespaceString(_ns), query, ExtensionsCallbackNoop()); uassertStatusOK(statusWithCQ.getStatus()); unique_ptr<CanonicalQuery> cq = std::move(statusWithCQ.getValue()); // Query validation if (QueryPlannerCommon::hasNode(cq->root(), MatchExpression::GEO_NEAR)) { uassert(13501, "use geoNear command rather than $near query", false); } // Fast path for targeting equalities on the shard key. auto shardKeyToFind = _keyPattern.extractShardKeyFromQuery(*cq); if (shardKeyToFind.isOK() && !shardKeyToFind.getValue().isEmpty()) { auto chunk = findIntersectingChunk(txn, shardKeyToFind.getValue()); shardIds->insert(chunk->getShardId()); return; } // 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(), *cq); // 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()); } }
// $near must be the only field in the expression object. TEST(MatchExpressionParserGeoNear, ParseNearExtraField) { BSONObj query = fromjson( "{loc:{$near:{$maxDistance:100, " "$geometry:{type:\"Point\", coordinates:[0,0]}}, foo: 1}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_THROWS(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures) .status_with_transitional_ignore(), AssertionException); }
StatusWith<LogicalSessionIdSet> SessionsCollectionSharded::findRemovedSessions( OperationContext* opCtx, const LogicalSessionIdSet& sessions) { auto send = [&](BSONObj toSend) -> StatusWith<BSONObj> { auto qr = QueryRequest::makeFromFindCommand( NamespaceString::kLogicalSessionsNamespace, toSend, false); if (!qr.isOK()) { return qr.getStatus(); } const boost::intrusive_ptr<ExpressionContext> expCtx; auto cq = CanonicalQuery::canonicalize(opCtx, std::move(qr.getValue()), expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kBanAllSpecialFeatures); if (!cq.isOK()) { return cq.getStatus(); } // Do the work to generate the first batch of results. This blocks waiting to get responses // from the shard(s). std::vector<BSONObj> batch; CursorId cursorId; try { cursorId = ClusterFind::runQuery( opCtx, *cq.getValue(), ReadPreferenceSetting::get(opCtx), &batch); } catch (const DBException& ex) { return ex.toStatus(); } rpc::OpMsgReplyBuilder replyBuilder; CursorResponseBuilder::Options options; options.isInitialResponse = true; CursorResponseBuilder firstBatch(&replyBuilder, options); for (const auto& obj : batch) { firstBatch.append(obj); } firstBatch.done(cursorId, NamespaceString::kLogicalSessionsNamespace.ns()); return replyBuilder.releaseBody(); }; return doFetch(NamespaceString::kLogicalSessionsNamespace, sessions, send); }
StatusWith<LogicalSessionIdSet> SessionsCollectionSharded::findRemovedSessions( OperationContext* opCtx, const LogicalSessionIdSet& sessions) { auto send = [&](BSONObj toSend) -> StatusWith<BSONObj> { const NamespaceString nss(SessionsCollection::kSessionsFullNS); auto qr = QueryRequest::makeFromFindCommand(nss, toSend, false); if (!qr.isOK()) { return qr.getStatus(); } const boost::intrusive_ptr<ExpressionContext> expCtx; auto cq = CanonicalQuery::canonicalize(opCtx, std::move(qr.getValue()), expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures & ~MatchExpressionParser::AllowedFeatures::kExpr); if (!cq.isOK()) { return cq.getStatus(); } // Do the work to generate the first batch of results. This blocks waiting to get responses // from the shard(s). std::vector<BSONObj> batch; BSONObj viewDefinition; auto cursorId = ClusterFind::runQuery( opCtx, *cq.getValue(), ReadPreferenceSetting::get(opCtx), &batch, &viewDefinition); if (!cursorId.isOK()) { return cursorId.getStatus(); } BSONObjBuilder result; CursorResponseBuilder firstBatch(/*firstBatch*/ true, &result); for (const auto& obj : batch) { firstBatch.append(obj); } firstBatch.done(cursorId.getValue(), nss.ns()); return result.obj(); }; return doFetch(sessions, send); }
TEST(MatchExpressionParserGeo, WithinBox) { BSONObj query = fromjson("{a:{$within:{$box:[{x: 4, y:4},[6,6]]}}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); ASSERT_TRUE(result.isOK()); ASSERT(!result.getValue()->matchesBSON(fromjson("{a: [3,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [4,4]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: [5,5.1]}"))); ASSERT(result.getValue()->matchesBSON(fromjson("{a: {x: 5, y:5.1}}"))); }
TEST(MatchExpressionParserGeoNear, ParseValidNearSphere) { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $maxDistance: 100, $minDistance: 50}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); ASSERT_TRUE(result.isOK()); MatchExpression* exp = result.getValue().get(); ASSERT_EQ(MatchExpression::GEO_NEAR, exp->matchType()); GeoNearMatchExpression* gnexp = static_cast<GeoNearMatchExpression*>(exp); ASSERT_EQ(gnexp->getData().maxDistance, 100); ASSERT_EQ(gnexp->getData().minDistance, 50); }
StatusWith<std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>>> ParsedUpdate::parseArrayFilters(const std::vector<BSONObj>& rawArrayFiltersIn, OperationContext* opCtx, CollatorInterface* collator) { std::map<StringData, std::unique_ptr<ExpressionWithPlaceholder>> arrayFiltersOut; for (auto rawArrayFilter : rawArrayFiltersIn) { boost::intrusive_ptr<ExpressionContext> expCtx(new ExpressionContext(opCtx, collator)); auto parsedArrayFilter = MatchExpressionParser::parse(rawArrayFilter, std::move(expCtx), ExtensionsCallbackNoop(), MatchExpressionParser::kBanAllSpecialFeatures); if (!parsedArrayFilter.isOK()) { return parsedArrayFilter.getStatus().withContext("Error parsing array filter"); } auto parsedArrayFilterWithPlaceholder = ExpressionWithPlaceholder::make(std::move(parsedArrayFilter.getValue())); if (!parsedArrayFilterWithPlaceholder.isOK()) { return parsedArrayFilterWithPlaceholder.getStatus().withContext( "Error parsing array filter"); } auto finalArrayFilter = std::move(parsedArrayFilterWithPlaceholder.getValue()); auto fieldName = finalArrayFilter->getPlaceholder(); if (!fieldName) { return Status( ErrorCodes::FailedToParse, "Cannot use an expression without a top-level field name in arrayFilters"); } if (arrayFiltersOut.find(*fieldName) != arrayFiltersOut.end()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Found multiple array filters with the same top-level field name " << *fieldName); } arrayFiltersOut[*fieldName] = std::move(finalArrayFilter); } return std::move(arrayFiltersOut); }
Status ParsedUpdate::parseArrayFilters() { for (auto rawArrayFilter : _request->getArrayFilters()) { boost::intrusive_ptr<ExpressionContext> expCtx( new ExpressionContext(_opCtx, _collator.get())); auto parsedArrayFilter = MatchExpressionParser::parse(rawArrayFilter, std::move(expCtx), ExtensionsCallbackNoop(), MatchExpressionParser::kBanAllSpecialFeatures); if (!parsedArrayFilter.isOK()) { return parsedArrayFilter.getStatus().withContext("Error parsing array filter"); } auto parsedArrayFilterWithPlaceholder = ExpressionWithPlaceholder::make(std::move(parsedArrayFilter.getValue())); if (!parsedArrayFilterWithPlaceholder.isOK()) { return parsedArrayFilterWithPlaceholder.getStatus().withContext( "Error parsing array filter"); } auto finalArrayFilter = std::move(parsedArrayFilterWithPlaceholder.getValue()); auto fieldName = finalArrayFilter->getPlaceholder(); if (!fieldName) { return Status( ErrorCodes::FailedToParse, "Cannot use an expression without a top-level field name in arrayFilters"); } if (_arrayFilters.find(*fieldName) != _arrayFilters.end()) { return Status(ErrorCodes::FailedToParse, str::stream() << "Found multiple array filters with the same top-level field name " << *fieldName); } _arrayFilters[*fieldName] = std::move(finalArrayFilter); } return Status::OK(); }
TEST(MatchExpressionParserGeoNear, ParseInvalidNearSphere) { { BSONObj query = fromjson("{loc: {$maxDistance: 100, $nearSphere: [0,0]}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$minDistance: 100, $nearSphere: [0,0]}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); StatusWithMatchExpression result = MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); ASSERT_FALSE(result.isOK()); } { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $maxDistance: {}}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_THROWS(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures) .status_with_transitional_ignore(), AssertionException); } { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $minDistance: {}}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_THROWS(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures) .status_with_transitional_ignore(), AssertionException); } { BSONObj query = fromjson("{loc: {$nearSphere: [0,0], $eq: 1}}"); const CollatorInterface* collator = nullptr; const boost::intrusive_ptr<ExpressionContextForTest> expCtx(new ExpressionContextForTest()); ASSERT_THROWS(MatchExpressionParser::parse(query, collator, expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures) .status_with_transitional_ignore(), AssertionException); } }
ASSERT_TRUE(next.isAdvanced()); ASSERT_DOCUMENT_EQ(next.releaseDocument(), (Document{{"a", 1}, {"b", 1}})); // The rest should not match. ASSERT_TRUE(match->getNext().isEOF()); ASSERT_TRUE(match->getNext().isEOF()); ASSERT_TRUE(match->getNext().isEOF()); } DEATH_TEST_F(DocumentSourceMatchTest, ShouldFailToDescendExpressionOnPathThatIsNotACommonPrefix, "Invariant failure expression::isPathPrefixOf") { const auto expCtx = getExpCtx(); const auto matchSpec = BSON("a.b" << 1 << "b.c" << 1); const auto matchExpression = unittest::assertGet( MatchExpressionParser::parse(matchSpec, ExtensionsCallbackNoop(), expCtx->getCollator())); DocumentSourceMatch::descendMatchOnPath(matchExpression.get(), "a", expCtx); } DEATH_TEST_F(DocumentSourceMatchTest, ShouldFailToDescendExpressionOnPathThatContainsElemMatchWithObject, "Invariant failure node->matchType()") { const auto expCtx = getExpCtx(); const auto matchSpec = BSON("a" << BSON("$elemMatch" << BSON("a.b" << 1))); const auto matchExpression = unittest::assertGet( MatchExpressionParser::parse(matchSpec, ExtensionsCallbackNoop(), expCtx->getCollator())); BSONObjBuilder out; matchExpression->serialize(&out); DocumentSourceMatch::descendMatchOnPath(matchExpression.get(), "a", expCtx); }
StatusWith<std::vector<ShardEndpoint>> ChunkManagerTargeter::targetDelete( OperationContext* opCtx, const write_ops::DeleteOpEntry& deleteDoc) const { BSONObj shardKey; if (_routingInfo->cm()) { // // Sharded collections have the following further requirements for targeting: // // Limit-1 deletes must be targeted exactly by shard key *or* exact _id // // Get the shard key StatusWith<BSONObj> status = _routingInfo->cm()->getShardKeyPattern().extractShardKeyFromQuery(opCtx, deleteDoc.getQ()); // Bad query if (!status.isOK()) return status.getStatus(); shardKey = status.getValue(); } const auto collation = write_ops::collationOf(deleteDoc); // Target the shard key or delete query if (!shardKey.isEmpty()) { try { return std::vector<ShardEndpoint>{_targetShardKey(shardKey, collation, 0)}; } catch (const DBException&) { // This delete is potentially not constrained to a single shard } } // We failed to target a single shard. // Parse delete query. auto qr = stdx::make_unique<QueryRequest>(getNS()); qr->setFilter(deleteDoc.getQ()); if (!collation.isEmpty()) { qr->setCollation(collation); } const boost::intrusive_ptr<ExpressionContext> expCtx; auto cq = CanonicalQuery::canonicalize(opCtx, std::move(qr), expCtx, ExtensionsCallbackNoop(), MatchExpressionParser::kAllowAllSpecialFeatures); if (!cq.isOK()) { return cq.getStatus().withContext(str::stream() << "Could not parse delete query " << deleteDoc.getQ()); } // Single deletes must target a single shard or be exact-ID. if (_routingInfo->cm() && !deleteDoc.getMulti() && !isExactIdQuery(opCtx, *cq.getValue(), _routingInfo->cm().get())) { return Status(ErrorCodes::ShardKeyNotFound, str::stream() << "A single delete on a sharded collection must contain an exact " "match on _id (and have the collection default collation) or " "contain the shard key (and have the simple collation). Delete " "request: " << deleteDoc.toBSON() << ", shard key pattern: " << _routingInfo->cm()->getShardKeyPattern().toString()); } return _targetQuery(opCtx, deleteDoc.getQ(), collation); }
void Strategy::queryOp(OperationContext* txn, Request& request) { verify(!NamespaceString(request.getns()).isCommand()); Timer queryTimer; globalOpCounters.gotQuery(); QueryMessage q(request.d()); NamespaceString ns(q.ns); ClientBasic* client = txn->getClient(); AuthorizationSession* authSession = AuthorizationSession::get(client); Status status = authSession->checkAuthForFind(ns, false); audit::logQueryAuthzCheck(client, ns, q.query, status.code()); uassertStatusOK(status); LOG(3) << "query: " << q.ns << " " << q.query << " ntoreturn: " << q.ntoreturn << " options: " << q.queryOptions; if (q.ntoreturn == 1 && strstr(q.ns, ".$cmd")) throw UserException(8010, "something is wrong, shouldn't see a command here"); if (q.queryOptions & QueryOption_Exhaust) { uasserted(18526, string("the 'exhaust' query option is invalid for mongos queries: ") + q.ns + " " + q.query.toString()); } // Spigot which controls whether OP_QUERY style find on mongos uses the new ClusterClientCursor // code path. // TODO: Delete the spigot and always use the new code. if (useClusterClientCursor) { // Determine the default read preference mode based on the value of the slaveOk flag. ReadPreference readPreferenceOption = (q.queryOptions & QueryOption_SlaveOk) ? ReadPreference::SecondaryPreferred : ReadPreference::PrimaryOnly; ReadPreferenceSetting readPreference(readPreferenceOption, TagSet()); BSONElement rpElem; auto readPrefExtractStatus = bsonExtractTypedField( q.query, LiteParsedQuery::kWrappedReadPrefField, mongo::Object, &rpElem); if (readPrefExtractStatus.isOK()) { auto parsedRps = ReadPreferenceSetting::fromBSON(rpElem.Obj()); uassertStatusOK(parsedRps.getStatus()); readPreference = parsedRps.getValue(); } else if (readPrefExtractStatus != ErrorCodes::NoSuchKey) { uassertStatusOK(readPrefExtractStatus); } auto canonicalQuery = CanonicalQuery::canonicalize(q, ExtensionsCallbackNoop()); uassertStatusOK(canonicalQuery.getStatus()); // If the $explain flag was set, we must run the operation on the shards as an explain // command rather than a find command. if (canonicalQuery.getValue()->getParsed().isExplain()) { const LiteParsedQuery& lpq = canonicalQuery.getValue()->getParsed(); BSONObj findCommand = lpq.asFindCommand(); // We default to allPlansExecution verbosity. auto verbosity = ExplainCommon::EXEC_ALL_PLANS; const bool secondaryOk = (readPreference.pref != ReadPreference::PrimaryOnly); rpc::ServerSelectionMetadata metadata(secondaryOk, readPreference); BSONObjBuilder explainBuilder; uassertStatusOK( Strategy::explainFind(txn, findCommand, lpq, verbosity, metadata, &explainBuilder)); BSONObj explainObj = explainBuilder.done(); replyToQuery(0, // query result flags request.p(), request.m(), static_cast<const void*>(explainObj.objdata()), explainObj.objsize(), 1, // numResults 0, // startingFrom CursorId(0)); return; } // Do the work to generate the first batch of results. This blocks waiting to get responses // from the shard(s). std::vector<BSONObj> batch; // 0 means the cursor is exhausted and // otherwise we assume that a cursor with the returned id can be retrieved via the // ClusterCursorManager auto cursorId = ClusterFind::runQuery(txn, *canonicalQuery.getValue(), readPreference, &batch); uassertStatusOK(cursorId.getStatus()); // TODO: this constant should be shared between mongos and mongod, and should // not be inside ShardedClientCursor. BufBuilder buffer(ShardedClientCursor::INIT_REPLY_BUFFER_SIZE); // Fill out the response buffer. int numResults = 0; for (const auto& obj : batch) { buffer.appendBuf((void*)obj.objdata(), obj.objsize()); numResults++; } replyToQuery(0, // query result flags request.p(), request.m(), buffer.buf(), buffer.len(), numResults, 0, // startingFrom cursorId.getValue()); return; } QuerySpec qSpec((string)q.ns, q.query, q.fields, q.ntoskip, q.ntoreturn, q.queryOptions); // Parse "$maxTimeMS". StatusWith<int> maxTimeMS = LiteParsedQuery::parseMaxTimeMS(q.query[LiteParsedQuery::queryOptionMaxTimeMS]); uassert(17233, maxTimeMS.getStatus().reason(), maxTimeMS.isOK()); if (_isSystemIndexes(q.ns) && doShardedIndexQuery(txn, request, qSpec)) { return; } ParallelSortClusteredCursor* cursor = new ParallelSortClusteredCursor(qSpec, CommandInfo()); verify(cursor); // TODO: Move out to Request itself, not strategy based try { cursor->init(txn); if (qSpec.isExplain()) { BSONObjBuilder explain_builder; cursor->explain(explain_builder); explain_builder.appendNumber("executionTimeMillis", static_cast<long long>(queryTimer.millis())); BSONObj b = explain_builder.obj(); replyToQuery(0, request.p(), request.m(), b); delete (cursor); return; } } catch (...) { delete cursor; throw; } // TODO: Revisit all of this when we revisit the sharded cursor cache if (cursor->getNumQueryShards() != 1) { // More than one shard (or zero), manage with a ShardedClientCursor // NOTE: We may also have *zero* shards here when the returnPartial flag is set. // Currently the code in ShardedClientCursor handles this. ShardedClientCursorPtr cc(new ShardedClientCursor(q, cursor)); BufBuilder buffer(ShardedClientCursor::INIT_REPLY_BUFFER_SIZE); int docCount = 0; const int startFrom = cc->getTotalSent(); bool hasMore = cc->sendNextBatch(q.ntoreturn, buffer, docCount); if (hasMore) { LOG(5) << "storing cursor : " << cc->getId(); int cursorLeftoverMillis = maxTimeMS.getValue() - queryTimer.millis(); if (maxTimeMS.getValue() == 0) { // 0 represents "no limit". cursorLeftoverMillis = kMaxTimeCursorNoTimeLimit; } else if (cursorLeftoverMillis <= 0) { cursorLeftoverMillis = kMaxTimeCursorTimeLimitExpired; } cursorCache.store(cc, cursorLeftoverMillis); } replyToQuery(0, request.p(), request.m(), buffer.buf(), buffer.len(), docCount, startFrom, hasMore ? cc->getId() : 0); } else { // Only one shard is used // Remote cursors are stored remotely, we shouldn't need this around. unique_ptr<ParallelSortClusteredCursor> cursorDeleter(cursor); ShardPtr shard = grid.shardRegistry()->getShard(txn, cursor->getQueryShardId()); verify(shard.get()); DBClientCursorPtr shardCursor = cursor->getShardCursor(shard->getId()); // Implicitly stores the cursor in the cache request.reply(*(shardCursor->getMessage()), shardCursor->originalHost()); // We don't want to kill the cursor remotely if there's still data left shardCursor->decouple(); } }