/** * 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 . * * 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kStorage #include "mongo/platform/basic.h" #include "mongo/db/views/durable_view_catalog.h" #include #include "mongo/db/catalog/collection.h" #include "mongo/db/catalog/database.h" #include "mongo/db/catalog/database_holder.h" #include "mongo/db/concurrency/d_concurrency.h" #include "mongo/db/curop.h" #include "mongo/db/dbhelpers.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" #include "mongo/db/storage/record_data.h" #include "mongo/stdx/unordered_set.h" #include "mongo/util/assert_util.h" #include "mongo/util/log.h" #include "mongo/util/string_map.h" namespace mongo { // DurableViewCatalog void DurableViewCatalog::onExternalChange(OperationContext* opCtx, const NamespaceString& name) { dassert(opCtx->lockState()->isDbLockedForMode(name.db(), MODE_IX)); Database* db = DatabaseHolder::getDatabaseHolder().get(opCtx, name.db()); if (db) { opCtx->recoveryUnit()->onCommit( [db](boost::optional) { db->getViewCatalog()->invalidate(); }); } } // DurableViewCatalogImpl const std::string& DurableViewCatalogImpl::getName() const { return _db->name(); } Status DurableViewCatalogImpl::iterate(OperationContext* opCtx, Callback callback) { dassert(opCtx->lockState()->isDbLockedForMode(_db->name(), MODE_IS) || opCtx->lockState()->isDbLockedForMode(_db->name(), MODE_IX)); Collection* systemViews = _db->getCollection(opCtx, _db->getSystemViewsName()); if (!systemViews) return Status::OK(); Lock::CollectionLock lk(opCtx->lockState(), _db->getSystemViewsName(), MODE_IS); auto cursor = systemViews->getCursor(opCtx); while (auto record = cursor->next()) { RecordData& data = record->data; // Check the document is valid BSON, with only the expected fields. // Use the latest BSON validation version. Existing view definitions are allowed to contain // decimal data even if decimal is disabled. fassert(40224, validateBSON(data.data(), data.size(), BSONVersion::kLatest)); BSONObj viewDef = data.toBson(); // Check read definitions for correct structure, and refuse reading past invalid // definitions. Ignore any further view definitions. bool valid = true; for (const BSONElement& e : viewDef) { std::string name(e.fieldName()); valid &= name == "_id" || name == "viewOn" || name == "pipeline" || name == "collation"; } const auto viewName = viewDef["_id"].str(); const auto viewNameIsValid = NamespaceString::validCollectionComponent(viewName) && NamespaceString::validDBName(nsToDatabaseSubstring(viewName)); valid &= viewNameIsValid; // Only perform validation via NamespaceString if the collection name has been determined to // be valid. If not valid then the NamespaceString constructor will uassert. if (viewNameIsValid) { NamespaceString viewNss(viewName); valid &= viewNss.isValid() && viewNss.db() == _db->name(); } valid &= NamespaceString::validCollectionName(viewDef["viewOn"].str()); const bool hasPipeline = viewDef.hasField("pipeline"); valid &= hasPipeline; if (hasPipeline) { valid &= viewDef["pipeline"].type() == mongo::Array; } valid &= (!viewDef.hasField("collation") || viewDef["collation"].type() == BSONType::Object); if (!valid) { return {ErrorCodes::InvalidViewDefinition, str::stream() << "found invalid view definition " << viewDef["_id"] << " while reading '" << _db->getSystemViewsName() << "'"}; } Status callbackStatus = callback(viewDef); if (!callbackStatus.isOK()) { return callbackStatus; } } return Status::OK(); } void DurableViewCatalogImpl::upsert(OperationContext* opCtx, const NamespaceString& name, const BSONObj& view) { dassert(opCtx->lockState()->isDbLockedForMode(_db->name(), MODE_X)); NamespaceString systemViewsNs(_db->getSystemViewsName()); Collection* systemViews = _db->getCollection(opCtx, systemViewsNs); invariant(systemViews); const bool requireIndex = false; RecordId id = Helpers::findOne(opCtx, systemViews, BSON("_id" << name.ns()), requireIndex); Snapshotted oldView; if (!id.isValid() || !systemViews->findDoc(opCtx, id, &oldView)) { LOG(2) << "insert view " << view << " into " << _db->getSystemViewsName(); uassertStatusOK( systemViews->insertDocument(opCtx, InsertStatement(view), &CurOp::get(opCtx)->debug())); } else { CollectionUpdateArgs args; args.update = view; args.criteria = BSON("_id" << name.ns()); args.fromMigrate = false; const bool assumeIndexesAreAffected = true; systemViews->updateDocument( opCtx, id, oldView, view, assumeIndexesAreAffected, &CurOp::get(opCtx)->debug(), &args); } } void DurableViewCatalogImpl::remove(OperationContext* opCtx, const NamespaceString& name) { dassert(opCtx->lockState()->isDbLockedForMode(_db->name(), MODE_X)); Collection* systemViews = _db->getCollection(opCtx, _db->getSystemViewsName()); if (!systemViews) return; const bool requireIndex = false; RecordId id = Helpers::findOne(opCtx, systemViews, BSON("_id" << name.ns()), requireIndex); if (!id.isValid()) return; LOG(2) << "remove view " << name << " from " << _db->getSystemViewsName(); systemViews->deleteDocument(opCtx, kUninitializedStmtId, id, &CurOp::get(opCtx)->debug()); } } // namespace mongo