Esempio n. 1
0
    /**
     * 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());
}
Esempio n. 3
0
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;
    }
Esempio n. 9
0
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());
    }
}
Esempio n. 10
0
// $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);
}
Esempio n. 12
0
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);
}
Esempio n. 13
0
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}}")));
}
Esempio n. 14
0
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);
}
Esempio n. 15
0
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();
}
Esempio n. 17
0
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);
}
Esempio n. 20
0
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();
    }
}