OpMsgRequest upconvertRequest(StringData db, BSONObj cmdObj, int queryFlags) { cmdObj = cmdObj.getOwned(); // Usually this is a no-op since it is already owned. auto readPrefContainer = BSONObj(); const StringData firstFieldName = cmdObj.firstElementFieldName(); if (firstFieldName == "$query" || firstFieldName == "query") { // Commands sent over OP_QUERY specify read preference by putting it at the top level and // putting the command in a nested field called either query or $query. // Check if legacyCommand has an invalid $maxTimeMS option. uassert(ErrorCodes::InvalidOptions, "cannot use $maxTimeMS query option with commands; use maxTimeMS command option " "instead", !cmdObj.hasField("$maxTimeMS")); if (auto readPref = cmdObj["$readPreference"]) readPrefContainer = readPref.wrap(); cmdObj = cmdObj.firstElement().Obj().shareOwnershipWith(cmdObj); } else if (auto queryOptions = cmdObj["$queryOptions"]) { // Mongos rewrites commands with $readPreference to put it in a field nested inside of // $queryOptions. Its command implementations often forward commands in that format to // shards. This function is responsible for rewriting it to a format that the shards // understand. readPrefContainer = queryOptions.Obj().shareOwnershipWith(cmdObj); cmdObj = cmdObj.removeField("$queryOptions"); } if (!readPrefContainer.isEmpty()) { cmdObj = BSONObjBuilder(std::move(cmdObj)).appendElements(readPrefContainer).obj(); } else if (!cmdObj.hasField("$readPreference") && (queryFlags & QueryOption_SlaveOk)) { BSONObjBuilder bodyBuilder(std::move(cmdObj)); ReadPreferenceSetting(ReadPreference::SecondaryPreferred).toContainingBSON(&bodyBuilder); cmdObj = bodyBuilder.obj(); } // Try to move supported array fields into document sequences. auto docSequenceIt = docSequenceFieldsForCommands.find(cmdObj.firstElementFieldName()); auto docSequenceElem = docSequenceIt == docSequenceFieldsForCommands.end() ? BSONElement() : cmdObj[docSequenceIt->second]; if (!isArrayOfObjects(docSequenceElem)) return OpMsgRequest::fromDBAndBody(db, std::move(cmdObj)); auto docSequenceName = docSequenceElem.fieldNameStringData(); // Note: removing field before adding "$db" to avoid the need to copy the potentially large // array. auto out = OpMsgRequest::fromDBAndBody(db, cmdObj.removeField(docSequenceName)); out.sequences.push_back({docSequenceName.toString()}); for (auto elem : docSequenceElem.Obj()) { out.sequences[0].objs.push_back(elem.Obj().shareOwnershipWith(cmdObj)); } return out; }
Message opCommandRequestFromOpMsgRequest(const OpMsgRequest& request) { const auto commandName = request.getCommandName(); BufBuilder builder; builder.skip(mongo::MsgData::MsgDataHeaderSize); // Leave room for message header. builder.appendStr(request.getDatabase()); builder.appendStr(commandName); // OP_COMMAND is only used when communicating with 3.4 nodes and they serialize their metadata // fields differently. In addition to field-level differences, some generic arguments are pulled // out to a metadata object, separate from the body. We do all down-conversion here so that the // rest of the code only has to deal with the current format. BSONObjBuilder metadataBuilder; // Will be appended to the message after we finish the body. { BSONObjBuilder bodyBuilder(builder); for (auto elem : request.body) { const auto fieldName = elem.fieldNameStringData(); if (fieldName == "$configServerState") { metadataBuilder.appendAs(elem, "configsvr"); } else if (fieldName == "$readPreference") { BSONObjBuilder ssmBuilder(metadataBuilder.subobjStart("$ssm")); ssmBuilder.append(elem); ssmBuilder.append("$secondaryOk", uassertStatusOK(ReadPreferenceSetting::fromInnerBSON(elem)) .canRunOnSecondary()); } else if (fieldName == "$db") { // skip } else if (fieldGoesInMetadata(commandName, fieldName)) { metadataBuilder.append(elem); } else { bodyBuilder.append(elem); } } for (auto&& seq : request.sequences) { invariant(seq.name.find('.') == std::string::npos); // Only support top-level for now. dassert(!bodyBuilder.asTempObj().hasField(seq.name)); bodyBuilder.append(seq.name, seq.objs); } } metadataBuilder.obj().appendSelfToBufBuilder(builder); MsgData::View msg = builder.buf(); msg.setLen(builder.len()); msg.setOperation(dbCommand); return Message(builder.release()); }