diff options
author | James Wahlin <james.wahlin@10gen.com> | 2016-08-30 16:57:35 -0400 |
---|---|---|
committer | James Wahlin <james.wahlin@10gen.com> | 2016-08-31 19:00:59 -0400 |
commit | bbc9748b355ae0ac07d6ac37db757712bc35af43 (patch) | |
tree | 770c7d6f0c71d2c515e4f0a1c2e7ca371776c803 | |
parent | f5c9d27ca6f0f4e1e2673c64b84b628ac29493ec (diff) | |
download | mongo-bbc9748b355ae0ac07d6ac37db757712bc35af43.tar.gz |
SERVER-25796 copydb support for views + additional cloner tests
-rw-r--r-- | jstests/noPassthroughWithMongod/clonecollection.js | 10 | ||||
-rw-r--r-- | jstests/replsets/cloneDb.js | 18 | ||||
-rw-r--r-- | jstests/replsets/copydb.js | 28 | ||||
-rw-r--r-- | jstests/views/views_all_commands.js | 6 | ||||
-rw-r--r-- | src/mongo/db/cloner.cpp | 35 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 10 | ||||
-rw-r--r-- | src/mongo/db/commands/list_collections_filter.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/commands/list_collections_filter_test.cpp | 71 | ||||
-rw-r--r-- | src/mongo/db/namespace_string.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/namespace_string.h | 4 | ||||
-rw-r--r-- | src/mongo/db/views/durable_view_catalog.h | 2 |
11 files changed, 185 insertions, 9 deletions
diff --git a/jstests/noPassthroughWithMongod/clonecollection.js b/jstests/noPassthroughWithMongod/clonecollection.js index 2f0ec45ad44..2c7cf24ea9a 100644 --- a/jstests/noPassthroughWithMongod/clonecollection.js +++ b/jstests/noPassthroughWithMongod/clonecollection.js @@ -54,3 +54,13 @@ assert.writeOK(f.a.insert({})); assert.gt(f.system.profile.count(), 0); t.system.profile.drop(); assert.commandFailed(t.cloneCollection("localhost:" + fromMongod.port, "system.profile")); + +// Check that cloning a view is disallowed. +f.a.drop(); +t.a.drop(); + +assert.commandWorked(f.createCollection("a")); +assert.commandWorked(f.createView("viewA", "a", [])); +assert.commandFailedWithCode(t.cloneCollection("localhost:" + fromMongod.port, "viewA"), + ErrorCodes.CommandNotSupportedOnView, + "cloneCollection on view expected to fail"); diff --git a/jstests/replsets/cloneDb.js b/jstests/replsets/cloneDb.js index a3a6970ff10..a24b9d80c11 100644 --- a/jstests/replsets/cloneDb.js +++ b/jstests/replsets/cloneDb.js @@ -16,6 +16,7 @@ var replsetDBName = 'cloneDBreplset'; var standaloneDBName = 'cloneDBstandalone'; var testColName = 'foo'; + var testViewName = 'view'; jsTest.log("Create replica set"); var replTest = new ReplSetTest({name: 'testSet', nodes: 3}); @@ -39,11 +40,17 @@ } assert.writeOK(bulk.execute({w: 3})); + jsTest.log("Create view on replica set"); + assert.commandWorked(masterDB.runCommand({create: testViewName, viewOn: testColName})); + jsTest.log("Clone db from replica set to standalone server"); standaloneDB.cloneDatabase(replTest.getURL()); assert.eq(numDocs, standaloneDB[testColName].find().itcount(), 'cloneDatabase from replset to standalone failed (document counts do not match)'); + assert.eq(numDocs, + standaloneDB[testViewName].find().itcount(), + 'cloneDatabase from replset to standalone failed (count on view incorrect)'); jsTest.log("Clone db from replica set PRIMARY to standalone server"); standaloneDB.dropDatabase(); @@ -51,6 +58,9 @@ assert.eq(numDocs, standaloneDB[testColName].find().itcount(), 'cloneDatabase from PRIMARY to standalone failed (document counts do not match)'); + assert.eq(numDocs, + standaloneDB[testViewName].find().itcount(), + 'cloneDatabase from PRIMARY to standalone failed (count on view incorrect)'); jsTest.log("Clone db from replica set SECONDARY to standalone server (should not copy)"); standaloneDB.dropDatabase(); @@ -67,6 +77,9 @@ numDocs, standaloneDB[testColName].find().itcount(), 'cloneDatabase from SECONDARY to standalone failed (document counts do not match)'); + assert.eq(numDocs, + standaloneDB[testViewName].find().itcount(), + 'cloneDatabase from SECONDARY to standalone failed (count on view incorrect)'); jsTest.log("Switch db and insert data into standalone server"); masterDB = master.getDB(standaloneDBName); @@ -82,12 +95,17 @@ } assert.writeOK(bulk.execute()); + assert.commandWorked(standaloneDB.runCommand({create: testViewName, viewOn: testColName})); + jsTest.log("Clone db from standalone server to replica set PRIMARY"); masterDB.cloneDatabase(standalone.host); replTest.awaitReplication(); assert.eq(numDocs, masterDB[testColName].find().itcount(), 'cloneDatabase from standalone to PRIMARY failed (document counts do not match)'); + assert.eq(numDocs, + masterDB[testViewName].find().itcount(), + 'cloneDatabase from standalone to PRIMARY failed (count on view incorrect)'); jsTest.log("Clone db from standalone server to replica set SECONDARY"); masterDB.dropDatabase(); diff --git a/jstests/replsets/copydb.js b/jstests/replsets/copydb.js index dcbe1deefc2..2746035f6ef 100644 --- a/jstests/replsets/copydb.js +++ b/jstests/replsets/copydb.js @@ -26,10 +26,15 @@ 'failed to insert document in source collection'); assert.commandWorked(primarySourceDB.foo.ensureIndex({a: 1}), 'failed to create index in source collection on primary'); + assert.commandWorked(primarySourceDB.runCommand({create: "fooView", viewOn: "foo"}), + 'failed to create view on source collection on primary'); assert.eq(1, primarySourceDB.foo.find().itcount(), 'incorrect number of documents in source collection on primary before copy'); + assert.eq(1, + primarySourceDB.fooView.find().itcount(), + 'incorrect number of documents in source view on primary before copy'); assert.eq(0, primaryTargetDB.foo.find().itcount(), 'target collection on primary should be empty before copy'); @@ -42,6 +47,19 @@ primaryTargetDB.foo.find().itcount(), 'incorrect number of documents in target collection on primary after copy'); + // Confirm that 'fooView' is still a view namespace after copy. + let res = primaryTargetDB.runCommand({listCollections: 1, filter: {name: "fooView"}}); + assert.commandWorked(res); + assert(res.cursor.firstBatch.length === 1); + assert(res.cursor.firstBatch[0].hasOwnProperty("type"), tojson(res)); + assert.eq("view", + res.cursor.firstBatch[0].type, + "Namespace exected to be view: " + tojson(res.cursor.firstBatch[0])); + + assert.eq(primarySourceDB.fooView.find().itcount(), + primaryTargetDB.fooView.find().itcount(), + 'incorrect number of documents in target view on primary after copy'); + assert.eq(primarySourceDB.foo.getIndexes().length, primaryTargetDB.foo.getIndexes().length, 'incorrect number of indexes in target collection on primary after copy'); @@ -54,6 +72,10 @@ secondarySourceDB.foo.find().itcount(), 'incorrect number of documents in source collection on secondary after copy'); + assert.eq(primarySourceDB.fooView.find().itcount(), + secondarySourceDB.fooView.find().itcount(), + 'incorrect number of documents in source view on secondary after copy'); + assert.eq(primarySourceDB.foo.getIndexes().length, secondarySourceDB.foo.getIndexes().length, 'incorrect number of indexes in source collection on secondary after copy'); @@ -64,7 +86,11 @@ secondaryTargetDB.foo.find().itcount(), 'incorrect number of documents in target collection on secondary after copy'); + assert.eq(primaryTargetDB.fooView.find().itcount(), + secondaryTargetDB.fooView.find().itcount(), + 'incorrect number of documents in target view on secondary after copy'); + assert.eq(primaryTargetDB.foo.getIndexes().length, secondaryTargetDB.foo.getIndexes().length, 'incorrect number of indexes in target collection on secondary after copy'); -}());
\ No newline at end of file +}()); diff --git a/jstests/views/views_all_commands.js b/jstests/views/views_all_commands.js index a35fa1830c5..2c4908a7b5b 100644 --- a/jstests/views/views_all_commands.js +++ b/jstests/views/views_all_commands.js @@ -104,8 +104,8 @@ cleanupOrphaned: { skip: "Tested in views/views_sharded.js", }, - clone: {skip: "TODO(SERVER-24506)"}, - cloneCollection: {skip: "TODO(SERVER-24506)"}, + clone: {skip: "Tested in replsets/cloneDb.js"}, + cloneCollection: {skip: "Tested in noPassthroughWithMongod/clonecollection.js"}, cloneCollectionAsCapped: { command: {cloneCollectionAsCapped: "view", toCollection: "testcapped", size: 10240}, expectFailure: true, @@ -118,7 +118,7 @@ connPoolSync: {skip: isUnrelated}, connectionStatus: {skip: isUnrelated}, convertToCapped: {command: {convertToCapped: "view", size: 12345}, expectFailure: true}, - copydb: {skip: "TODO(SERVER-24506)"}, + copydb: {skip: "Tested in replsets/copydb.js"}, copydbgetnonce: {skip: isUnrelated}, copydbsaslstart: {skip: isUnrelated}, count: {command: {count: "view"}}, diff --git a/src/mongo/db/cloner.cpp b/src/mongo/db/cloner.cpp index 3ea687fdd5d..9afef63d2e4 100644 --- a/src/mongo/db/cloner.cpp +++ b/src/mongo/db/cloner.cpp @@ -155,6 +155,8 @@ struct Cloner::Fun { MONGO_WRITE_CONFLICT_RETRY_LOOP_END(txn, "createCollection", to_collection.ns()); } + const bool isSystemViewsClone = to_collection.isSystemDotViews(); + while (i.moreInCurrentBatch()) { if (numSeen % 128 == 127) { time_t now = time(0); @@ -198,6 +200,23 @@ struct Cloner::Fun { BSONObj tmp = i.nextSafe(); + // If copying the system.views collection to a database with a different name, then any + // view definitions must be modified to refer to the 'to' database. + if (isSystemViewsClone && from_collection.db() != to_collection.db()) { + BSONObjBuilder bob; + for (auto&& item : tmp) { + if (item.fieldNameStringData() == "_id") { + auto viewNss = NamespaceString(item.checkAndGetStringData()); + + bob.append("_id", + NamespaceString(to_collection.db(), viewNss.coll()).toString()); + } else { + bob.append(item); + } + } + tmp = bob.obj(); + } + /* assure object is valid. note this will slow us down a little. */ const Status status = validateBSON(tmp.objdata(), tmp.objsize()); if (!status.isOK()) { @@ -396,6 +415,22 @@ bool Cloner::copyCollection(OperationContext* txn, if (!collList.empty()) { invariant(collList.size() <= 1); BSONObj col = collList.front(); + + // Confirm that 'col' is not a view. + { + std::string namespaceType; + auto status = bsonExtractStringField(col, "type", &namespaceType); + + uassert(ErrorCodes::InternalError, + str::stream() << "Collection 'type' expected to be a string: " << col, + ErrorCodes::TypeMismatch != status.code()); + + uassert(ErrorCodes::CommandNotSupportedOnView, + str::stream() << "copyCollection not supported for views. ns: " + << col["name"].valuestrsafe(), + !(status.isOK() && namespaceType == "view")); + } + if (col["options"].isABSONObj()) { options = col["options"].Obj(); } diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 3a1a9f34b66..86f8bc3261f 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -184,6 +184,16 @@ env.Library( ) env.CppUnitTest( + target="list_collections_filter_test", + source=[ + "list_collections_filter_test.cpp" + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/db/commands/list_collections_filter', + ], +) + +env.CppUnitTest( target="index_filter_commands_test", source=[ "index_filter_commands_test.cpp", diff --git a/src/mongo/db/commands/list_collections_filter.cpp b/src/mongo/db/commands/list_collections_filter.cpp index 6b04c0a286d..2d9ba267ba9 100644 --- a/src/mongo/db/commands/list_collections_filter.cpp +++ b/src/mongo/db/commands/list_collections_filter.cpp @@ -47,10 +47,16 @@ BSONObj ListCollectionsFilter::makeTypeViewFilter() { } BSONObj ListCollectionsFilter::addTypeCollectionFilter(const BSONObj& filter) { + if (filter.isEmpty()) + return makeTypeCollectionFilter(); + return BSON("$and" << BSON_ARRAY(filter << makeTypeCollectionFilter())); } BSONObj ListCollectionsFilter::addTypeViewFilter(const BSONObj& filter) { + if (filter.isEmpty()) + return makeTypeViewFilter(); + return BSON("$and" << BSON_ARRAY(filter << makeTypeViewFilter())); } diff --git a/src/mongo/db/commands/list_collections_filter_test.cpp b/src/mongo/db/commands/list_collections_filter_test.cpp new file mode 100644 index 00000000000..f72e866b6b5 --- /dev/null +++ b/src/mongo/db/commands/list_collections_filter_test.cpp @@ -0,0 +1,71 @@ +/** + * 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/commands/list_collections_filter.h" + +#include "mongo/bson/json.h" +#include "mongo/unittest/unittest.h" + +namespace { + +using namespace mongo; + +TEST(ListCollectionsFilterTest, AddCollectionFilter) { + BSONObj filter = fromjson("{'options.capped': false}"); + BSONObj expected = fromjson( + "{$and: [" + "{'options.capped': false}, " + "{$or: [{type: 'collection'}, {type: {$exists: false}}]}]}"); + + ASSERT_BSONOBJ_EQ(expected, ListCollectionsFilter::addTypeCollectionFilter(filter)); +} + +TEST(ListCollectionsFilterTest, AddCollectionFilterToEmptyInputFilter) { + BSONObj filter; + BSONObj expected = fromjson("{$or: [{type: 'collection'}, {type: {$exists: false}}]}"); + + ASSERT_BSONOBJ_EQ(expected, ListCollectionsFilter::addTypeCollectionFilter(filter)); +} + +TEST(ListCollectionsFilterTest, AddViewFilter) { + BSONObj filter = fromjson("{'options.capped': false}"); + BSONObj expected = fromjson("{$and: [{'options.capped': false}, {type: 'view'}]}"); + + ASSERT_BSONOBJ_EQ(expected, ListCollectionsFilter::addTypeViewFilter(filter)); +} + +TEST(ListCollectionsFilterTest, AddViewFilterToEmptyInputFilter) { + BSONObj filter; + BSONObj expected = fromjson("{type: 'view'}"); + + ASSERT_BSONOBJ_EQ(expected, ListCollectionsFilter::addTypeViewFilter(filter)); +} + +} // namespace diff --git a/src/mongo/db/namespace_string.cpp b/src/mongo/db/namespace_string.cpp index 27495baa9d0..f2eea383541 100644 --- a/src/mongo/db/namespace_string.cpp +++ b/src/mongo/db/namespace_string.cpp @@ -96,7 +96,7 @@ bool legalClientSystemNS(StringData ns) { if (ns.find(".system.js") != string::npos) return true; - if (nsToCollectionSubstring(ns) == NamespaceString::kSystemDotViewsCol) + if (nsToCollectionSubstring(ns) == NamespaceString::kSystemDotViewsCollectionName) return true; return false; @@ -104,7 +104,7 @@ bool legalClientSystemNS(StringData ns) { const StringData NamespaceString::kAdminDb = "admin"_sd; const StringData NamespaceString::kLocalDb = "local"_sd; -constexpr StringData NamespaceString::kSystemDotViewsCol; +constexpr StringData NamespaceString::kSystemDotViewsCollectionName; const NamespaceString NamespaceString::kConfigCollectionNamespace(kConfigCollection); diff --git a/src/mongo/db/namespace_string.h b/src/mongo/db/namespace_string.h index 93810d6307b..ce8bfa1884d 100644 --- a/src/mongo/db/namespace_string.h +++ b/src/mongo/db/namespace_string.h @@ -64,7 +64,7 @@ public: static const StringData kLocalDb; // Name for the system views collection - static constexpr StringData kSystemDotViewsCol = "system.views"_sd; + static constexpr StringData kSystemDotViewsCollectionName = "system.views"_sd; // Namespace for storing configuration data, which needs to be replicated if the server is // running as a replica set. Documents in this collection should represent some configuration @@ -166,7 +166,7 @@ public: return coll() == "system.profile"; } bool isSystemDotViews() const { - return coll() == kSystemDotViewsCol; + return coll() == kSystemDotViewsCollectionName; } bool isConfigDB() const { return db() == "config"; diff --git a/src/mongo/db/views/durable_view_catalog.h b/src/mongo/db/views/durable_view_catalog.h index 3ca577cdfbb..6ad323198e2 100644 --- a/src/mongo/db/views/durable_view_catalog.h +++ b/src/mongo/db/views/durable_view_catalog.h @@ -49,7 +49,7 @@ class OperationContext; class DurableViewCatalog { public: static constexpr StringData viewsCollectionName() { - return NamespaceString::kSystemDotViewsCol; + return NamespaceString::kSystemDotViewsCollectionName; } /** |