summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Wahlin <james.wahlin@10gen.com>2016-08-30 16:57:35 -0400
committerJames Wahlin <james.wahlin@10gen.com>2016-08-31 19:00:59 -0400
commitbbc9748b355ae0ac07d6ac37db757712bc35af43 (patch)
tree770c7d6f0c71d2c515e4f0a1c2e7ca371776c803
parentf5c9d27ca6f0f4e1e2673c64b84b628ac29493ec (diff)
downloadmongo-bbc9748b355ae0ac07d6ac37db757712bc35af43.tar.gz
SERVER-25796 copydb support for views + additional cloner tests
-rw-r--r--jstests/noPassthroughWithMongod/clonecollection.js10
-rw-r--r--jstests/replsets/cloneDb.js18
-rw-r--r--jstests/replsets/copydb.js28
-rw-r--r--jstests/views/views_all_commands.js6
-rw-r--r--src/mongo/db/cloner.cpp35
-rw-r--r--src/mongo/db/commands/SConscript10
-rw-r--r--src/mongo/db/commands/list_collections_filter.cpp6
-rw-r--r--src/mongo/db/commands/list_collections_filter_test.cpp71
-rw-r--r--src/mongo/db/namespace_string.cpp4
-rw-r--r--src/mongo/db/namespace_string.h4
-rw-r--r--src/mongo/db/views/durable_view_catalog.h2
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;
}
/**