From 0452a3528152bef06794bc4ec9e676a985bbb3f5 Mon Sep 17 00:00:00 2001 From: Tyler Brock Date: Mon, 9 Jun 2014 11:58:41 -0400 Subject: [PATCH] CXX-238 Enforce update/replace bulk semantics --- src/mongo/client/bulk_update_builder.cpp | 14 +++ src/mongo/client/bulk_upsert_builder.cpp | 14 +++ src/mongo/unittest/bulk_operation_test.cpp | 132 +++++++++++++++++++++ 3 files changed, 160 insertions(+) diff --git a/src/mongo/client/bulk_update_builder.cpp b/src/mongo/client/bulk_update_builder.cpp index ea80f6bbda..d96f46cd9d 100644 --- a/src/mongo/client/bulk_update_builder.cpp +++ b/src/mongo/client/bulk_update_builder.cpp @@ -30,17 +30,31 @@ namespace mongo { {} void BulkUpdateBuilder::updateOne(const BSONObj& update) { + uassert(0, "update object must not be empty", + !update.isEmpty()); + uassert(0, "update object must consist of $-prefixed modifiers", + update.firstElementFieldName()[0] == '$'); + UpdateWriteOperation* update_op = new UpdateWriteOperation(_selector, update, 0); _builder->enqueue(update_op); } void BulkUpdateBuilder::update(const BSONObj& update) { + uassert(0, "update object must not be empty", + !update.isEmpty()); + uassert(0, "update object must consist of $-prefixed modifiers", + update.firstElementFieldName()[0] == '$'); + UpdateWriteOperation* update_op = new UpdateWriteOperation( _selector, update, UpdateOption_Multi); _builder->enqueue(update_op); } void BulkUpdateBuilder::replaceOne(const BSONObj& replacement) { + if (!replacement.isEmpty()) + uassert(0, "replacement object must not include $ operators", + replacement.firstElementFieldName()[0] != '$'); + UpdateWriteOperation* update_op = new UpdateWriteOperation( _selector, replacement, 0); _builder->enqueue(update_op); diff --git a/src/mongo/client/bulk_upsert_builder.cpp b/src/mongo/client/bulk_upsert_builder.cpp index 6bf32f1cb9..0044d268a7 100644 --- a/src/mongo/client/bulk_upsert_builder.cpp +++ b/src/mongo/client/bulk_upsert_builder.cpp @@ -29,18 +29,32 @@ namespace mongo { {} void BulkUpsertBuilder::updateOne(const BSONObj& update) { + uassert(0, "update object must not be empty", + !update.isEmpty()); + uassert(0, "update object must consist of $-prefixed modifiers", + update.firstElementFieldName()[0] == '$'); + UpdateWriteOperation* update_op = new UpdateWriteOperation( _selector, update, UpdateOption_Upsert); _builder->enqueue(update_op); } void BulkUpsertBuilder::update(const BSONObj& update) { + uassert(0, "update object must not be empty", + !update.isEmpty()); + uassert(0, "update object must consist of $-prefixed modifiers", + update.firstElementFieldName()[0] == '$'); + UpdateWriteOperation* update_op = new UpdateWriteOperation( _selector, update, UpdateOption_Upsert + UpdateOption_Multi); _builder->enqueue(update_op); } void BulkUpsertBuilder::replaceOne(const BSONObj& replacement) { + if (!replacement.isEmpty()) + uassert(0, "replacement object must not include $ operators", + replacement.firstElementFieldName()[0] != '$'); + UpdateWriteOperation* update_op = new UpdateWriteOperation( _selector, replacement, UpdateOption_Upsert); _builder->enqueue(update_op); diff --git a/src/mongo/unittest/bulk_operation_test.cpp b/src/mongo/unittest/bulk_operation_test.cpp index 70efae6608..b218baada7 100644 --- a/src/mongo/unittest/bulk_operation_test.cpp +++ b/src/mongo/unittest/bulk_operation_test.cpp @@ -580,6 +580,138 @@ namespace { ASSERT_FALSE(result.hasErrors()); } + TYPED_TEST(BulkOperationTest, UpdateEmpty) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).update(BSONObj()), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateMissingDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).update(BSON("a" << 2)), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateOneMissingDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).updateOne(BSON("a" << 2)), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateMixedDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + bulk.find(BSON("b" << 1)).update( + BSON("$set" << BSON("a" << "2") << "a" << 2) + ); + + WriteResult result; + ASSERT_THROWS( + bulk.execute(&WriteConcern::acknowledged, &result), + OperationException + ); + } + + TYPED_TEST(BulkOperationTest, ReplaceOneEmpty) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + bulk.insert(BSON("b" << 1)); + bulk.find(BSON("b" << 1)).replaceOne(BSONObj()); + ASSERT_EQUALS(this->c->count(TEST_NS, BSON("b" << 1)), 0U); + } + + TYPED_TEST(BulkOperationTest, ReplaceOneHavingDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).replaceOne( + BSON("$set" << BSON("a" << 2)) + ), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateUpsertEmpty) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).upsert().update(BSONObj()), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateUpsertMissingDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).upsert().update(BSON("a" << 2)), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateOneUpsertMissingDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).upsert().updateOne(BSON("a" << 2)), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, UpdateUpsertMixedDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + bulk.find(BSON("b" << 1)).upsert().update( + BSON("$set" << BSON("a" << "2") << "a" << 2) + ); + + WriteResult result; + ASSERT_THROWS( + bulk.execute(&WriteConcern::acknowledged, &result), + OperationException + ); + } + + TYPED_TEST(BulkOperationTest, ReplaceOneUpsertHavingDollarSign) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + ASSERT_THROWS( + bulk.find(BSON("b" << 1)).upsert().replaceOne( + BSON("$set" << BSON("a" << 2)) + ), + std::exception + ); + } + + TYPED_TEST(BulkOperationTest, ReplaceOneUpsertEmpty) { + if (!this->testSupported()) return; + BulkOperationBuilder bulk(this->c, TEST_NS, true); + + bulk.insert(BSON("b" << 1)); + bulk.find(BSON("b" << 1)).upsert().replaceOne(BSONObj()); + ASSERT_EQUALS(this->c->count(TEST_NS, BSON("b" << 1)), 0U); + } + TYPED_TEST(BulkOperationTest, UnorderedBatchWithErrors) { if (!this->testSupported()) return;