summaryrefslogtreecommitdiff
path: root/src/mongo/db/ops/write_ops.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/ops/write_ops.cpp')
-rw-r--r--src/mongo/db/ops/write_ops.cpp297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/mongo/db/ops/write_ops.cpp b/src/mongo/db/ops/write_ops.cpp
index 54cef4d3d2a..de7df85e564 100644
--- a/src/mongo/db/ops/write_ops.cpp
+++ b/src/mongo/db/ops/write_ops.cpp
@@ -54,6 +54,18 @@ using write_ops::WriteCommandRequestBase;
namespace {
+// This constant accounts for the null terminator in each field name and the BSONType byte for
+// each element.
+static constexpr int kPerElementOverhead = 2;
+
+// This constant accounts for the size of a bool.
+static constexpr int kBoolSize = 1;
+
+// This constant tracks the overhead for serializing UUIDs. It includes 1 byte for the
+// 'BinDataType', 4 bytes for serializing the integer size of the UUID, and finally, 16 bytes
+// for the UUID itself.
+static const int kUUIDSize = 21;
+
template <class T>
void checkOpCountForCommand(const T& op, size_t numOps) {
uassert(ErrorCodes::InvalidLength,
@@ -78,6 +90,50 @@ void checkOpCountForCommand(const T& op, size_t numOps) {
}
}
+// Utility which estimates the size of 'WriteCommandRequestBase' when serialized.
+int getWriteCommandRequestBaseSize(const WriteCommandRequestBase& base) {
+ static const int kSizeOfOrderedField =
+ write_ops::WriteCommandRequestBase::kOrderedFieldName.size() + kBoolSize +
+ kPerElementOverhead;
+ static const int kSizeOfBypassDocumentValidationField =
+ write_ops::WriteCommandRequestBase::kBypassDocumentValidationFieldName.size() + kBoolSize +
+ kPerElementOverhead;
+
+ auto estSize = static_cast<int>(BSONObj::kMinBSONLength) + kSizeOfOrderedField +
+ kSizeOfBypassDocumentValidationField;
+
+ if (auto stmtId = base.getStmtId(); stmtId) {
+ estSize += write_ops::WriteCommandRequestBase::kStmtIdFieldName.size() +
+ sizeof(std::int32_t) + kPerElementOverhead;
+ }
+
+ if (auto stmtIds = base.getStmtIds(); stmtIds) {
+ estSize += write_ops::WriteCommandRequestBase::kStmtIdsFieldName.size();
+ estSize += static_cast<int>(BSONObj::kMinBSONLength);
+ estSize +=
+ ((sizeof(std::int32_t) + write_ops::kWriteCommandBSONArrayPerElementOverheadBytes) *
+ stmtIds->size());
+ estSize += kPerElementOverhead;
+ }
+
+ if (auto isTimeseries = base.getIsTimeseriesNamespace(); isTimeseries.has_value()) {
+ estSize += write_ops::WriteCommandRequestBase::kIsTimeseriesNamespaceFieldName.size() +
+ kBoolSize + kPerElementOverhead;
+ }
+
+ if (auto collUUID = base.getCollectionUUID(); collUUID) {
+ estSize += write_ops::WriteCommandRequestBase::kCollectionUUIDFieldName.size() + kUUIDSize +
+ kPerElementOverhead;
+ }
+
+ if (auto encryptionInfo = base.getEncryptionInformation(); encryptionInfo) {
+ estSize += write_ops::WriteCommandRequestBase::kEncryptionInformationFieldName.size() +
+ encryptionInfo->toBSON().objsize() + kPerElementOverhead;
+ }
+
+ return estSize;
+}
+
} // namespace
namespace write_ops {
@@ -137,6 +193,247 @@ int32_t getStmtIdForWriteAt(const WriteCommandRequestBase& writeCommandBase, siz
return kFirstStmtId + writePos;
}
+int estimateRuntimeConstantsSize(const mongo::LegacyRuntimeConstants& constants) {
+ int size = write_ops::UpdateCommandRequest::kLegacyRuntimeConstantsFieldName.size() +
+ static_cast<int>(BSONObj::kMinBSONLength) + kPerElementOverhead;
+
+ // $$NOW
+ size +=
+ LegacyRuntimeConstants::kLocalNowFieldName.size() + sizeof(Date_t) + kPerElementOverhead;
+
+ // $$CLUSTER_TIME
+ size += LegacyRuntimeConstants::kClusterTimeFieldName.size() + sizeof(Timestamp) +
+ kPerElementOverhead;
+
+ // $$JS_SCOPE
+ if (const auto& scope = constants.getJsScope(); scope.has_value()) {
+ size += LegacyRuntimeConstants::kJsScopeFieldName.size() + scope->objsize() +
+ kPerElementOverhead;
+ }
+
+ // $$IS_MR
+ if (const auto& isMR = constants.getIsMapReduce(); isMR.has_value()) {
+ size +=
+ LegacyRuntimeConstants::kIsMapReduceFieldName.size() + kBoolSize + kPerElementOverhead;
+ }
+
+ return size;
+}
+
+int getUpdateSizeEstimate(const BSONObj& q,
+ const write_ops::UpdateModification& u,
+ const boost::optional<mongo::BSONObj>& c,
+ const bool includeUpsertSupplied,
+ const boost::optional<mongo::BSONObj>& collation,
+ const boost::optional<std::vector<mongo::BSONObj>>& arrayFilters,
+ const mongo::BSONObj& hint) {
+ using UpdateOpEntry = write_ops::UpdateOpEntry;
+ int estSize = static_cast<int>(BSONObj::kMinBSONLength);
+
+ // Add the sizes of the 'multi' and 'upsert' fields.
+ estSize += UpdateOpEntry::kUpsertFieldName.size() + kBoolSize + kPerElementOverhead;
+ estSize += UpdateOpEntry::kMultiFieldName.size() + kBoolSize + kPerElementOverhead;
+
+ // Add the size of 'upsertSupplied' field if present.
+ if (includeUpsertSupplied) {
+ estSize += UpdateOpEntry::kUpsertSuppliedFieldName.size() + kBoolSize + kPerElementOverhead;
+ }
+
+ // Add the sizes of the 'q' and 'u' fields.
+ estSize += (UpdateOpEntry::kQFieldName.size() + q.objsize() + kPerElementOverhead +
+ UpdateOpEntry::kUFieldName.size() + u.objsize() + kPerElementOverhead);
+
+ // Add the size of the 'c' field, if present.
+ if (c) {
+ estSize += (UpdateOpEntry::kCFieldName.size() + c->objsize() + kPerElementOverhead);
+ }
+
+ // Add the size of the 'collation' field, if present.
+ if (collation) {
+ estSize += (UpdateOpEntry::kCollationFieldName.size() + collation->objsize() +
+ kPerElementOverhead);
+ }
+
+ // Add the size of the 'arrayFilters' field, if present.
+ if (arrayFilters) {
+ estSize += ([&]() {
+ auto size = BSONObj::kMinBSONLength + UpdateOpEntry::kArrayFiltersFieldName.size() +
+ kPerElementOverhead;
+ for (auto&& filter : *arrayFilters) {
+ // For each filter, we not only need to account for the size of the filter itself,
+ // but also for the per array element overhead.
+ size += filter.objsize();
+ size += write_ops::kWriteCommandBSONArrayPerElementOverheadBytes;
+ }
+ return size;
+ })();
+ }
+
+ // Add the size of 'hint' field if present.
+ if (!hint.isEmpty()) {
+ estSize += UpdateOpEntry::kHintFieldName.size() + hint.objsize() + kPerElementOverhead;
+ }
+
+ return estSize;
+}
+
+int getDeleteSizeEstimate(const BSONObj& q,
+ const boost::optional<mongo::BSONObj>& collation,
+ const mongo::BSONObj& hint) {
+ using DeleteOpEntry = write_ops::DeleteOpEntry;
+
+ static const int kIntSize = 4;
+ int estSize = static_cast<int>(BSONObj::kMinBSONLength);
+
+ // Add the size of the 'q' field.
+ estSize += DeleteOpEntry::kQFieldName.size() + q.objsize() + kPerElementOverhead;
+
+ // Add the size of the 'collation' field, if present.
+ if (collation) {
+ estSize +=
+ DeleteOpEntry::kCollationFieldName.size() + collation->objsize() + kPerElementOverhead;
+ }
+
+ // Add the size of the 'limit' field.
+ estSize += DeleteOpEntry::kMultiFieldName.size() + kIntSize + kPerElementOverhead;
+
+ // Add the size of the 'hint' field, if present.
+ if (!hint.isEmpty()) {
+ estSize += DeleteOpEntry::kHintFieldName.size() + hint.objsize() + kPerElementOverhead;
+ }
+
+ return estSize;
+}
+
+bool verifySizeEstimate(const write_ops::UpdateOpEntry& update) {
+ return write_ops::getUpdateSizeEstimate(update.getQ(),
+ update.getU(),
+ update.getC(),
+ update.getUpsertSupplied().has_value(),
+ update.getCollation(),
+ update.getArrayFilters(),
+ update.getHint()) >= update.toBSON().objsize();
+}
+
+bool verifySizeEstimate(const InsertCommandRequest& insertReq,
+ const OpMsgRequest* unparsedRequest) {
+ int size = getInsertHeaderSizeEstimate(insertReq);
+ for (auto&& docToInsert : insertReq.getDocuments()) {
+ size += docToInsert.objsize() + kWriteCommandBSONArrayPerElementOverheadBytes;
+ }
+
+ // Return true if 'insertReq' originated from a document sequence and our size estimate exceeds
+ // the size limit.
+ if (unparsedRequest && !unparsedRequest->sequences.empty() && size > BSONObjMaxUserSize) {
+ return true;
+ }
+ return size >= insertReq.toBSON({} /* commandPassthroughFields */).objsize();
+}
+
+bool verifySizeEstimate(const UpdateCommandRequest& updateReq,
+ const OpMsgRequest* unparsedRequest) {
+ int size = getUpdateHeaderSizeEstimate(updateReq);
+
+ for (auto&& update : updateReq.getUpdates()) {
+ size += getUpdateSizeEstimate(update.getQ(),
+ update.getU(),
+ update.getC(),
+ update.getUpsertSupplied().has_value(),
+ update.getCollation(),
+ update.getArrayFilters(),
+ update.getHint()) +
+ kWriteCommandBSONArrayPerElementOverheadBytes;
+ }
+
+ // Return true if 'updateReq' originated from a document sequence and our size estimate exceeds
+ // the size limit.
+ if (unparsedRequest && !unparsedRequest->sequences.empty() && size > BSONObjMaxUserSize) {
+ return true;
+ }
+ return size >= updateReq.toBSON({} /* commandPassthroughFields */).objsize();
+}
+
+bool verifySizeEstimate(const DeleteCommandRequest& deleteReq,
+ const OpMsgRequest* unparsedRequest) {
+ int size = getDeleteHeaderSizeEstimate(deleteReq);
+
+ for (auto&& deleteOp : deleteReq.getDeletes()) {
+ size += write_ops::getDeleteSizeEstimate(
+ deleteOp.getQ(), deleteOp.getCollation(), deleteOp.getHint()) +
+ kWriteCommandBSONArrayPerElementOverheadBytes;
+ }
+
+ // Return true if 'deleteReq' originated from a document sequence and our size estimate exceeds
+ // the size limit.
+ if (unparsedRequest && !unparsedRequest->sequences.empty() && size > BSONObjMaxUserSize) {
+ return true;
+ }
+ return size >= deleteReq.toBSON({} /* commandPassthroughFields */).objsize();
+}
+
+int getInsertHeaderSizeEstimate(const InsertCommandRequest& insertReq) {
+ int size = getWriteCommandRequestBaseSize(insertReq.getWriteCommandRequestBase()) +
+ write_ops::InsertCommandRequest::kDocumentsFieldName.size() + kPerElementOverhead +
+ static_cast<int>(BSONObj::kMinBSONLength);
+
+ size += InsertCommandRequest::kCommandName.size() + kPerElementOverhead +
+ insertReq.getNamespace().size() + 1 /* ns string null terminator */;
+
+ return size;
+}
+
+int getUpdateHeaderSizeEstimate(const UpdateCommandRequest& updateReq) {
+ int size = getWriteCommandRequestBaseSize(updateReq.getWriteCommandRequestBase());
+
+ size += UpdateCommandRequest::kCommandName.size() + kPerElementOverhead +
+ updateReq.getNamespace().size() + 1 /* ns string null terminator */;
+
+ size += write_ops::UpdateCommandRequest::kUpdatesFieldName.size() + kPerElementOverhead +
+ static_cast<int>(BSONObj::kMinBSONLength);
+
+ // Handle legacy runtime constants.
+ if (auto runtimeConstants = updateReq.getLegacyRuntimeConstants();
+ runtimeConstants.has_value()) {
+ size += estimateRuntimeConstantsSize(*runtimeConstants);
+ }
+
+ // Handle let parameters.
+ if (auto let = updateReq.getLet(); let.has_value()) {
+ size += write_ops::UpdateCommandRequest::kLetFieldName.size() + let->objsize() +
+ kPerElementOverhead;
+ }
+ return size;
+}
+
+int getDeleteHeaderSizeEstimate(const DeleteCommandRequest& deleteReq) {
+ int size = getWriteCommandRequestBaseSize(deleteReq.getWriteCommandRequestBase());
+
+ size += DeleteCommandRequest::kCommandName.size() + kPerElementOverhead +
+ deleteReq.getNamespace().size() + 1 /* ns string null terminator */;
+
+ size += write_ops::DeleteCommandRequest::kDeletesFieldName.size() + kPerElementOverhead +
+ static_cast<int>(BSONObj::kMinBSONLength);
+
+ // Handle legacy runtime constants.
+ if (auto runtimeConstants = deleteReq.getLegacyRuntimeConstants();
+ runtimeConstants.has_value()) {
+ size += estimateRuntimeConstantsSize(*runtimeConstants);
+ }
+
+ // Handle let parameters.
+ if (auto let = deleteReq.getLet(); let.has_value()) {
+ size += write_ops::UpdateCommandRequest::kLetFieldName.size() + let->objsize() +
+ kPerElementOverhead;
+ }
+ return size;
+}
+
+bool verifySizeEstimate(const write_ops::DeleteOpEntry& deleteOp) {
+ return write_ops::getDeleteSizeEstimate(deleteOp.getQ(),
+ deleteOp.getCollation(),
+ deleteOp.getHint()) >= deleteOp.toBSON().objsize();
+}
+
bool isClassicalUpdateReplacement(const BSONObj& update) {
// An empty update object will be treated as replacement as firstElementFieldName() returns "".
return update.firstElementFieldName()[0] != '$';