summaryrefslogtreecommitdiff
path: root/src/mongo/db/views
diff options
context:
space:
mode:
authorDan Larkin-York <dan.larkin-york@mongodb.com>2022-02-26 13:50:02 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2022-02-26 14:34:46 +0000
commitcd92f1325982f82314e0cbb08ced8d254198a7b2 (patch)
tree5539b750af2e6ed189698edbe994d3f3b64bf194 /src/mongo/db/views
parentfcad5cd7a9267980fefda51b1e4a3db0a12000ec (diff)
downloadmongo-cd92f1325982f82314e0cbb08ced8d254198a7b2.tar.gz
SERVER-57250 Merge ViewCatalog into CollectionCatalog
Diffstat (limited to 'src/mongo/db/views')
-rw-r--r--src/mongo/db/views/SConscript22
-rw-r--r--src/mongo/db/views/durable_view_catalog.cpp52
-rw-r--r--src/mongo/db/views/durable_view_catalog.h8
-rw-r--r--src/mongo/db/views/view_catalog.cpp911
-rw-r--r--src/mongo/db/views/view_catalog.h271
-rw-r--r--src/mongo/db/views/view_catalog_helpers.cpp235
-rw-r--r--src/mongo/db/views/view_catalog_helpers.h69
-rw-r--r--src/mongo/db/views/view_catalog_test.cpp77
8 files changed, 389 insertions, 1256 deletions
diff --git a/src/mongo/db/views/SConscript b/src/mongo/db/views/SConscript
index 5f4388c0d22..62fb715de39 100644
--- a/src/mongo/db/views/SConscript
+++ b/src/mongo/db/views/SConscript
@@ -14,6 +14,7 @@ env.Library(
'$BUILD_DIR/mongo/db/views/views',
],
LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/audit',
'$BUILD_DIR/mongo/db/catalog/database_holder',
'$BUILD_DIR/mongo/db/multitenancy',
],
@@ -23,21 +24,23 @@ env.Library(
target='views',
source=[
'view.cpp',
- 'view_catalog.cpp',
'view_graph.cpp',
],
LIBDEPS=[
- '$BUILD_DIR/mongo/base',
- '$BUILD_DIR/mongo/db/catalog/collection',
- '$BUILD_DIR/mongo/db/pipeline/aggregation',
'$BUILD_DIR/mongo/db/query/collation/collator_factory_interface',
- '$BUILD_DIR/mongo/db/repl/repl_coordinator_interface',
+ ],
+)
+
+env.Library(
+ target='view_catalog_helpers',
+ source=[
+ 'view_catalog_helpers.cpp',
+ ],
+ LIBDEPS=[
+ '$BUILD_DIR/mongo/db/pipeline/aggregation',
'resolved_view',
+ 'views',
],
- LIBDEPS_PRIVATE=[
- '$BUILD_DIR/mongo/db/audit',
- '$BUILD_DIR/mongo/db/multitenancy',
- ]
)
env.Library(
@@ -71,6 +74,7 @@ env.CppUnitTest(
'$BUILD_DIR/mongo/db/repl/replmocks',
'$BUILD_DIR/mongo/s/is_mongos',
'$BUILD_DIR/mongo/unittest/unittest',
+ 'view_catalog_helpers',
'views',
'views_mongod',
],
diff --git a/src/mongo/db/views/durable_view_catalog.cpp b/src/mongo/db/views/durable_view_catalog.cpp
index f756c052d0e..bbc2d1f59ef 100644
--- a/src/mongo/db/views/durable_view_catalog.cpp
+++ b/src/mongo/db/views/durable_view_catalog.cpp
@@ -35,7 +35,9 @@
#include <string>
+#include "mongo/db/audit.h"
#include "mongo/db/catalog/collection.h"
+#include "mongo/db/catalog/collection_catalog.h"
#include "mongo/db/catalog/database.h"
#include "mongo/db/catalog/database_holder.h"
#include "mongo/db/concurrency/d_concurrency.h"
@@ -45,7 +47,6 @@
#include "mongo/db/operation_context.h"
#include "mongo/db/storage/record_data.h"
#include "mongo/db/tenant_database_name.h"
-#include "mongo/db/views/view_catalog.h"
#include "mongo/logv2/log.h"
#include "mongo/stdx/unordered_set.h"
#include "mongo/util/assert_util.h"
@@ -61,15 +62,10 @@ void DurableViewCatalog::onExternalChange(OperationContext* opCtx, const Namespa
NamespaceString(name.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X));
// On an external change, an invalid view definition can be detected when the view catalog
- // is reloaded. This will prevent any further usage of the view catalog until the invalid
- // view definitions are removed. We use kValidateDurableViews here to catch any invalid view
- // definitions in the view catalog to make it unusable for subsequent callers.
- if (ViewCatalog::shouldIgnoreExternalChange(opCtx, name)) {
- return;
- }
-
- ViewCatalog::reload(opCtx, name.db(), ViewCatalogLookupBehavior::kValidateDurableViews)
- .ignore();
+ // is reloaded. This will prevent any further usage of the views for this database until the
+ // invalid view definitions are removed.
+ auto catalog = CollectionCatalog::get(opCtx);
+ catalog->reloadViews(opCtx, name.db()).ignore();
}
void DurableViewCatalog::onSystemViewsCollectionDrop(OperationContext* opCtx,
@@ -79,14 +75,24 @@ void DurableViewCatalog::onSystemViewsCollectionDrop(OperationContext* opCtx,
NamespaceString(name.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X));
dassert(name.coll() == NamespaceString::kSystemDotViewsCollectionName);
- const TenantDatabaseName tenantDbName(boost::none, name.db());
- auto databaseHolder = DatabaseHolder::get(opCtx);
- auto db = databaseHolder->getDb(opCtx, tenantDbName);
- if (db) {
- // If the 'system.views' collection is dropped, we need to clear the in-memory state of the
- // view catalog.
- ViewCatalog::clear(opCtx, name.db());
- }
+ auto catalog = CollectionCatalog::get(opCtx);
+
+ // First, iterate through the views on this database and audit them before they are dropped.
+ catalog->iterateViews(opCtx,
+ name.db(),
+ [&](const ViewDefinition& view) -> bool {
+ audit::logDropView(opCtx->getClient(),
+ view.name(),
+ view.viewOn().ns(),
+ view.pipeline(),
+ ErrorCodes::OK);
+ return true;
+ },
+ ViewCatalogLookupBehavior::kAllowInvalidViews);
+
+ // If the 'system.views' collection is dropped, we need to clear the in-memory state of the
+ // view catalog.
+ catalog->clearViews(opCtx, name.db());
}
// DurableViewCatalogImpl
@@ -95,17 +101,13 @@ const std::string& DurableViewCatalogImpl::getName() const {
return _db->name().dbName();
}
-bool DurableViewCatalogImpl::belongsTo(const Database* db) const {
- return _db == db;
-}
-
void DurableViewCatalogImpl::iterate(OperationContext* opCtx, Callback callback) {
- _iterate(opCtx, callback, ViewCatalogLookupBehavior::kValidateDurableViews);
+ _iterate(opCtx, callback, ViewCatalogLookupBehavior::kValidateViews);
}
void DurableViewCatalogImpl::iterateIgnoreInvalidEntries(OperationContext* opCtx,
Callback callback) {
- _iterate(opCtx, callback, ViewCatalogLookupBehavior::kAllowInvalidDurableViews);
+ _iterate(opCtx, callback, ViewCatalogLookupBehavior::kAllowInvalidViews);
}
void DurableViewCatalogImpl::_iterate(OperationContext* opCtx,
@@ -126,7 +128,7 @@ void DurableViewCatalogImpl::_iterate(OperationContext* opCtx,
viewDefinition = _validateViewDefinition(opCtx, record->data);
uassertStatusOK(callback(viewDefinition));
} catch (const ExceptionFor<ErrorCodes::InvalidViewDefinition>& ex) {
- if (lookupBehavior == ViewCatalogLookupBehavior::kValidateDurableViews) {
+ if (lookupBehavior == ViewCatalogLookupBehavior::kValidateViews) {
throw ex;
}
}
diff --git a/src/mongo/db/views/durable_view_catalog.h b/src/mongo/db/views/durable_view_catalog.h
index bf27160e3f1..0546636225d 100644
--- a/src/mongo/db/views/durable_view_catalog.h
+++ b/src/mongo/db/views/durable_view_catalog.h
@@ -44,11 +44,11 @@ class OperationContext;
class RecordData;
/**
- * ViewCatalogLookupBehavior specifies whether a lookup into the view catalog should attempt to
- * validate the durable entries that currently exist within the catalog. This validation should
+ * ViewCatalogLookupBehavior specifies whether a lookup into the view catalog should validate the
+ * entries or allow the operation to proceed with an invalid entry present. This validation should
* rarely be skipped.
*/
-enum class ViewCatalogLookupBehavior { kValidateDurableViews, kAllowInvalidDurableViews };
+enum class ViewCatalogLookupBehavior { kValidateViews, kAllowInvalidViews };
/**
* Interface for system.views collection operations associated with view catalog management.
@@ -81,7 +81,6 @@ public:
const BSONObj& view) = 0;
virtual void remove(OperationContext* opCtx, const NamespaceString& name) = 0;
virtual const std::string& getName() const = 0;
- virtual bool belongsTo(const Database* db) const = 0;
virtual ~DurableViewCatalog() = default;
};
@@ -100,7 +99,6 @@ public:
void upsert(OperationContext* opCtx, const NamespaceString& name, const BSONObj& view);
void remove(OperationContext* opCtx, const NamespaceString& name);
const std::string& getName() const;
- bool belongsTo(const Database* db) const;
private:
void _iterate(OperationContext* opCtx,
diff --git a/src/mongo/db/views/view_catalog.cpp b/src/mongo/db/views/view_catalog.cpp
deleted file mode 100644
index e33a250d4d6..00000000000
--- a/src/mongo/db/views/view_catalog.cpp
+++ /dev/null
@@ -1,911 +0,0 @@
-/**
- * Copyright (C) 2018-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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.
- */
-
-#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault
-
-#include "mongo/platform/basic.h"
-
-#include "mongo/db/views/view_catalog.h"
-
-#include <memory>
-#include <string>
-
-#include "mongo/base/status_with.h"
-#include "mongo/base/string_data.h"
-#include "mongo/bson/util/builder.h"
-#include "mongo/db/api_parameters.h"
-#include "mongo/db/audit.h"
-#include "mongo/db/catalog/collection_catalog.h"
-#include "mongo/db/catalog/database.h"
-#include "mongo/db/curop.h"
-#include "mongo/db/namespace_string.h"
-#include "mongo/db/operation_context.h"
-#include "mongo/db/pipeline/aggregate_command_gen.h"
-#include "mongo/db/pipeline/document_source.h"
-#include "mongo/db/pipeline/expression_context.h"
-#include "mongo/db/pipeline/lite_parsed_pipeline.h"
-#include "mongo/db/pipeline/pipeline.h"
-#include "mongo/db/pipeline/process_interface/stub_mongo_process_interface.h"
-#include "mongo/db/query/collation/collator_factory_interface.h"
-#include "mongo/db/storage/recovery_unit.h"
-#include "mongo/db/views/resolved_view.h"
-#include "mongo/db/views/view.h"
-#include "mongo/db/views/view_graph.h"
-#include "mongo/logv2/log.h"
-#include "mongo/util/fail_point.h"
-
-namespace mongo {
-
-namespace {
-/**
- * Helper class to manage copy-on-write for the ViewCatalog.
- */
-class ViewCatalogWriter {
-public:
- ViewCatalogWriter(Mutex& mutex,
- std::shared_ptr<const ViewCatalog> instance,
- std::shared_ptr<ViewCatalog>* storage)
- : _mutex(mutex), _read(std::move(instance)), _storage(storage) {}
-
- ViewCatalogWriter(ViewCatalogWriter&&) = delete;
- ViewCatalogWriter& operator=(ViewCatalogWriter&&) = delete;
-
- const ViewCatalog* operator->() const {
- if (_write)
- return _write.get();
-
- return _read.get();
- }
-
- ViewCatalog* writable() {
- if (!_write) {
- _lock = stdx::unique_lock<Mutex>(_mutex);
- // TODO (SERVER-57250): This atomic_load will be deprecated in C++20
- // We must copy from `_storage` here under the lock so we include any changes that may
- // have happened since we copied '_read'.
- _write = std::make_shared<ViewCatalog>(*atomic_load(_storage));
- _read.reset();
- }
- return _write.get();
- }
-
- void commit() {
- if (_write) {
- atomic_store(_storage, _write);
- // Set _read and clear _write so we can use this instance in read mode after the commit.
- _read = std::move(_write);
- _lock.unlock();
- }
- }
-
-private:
- Mutex& _mutex;
- stdx::unique_lock<Mutex> _lock;
- std::shared_ptr<const ViewCatalog> _read;
- std::shared_ptr<ViewCatalog> _write;
- std::shared_ptr<ViewCatalog>* _storage;
-};
-
-/**
- * Decoration on the ServiceContext for storing the latest ViewCatalog instance and its associated
- * write mutex.
- */
-class ViewCatalogStorage {
-public:
- std::shared_ptr<const ViewCatalog> get() const {
- return atomic_load(&_catalog);
- }
-
- void set(std::shared_ptr<ViewCatalog> instance) {
- atomic_store(&_catalog, std::move(instance));
- }
-
- ViewCatalogWriter writer() {
- return ViewCatalogWriter(_mutex, get(), &_catalog);
- }
-
- void setIgnoreExternalChange(StringData dbName, bool value) {
- stdx::lock_guard lk{_externalChangeMutex};
- if (value) {
- _ignoreExternalChange.emplace(dbName);
- } else {
- _ignoreExternalChange.erase(dbName);
- }
- }
-
- bool shouldIgnoreExternalChange(StringData dbName) const {
- stdx::lock_guard lk{_externalChangeMutex};
- auto it = _ignoreExternalChange.find(dbName);
- return it != _ignoreExternalChange.end();
- }
-
-private:
- std::shared_ptr<ViewCatalog> _catalog = std::make_shared<ViewCatalog>();
- mutable Mutex _mutex = MONGO_MAKE_LATCH("ViewCatalogStorage::_mutex"); // Serializes writes
- mutable Mutex _externalChangeMutex = MONGO_MAKE_LATCH(
- "ViewCatalogStorage::_externalChangeMutex"); // Guards _ignoreExternalChange set
- StringSet _ignoreExternalChange;
-}; // namespace
-const auto getViewCatalog = ServiceContext::declareDecoration<ViewCatalogStorage>();
-
-StatusWith<std::unique_ptr<CollatorInterface>> parseCollator(OperationContext* opCtx,
- BSONObj collationSpec) {
- // If 'collationSpec' is empty, return the null collator, which represents the "simple"
- // collation.
- if (collationSpec.isEmpty()) {
- return {nullptr};
- }
- return CollatorFactoryInterface::get(opCtx->getServiceContext())->makeFromBSON(collationSpec);
-}
-} // namespace
-
-std::shared_ptr<const ViewCatalog> ViewCatalog::get(ServiceContext* svcCtx) {
- return getViewCatalog(svcCtx).get();
-}
-
-std::shared_ptr<const ViewCatalog> ViewCatalog::get(OperationContext* opCtx) {
- return get(opCtx->getServiceContext());
-}
-
-Status ViewCatalog::registerDatabase(OperationContext* opCtx,
- StringData dbName,
- std::unique_ptr<DurableViewCatalog> durable) {
- auto catalog = getViewCatalog(opCtx->getServiceContext()).writer();
- auto it = catalog.writable()->_viewsForDatabase.find(dbName);
- if (it != catalog.writable()->_viewsForDatabase.end()) {
- return {ErrorCodes::AlreadyInitialized, "ViewCatalog entry for database already set"};
- }
-
- auto& vfdb = catalog.writable()->_viewsForDatabase[dbName];
- vfdb.durable = std::move(durable);
- vfdb.valid = false;
- vfdb.viewGraphNeedsRefresh = true;
- catalog.commit();
- return Status::OK();
-}
-
-void ViewCatalog::unregisterDatabase(OperationContext* opCtx, Database* db) {
- auto catalog = getViewCatalog(opCtx->getServiceContext()).writer();
- auto it = catalog.writable()->_viewsForDatabase.find(db->name().dbName());
- if (it != catalog.writable()->_viewsForDatabase.end() && it->second.durable->belongsTo(db)) {
- catalog.writable()->_viewsForDatabase.erase(it);
- catalog.commit();
- }
-}
-
-Status ViewCatalog::reload(OperationContext* opCtx,
- StringData dbName,
- ViewCatalogLookupBehavior lookupBehavior) {
- auto catalog = getViewCatalog(opCtx->getServiceContext()).writer();
- invariant(opCtx->lockState()->isCollectionLockedForMode(
- NamespaceString(dbName, NamespaceString::kSystemDotViewsCollectionName), MODE_IS));
- auto result = catalog.writable()->_reload(
- opCtx, dbName, ViewCatalogLookupBehavior::kValidateDurableViews, true);
- catalog.commit();
- return result;
-}
-
-Status ViewCatalog::_reload(OperationContext* opCtx,
- StringData dbName,
- ViewCatalogLookupBehavior lookupBehavior,
- bool reloadForCollectionCatalog) {
- LOGV2_DEBUG(22546, 1, "Reloading view catalog for database", "db"_attr = dbName);
-
- auto it = _viewsForDatabase.find(dbName);
- invariant(it != _viewsForDatabase.end());
- auto& vfdb = it->second;
-
- vfdb.viewMap.clear();
- vfdb.valid = false;
- vfdb.viewGraphNeedsRefresh = true;
- vfdb.stats = {};
-
- absl::flat_hash_set<NamespaceString> viewNamesForDb;
-
- auto reloadCallback = [&](const BSONObj& view) -> Status {
- BSONObj collationSpec = view.hasField("collation") ? view["collation"].Obj() : BSONObj();
- auto collator = parseCollator(opCtx, collationSpec);
- if (!collator.isOK()) {
- return collator.getStatus();
- }
-
- NamespaceString viewName(view["_id"].str());
-
- auto pipeline = view["pipeline"].Obj();
- for (auto&& stage : pipeline) {
- if (BSONType::Object != stage.type()) {
- return Status(ErrorCodes::InvalidViewDefinition,
- str::stream() << "View 'pipeline' entries must be objects, but "
- << viewName.toString()
- << " has a pipeline element of type " << stage.type());
- }
- }
-
- auto viewDef = std::make_shared<ViewDefinition>(viewName.db(),
- viewName.coll(),
- view["viewOn"].str(),
- pipeline,
- std::move(collator.getValue()));
-
- if (!viewName.isOnInternalDb() && !viewName.isSystem()) {
- if (viewDef->timeseries()) {
- vfdb.stats.userTimeseries += 1;
- } else {
- vfdb.stats.userViews += 1;
- }
- } else {
- vfdb.stats.internal += 1;
- }
-
- vfdb.viewMap[viewName.ns()] = std::move(viewDef);
- if (reloadForCollectionCatalog) {
- viewNamesForDb.insert(viewName);
- }
- return Status::OK();
- };
-
- try {
- if (lookupBehavior == ViewCatalogLookupBehavior::kValidateDurableViews) {
- vfdb.durable->iterate(opCtx, reloadCallback);
- } else if (lookupBehavior == ViewCatalogLookupBehavior::kAllowInvalidDurableViews) {
- vfdb.durable->iterateIgnoreInvalidEntries(opCtx, reloadCallback);
- } else {
- MONGO_UNREACHABLE;
- }
- if (reloadForCollectionCatalog) {
- CollectionCatalog::write(
- opCtx,
- [&dbName, viewsForDb = std::move(viewNamesForDb)](CollectionCatalog& catalog) {
- // TODO SERVER-63206: Use instead TenantDatabaseName passed in by caller.
- catalog.replaceViewsForDatabase(TenantDatabaseName(boost::none, dbName),
- std::move(viewsForDb));
- });
- }
- } catch (const DBException& ex) {
- auto status = ex.toStatus();
- LOGV2(22547,
- "Could not load view catalog for database",
- "db"_attr = vfdb.durable->getName(),
- "error"_attr = status);
- return status;
- }
-
- vfdb.valid = true;
- return Status::OK();
-}
-
-void ViewCatalog::clear(OperationContext* opCtx, StringData dbName) {
- auto catalog = getViewCatalog(opCtx->getServiceContext()).writer();
- auto it = catalog.writable()->_viewsForDatabase.find(dbName);
- invariant(it != catalog.writable()->_viewsForDatabase.end());
- auto& vfdb = it->second;
-
- // First, iterate through the views on this database and audit them before they are dropped.
- for (auto&& view : vfdb.viewMap) {
- audit::logDropView(opCtx->getClient(),
- (*view.second).name(),
- (*view.second).viewOn().ns(),
- (*view.second).pipeline(),
- ErrorCodes::OK);
- }
-
- vfdb.viewMap.clear();
- vfdb.viewGraph.clear();
- vfdb.valid = true;
- vfdb.viewGraphNeedsRefresh = false;
- vfdb.stats = {};
- // TODO SERVER-63206: Use instead TenantDatabaseName passed in by caller.
- CollectionCatalog::write(opCtx, [db = dbName.toString()](CollectionCatalog& catalog) {
- catalog.replaceViewsForDatabase(TenantDatabaseName(boost::none, db), {});
- });
- catalog.commit();
-}
-
-bool ViewCatalog::shouldIgnoreExternalChange(OperationContext* opCtx, const NamespaceString& name) {
- return getViewCatalog(opCtx->getServiceContext()).shouldIgnoreExternalChange(name.db());
-}
-
-void ViewCatalog::ViewsForDatabase::requireValidCatalog() const {
- uassert(ErrorCodes::InvalidViewDefinition,
- "Invalid view definition detected in the view catalog. Remove the invalid view "
- "manually to prevent disallowing any further usage of the view catalog.",
- valid);
-}
-
-void ViewCatalog::iterate(StringData dbName, ViewIteratorCallback callback) const {
- auto it = _viewsForDatabase.find(dbName);
- if (it == _viewsForDatabase.end()) {
- return;
- }
- auto& vfdb = it->second;
-
- vfdb.requireValidCatalog();
- for (auto&& view : vfdb.viewMap) {
- if (!callback(*view.second)) {
- break;
- }
- }
-}
-
-Status ViewCatalog::_createOrUpdateView(OperationContext* opCtx,
- const NamespaceString& viewName,
- const NamespaceString& viewOn,
- const BSONArray& pipeline,
- std::unique_ptr<CollatorInterface> collator) {
- invariant(opCtx->lockState()->isDbLockedForMode(viewName.db(), MODE_IX));
- invariant(opCtx->lockState()->isCollectionLockedForMode(viewName, MODE_IX));
- invariant(opCtx->lockState()->isCollectionLockedForMode(
- NamespaceString(viewName.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X));
-
- auto it = _viewsForDatabase.find(viewName.db());
- invariant(it != _viewsForDatabase.end());
- auto& vfdb = it->second;
- vfdb.requireValidCatalog();
-
- // Build the BSON definition for this view to be saved in the durable view catalog. If the
- // collation is empty, omit it from the definition altogether.
- BSONObjBuilder viewDefBuilder;
- viewDefBuilder.append("_id", viewName.ns());
- viewDefBuilder.append("viewOn", viewOn.coll());
- viewDefBuilder.append("pipeline", pipeline);
- if (collator) {
- viewDefBuilder.append("collation", collator->getSpec().toBSON());
- }
-
- BSONObj ownedPipeline = pipeline.getOwned();
- auto view = std::make_shared<ViewDefinition>(
- viewName.db(), viewName.coll(), viewOn.coll(), ownedPipeline, std::move(collator));
-
- // Check that the resulting dependency graph is acyclic and within the maximum depth.
- Status graphStatus = _upsertIntoGraph(opCtx, *(view.get()));
- if (!graphStatus.isOK()) {
- return graphStatus;
- }
-
- vfdb.durable->upsert(opCtx, viewName, viewDefBuilder.obj());
- vfdb.viewMap[viewName.ns()] = view;
-
- // Reload the view catalog with the changes applied.
- auto res =
- _reload(opCtx, viewName.db(), ViewCatalogLookupBehavior::kValidateDurableViews, false);
- if (res.isOK()) {
- // Register the view in the CollectionCatalog mapping from ResourceID->namespace
- auto viewRid = ResourceId(RESOURCE_COLLECTION, viewName.ns());
-
- CollectionCatalog::write(opCtx, [&](CollectionCatalog& catalog) {
- catalog.registerView(viewName);
- catalog.addResource(viewRid, viewName.ns());
- });
-
- opCtx->recoveryUnit()->onRollback([viewName, opCtx, viewRid]() {
- CollectionCatalog::write(opCtx, [&](CollectionCatalog& catalog) {
- catalog.removeResource(viewRid, viewName.ns());
- catalog.deregisterView(viewName);
- });
- });
- }
- return res;
-}
-
-Status ViewCatalog::_upsertIntoGraph(OperationContext* opCtx, const ViewDefinition& viewDef) {
- auto it = _viewsForDatabase.find(viewDef.name().db());
- invariant(it != _viewsForDatabase.end());
- auto& vfdb = it->second;
-
- // Performs the insert into the graph.
- auto doInsert = [this, opCtx, &vfdb](const ViewDefinition& viewDef,
- bool needsValidation) -> Status {
- // Validate that the pipeline is eligible to serve as a view definition. If it is, this
- // will also return the set of involved namespaces.
- auto pipelineStatus = validatePipeline(opCtx, viewDef);
- if (!pipelineStatus.isOK()) {
- if (needsValidation) {
- uassertStatusOKWithContext(pipelineStatus.getStatus(),
- str::stream() << "Invalid pipeline for view "
- << viewDef.name().ns());
- }
- return pipelineStatus.getStatus();
- }
-
- auto involvedNamespaces = pipelineStatus.getValue();
- std::vector<NamespaceString> refs(involvedNamespaces.begin(), involvedNamespaces.end());
- refs.push_back(viewDef.viewOn());
-
- int pipelineSize = 0;
- for (auto obj : viewDef.pipeline()) {
- pipelineSize += obj.objsize();
- }
-
- if (needsValidation) {
- // Check the collation of all the dependent namespaces before updating the graph.
- auto collationStatus = _validateCollation(opCtx, viewDef, refs);
- if (!collationStatus.isOK()) {
- return collationStatus;
- }
- return vfdb.viewGraph.insertAndValidate(viewDef, refs, pipelineSize);
- } else {
- vfdb.viewGraph.insertWithoutValidating(viewDef, refs, pipelineSize);
- return Status::OK();
- }
- };
-
- if (vfdb.viewGraphNeedsRefresh) {
- vfdb.viewGraph.clear();
- for (auto&& iter : vfdb.viewMap) {
- auto status = doInsert(*(iter.second.get()), false);
- // If we cannot fully refresh the graph, we will keep '_viewGraphNeedsRefresh' true.
- if (!status.isOK()) {
- return status;
- }
- }
- // Only if the inserts completed without error will we no longer need a refresh.
- vfdb.viewGraphNeedsRefresh = false;
- }
-
- // Remove the view definition first in case this is an update. If it is not in the graph, it
- // is simply a no-op.
- vfdb.viewGraph.remove(viewDef.name());
-
- return doInsert(viewDef, true);
-}
-
-StatusWith<stdx::unordered_set<NamespaceString>> ViewCatalog::validatePipeline(
- OperationContext* opCtx, const ViewDefinition& viewDef) {
- const LiteParsedPipeline liteParsedPipeline(viewDef.viewOn(), viewDef.pipeline());
- const auto involvedNamespaces = liteParsedPipeline.getInvolvedNamespaces();
-
- // The API version pipeline validation should be skipped for time-series view because of
- // following reasons:
- // - the view pipeline is not created by (or visible to) the end-user and should be skipped.
- // - the view pipeline can have stages that are not allowed in stable API version '1' eg.
- // '$_internalUnpackBucket'.
- bool performApiVersionChecks = !viewDef.timeseries();
-
- liteParsedPipeline.validate(opCtx, performApiVersionChecks);
-
- // Verify that this is a legitimate pipeline specification by making sure it parses
- // correctly. In order to parse a pipeline we need to resolve any namespaces involved to a
- // collection and a pipeline, but in this case we don't need this map to be accurate since
- // we will not be evaluating the pipeline.
- StringMap<ExpressionContext::ResolvedNamespace> resolvedNamespaces;
- for (auto&& nss : involvedNamespaces) {
- resolvedNamespaces[nss.coll()] = {nss, {}};
- }
- boost::intrusive_ptr<ExpressionContext> expCtx =
- new ExpressionContext(opCtx,
- AggregateCommandRequest(viewDef.viewOn(), viewDef.pipeline()),
- CollatorInterface::cloneCollator(viewDef.defaultCollator()),
- // We can use a stub MongoProcessInterface because we are only parsing
- // the Pipeline for validation here. We won't do anything with the
- // pipeline that will require a real implementation.
- std::make_shared<StubMongoProcessInterface>(),
- std::move(resolvedNamespaces),
- boost::none);
-
- // If the feature compatibility version is not kLatest, and we are validating features as
- // primary, ban the use of new agg features introduced in kLatest to prevent them from being
- // persisted in the catalog.
- // (Generic FCV reference): This FCV check should exist across LTS binary versions.
- multiversion::FeatureCompatibilityVersion fcv;
- if (serverGlobalParams.validateFeaturesAsPrimary.load() &&
- serverGlobalParams.featureCompatibility.isLessThan(multiversion::GenericFCV::kLatest,
- &fcv)) {
- expCtx->maxFeatureCompatibilityVersion = fcv;
- }
-
- // The pipeline parser needs to know that we're parsing a pipeline for a view definition
- // to apply some additional checks.
- expCtx->isParsingViewDefinition = true;
-
- try {
- auto pipeline =
- Pipeline::parse(viewDef.pipeline(), std::move(expCtx), [&](const Pipeline& pipeline) {
- // Validate that the view pipeline does not contain any ineligible stages.
- const auto& sources = pipeline.getSources();
- const auto firstPersistentStage =
- std::find_if(sources.begin(), sources.end(), [](const auto& source) {
- return source->constraints().writesPersistentData();
- });
-
- uassert(ErrorCodes::OptionNotSupportedOnView,
- str::stream()
- << "The aggregation stage "
- << firstPersistentStage->get()->getSourceName() << " in location "
- << std::distance(sources.begin(), firstPersistentStage)
- << " of the pipeline cannot be used in the view definition of "
- << viewDef.name().ns() << " because it writes to disk",
- firstPersistentStage == sources.end());
-
- uassert(ErrorCodes::OptionNotSupportedOnView,
- "$changeStream cannot be used in a view definition",
- sources.empty() || !sources.front()->constraints().isChangeStreamStage());
-
- std::for_each(sources.begin(), sources.end(), [](auto& stage) {
- uassert(ErrorCodes::InvalidNamespace,
- str::stream() << "'" << stage->getSourceName()
- << "' cannot be used in a view definition",
- !stage->constraints().isIndependentOfAnyCollection);
- });
- });
- } catch (const DBException& ex) {
- return ex.toStatus();
- }
-
- return std::move(involvedNamespaces);
-}
-
-Status ViewCatalog::_validateCollation(OperationContext* opCtx,
- const ViewDefinition& view,
- const std::vector<NamespaceString>& refs) const {
- for (auto&& potentialViewNss : refs) {
- auto otherView =
- _lookup(opCtx, potentialViewNss, ViewCatalogLookupBehavior::kValidateDurableViews);
- if (otherView &&
- !CollatorInterface::collatorsMatch(view.defaultCollator(),
- otherView->defaultCollator())) {
- return {ErrorCodes::OptionNotSupportedOnView,
- str::stream() << "View " << view.name().toString()
- << " has conflicting collation with view "
- << otherView->name().toString()};
- }
- }
- return Status::OK();
-}
-
-Status ViewCatalog::createView(OperationContext* opCtx,
- const NamespaceString& viewName,
- const NamespaceString& viewOn,
- const BSONArray& pipeline,
- const BSONObj& collation) {
- invariant(opCtx->lockState()->isDbLockedForMode(viewName.db(), MODE_IX));
- invariant(opCtx->lockState()->isCollectionLockedForMode(viewName, MODE_IX));
- invariant(opCtx->lockState()->isCollectionLockedForMode(
- NamespaceString(viewName.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X));
-
- auto& catalogStorage = getViewCatalog(opCtx->getServiceContext());
- auto catalog = catalogStorage.writer();
-
- if (viewName.db() != viewOn.db())
- return Status(ErrorCodes::BadValue,
- "View must be created on a view or collection in the same database");
-
- if (catalog->_lookup(opCtx, viewName, ViewCatalogLookupBehavior::kValidateDurableViews))
- return Status(ErrorCodes::NamespaceExists, "Namespace already exists");
-
- if (!NamespaceString::validCollectionName(viewOn.coll()))
- return Status(ErrorCodes::InvalidNamespace,
- str::stream() << "invalid name for 'viewOn': " << viewOn.coll());
-
- auto collator = parseCollator(opCtx, collation);
- if (!collator.isOK())
- return collator.getStatus();
-
- Status result = Status::OK();
- {
- ON_BLOCK_EXIT([&catalogStorage, &viewName] {
- catalogStorage.setIgnoreExternalChange(viewName.db(), false);
- });
- catalogStorage.setIgnoreExternalChange(viewName.db(), true);
-
- result = catalog.writable()->_createOrUpdateView(
- opCtx, viewName, viewOn, pipeline, std::move(collator.getValue()));
- }
- if (result.isOK()) {
- catalog.commit();
- }
- return result;
-}
-
-Status ViewCatalog::modifyView(OperationContext* opCtx,
- const NamespaceString& viewName,
- const NamespaceString& viewOn,
- const BSONArray& pipeline) {
- invariant(opCtx->lockState()->isCollectionLockedForMode(viewName, MODE_X));
- invariant(opCtx->lockState()->isCollectionLockedForMode(
- NamespaceString(viewName.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X));
-
- auto& catalogStorage = getViewCatalog(opCtx->getServiceContext());
- auto catalog = catalogStorage.writer();
-
- if (viewName.db() != viewOn.db())
- return Status(ErrorCodes::BadValue,
- "View must be created on a view or collection in the same database");
-
- auto viewPtr =
- catalog->_lookup(opCtx, viewName, ViewCatalogLookupBehavior::kValidateDurableViews);
- if (!viewPtr)
- return Status(ErrorCodes::NamespaceNotFound,
- str::stream() << "cannot modify missing view " << viewName.ns());
-
- if (!NamespaceString::validCollectionName(viewOn.coll()))
- return Status(ErrorCodes::InvalidNamespace,
- str::stream() << "invalid name for 'viewOn': " << viewOn.coll());
-
- opCtx->recoveryUnit()->onRollback([viewName, opCtx]() {
- auto viewRid = ResourceId(RESOURCE_COLLECTION, viewName.ns());
-
- CollectionCatalog::write(opCtx, [&](CollectionCatalog& catalog) {
- catalog.addResource(viewRid, viewName.ns());
- });
- });
-
- Status result = Status::OK();
- {
- ON_BLOCK_EXIT([&catalogStorage, &viewName] {
- catalogStorage.setIgnoreExternalChange(viewName.db(), false);
- });
- catalogStorage.setIgnoreExternalChange(viewName.db(), true);
-
- result = catalog.writable()->_createOrUpdateView(
- opCtx,
- viewName,
- viewOn,
- pipeline,
- CollatorInterface::cloneCollator(viewPtr->defaultCollator()));
- }
-
- if (result.isOK()) {
- catalog.commit();
- }
-
- return result;
-}
-
-Status ViewCatalog::dropView(OperationContext* opCtx, const NamespaceString& viewName) {
- invariant(opCtx->lockState()->isDbLockedForMode(viewName.db(), MODE_IX));
- invariant(opCtx->lockState()->isCollectionLockedForMode(viewName, MODE_IX));
- invariant(opCtx->lockState()->isCollectionLockedForMode(
- NamespaceString(viewName.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X));
-
- auto& catalogStorage = getViewCatalog(opCtx->getServiceContext());
- auto catalog = catalogStorage.writer();
-
- auto it = catalog.writable()->_viewsForDatabase.find(viewName.db());
- invariant(it != catalog.writable()->_viewsForDatabase.end());
- auto& vfdb = it->second;
- vfdb.requireValidCatalog();
-
- Status result = Status::OK();
-
- {
- ON_BLOCK_EXIT([&catalogStorage, &viewName] {
- catalogStorage.setIgnoreExternalChange(viewName.db(), false);
- });
-
- catalogStorage.setIgnoreExternalChange(viewName.db(), true);
-
- // Save a copy of the view definition in case we need to roll back.
- auto viewPtr =
- catalog->_lookup(opCtx, viewName, ViewCatalogLookupBehavior::kValidateDurableViews);
- if (!viewPtr) {
- return {ErrorCodes::NamespaceNotFound,
- str::stream() << "cannot drop missing view: " << viewName.ns()};
- }
-
- invariant(vfdb.valid);
- vfdb.durable->remove(opCtx, viewName);
- vfdb.viewGraph.remove(viewPtr->name());
- vfdb.viewMap.erase(viewName.ns());
-
- auto viewRid = ResourceId(RESOURCE_COLLECTION, viewName.ns());
- CollectionCatalog::write(opCtx, [&](CollectionCatalog& catalog) {
- catalog.removeResource(viewRid, viewName.ns());
- });
-
- opCtx->recoveryUnit()->onCommit([viewName, opCtx](auto ts) {
- CollectionCatalog::write(
- opCtx, [&](CollectionCatalog& catalog) { catalog.deregisterView(viewName); });
- });
-
- opCtx->recoveryUnit()->onRollback([viewName, opCtx, viewRid]() {
- CollectionCatalog::write(opCtx, [&](CollectionCatalog& catalog) {
- catalog.addResource(viewRid, viewName.ns());
- });
- });
-
- // Reload the view catalog with the changes applied.
- result = catalog.writable()->_reload(
- opCtx, viewName.db(), ViewCatalogLookupBehavior::kValidateDurableViews, false);
- }
- catalog.commit();
- return result;
-}
-
-std::shared_ptr<const ViewDefinition> ViewCatalog::_lookup(
- OperationContext* opCtx,
- const NamespaceString& ns,
- ViewCatalogLookupBehavior lookupBehavior) const {
- auto it = _viewsForDatabase.find(ns.db());
- if (it == _viewsForDatabase.end()) {
- return nullptr;
- }
- auto& vfdb = it->second;
-
- ViewMap::const_iterator vmit = vfdb.viewMap.find(ns.ns());
- if (vmit != vfdb.viewMap.end()) {
- return vmit->second;
- }
- return nullptr;
-}
-
-std::shared_ptr<ViewDefinition> ViewCatalog::_lookup(OperationContext* opCtx,
- const NamespaceString& ns,
- ViewCatalogLookupBehavior lookupBehavior) {
- return std::const_pointer_cast<ViewDefinition>(
- std::as_const(*this)._lookup(opCtx, ns, lookupBehavior));
-}
-
-std::shared_ptr<const ViewDefinition> ViewCatalog::lookup(OperationContext* opCtx,
- const NamespaceString& ns) const {
- auto it = _viewsForDatabase.find(ns.db());
- if (it == _viewsForDatabase.end()) {
- return nullptr;
- }
- auto& vfdb = it->second;
-
- if (!vfdb.valid && opCtx->getClient()->isFromUserConnection()) {
- // We want to avoid lookups on invalid collection names.
- if (!NamespaceString::validCollectionName(ns.ns())) {
- return nullptr;
- }
-
- // ApplyOps should work on a valid existing collection, despite the presence of bad views
- // otherwise the server would crash. The view catalog will remain invalid until the bad view
- // definitions are removed.
- vfdb.requireValidCatalog();
- }
-
- return _lookup(opCtx, ns, ViewCatalogLookupBehavior::kValidateDurableViews);
-}
-
-std::shared_ptr<const ViewDefinition> ViewCatalog::lookupWithoutValidatingDurableViews(
- OperationContext* opCtx, const NamespaceString& ns) const {
- return _lookup(opCtx, ns, ViewCatalogLookupBehavior::kAllowInvalidDurableViews);
-}
-
-StatusWith<ResolvedView> ViewCatalog::resolveView(
- OperationContext* opCtx,
- const NamespaceString& nss,
- boost::optional<BSONObj> timeSeriesCollator) const {
- auto it = _viewsForDatabase.find(nss.db());
- uassert(ErrorCodes::NamespaceNotFound,
- str::stream() << "View " << nss << " not found",
- it != _viewsForDatabase.end());
- auto& vfdb = it->second;
- vfdb.requireValidCatalog();
-
- // Points to the name of the most resolved namespace.
- const NamespaceString* resolvedNss = &nss;
-
- // Holds the combination of all the resolved views.
- std::vector<BSONObj> resolvedPipeline;
-
- // If the catalog has not been tampered with, all views seen during the resolution will have
- // the same collation. As an optimization, we fill out the collation spec only once.
- boost::optional<BSONObj> collation;
-
- // The last seen view definition, which owns the NamespaceString pointed to by
- // 'resolvedNss'.
- std::shared_ptr<ViewDefinition> lastViewDefinition;
-
- std::vector<NamespaceString> dependencyChain{nss};
-
- int depth = 0;
- boost::optional<bool> mixedData = boost::none;
- boost::optional<TimeseriesOptions> tsOptions = boost::none;
- for (; depth < ViewGraph::kMaxViewDepth; depth++) {
- auto view = _lookup(opCtx, *resolvedNss, ViewCatalogLookupBehavior::kValidateDurableViews);
- if (!view) {
- // Return error status if pipeline is too large.
- int pipelineSize = 0;
- for (auto obj : resolvedPipeline) {
- pipelineSize += obj.objsize();
- }
- if (pipelineSize > ViewGraph::kMaxViewPipelineSizeBytes) {
- return {ErrorCodes::ViewPipelineMaxSizeExceeded,
- str::stream() << "View pipeline exceeds maximum size; maximum size is "
- << ViewGraph::kMaxViewPipelineSizeBytes};
- }
-
- auto curOp = CurOp::get(opCtx);
- curOp->debug().addResolvedViews(dependencyChain, resolvedPipeline);
-
- return StatusWith<ResolvedView>(
- {*resolvedNss,
- std::move(resolvedPipeline),
- collation ? std::move(collation.get()) : CollationSpec::kSimpleSpec,
- tsOptions,
- mixedData});
- }
-
- resolvedNss = &view->viewOn();
-
- if (storageGlobalParams.restore) {
- // During a selective restore procedure, skip checking options as the collection may no
- // longer exist.
- continue;
- }
-
- if (view->timeseries()) {
- // Use the lock-free collection lookup, to ensure compatibility with lock-free read
- // operations.
- auto tsCollection = CollectionCatalog::get(opCtx)->lookupCollectionByNamespaceForRead(
- opCtx, *resolvedNss);
- uassert(6067201,
- str::stream() << "expected time-series buckets collection " << *resolvedNss
- << " to exist",
- tsCollection);
- if (tsCollection) {
- mixedData = tsCollection->getTimeseriesBucketsMayHaveMixedSchemaData();
- tsOptions = tsCollection->getTimeseriesOptions();
- }
- }
-
- dependencyChain.push_back(*resolvedNss);
- if (!collation) {
- if (timeSeriesCollator) {
- collation = *timeSeriesCollator;
- } else {
- collation = view->defaultCollator() ? view->defaultCollator()->getSpec().toBSON()
- : CollationSpec::kSimpleSpec;
- }
- }
-
- // Prepend the underlying view's pipeline to the current working pipeline.
- const std::vector<BSONObj>& toPrepend = view->pipeline();
- resolvedPipeline.insert(resolvedPipeline.begin(), toPrepend.begin(), toPrepend.end());
-
- // If the first stage is a $collStats, then we return early with the viewOn namespace.
- if (toPrepend.size() > 0 && !toPrepend[0]["$collStats"].eoo()) {
- auto curOp = CurOp::get(opCtx);
- curOp->debug().addResolvedViews(dependencyChain, resolvedPipeline);
-
- return StatusWith<ResolvedView>(
- {*resolvedNss, std::move(resolvedPipeline), std::move(collation.get())});
- }
- }
-
- if (depth >= ViewGraph::kMaxViewDepth) {
- return {ErrorCodes::ViewDepthLimitExceeded,
- str::stream() << "View depth too deep or view cycle detected; maximum depth is "
- << ViewGraph::kMaxViewDepth};
- }
-
- MONGO_UNREACHABLE;
-}
-
-boost::optional<ViewCatalog::Stats> ViewCatalog::getStats(StringData dbName) const {
- auto it = _viewsForDatabase.find(dbName);
- if (it == _viewsForDatabase.end()) {
- return boost::none;
- }
- auto& vfdb = it->second;
- return vfdb.stats;
-}
-} // namespace mongo
diff --git a/src/mongo/db/views/view_catalog.h b/src/mongo/db/views/view_catalog.h
deleted file mode 100644
index a55ff015c14..00000000000
--- a/src/mongo/db/views/view_catalog.h
+++ /dev/null
@@ -1,271 +0,0 @@
-/**
- * Copyright (C) 2018-present MongoDB, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the Server Side Public License, version 1,
- * as published by MongoDB, Inc.
- *
- * 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
- * Server Side Public License for more details.
- *
- * You should have received a copy of the Server Side Public License
- * along with this program. If not, see
- * <http://www.mongodb.com/licensing/server-side-public-license>.
- *
- * 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 Server Side 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.
- */
-
-#pragma once
-
-#include <functional>
-#include <map>
-#include <memory>
-#include <string>
-#include <tuple>
-#include <vector>
-
-#include "mongo/base/status.h"
-#include "mongo/base/status_with.h"
-#include "mongo/base/string_data.h"
-#include "mongo/db/namespace_string.h"
-#include "mongo/db/views/durable_view_catalog.h"
-#include "mongo/db/views/resolved_view.h"
-#include "mongo/db/views/view.h"
-#include "mongo/db/views/view_graph.h"
-#include "mongo/platform/mutex.h"
-#include "mongo/util/concurrency/with_lock.h"
-#include "mongo/util/string_map.h"
-
-namespace mongo {
-class OperationContext;
-class Database;
-
-/**
- * In-memory data structure for view definitions. Instances returned by get() are immutable,
- * modifications through the static functions copy the existing instance and perform the
- * modification on the copy. A new call to get() is necessary to observe the modification.
- *
- * Writes via the static functions are thread-safe and serialized with a mutex.
- *
- * The static methods refresh the in-memory map with the views catalog collection if necessary,
- * throwing if the refresh fails.
- */
-class ViewCatalog {
-public:
- using ViewMap = StringMap<std::shared_ptr<ViewDefinition>>;
- using ViewIteratorCallback = std::function<bool(const ViewDefinition& view)>;
-
- static std::shared_ptr<const ViewCatalog> get(ServiceContext* svcCtx);
- static std::shared_ptr<const ViewCatalog> get(OperationContext* opCtx);
-
- /**
- * Add an entry to the ViewCatalog for the given database, backed by the durable storage
- * 'catalog'.
- */
- static Status registerDatabase(OperationContext* opCtx,
- StringData dbName,
- std::unique_ptr<DurableViewCatalog> catalog);
-
- /**
- * Removes the ViewCatalog entries assocated with 'db' if any. Should be called when when a
- * `DatabaseImpl` that has previously registered is about to be destructed (e.g. when closing a
- * database).
- */
- static void unregisterDatabase(OperationContext* opCtx, Database* db);
-
- /**
- * Iterates through the catalog, applying 'callback' to each view. This callback function
- * executes under the catalog's mutex, so it must not access other methods of the catalog,
- * acquire locks or run for a long time. If the 'callback' returns false, the iterator exits
- * early.
- *
- * Caller must ensure corresponding database exists.
- */
- void iterate(StringData dbName, ViewIteratorCallback callback) const;
-
- /**
- * Create a new view 'viewName' with contents defined by running the specified aggregation
- * 'pipeline' with collation 'collation' on a collection or view 'viewOn'. This method will
- * check correctness with respect to the view catalog, but will not check for conflicts with the
- * database's catalog, so the check for an existing collection with the same name must be done
- * before calling createView.
- *
- * Must be in WriteUnitOfWork. View creation rolls back if the unit of work aborts.
- *
- * Caller must ensure corresponding database exists.
- */
- static Status createView(OperationContext* opCtx,
- const NamespaceString& viewName,
- const NamespaceString& viewOn,
- const BSONArray& pipeline,
- const BSONObj& collation);
-
- /**
- * Drop the view named 'viewName'.
- *
- * Must be in WriteUnitOfWork. The drop rolls back if the unit of work aborts.
- *
- * Caller must ensure corresponding database exists.
- */
- static Status dropView(OperationContext* opCtx, const NamespaceString& viewName);
-
- /**
- * Modify the view named 'viewName' to have the new 'viewOn' and 'pipeline'.
- *
- * Must be in WriteUnitOfWork. The modification rolls back if the unit of work aborts.
- *
- * Caller must ensure corresponding database exists.
- */
- static Status modifyView(OperationContext* opCtx,
- const NamespaceString& viewName,
- const NamespaceString& viewOn,
- const BSONArray& pipeline);
-
- /**
- * Look up the 'nss' in the view catalog, returning a shared pointer to a View definition, or
- * nullptr if it doesn't exist.
- *
- * Caller must ensure corresponding database exists.
- */
- std::shared_ptr<const ViewDefinition> lookup(OperationContext* opCtx,
- const NamespaceString& nss) const;
-
- /**
- * Same functionality as above, except this function skips validating durable views in the view
- * catalog.
- *
- * Caller must ensure corresponding database exists.
- */
- std::shared_ptr<const ViewDefinition> lookupWithoutValidatingDurableViews(
- OperationContext* opCtx, const NamespaceString& nss) const;
-
- /**
- * Resolve the views on 'nss', transforming the pipeline appropriately. This function returns a
- * fully-resolved view definition containing the backing namespace, the resolved pipeline and
- * the collation to use for the operation.
- *
- * With SERVER-54597, we allow queries on timeseries collections *only* to specify non-default
- * collations. So in the case of queries on timeseries collections, we create a ResolvedView
- * with the request's collation (timeSeriesCollator) rather than the collection's default
- * collator.
- *
- * Caller must ensure corresponding database exists.
- */
- StatusWith<ResolvedView> resolveView(OperationContext* opCtx,
- const NamespaceString& nss,
- boost::optional<BSONObj> timeseriesCollator) const;
-
- /**
- * Usage statistics about this view catalog.
- * Total views = internal + userViews + userTimeseries.
- */
- struct Stats {
- int userViews = 0;
- int userTimeseries = 0;
- int internal = 0;
- };
-
- /**
- * Returns view statistics for the specified database.
- */
- boost::optional<Stats> getStats(StringData dbName) const;
-
- /**
- * Returns Status::OK with the set of involved namespaces if the given pipeline is eligible to
- * act as a view definition. Otherwise, returns ErrorCodes::OptionNotSupportedOnView.
- */
- static StatusWith<stdx::unordered_set<NamespaceString>> validatePipeline(
- OperationContext* opCtx, const ViewDefinition& viewDef);
-
- /**
- * Reloads the in-memory state of the view catalog from the 'system.views' collection catalog.
- * If the 'lookupBehavior' is 'kValidateDurableViews', then the durable view definitions will be
- * validated. Reading stops on the first invalid entry with errors logged and returned. Performs
- * no cycle detection, etc.
- * This is implicitly called by other methods when write operations are performed on the view
- * catalog, on external changes to the 'system.views' collection and on the first opening of a
- * database.
- */
- static Status reload(OperationContext* opCtx,
- StringData dbName,
- ViewCatalogLookupBehavior lookupBehavior);
-
- /**
- * Clears the in-memory state of the view catalog.
- */
- static void clear(OperationContext* opCtx, StringData dbName);
-
- /**
- * The view catalog needs to ignore external changes for its own modifications.
- */
- static bool shouldIgnoreExternalChange(OperationContext* opCtx, const NamespaceString& name);
-
-private:
- Status _createOrUpdateView(OperationContext* opCtx,
- const NamespaceString& viewName,
- const NamespaceString& viewOn,
- const BSONArray& pipeline,
- std::unique_ptr<CollatorInterface> collator);
- /**
- * Parses the view definition pipeline, attempts to upsert into the view graph, and refreshes
- * the graph if necessary. Returns an error status if the resulting graph would be invalid.
- */
- Status _upsertIntoGraph(OperationContext* opCtx, const ViewDefinition& viewDef);
-
- /**
- * Returns Status::OK if each view namespace in 'refs' has the same default collation as 'view'.
- * Otherwise, returns ErrorCodes::OptionNotSupportedOnView.
- */
- Status _validateCollation(OperationContext* opCtx,
- const ViewDefinition& view,
- const std::vector<NamespaceString>& refs) const;
-
- std::shared_ptr<const ViewDefinition> _lookup(OperationContext* opCtx,
- const NamespaceString& ns,
- ViewCatalogLookupBehavior lookupBehavior) const;
- std::shared_ptr<ViewDefinition> _lookup(OperationContext* opCtx,
- const NamespaceString& ns,
- ViewCatalogLookupBehavior lookupBehavior);
-
- Status _reload(OperationContext* opCtx,
- StringData dbName,
- ViewCatalogLookupBehavior lookupBehavior,
- bool reloadForCollectionCatalog);
-
- /**
- * Holds all data for the views associated with a particular database. Prior to 5.3, the
- * ViewCatalog object was owned by the Database object as a decoration. It has now transitioned
- * to a global catalog, as a decoration on the ServiceContext. Each database gets its own record
- * here, comprising the same information that was previously stored as top-level information
- * prior to 5.3.
- */
- struct ViewsForDatabase {
- ViewMap viewMap;
- std::shared_ptr<DurableViewCatalog> durable;
- bool valid = false;
- ViewGraph viewGraph;
- bool viewGraphNeedsRefresh = true;
- Stats stats;
-
- /**
- * uasserts with the InvalidViewDefinition error if the current in-memory state of the view
- * catalog for the given database is invalid. This ensures that calling into the view
- * catalog while it is invalid renders it inoperable.
- */
- void requireValidCatalog() const;
- };
- StringMap<ViewsForDatabase> _viewsForDatabase;
-};
-} // namespace mongo
diff --git a/src/mongo/db/views/view_catalog_helpers.cpp b/src/mongo/db/views/view_catalog_helpers.cpp
new file mode 100644
index 00000000000..a253f16adc3
--- /dev/null
+++ b/src/mongo/db/views/view_catalog_helpers.cpp
@@ -0,0 +1,235 @@
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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/db/views/view_catalog_helpers.h"
+
+#include "mongo/db/catalog/collection_catalog.h"
+#include "mongo/db/curop.h"
+#include "mongo/db/pipeline/aggregate_command_gen.h"
+#include "mongo/db/pipeline/document_source.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "mongo/db/pipeline/lite_parsed_pipeline.h"
+#include "mongo/db/pipeline/pipeline.h"
+#include "mongo/db/pipeline/process_interface/stub_mongo_process_interface.h"
+#include "mongo/db/views/view_graph.h"
+
+namespace mongo {
+namespace view_catalog_helpers {
+
+StatusWith<stdx::unordered_set<NamespaceString>> validatePipeline(OperationContext* opCtx,
+ const ViewDefinition& viewDef) {
+ const LiteParsedPipeline liteParsedPipeline(viewDef.viewOn(), viewDef.pipeline());
+ const auto involvedNamespaces = liteParsedPipeline.getInvolvedNamespaces();
+
+ // The API version pipeline validation should be skipped for time-series view because of
+ // following reasons:
+ // - the view pipeline is not created by (or visible to) the end-user and should be skipped.
+ // - the view pipeline can have stages that are not allowed in stable API version '1' eg.
+ // '$_internalUnpackBucket'.
+ bool performApiVersionChecks = !viewDef.timeseries();
+
+ liteParsedPipeline.validate(opCtx, performApiVersionChecks);
+
+ // Verify that this is a legitimate pipeline specification by making sure it parses
+ // correctly. In order to parse a pipeline we need to resolve any namespaces involved to a
+ // collection and a pipeline, but in this case we don't need this map to be accurate since
+ // we will not be evaluating the pipeline.
+ StringMap<ExpressionContext::ResolvedNamespace> resolvedNamespaces;
+ for (auto&& nss : involvedNamespaces) {
+ resolvedNamespaces[nss.coll()] = {nss, {}};
+ }
+ boost::intrusive_ptr<ExpressionContext> expCtx =
+ new ExpressionContext(opCtx,
+ AggregateCommandRequest(viewDef.viewOn(), viewDef.pipeline()),
+ CollatorInterface::cloneCollator(viewDef.defaultCollator()),
+ // We can use a stub MongoProcessInterface because we are only parsing
+ // the Pipeline for validation here. We won't do anything with the
+ // pipeline that will require a real implementation.
+ std::make_shared<StubMongoProcessInterface>(),
+ std::move(resolvedNamespaces),
+ boost::none);
+
+ // If the feature compatibility version is not kLatest, and we are validating features as
+ // primary, ban the use of new agg features introduced in kLatest to prevent them from being
+ // persisted in the catalog.
+ // (Generic FCV reference): This FCV check should exist across LTS binary versions.
+ multiversion::FeatureCompatibilityVersion fcv;
+ if (serverGlobalParams.validateFeaturesAsPrimary.load() &&
+ serverGlobalParams.featureCompatibility.isLessThan(multiversion::GenericFCV::kLatest,
+ &fcv)) {
+ expCtx->maxFeatureCompatibilityVersion = fcv;
+ }
+
+ // The pipeline parser needs to know that we're parsing a pipeline for a view definition
+ // to apply some additional checks.
+ expCtx->isParsingViewDefinition = true;
+
+ try {
+ auto pipeline =
+ Pipeline::parse(viewDef.pipeline(), std::move(expCtx), [&](const Pipeline& pipeline) {
+ // Validate that the view pipeline does not contain any ineligible stages.
+ const auto& sources = pipeline.getSources();
+ const auto firstPersistentStage =
+ std::find_if(sources.begin(), sources.end(), [](const auto& source) {
+ return source->constraints().writesPersistentData();
+ });
+
+ uassert(ErrorCodes::OptionNotSupportedOnView,
+ str::stream()
+ << "The aggregation stage "
+ << firstPersistentStage->get()->getSourceName() << " in location "
+ << std::distance(sources.begin(), firstPersistentStage)
+ << " of the pipeline cannot be used in the view definition of "
+ << viewDef.name().ns() << " because it writes to disk",
+ firstPersistentStage == sources.end());
+
+ uassert(ErrorCodes::OptionNotSupportedOnView,
+ "$changeStream cannot be used in a view definition",
+ sources.empty() || !sources.front()->constraints().isChangeStreamStage());
+
+ std::for_each(sources.begin(), sources.end(), [](auto& stage) {
+ uassert(ErrorCodes::InvalidNamespace,
+ str::stream() << "'" << stage->getSourceName()
+ << "' cannot be used in a view definition",
+ !stage->constraints().isIndependentOfAnyCollection);
+ });
+ });
+ } catch (const DBException& ex) {
+ return ex.toStatus();
+ }
+
+ return std::move(involvedNamespaces);
+}
+
+StatusWith<ResolvedView> resolveView(OperationContext* opCtx,
+ std::shared_ptr<const CollectionCatalog> catalog,
+ const NamespaceString& nss,
+ boost::optional<BSONObj> timeSeriesCollator) {
+ // Points to the name of the most resolved namespace.
+ const NamespaceString* resolvedNss = &nss;
+
+ // Holds the combination of all the resolved views.
+ std::vector<BSONObj> resolvedPipeline;
+
+ // If the catalog has not been tampered with, all views seen during the resolution will have
+ // the same collation. As an optimization, we fill out the collation spec only once.
+ boost::optional<BSONObj> collation;
+
+ // The last seen view definition, which owns the NamespaceString pointed to by
+ // 'resolvedNss'.
+ std::shared_ptr<ViewDefinition> lastViewDefinition;
+
+ std::vector<NamespaceString> dependencyChain{nss};
+
+ int depth = 0;
+ boost::optional<bool> mixedData = boost::none;
+ boost::optional<TimeseriesOptions> tsOptions = boost::none;
+
+ for (; depth < ViewGraph::kMaxViewDepth; depth++) {
+ auto view = catalog->lookupView(opCtx, *resolvedNss);
+ if (!view) {
+ // Return error status if pipeline is too large.
+ int pipelineSize = 0;
+ for (auto obj : resolvedPipeline) {
+ pipelineSize += obj.objsize();
+ }
+ if (pipelineSize > ViewGraph::kMaxViewPipelineSizeBytes) {
+ return {ErrorCodes::ViewPipelineMaxSizeExceeded,
+ str::stream() << "View pipeline exceeds maximum size; maximum size is "
+ << ViewGraph::kMaxViewPipelineSizeBytes};
+ }
+
+ auto curOp = CurOp::get(opCtx);
+ curOp->debug().addResolvedViews(dependencyChain, resolvedPipeline);
+
+ return StatusWith<ResolvedView>(
+ {*resolvedNss,
+ std::move(resolvedPipeline),
+ collation ? std::move(collation.get()) : CollationSpec::kSimpleSpec,
+ tsOptions,
+ mixedData});
+ }
+
+ resolvedNss = &view->viewOn();
+
+ if (storageGlobalParams.restore) {
+ // During a selective restore procedure, skip checking options as the collection may no
+ // longer exist.
+ continue;
+ }
+
+ if (view->timeseries()) {
+ // Use the lock-free collection lookup, to ensure compatibility with lock-free read
+ // operations.
+ auto tsCollection = catalog->lookupCollectionByNamespaceForRead(opCtx, *resolvedNss);
+ uassert(6067201,
+ str::stream() << "expected time-series buckets collection " << *resolvedNss
+ << " to exist",
+ tsCollection);
+ if (tsCollection) {
+ mixedData = tsCollection->getTimeseriesBucketsMayHaveMixedSchemaData();
+ tsOptions = tsCollection->getTimeseriesOptions();
+ }
+ }
+
+ dependencyChain.push_back(*resolvedNss);
+ if (!collation) {
+ if (timeSeriesCollator) {
+ collation = *timeSeriesCollator;
+ } else {
+ collation = view->defaultCollator() ? view->defaultCollator()->getSpec().toBSON()
+ : CollationSpec::kSimpleSpec;
+ }
+ }
+
+ // Prepend the underlying view's pipeline to the current working pipeline.
+ const std::vector<BSONObj>& toPrepend = view->pipeline();
+ resolvedPipeline.insert(resolvedPipeline.begin(), toPrepend.begin(), toPrepend.end());
+
+ // If the first stage is a $collStats, then we return early with the viewOn namespace.
+ if (toPrepend.size() > 0 && !toPrepend[0]["$collStats"].eoo()) {
+ auto curOp = CurOp::get(opCtx);
+ curOp->debug().addResolvedViews(dependencyChain, resolvedPipeline);
+
+ return StatusWith<ResolvedView>(
+ {*resolvedNss, std::move(resolvedPipeline), std::move(collation.get())});
+ }
+ }
+
+ if (depth >= ViewGraph::kMaxViewDepth) {
+ return {ErrorCodes::ViewDepthLimitExceeded,
+ str::stream() << "View depth too deep or view cycle detected; maximum depth is "
+ << ViewGraph::kMaxViewDepth};
+ }
+
+ MONGO_UNREACHABLE;
+}
+
+} // namespace view_catalog_helpers
+} // namespace mongo
diff --git a/src/mongo/db/views/view_catalog_helpers.h b/src/mongo/db/views/view_catalog_helpers.h
new file mode 100644
index 00000000000..02f87dd2b4e
--- /dev/null
+++ b/src/mongo/db/views/view_catalog_helpers.h
@@ -0,0 +1,69 @@
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * 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
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * 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 Server Side 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.
+ */
+
+#pragma once
+
+#include <boost/optional.hpp>
+
+#include "mongo/base/status_with.h"
+#include "mongo/db/catalog/collection_catalog.h"
+#include "mongo/db/namespace_string.h"
+#include "mongo/db/operation_context.h"
+#include "mongo/db/views/resolved_view.h"
+#include "mongo/db/views/view.h"
+
+namespace mongo {
+namespace view_catalog_helpers {
+
+/**
+ * Returns Status::OK with the set of involved namespaces if the given pipeline is eligible to
+ * act as a view definition. Otherwise, returns ErrorCodes::OptionNotSupportedOnView.
+ */
+StatusWith<stdx::unordered_set<NamespaceString>> validatePipeline(OperationContext* opCtx,
+ const ViewDefinition& viewDef);
+
+/**
+ * Resolve the views on 'nss', transforming the pipeline appropriately. This function returns a
+ * fully-resolved view definition containing the backing namespace, the resolved pipeline and
+ * the collation to use for the operation.
+ *
+ * With SERVER-54597, we allow queries on timeseries collections *only* to specify non-default
+ * collations. So in the case of queries on timeseries collections, we create a ResolvedView
+ * with the request's collation (timeSeriesCollator) rather than the collection's default
+ * collator.
+ *
+ * Caller must ensure corresponding database exists.
+ */
+StatusWith<ResolvedView> resolveView(OperationContext* opCtx,
+ std::shared_ptr<const CollectionCatalog> catalog,
+ const NamespaceString& nss,
+ boost::optional<BSONObj> timeseriesCollator);
+
+} // namespace view_catalog_helpers
+} // namespace mongo
diff --git a/src/mongo/db/views/view_catalog_test.cpp b/src/mongo/db/views/view_catalog_test.cpp
index 8fb0712477f..119b5b5456a 100644
--- a/src/mongo/db/views/view_catalog_test.cpp
+++ b/src/mongo/db/views/view_catalog_test.cpp
@@ -53,7 +53,7 @@
#include "mongo/db/service_context.h"
#include "mongo/db/views/durable_view_catalog.h"
#include "mongo/db/views/view.h"
-#include "mongo/db/views/view_catalog.h"
+#include "mongo/db/views/view_catalog_helpers.h"
#include "mongo/db/views/view_graph.h"
#include "mongo/unittest/unittest.h"
#include "mongo/util/str.h"
@@ -105,8 +105,8 @@ public:
CatalogTestFixture::tearDown();
}
- auto getViewCatalog() {
- return ViewCatalog::get(operationContext());
+ auto getCatalog() {
+ return CollectionCatalog::get(operationContext());
}
Status createView(OperationContext* opCtx,
@@ -122,7 +122,8 @@ public:
MODE_X);
WriteUnitOfWork wuow(opCtx);
- Status s = ViewCatalog::createView(opCtx, viewName, viewOn, pipeline, collation);
+ Status s = getCatalog()->createView(
+ opCtx, viewName, viewOn, pipeline, collation, view_catalog_helpers::validatePipeline);
wuow.commit();
return s;
@@ -140,7 +141,8 @@ public:
MODE_X);
WriteUnitOfWork wuow(opCtx);
- Status s = ViewCatalog::modifyView(opCtx, viewName, viewOn, pipeline);
+ Status s = getCatalog()->modifyView(
+ opCtx, viewName, viewOn, pipeline, view_catalog_helpers::validatePipeline);
wuow.commit();
return s;
@@ -155,7 +157,7 @@ public:
MODE_X);
WriteUnitOfWork wuow(opCtx);
- Status s = ViewCatalog::dropView(opCtx, viewName);
+ Status s = getCatalog()->dropView(opCtx, viewName);
wuow.commit();
return s;
@@ -168,7 +170,7 @@ public:
std::shared_ptr<const ViewDefinition> lookup(OperationContext* opCtx,
const NamespaceString& ns) {
Lock::DBLock dbLock(operationContext(), NamespaceString(ns).db(), MODE_IS);
- return getViewCatalog()->lookup(operationContext(), ns);
+ return getCatalog()->lookupView(operationContext(), ns);
}
private:
@@ -516,8 +518,7 @@ TEST_F(ViewCatalogFixture, LookupRIDExistingView) {
ASSERT_OK(createView(operationContext(), viewName, viewOn, emptyPipeline, emptyCollation));
auto resourceID = ResourceId(RESOURCE_COLLECTION, "db.view"_sd);
- auto collectionCatalog = CollectionCatalog::get(operationContext());
- ASSERT(collectionCatalog->lookupResourceName(resourceID).get() == "db.view");
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == "db.view");
}
TEST_F(ViewCatalogFixture, LookupRIDExistingViewRollback) {
@@ -532,12 +533,15 @@ TEST_F(ViewCatalogFixture, LookupRIDExistingViewRollback) {
MODE_X);
WriteUnitOfWork wunit(operationContext());
- ASSERT_OK(ViewCatalog::createView(
- operationContext(), viewName, viewOn, emptyPipeline, emptyCollation));
+ ASSERT_OK(getCatalog()->createView(operationContext(),
+ viewName,
+ viewOn,
+ emptyPipeline,
+ emptyCollation,
+ view_catalog_helpers::validatePipeline));
}
auto resourceID = ResourceId(RESOURCE_COLLECTION, "db.view"_sd);
- auto collectionCatalog = CollectionCatalog::get(operationContext());
- ASSERT(!collectionCatalog->lookupResourceName(resourceID));
+ ASSERT(!getCatalog()->lookupResourceName(resourceID));
}
TEST_F(ViewCatalogFixture, LookupRIDAfterDrop) {
@@ -548,8 +552,7 @@ TEST_F(ViewCatalogFixture, LookupRIDAfterDrop) {
ASSERT_OK(dropView(operationContext(), viewName));
auto resourceID = ResourceId(RESOURCE_COLLECTION, "db.view"_sd);
- auto collectionCatalog = CollectionCatalog::get(operationContext());
- ASSERT(!collectionCatalog->lookupResourceName(resourceID));
+ ASSERT(!getCatalog()->lookupResourceName(resourceID));
}
TEST_F(ViewCatalogFixture, LookupRIDAfterDropRollback) {
@@ -560,9 +563,8 @@ TEST_F(ViewCatalogFixture, LookupRIDAfterDropRollback) {
{
WriteUnitOfWork wunit(operationContext());
ASSERT_OK(createView(operationContext(), viewName, viewOn, emptyPipeline, emptyCollation));
- ASSERT(CollectionCatalog::get(operationContext())->lookupResourceName(resourceID).get() ==
- viewName.ns());
wunit.commit();
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == viewName.ns());
}
{
@@ -574,11 +576,11 @@ TEST_F(ViewCatalogFixture, LookupRIDAfterDropRollback) {
MODE_X);
WriteUnitOfWork wunit(operationContext());
- ASSERT_OK(ViewCatalog::dropView(operationContext(), viewName));
+ ASSERT_OK(getCatalog()->dropView(operationContext(), viewName));
+ // Do not commit, rollback.
}
-
- ASSERT(CollectionCatalog::get(operationContext())->lookupResourceName(resourceID).get() ==
- viewName.ns());
+ // Make sure drop was rolled back and view is still in catalog.
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == viewName.ns());
}
TEST_F(ViewCatalogFixture, LookupRIDAfterModify) {
@@ -588,8 +590,7 @@ TEST_F(ViewCatalogFixture, LookupRIDAfterModify) {
auto resourceID = ResourceId(RESOURCE_COLLECTION, "db.view"_sd);
ASSERT_OK(createView(operationContext(), viewName, viewOn, emptyPipeline, emptyCollation));
ASSERT_OK(modifyView(operationContext(), viewName, viewOn, emptyPipeline));
- ASSERT(CollectionCatalog::get(operationContext())->lookupResourceName(resourceID).get() ==
- viewName.ns());
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == viewName.ns());
}
TEST_F(ViewCatalogFixture, LookupRIDAfterModifyRollback) {
@@ -600,10 +601,10 @@ TEST_F(ViewCatalogFixture, LookupRIDAfterModifyRollback) {
{
WriteUnitOfWork wunit(operationContext());
ASSERT_OK(createView(operationContext(), viewName, viewOn, emptyPipeline, emptyCollation));
- ASSERT(CollectionCatalog::get(operationContext())->lookupResourceName(resourceID).get() ==
- viewName.ns());
wunit.commit();
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == viewName.ns());
}
+
{
Lock::DBLock dbLock(operationContext(), viewName.db(), MODE_IX);
Lock::CollectionLock collLock(operationContext(), viewName, MODE_X);
@@ -613,12 +614,16 @@ TEST_F(ViewCatalogFixture, LookupRIDAfterModifyRollback) {
MODE_X);
WriteUnitOfWork wunit(operationContext());
- ASSERT_OK(ViewCatalog::modifyView(operationContext(), viewName, viewOn, emptyPipeline));
- ASSERT(CollectionCatalog::get(operationContext())->lookupResourceName(resourceID).get() ==
- viewName.ns());
+ ASSERT_OK(getCatalog()->modifyView(operationContext(),
+ viewName,
+ viewOn,
+ emptyPipeline,
+ view_catalog_helpers::validatePipeline));
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == viewName.ns());
+ // Do not commit, rollback.
}
- ASSERT(CollectionCatalog::get(operationContext())->lookupResourceName(resourceID).get() ==
- viewName.ns());
+ // Make sure view resource is still available after rollback.
+ ASSERT(getCatalog()->lookupResourceName(resourceID).get() == viewName.ns());
}
TEST_F(ViewCatalogFixture, CreateViewThenDropAndLookup) {
@@ -644,7 +649,7 @@ TEST_F(ViewCatalogFixture, Iterate) {
std::set<std::string> viewNames = {"db.view1", "db.view2", "db.view3"};
Lock::DBLock dbLock(operationContext(), "db", MODE_IX);
- getViewCatalog()->iterate("db", [&viewNames](const ViewDefinition& view) {
+ getCatalog()->iterateViews(operationContext(), "db", [&viewNames](const ViewDefinition& view) {
std::string name = view.name().toString();
ASSERT(viewNames.end() != viewNames.find(name));
viewNames.erase(name);
@@ -672,7 +677,8 @@ TEST_F(ViewCatalogFixture, ResolveViewCorrectPipeline) {
ASSERT_OK(createView(operationContext(), view3, view2, pipeline3.arr(), emptyCollation));
Lock::DBLock dbLock(operationContext(), "db", MODE_IX);
- auto resolvedView = getViewCatalog()->resolveView(operationContext(), view3, boost::none);
+ auto resolvedView =
+ view_catalog_helpers::resolveView(operationContext(), getCatalog(), view3, boost::none);
ASSERT(resolvedView.isOK());
std::vector<BSONObj> expected = {BSON("$match" << BSON("foo" << 1)),
@@ -692,8 +698,8 @@ TEST_F(ViewCatalogFixture, ResolveViewOnCollectionNamespace) {
const NamespaceString collectionNamespace("db.coll");
Lock::DBLock dbLock(operationContext(), "db", MODE_IS);
- auto resolvedView = uassertStatusOK(
- getViewCatalog()->resolveView(operationContext(), collectionNamespace, boost::none));
+ auto resolvedView = uassertStatusOK(view_catalog_helpers::resolveView(
+ operationContext(), getCatalog(), collectionNamespace, boost::none));
ASSERT_EQ(resolvedView.getNamespace(), collectionNamespace);
ASSERT_EQ(resolvedView.getPipeline().size(), 0U);
@@ -716,7 +722,8 @@ TEST_F(ViewCatalogFixture, ResolveViewCorrectlyExtractsDefaultCollation) {
ASSERT_OK(createView(operationContext(), view2, view1, pipeline2.arr(), collation));
Lock::DBLock dbLock(operationContext(), "db", MODE_IS);
- auto resolvedView = getViewCatalog()->resolveView(operationContext(), view2, boost::none);
+ auto resolvedView =
+ view_catalog_helpers::resolveView(operationContext(), getCatalog(), view2, boost::none);
ASSERT(resolvedView.isOK());
ASSERT_EQ(resolvedView.getValue().getNamespace(), viewOn);