diff options
Diffstat (limited to 'src/mongo/db/views/view_graph_test.cpp')
-rw-r--r-- | src/mongo/db/views/view_graph_test.cpp | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/src/mongo/db/views/view_graph_test.cpp b/src/mongo/db/views/view_graph_test.cpp new file mode 100644 index 00000000000..15638204c5c --- /dev/null +++ b/src/mongo/db/views/view_graph_test.cpp @@ -0,0 +1,246 @@ +/** + * 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 <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/bson/bsonmisc.h" +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/namespace_string.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/query/collation/collator_factory_interface.h" +#include "mongo/db/query/query_test_service_context.h" +#include "mongo/db/views/view.h" +#include "mongo/db/views/view_graph.h" +#include "mongo/stdx/memory.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { +constexpr auto kEmptyPipelineSize = 0; +constexpr auto kTestDb = "test"_sd; +constexpr auto kFooName = "foo"_sd; +constexpr auto kBarName = "bar"_sd; +constexpr auto kQuxName = "qux"_sd; +const auto kFooNamespace = NamespaceString(kTestDb, kFooName); +const auto kBarNamespace = NamespaceString(kTestDb, kBarName); +const auto kQuxNamespace = NamespaceString(kTestDb, kQuxName); +const auto kEmptyPipeline = BSONArray(); +const auto kBinaryCollation = BSONObj(); +const auto kFilipinoCollation = BSON("locale" + << "fil"); + +class ViewGraphFixture : public unittest::Test { +public: + ViewGraphFixture() + : _queryServiceContext(), _opCtx(_queryServiceContext.makeOperationContext()) {} + + const OperationContext* opCtx() const { + return _opCtx.get(); + } + + ViewGraph* viewGraph() { + return &_viewGraph; + } + + ViewDefinition makeViewDefinition(StringData db, + StringData view, + StringData viewOn, + BSONArray pipeline, + BSONObj collatorSpec) const { + auto collator = std::unique_ptr<CollatorInterface>(nullptr); + if (!collatorSpec.isEmpty()) { + auto factoryCollator = CollatorFactoryInterface::get(_opCtx->getServiceContext()) + ->makeFromBSON(collatorSpec); + ASSERT_OK(factoryCollator.getStatus()); + collator = std::move(factoryCollator.getValue()); + } + + return {db, view, viewOn, pipeline, std::move(collator)}; + } + +private: + QueryTestServiceContext _queryServiceContext; + ServiceContext::UniqueOperationContext _opCtx; + ViewGraph _viewGraph; +}; + +TEST_F(ViewGraphFixture, CanInsertViewsWithMatchingBinaryCollations) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 3UL); +} + +TEST_F(ViewGraphFixture, CanInsertViewsWithMatchingNonTrivialCollations) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kFilipinoCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kFilipinoCollation); + ASSERT_OK(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 3UL); +} + +TEST_F(ViewGraphFixture, CannotInsertViewsWithNonMatchingCollations) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kFilipinoCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kBinaryCollation); + ASSERT_EQ(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize), + ErrorCodes::OptionNotSupportedOnView); + ASSERT_EQ(viewGraph()->size(), 2UL); +} + +TEST_F(ViewGraphFixture, CannotRecreateViewWithDifferentCollationIfDependedOnByOtherViews) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kFilipinoCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kFilipinoCollation); + ASSERT_OK(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 3UL); + + viewGraph()->remove(kBarNamespace); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barViewBinaryCollation = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kBinaryCollation); + ASSERT_EQ( + viewGraph()->insertAndValidate(barViewBinaryCollation, {kQuxNamespace}, kEmptyPipelineSize), + ErrorCodes::OptionNotSupportedOnView); + ASSERT_EQ(viewGraph()->size(), 2UL); +} + +// Tests that an insertion that would create a view cycle is rejected, but will later be accepted if +// the cycle is broken by removing another existing view. +TEST_F(ViewGraphFixture, CanCreateViewThatReferencesDroppedView) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 3UL); + + const auto quxView = + makeViewDefinition(kTestDb, kQuxName, kBarName, kEmptyPipeline, kBinaryCollation); + + // Inserting qux should fail, as it cycles with bar. + ASSERT_EQ(viewGraph()->insertAndValidate(quxView, {kBarNamespace}, kEmptyPipelineSize), + ErrorCodes::GraphContainsCycle); + ASSERT_EQ(viewGraph()->size(), 3UL); + + viewGraph()->remove(kBarNamespace); + ASSERT_EQ(viewGraph()->size(), 2UL); + + // With bar removed, we expect qux to be inserted successfully. + ASSERT_OK(viewGraph()->insertAndValidate(quxView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 3UL); +} + +// Tests that an insertion that would create mismatching collators is rejected, but will later be +// accepted if the existing view with the conflicting collator is removed. +TEST_F(ViewGraphFixture, CanCreateViewWithDifferentCollationThanDroppedView) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kFilipinoCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kBinaryCollation); + + // Inserting bar should fail, as foo depends on bar and has a different collation. + ASSERT_EQ(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize), + ErrorCodes::OptionNotSupportedOnView); + ASSERT_EQ(viewGraph()->size(), 2UL); + + viewGraph()->remove(kFooNamespace); + ASSERT_EQ(viewGraph()->size(), 0UL); + + // Now bar should be inserted successfully, as there are no existing views in the graph that + // depend on it. + ASSERT_OK(viewGraph()->insertAndValidate(barView, {kFooNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); +} + +// Tests that a node in the graph is properly converted from a "view" node to a "non-view" node when +// a view with that namespace is removed. +TEST_F(ViewGraphFixture, DroppingViewPreservesNodeInGraphIfDependedOnByOtherViews) { + const auto fooView = + makeViewDefinition(kTestDb, kFooName, kBarName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(fooView, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 2UL); + + const auto barView = + makeViewDefinition(kTestDb, kBarName, kQuxName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(barView, {kQuxNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 3UL); + + // Inserts baz into the graph so that qux has another namespace that depends on it. This way, + // the node for qux won't be destroyed when baz is removed. + const auto bazView = + makeViewDefinition(kTestDb, "baz"_sd, kQuxName, kEmptyPipeline, kBinaryCollation); + ASSERT_OK(viewGraph()->insertAndValidate(bazView, {kQuxNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 4UL); + + // Inserting a view that depends on bar but has a different collation should fail. + const auto viewWithDifferentCollation = makeViewDefinition( + kTestDb, "badCollation"_sd, kBarName, kEmptyPipeline, kFilipinoCollation); + ASSERT_EQ(viewGraph()->insertAndValidate( + viewWithDifferentCollation, {kBarNamespace}, kEmptyPipelineSize), + ErrorCodes::OptionNotSupportedOnView); + ASSERT_EQ(viewGraph()->size(), 4UL); + + // Removes bar from the graph. The graph's size should remain at 4, since bar is still depended + // on by qux. + viewGraph()->remove(kBarNamespace); + ASSERT_EQ(viewGraph()->size(), 4UL); + + // Inserting the viewWithDifferentCollation from above should now succeed, since bar is no + // longer a view. + ASSERT_OK(viewGraph()->insertAndValidate( + viewWithDifferentCollation, {kBarNamespace}, kEmptyPipelineSize)); + ASSERT_EQ(viewGraph()->size(), 5UL); +} +} // namespace +} // namespace mongo |