/** * Copyright (C) 2015 MongoDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * As a special exception, the copyright holders give permission to link the * code of portions of this program with the OpenSSL library under certain * conditions as described in each individual source file and distribute * linked combinations including the program with the OpenSSL library. You * must comply with the GNU Affero General Public License in all respects for * all of the code used other than as permitted herein. If you modify file(s) * with this exception, you may extend this exception to your version of the * file(s), but you are not obligated to do so. If you do not wish to do so, * delete this exception statement from your version. If you delete this * exception statement from all source files in the program, then also delete * it in the license file. */ #include "mongo/platform/basic.h" #include "mongo/db/query/count_request.h" #include "mongo/util/mongoutils/str.h" namespace mongo { namespace { const char kCmdName[] = "count"; const char kQueryField[] = "query"; const char kLimitField[] = "limit"; const char kSkipField[] = "skip"; const char kHintField[] = "hint"; const char kCollationField[] = "collation"; const char kExplainField[] = "explain"; } // namespace CountRequest::CountRequest(NamespaceString nss, BSONObj query) : _nss(std::move(nss)), _query(query.getOwned()) {} void CountRequest::setHint(BSONObj hint) { _hint = hint.getOwned(); } void CountRequest::setCollation(BSONObj collation) { _collation = collation.getOwned(); } BSONObj CountRequest::toBSON() const { BSONObjBuilder builder; builder.append(kCmdName, _nss.ns()); builder.append(kQueryField, _query); if (_limit) { builder.append(kLimitField, _limit.get()); } if (_skip) { builder.append(kSkipField, _skip.get()); } if (_hint) { builder.append(kHintField, _hint.get()); } if (_collation) { builder.append(kCollationField, _collation.get()); } return builder.obj(); } StatusWith CountRequest::parseFromBSON(const std::string& dbname, const BSONObj& cmdObj, bool isExplain) { BSONElement firstElt = cmdObj.firstElement(); const std::string coll = (firstElt.type() == BSONType::String) ? firstElt.str() : ""; NamespaceString nss(dbname, coll); if (!nss.isValid()) { return Status(ErrorCodes::InvalidNamespace, "invalid collection name"); } // We don't validate that "query" is a nested object due to SERVER-15456. CountRequest request(std::move(nss), cmdObj.getObjectField(kQueryField)); // Limit if (cmdObj[kLimitField].isNumber()) { long long limit = cmdObj[kLimitField].numberLong(); // For counts, limit and -limit mean the same thing. if (limit < 0) { limit = -limit; } request.setLimit(limit); } else if (cmdObj[kLimitField].ok()) { return Status(ErrorCodes::BadValue, "limit value is not a valid number"); } // Skip if (cmdObj[kSkipField].isNumber()) { long long skip = cmdObj[kSkipField].numberLong(); if (skip < 0) { return Status(ErrorCodes::BadValue, "skip value is negative in count query"); } request.setSkip(skip); } else if (cmdObj[kSkipField].ok()) { return Status(ErrorCodes::BadValue, "skip value is not a valid number"); } // Hint if (Object == cmdObj[kHintField].type()) { request.setHint(cmdObj[kHintField].Obj()); } else if (String == cmdObj[kHintField].type()) { const std::string hint = cmdObj.getStringField(kHintField); request.setHint(BSON("$hint" << hint)); } // Collation if (Object == cmdObj[kCollationField].type()) { request.setCollation(cmdObj[kCollationField].Obj()); } else if (cmdObj[kCollationField].ok()) { return Status(ErrorCodes::BadValue, "collation value is not a document"); } // Explain request.setExplain(isExplain); return request; } StatusWith CountRequest::asAggregationCommand() const { // The 'hint' option is not supported in aggregation. if (_hint) { return {ErrorCodes::InvalidPipelineOperator, str::stream() << "Option " << kHintField << " not supported in aggregation."}; } BSONObjBuilder aggregationBuilder; aggregationBuilder.append("aggregate", _nss.coll()); // Build an aggregation pipeline that performs the counting. We add stages that satisfy the // query, skip and limit before finishing with the actual $count stage. BSONArrayBuilder pipelineBuilder(aggregationBuilder.subarrayStart("pipeline")); if (!_query.isEmpty()) { BSONObjBuilder matchBuilder(pipelineBuilder.subobjStart()); matchBuilder.append("$match", _query); matchBuilder.doneFast(); } if (_skip) { BSONObjBuilder skipBuilder(pipelineBuilder.subobjStart()); skipBuilder.append("$skip", *_skip); skipBuilder.doneFast(); } if (_limit) { BSONObjBuilder limitBuilder(pipelineBuilder.subobjStart()); limitBuilder.append("$limit", *_limit); limitBuilder.doneFast(); } BSONObjBuilder countBuilder(pipelineBuilder.subobjStart()); countBuilder.append("$count", "count"); countBuilder.doneFast(); pipelineBuilder.doneFast(); // Complete the command by appending the other options to count. if (_explain) { aggregationBuilder.append(kExplainField, _explain); } if (_collation) { aggregationBuilder.append(kCollationField, *_collation); } // The 'cursor' option is always specified so that aggregation uses the cursor interface. aggregationBuilder.append("cursor", BSONObj()); return aggregationBuilder.obj(); } } // namespace mongo