summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops/write_ops_parsers.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops/write_ops_parsers.cpp')
-rw-r--r--src/mongo/db/ops/write_ops_parsers.cpp275
1 files changed, 275 insertions, 0 deletions
diff --git a/src/mongo/db/ops/write_ops_parsers.cpp b/src/mongo/db/ops/write_ops_parsers.cpp
new file mode 100644
index 00000000000..8e01ee43eaa
--- /dev/null
+++ b/src/mongo/db/ops/write_ops_parsers.cpp
@@ -0,0 +1,275 @@
+/**
+ * Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+ *
+ * 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/ops/write_ops_parsers.h"
+
+#include "mongo/client/dbclientinterface.h"
+#include "mongo/db/catalog/document_validation.h"
+#include "mongo/db/dbmessage.h"
+#include "mongo/util/assert_util.h"
+#include "mongo/util/mongoutils/str.h"
+
+namespace mongo {
+namespace {
+
+// The specified limit to the number of operations that can be included in a single write command.
+// This is an attempt to avoid a large number of errors resulting in a reply that exceeds 16MB. It
+// doesn't fully ensure that goal, but it reduces the probability of it happening. This limit should
+// not be used if the protocol changes to avoid the 16MB limit on reply size.
+const size_t kMaxWriteBatchSize = 1000;
+
+void checkTypeInArray(BSONType expectedType,
+ const BSONElement& elem,
+ const BSONElement& arrayElem) {
+ uassert(ErrorCodes::TypeMismatch,
+ str::stream() << "Wrong type for " << arrayElem.fieldNameStringData() << '['
+ << elem.fieldNameStringData() << "]. Expected a "
+ << typeName(expectedType) << ", got a " << typeName(elem.type()) << '.',
+ elem.type() == expectedType);
+}
+
+void checkType(BSONType expectedType, const BSONElement& elem) {
+ uassert(ErrorCodes::TypeMismatch,
+ str::stream() << "Wrong type for '" << elem.fieldNameStringData() << "'. Expected a "
+ << typeName(expectedType) << ", got a " << typeName(elem.type()) << '.',
+ elem.type() == expectedType);
+}
+
+void checkOpCountForCommand(size_t numOps) {
+ uassert(ErrorCodes::InvalidLength,
+ str::stream() << "Write batch sizes must be between 1 and " << kMaxWriteBatchSize
+ << ". Got " << numOps << " operations.",
+ numOps != 0 && numOps <= kMaxWriteBatchSize);
+}
+
+/**
+ * Parses the fields common to all write commands and sets uniqueField to the element named
+ * uniqueFieldName. The uniqueField is the only top-level field that is unique to the specific type
+ * of write command.
+ */
+void parseWriteCommand(StringData dbName,
+ const BSONObj& cmd,
+ StringData uniqueFieldName,
+ BSONElement* uniqueField,
+ ParsedWriteOp* op) {
+ // Command dispatch wouldn't get here with an empty object because the first field indicates
+ // which command to run.
+ invariant(!cmd.isEmpty());
+
+ bool haveUniqueField = false;
+ bool firstElement = true;
+ for (BSONElement field : cmd) {
+ if (firstElement) {
+ // The key is the command name and the value is the collection name
+ checkType(String, field);
+ op->ns = NamespaceString(dbName, field.valueStringData());
+ firstElement = false;
+ continue;
+ }
+
+ const StringData fieldName = field.fieldNameStringData();
+ if (fieldName == "writeConcern") {
+ checkType(Object, field);
+ op->writeConcern = field.Obj();
+ } else if (fieldName == "bypassDocumentValidation") {
+ checkType(Bool, field);
+ op->bypassDocumentValidation = field.Bool();
+ } else if (fieldName == "ordered") {
+ checkType(Bool, field);
+ op->continueOnError = !field.Bool();
+ } else if (fieldName == uniqueFieldName) {
+ haveUniqueField = true;
+ *uniqueField = field;
+ } else if (fieldName[0] != '$') {
+ std::initializer_list<StringData> ignoredFields = {"maxTimeMS", "shardVersion"};
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "Unknown option to " << cmd.firstElementFieldName()
+ << " command: " << fieldName,
+ std::find(ignoredFields.begin(), ignoredFields.end(), fieldName) !=
+ ignoredFields.end());
+ }
+ }
+
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "The " << uniqueFieldName << " option is required to the "
+ << cmd.firstElementFieldName() << " command.",
+ haveUniqueField);
+}
+}
+
+InsertOp parseInsertCommand(StringData dbName, const BSONObj& cmd) {
+ BSONElement documents;
+ InsertOp op;
+ parseWriteCommand(dbName, cmd, "documents", &documents, &op);
+ checkType(Array, documents);
+ for (auto doc : documents.Obj()) {
+ checkTypeInArray(Object, doc, documents);
+ op.documents.push_back(doc.Obj());
+ }
+ checkOpCountForCommand(op.documents.size());
+ return op;
+}
+
+UpdateOp parseUpdateCommand(StringData dbName, const BSONObj& cmd) {
+ BSONElement updates;
+ UpdateOp op;
+ parseWriteCommand(dbName, cmd, "updates", &updates, &op);
+ checkType(Array, updates);
+ for (auto doc : updates.Obj()) {
+ checkTypeInArray(Object, doc, updates);
+ op.updates.emplace_back();
+ auto& update = op.updates.back();
+ bool haveQ = false;
+ bool haveU = false;
+ for (auto field : doc.Obj()) {
+ const StringData fieldName = field.fieldNameStringData();
+ if (fieldName == "q") {
+ haveQ = true;
+ checkType(Object, field);
+ update.query = field.Obj();
+ } else if (fieldName == "u") {
+ haveU = true;
+ checkType(Object, field);
+ update.update = field.Obj();
+ } else if (fieldName == "multi") {
+ checkType(Bool, field);
+ update.multi = field.Bool();
+ } else if (fieldName == "upsert") {
+ checkType(Bool, field);
+ update.upsert = field.Bool();
+ } else {
+ uasserted(ErrorCodes::FailedToParse,
+ str::stream() << "Unrecognized field in update operation: " << fieldName);
+ }
+ }
+
+ uassert(ErrorCodes::FailedToParse, "The 'q' field is required for all updates", haveQ);
+ uassert(ErrorCodes::FailedToParse, "The 'u' field is required for all updates", haveU);
+ }
+ checkOpCountForCommand(op.updates.size());
+ return op;
+}
+
+DeleteOp parseDeleteCommand(StringData dbName, const BSONObj& cmd) {
+ BSONElement deletes;
+ DeleteOp op;
+ parseWriteCommand(dbName, cmd, "deletes", &deletes, &op);
+ checkType(Array, deletes);
+ for (auto doc : deletes.Obj()) {
+ checkTypeInArray(Object, doc, deletes);
+ op.deletes.emplace_back();
+ auto& del = op.deletes.back(); // delete is a reserved word.
+ bool haveQ = false;
+ bool haveLimit = false;
+ for (auto field : doc.Obj()) {
+ const StringData fieldName = field.fieldNameStringData();
+ if (fieldName == "q") {
+ haveQ = true;
+ checkType(Object, field);
+ del.query = field.Obj();
+ } else if (fieldName == "limit") {
+ haveLimit = true;
+ uassert(ErrorCodes::TypeMismatch,
+ str::stream()
+ << "The limit field in delete objects must be a number. Got a "
+ << typeName(field.type()),
+ field.isNumber());
+
+ // Using a double to avoid throwing away illegal fractional portion. Don't want to
+ // accept 0.5 here.
+ const double limit = field.numberDouble();
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "The limit field in delete objects must be 0 or 1. Got "
+ << limit,
+ limit == 0 || limit == 1);
+ del.multi = (limit == 0);
+ } else {
+ uasserted(ErrorCodes::FailedToParse,
+ str::stream() << "Unrecognized field in delete operation: " << fieldName);
+ }
+ }
+ uassert(ErrorCodes::FailedToParse, "The 'q' field is required for all deletes", haveQ);
+ uassert(
+ ErrorCodes::FailedToParse, "The 'limit' field is required for all deletes", haveLimit);
+ }
+ checkOpCountForCommand(op.deletes.size());
+ return op;
+}
+
+InsertOp parseLegacyInsert(const Message& msgRaw) {
+ DbMessage msg(msgRaw);
+
+ InsertOp op;
+ op.ns = NamespaceString(msg.getns());
+ op.continueOnError = msg.reservedField() & InsertOption_ContinueOnError;
+ uassert(ErrorCodes::InvalidLength, "Need at least one object to insert", msg.moreJSObjs());
+ while (msg.moreJSObjs()) {
+ op.documents.push_back(msg.nextJsObj());
+ }
+ // There is no limit on the number of inserts in a legacy batch.
+
+ return op;
+}
+
+UpdateOp parseLegacyUpdate(const Message& msgRaw) {
+ DbMessage msg(msgRaw);
+
+ UpdateOp op;
+ op.ns = NamespaceString(msg.getns());
+
+ // Legacy updates only allowed one update per operation. Layout is flags, query, update.
+ op.updates.emplace_back();
+ auto& singleUpdate = op.updates.back();
+ const int flags = msg.pullInt();
+ singleUpdate.upsert = flags & UpdateOption_Upsert;
+ singleUpdate.multi = flags & UpdateOption_Multi;
+ singleUpdate.query = msg.nextJsObj();
+ singleUpdate.update = msg.nextJsObj();
+
+ return op;
+}
+
+DeleteOp parseLegacyDelete(const Message& msgRaw) {
+ DbMessage msg(msgRaw);
+
+ DeleteOp op;
+ op.ns = NamespaceString(msg.getns());
+
+ // Legacy deletes only allowed one delete per operation. Layout is flags, query.
+ op.deletes.emplace_back();
+ auto& singleDelete = op.deletes.back();
+ const int flags = msg.pullInt();
+ singleDelete.multi = !(flags & RemoveOption_JustOne);
+ singleDelete.query = msg.nextJsObj();
+
+ return op;
+}
+
+} // namespace mongo