/**
* Copyright (C) 2017 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
#include "mongo/bson/bson_depth.h"
#include "mongo/bson/bson_validate.h"
#include "mongo/bson/json.h"
#include "mongo/client/connection_string.h"
#include "mongo/executor/network_interface_integration_fixture.h"
#include "mongo/util/concurrency/thread_pool.h"
namespace mongo {
namespace executor {
namespace {
class NestingDepthFixture : public NetworkInterfaceIntegrationFixture {
public:
void setUp() final {
startNet();
}
};
constexpr auto kCollectionName = "depthTest";
/**
* Appends an object to 'builder' that is nested 'depth' levels deep.
*/
void appendNestedObject(BSONObjBuilder* builder, size_t depth) {
if (depth == 1) {
builder->append("a", 1);
} else {
BSONObjBuilder subobj(builder->subobjStart("a"));
appendNestedObject(&subobj, depth - 1);
subobj.doneFast();
}
}
/**
* Appends a command to 'builder' that inserts a document nested 'depth' levels deep. Calling
* obj() on the builder returns the completed insert command as a BSONObj.
*/
void appendInsertCommandWithNestedDocument(BSONObjBuilder* builder, size_t depth) {
builder->append("insert", kCollectionName);
{
BSONArrayBuilder array(builder->subarrayStart("documents"));
{
BSONObjBuilder document(array.subobjStart());
appendNestedObject(&document, depth);
document.doneFast();
}
array.doneFast();
}
builder->doneFast();
}
TEST_F(NestingDepthFixture, CanInsertLargeNestedDocumentAtOrUnderDepthLimit) {
BSONObjBuilder insertDocumentOneLessThanLimit;
appendInsertCommandWithNestedDocument(&insertDocumentOneLessThanLimit,
BSONDepth::getMaxDepthForUserStorage() - 1);
assertCommandOK(kCollectionName, insertDocumentOneLessThanLimit.obj());
// Insert a document exactly at the BSON nesting limit.
BSONObjBuilder insertCommandExactLimit;
appendInsertCommandWithNestedDocument(&insertCommandExactLimit,
BSONDepth::getMaxDepthForUserStorage());
assertCommandOK(kCollectionName, insertCommandExactLimit.obj());
}
TEST_F(NestingDepthFixture, CannotInsertLargeNestedDocumentExceedingDepthLimit) {
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, BSONDepth::getMaxDepthForUserStorage() + 1);
assertWriteError(kCollectionName, insertCmd.obj(), ErrorCodes::Overflow);
}
/**
* Appends an array to 'builder' that is nested 'depth' levels deep.
*/
void appendNestedArray(BSONArrayBuilder* builder, size_t depth) {
if (depth == 1) {
builder->append(1);
} else {
BSONArrayBuilder subarr(builder->subarrayStart());
appendNestedArray(&subarr, depth - 1);
subarr.doneFast();
}
}
/**
* Appends a command to 'builder' that inserts a document with an array nested 'depth' levels deep.
* Calling obj() on the builder returns the completed insert command as a BSONObj.
*/
void appendInsertCommandWithNestedArray(BSONObjBuilder* builder, size_t depth) {
builder->append("insert", kCollectionName);
{
BSONArrayBuilder documentsBuilder(builder->subarrayStart("documents"));
{
BSONObjBuilder docBuilder(documentsBuilder.subobjStart());
{
BSONArrayBuilder arrayBuilder(docBuilder.subarrayStart("a"));
appendNestedArray(&arrayBuilder, depth - 1);
arrayBuilder.doneFast();
}
docBuilder.doneFast();
}
documentsBuilder.doneFast();
}
builder->doneFast();
}
TEST_F(NestingDepthFixture, CanInsertLargeNestedArrayAtOrUnderDepthLimit) {
BSONObjBuilder insertDocumentOneLessThanLimit;
appendInsertCommandWithNestedArray(&insertDocumentOneLessThanLimit,
BSONDepth::getMaxDepthForUserStorage() - 1);
assertCommandOK(kCollectionName, insertDocumentOneLessThanLimit.obj());
// Insert a document exactly at the BSON nesting limit.
BSONObjBuilder insertCommandExactLimit;
appendInsertCommandWithNestedArray(&insertCommandExactLimit,
BSONDepth::getMaxDepthForUserStorage());
assertCommandOK(kCollectionName, insertCommandExactLimit.obj());
}
TEST_F(NestingDepthFixture, CannotInsertLargeNestedArrayExceedingDepthLimit) {
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, BSONDepth::getMaxDepthForUserStorage() + 1);
assertWriteError(kCollectionName, insertCmd.obj(), ErrorCodes::Overflow);
}
/**
* Creates a field name string that represents a document nested 'depth' levels deep.
*/
std::string getRepeatedFieldName(size_t depth) {
ASSERT_GT(depth, 0U);
StringBuilder builder;
for (size_t i = 0U; i < depth - 1; i++) {
builder << "a.";
}
builder << "a";
return builder.str();
}
/**
* Appends a command to 'builder' that updates a document with nesting depth 'originalNestingDepth'
* to be 'newNestingDepth' levels deep. For example,
*
* BSONObjBuilder b;
* appendUpdateCommandWithNestedDocuments(&b, 1, 2);
*
* appends an update to 'b' that updates { a: 1 } to be { a: { a: 1 } }.
*/
void appendUpdateCommandWithNestedDocuments(BSONObjBuilder* builder,
size_t originalNestingDepth,
size_t newNestingDepth) {
ASSERT_GT(newNestingDepth, originalNestingDepth);
auto originalFieldName = getRepeatedFieldName(originalNestingDepth);
builder->append("update", kCollectionName);
{
BSONArrayBuilder updates(builder->subarrayStart("updates"));
{
BSONObjBuilder updateDoc(updates.subobjStart());
{
BSONObjBuilder query(updateDoc.subobjStart("q"));
query.append(originalFieldName, 1);
query.doneFast();
}
{
BSONObjBuilder update(updateDoc.subobjStart("u"));
BSONObjBuilder set(update.subobjStart("$set"));
BSONObjBuilder field(set.subobjStart(originalFieldName));
appendNestedObject(&field, newNestingDepth - originalNestingDepth);
field.doneFast();
set.doneFast();
update.doneFast();
}
updateDoc.doneFast();
}
}
builder->doneFast();
}
/**
* Appends a command to 'builder' that replaces a document with nesting depth 'originalNestingDepth'
* with a document 'newNestingDepth' levels deep. For example,
*
* BSONObjBuilder b;
* appendUpdateCommandWithNestedDocuments(&b, 1, 2);
*
* appends an update to 'b' that replaces { a: 1 } with { a: { a: 1 } }.
*/
void appendUpdateReplaceCommandWithNestedDocuments(BSONObjBuilder* builder,
size_t originalNestingDepth,
size_t newNestingDepth) {
ASSERT_GT(newNestingDepth, originalNestingDepth);
auto originalFieldName = getRepeatedFieldName(originalNestingDepth);
builder->append("update", kCollectionName);
{
BSONArrayBuilder updates(builder->subarrayStart("updates"));
{
BSONObjBuilder updateDoc(updates.subobjStart());
{
BSONObjBuilder query(updateDoc.subobjStart("q"));
query.append(originalFieldName, 1);
query.doneFast();
}
{
BSONObjBuilder update(updateDoc.subobjStart("u"));
appendNestedObject(&update, newNestingDepth);
update.doneFast();
}
updateDoc.doneFast();
}
}
builder->doneFast();
}
TEST_F(NestingDepthFixture, CanUpdateDocumentIfItStaysWithinDepthLimit) {
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, 3);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateCommandWithNestedDocuments(&updateCmd, 3, 5);
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CanUpdateDocumentToBeExactlyAtDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 2;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateCommandWithNestedDocuments(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage());
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CannotUpdateDocumentToExceedDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 3;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateCommandWithNestedDocuments(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage() + 1);
assertWriteError(kCollectionName, updateCmd.obj(), ErrorCodes::Overflow);
}
TEST_F(NestingDepthFixture, CanReplaceDocumentIfItStaysWithinDepthLimit) {
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, 3);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateReplaceCommandWithNestedDocuments(&updateCmd, 3, 5);
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CanReplaceDocumentToBeExactlyAtDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 2;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateReplaceCommandWithNestedDocuments(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage());
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CannotReplaceDocumentToExceedDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 3;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedDocument(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateReplaceCommandWithNestedDocuments(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage() + 1);
assertWriteError(kCollectionName, updateCmd.obj(), ErrorCodes::Overflow);
}
/**
* Creates a field name string that represents an array nested 'depth' levels deep.
*/
std::string getRepeatedArrayPath(size_t depth) {
ASSERT_GT(depth, 0U);
StringBuilder builder;
builder << "a";
for (size_t i = 0U; i < depth - 1; i++) {
builder << ".0";
}
return builder.str();
}
/**
* Appends a command to 'builder' that updates a document with an array nested
* 'originalNestingDepth' levels deep to be 'newNestingDepth' levels deep. For example,
*
* BSONObjBuilder b;
* appendUpdateCommandWithNestedDocuments(&b, 3, 4);
*
* appends an update to 'b' that updates { a: [[1]] } to be { a: [[[1]]] }.
*/
void appendUpdateCommandWithNestedArrays(BSONObjBuilder* builder,
size_t originalNestingDepth,
size_t newNestingDepth) {
ASSERT_GT(newNestingDepth, originalNestingDepth);
auto originalFieldName = getRepeatedArrayPath(originalNestingDepth);
builder->append("update", kCollectionName);
{
BSONArrayBuilder updates(builder->subarrayStart("updates"));
{
BSONObjBuilder updateDoc(updates.subobjStart());
{
BSONObjBuilder query(updateDoc.subobjStart("q"));
query.append(originalFieldName, 1);
query.doneFast();
}
{
BSONObjBuilder update(updateDoc.subobjStart("u"));
BSONObjBuilder set(update.subobjStart("$set"));
BSONArrayBuilder field(set.subobjStart(originalFieldName));
appendNestedArray(&field, newNestingDepth - originalNestingDepth);
field.doneFast();
set.doneFast();
update.doneFast();
}
updateDoc.doneFast();
}
}
builder->doneFast();
}
/**
* Appends a command to 'builder' that replaces a document with an array nested
* 'originalNestingDepth' levels deep with a document with an array nested 'newNestingDepth' levels
* deep. For example,
*
* BSONObjBuilder b;
* appendUpdateCommandWithNestedDocuments(&b, 3, 4);
*
* appends an update to 'b' that replaces { a: [[1]] } with { a: [[[1]]] }.
*/
void appendUpdateReplaceCommandWithNestedArrays(BSONObjBuilder* builder,
size_t originalNestingDepth,
size_t newNestingDepth) {
ASSERT_GT(newNestingDepth, originalNestingDepth);
auto originalFieldName = getRepeatedArrayPath(originalNestingDepth);
builder->append("update", kCollectionName);
{
BSONArrayBuilder updates(builder->subarrayStart("updates"));
{
BSONObjBuilder updateDoc(updates.subobjStart());
{
BSONObjBuilder query(updateDoc.subobjStart("q"));
query.append(originalFieldName, 1);
query.doneFast();
}
{
BSONObjBuilder update(updateDoc.subobjStart("u"));
BSONArrayBuilder field(update.subobjStart(originalFieldName));
appendNestedArray(&field, newNestingDepth - 1);
field.doneFast();
update.doneFast();
}
updateDoc.doneFast();
}
}
builder->doneFast();
}
TEST_F(NestingDepthFixture, CanUpdateArrayIfItStaysWithinDepthLimit) {
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, 3);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateCommandWithNestedArrays(&updateCmd, 3, 5);
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CanUpdateArrayToBeExactlyAtDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 1;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateCommandWithNestedArrays(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage());
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CannotUpdateArrayToExceedDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 4;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateCommandWithNestedArrays(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage() + 1);
assertWriteError(kCollectionName, updateCmd.obj(), ErrorCodes::Overflow);
}
TEST_F(NestingDepthFixture, CanReplaceArrayIfItStaysWithinDepthLimit) {
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, 3);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateReplaceCommandWithNestedArrays(&updateCmd, 3, 5);
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CanReplaceArrayToBeExactlyAtDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 1;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateReplaceCommandWithNestedArrays(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage());
assertCommandOK(kCollectionName, updateCmd.obj());
}
TEST_F(NestingDepthFixture, CannotReplaceArrayToExceedDepthLimit) {
const auto largeButValidDepth = BSONDepth::getMaxDepthForUserStorage() - 4;
BSONObjBuilder insertCmd;
appendInsertCommandWithNestedArray(&insertCmd, largeButValidDepth);
assertCommandOK(kCollectionName, insertCmd.obj());
BSONObjBuilder updateCmd;
appendUpdateReplaceCommandWithNestedArrays(
&updateCmd, largeButValidDepth, BSONDepth::getMaxDepthForUserStorage() + 1);
assertWriteError(kCollectionName, updateCmd.obj(), ErrorCodes::Overflow);
}
} // namespace
} // namespace executor
} // namespace mongo