/** * 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kQuery #include "mongo/platform/basic.h" #include "mongo/db/query/cursor_response.h" #include "mongo/bson/bsontypes.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/s/chunk_version.h" namespace mongo { namespace { const char kCursorField[] = "cursor"; const char kIdField[] = "id"; const char kNsField[] = "ns"; const char kBatchField[] = "nextBatch"; const char kBatchFieldInitial[] = "firstBatch"; } // namespace CursorResponseBuilder::CursorResponseBuilder(bool isInitialResponse, BSONObjBuilder* commandResponse) : _responseInitialLen(commandResponse->bb().len()), _commandResponse(commandResponse), _cursorObject(commandResponse->subobjStart(kCursorField)), _batch(_cursorObject.subarrayStart(isInitialResponse ? kBatchFieldInitial : kBatchField)) {} void CursorResponseBuilder::done(CursorId cursorId, StringData cursorNamespace) { invariant(_active); _batch.doneFast(); _cursorObject.append(kIdField, cursorId); _cursorObject.append(kNsField, cursorNamespace); _cursorObject.doneFast(); _active = false; } void CursorResponseBuilder::abandon() { invariant(_active); _batch.doneFast(); _cursorObject.doneFast(); _commandResponse->bb().setlen(_responseInitialLen); // Removes everything we've added. _active = false; } void appendCursorResponseObject(long long cursorId, StringData cursorNamespace, BSONArray firstBatch, BSONObjBuilder* builder) { BSONObjBuilder cursorObj(builder->subobjStart(kCursorField)); cursorObj.append(kIdField, cursorId); cursorObj.append(kNsField, cursorNamespace); cursorObj.append(kBatchFieldInitial, firstBatch); cursorObj.done(); } void appendGetMoreResponseObject(long long cursorId, StringData cursorNamespace, BSONArray nextBatch, BSONObjBuilder* builder) { BSONObjBuilder cursorObj(builder->subobjStart(kCursorField)); cursorObj.append(kIdField, cursorId); cursorObj.append(kNsField, cursorNamespace); cursorObj.append(kBatchField, nextBatch); cursorObj.done(); } CursorResponse::CursorResponse(NamespaceString nss, CursorId cursorId, std::vector batch, boost::optional numReturnedSoFar) : _nss(std::move(nss)), _cursorId(cursorId), _batch(std::move(batch)), _numReturnedSoFar(numReturnedSoFar) {} StatusWith CursorResponse::parseFromBSON(const BSONObj& cmdResponse) { Status cmdStatus = getStatusFromCommandResult(cmdResponse); if (!cmdStatus.isOK()) { if (ErrorCodes::isStaleShardingError(cmdStatus.code())) { auto vWanted = ChunkVersion::fromBSON(cmdResponse, "vWanted"); auto vReceived = ChunkVersion::fromBSON(cmdResponse, "vReceived"); if (!vWanted.hasEqualEpoch(vReceived)) { return Status(ErrorCodes::StaleEpoch, cmdStatus.reason()); } } return cmdStatus; } std::string fullns; BSONObj batchObj; CursorId cursorId; BSONElement cursorElt = cmdResponse[kCursorField]; if (cursorElt.type() != BSONType::Object) { return {ErrorCodes::TypeMismatch, str::stream() << "Field '" << kCursorField << "' must be a nested object in: " << cmdResponse}; } BSONObj cursorObj = cursorElt.Obj(); BSONElement idElt = cursorObj[kIdField]; if (idElt.type() != BSONType::NumberLong) { return { ErrorCodes::TypeMismatch, str::stream() << "Field '" << kIdField << "' must be of type long in: " << cmdResponse}; } cursorId = idElt.Long(); BSONElement nsElt = cursorObj[kNsField]; if (nsElt.type() != BSONType::String) { return {ErrorCodes::TypeMismatch, str::stream() << "Field '" << kNsField << "' must be of type string in: " << cmdResponse}; } fullns = nsElt.String(); BSONElement batchElt = cursorObj[kBatchField]; if (batchElt.eoo()) { batchElt = cursorObj[kBatchFieldInitial]; } if (batchElt.type() != BSONType::Array) { return {ErrorCodes::TypeMismatch, str::stream() << "Must have array field '" << kBatchFieldInitial << "' or '" << kBatchField << "' in: " << cmdResponse}; } batchObj = batchElt.Obj(); std::vector batch; for (BSONElement elt : batchObj) { if (elt.type() != BSONType::Object) { return {ErrorCodes::BadValue, str::stream() << "getMore response batch contains a non-object element: " << elt}; } batch.push_back(elt.Obj()); } for (auto& doc : batch) { doc.shareOwnershipWith(cmdResponse); } return {{NamespaceString(fullns), cursorId, std::move(batch)}}; } void CursorResponse::addToBSON(CursorResponse::ResponseType responseType, BSONObjBuilder* builder) const { BSONObjBuilder cursorBuilder(builder->subobjStart(kCursorField)); cursorBuilder.append(kIdField, _cursorId); cursorBuilder.append(kNsField, _nss.ns()); const char* batchFieldName = (responseType == ResponseType::InitialResponse) ? kBatchFieldInitial : kBatchField; BSONArrayBuilder batchBuilder(cursorBuilder.subarrayStart(batchFieldName)); for (const BSONObj& obj : _batch) { batchBuilder.append(obj); } batchBuilder.doneFast(); cursorBuilder.doneFast(); builder->append("ok", 1.0); } BSONObj CursorResponse::toBSON(CursorResponse::ResponseType responseType) const { BSONObjBuilder builder; addToBSON(responseType, &builder); return builder.obj(); } } // namespace mongo