Status ViewCatalog::createView(OperationContext* txn, const NamespaceString& viewName, const NamespaceString& viewOn, const BSONArray& pipeline) { stdx::lock_guard<stdx::mutex> lk(_mutex); if (!enableViews) return Status(ErrorCodes::CommandNotSupported, "View support not enabled"); if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); if (_lookup_inlock(txn, StringData(viewName.ns()))) return Status(ErrorCodes::NamespaceExists, "Namespace already exists"); if (!NamespaceString::validCollectionName(viewOn.coll())) return Status(ErrorCodes::InvalidNamespace, str::stream() << "invalid name for 'viewOn': " << viewOn.coll()); // TODO(SERVER-24768): Need to ensure view is correct and doesn't introduce a cycle. _createOrUpdateView_inlock(txn, viewName, viewOn, pipeline); return Status::OK(); }
Status ViewCatalog::dropView(OperationContext* opCtx, const NamespaceString& viewName) { stdx::lock_guard<stdx::mutex> lk(_mutex); _requireValidCatalog_inlock(opCtx); // Save a copy of the view definition in case we need to roll back. auto viewPtr = _lookup_inlock(opCtx, viewName.ns()); if (!viewPtr) { return {ErrorCodes::NamespaceNotFound, str::stream() << "cannot drop missing view: " << viewName.ns()}; } ViewDefinition savedDefinition = *viewPtr; invariant(_valid.load()); _durable->remove(opCtx, viewName); _viewGraph.remove(savedDefinition.name()); _viewMap.erase(viewName.ns()); opCtx->recoveryUnit()->onRollback([this, viewName, savedDefinition]() { this->_viewGraphNeedsRefresh = true; this->_viewMap[viewName.ns()] = std::make_shared<ViewDefinition>(savedDefinition); }); // We may get invalidated, but we're exclusively locked, so the change must be ours. opCtx->recoveryUnit()->onCommit([this]() { this->_valid.store(true); }); return Status::OK(); }
Status ViewCatalog::createView(OperationContext* opCtx, const NamespaceString& viewName, const NamespaceString& viewOn, const BSONArray& pipeline, const BSONObj& collation) { stdx::lock_guard<stdx::mutex> lk(_mutex); if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); if (_lookup_inlock(opCtx, StringData(viewName.ns()))) return Status(ErrorCodes::NamespaceExists, "Namespace already exists"); if (!NamespaceString::validCollectionName(viewOn.coll())) return Status(ErrorCodes::InvalidNamespace, str::stream() << "invalid name for 'viewOn': " << viewOn.coll()); if (viewName.isSystem()) return Status( ErrorCodes::InvalidNamespace, "View name cannot start with 'system.', which is reserved for system namespaces"); auto collator = parseCollator(opCtx, collation); if (!collator.isOK()) return collator.getStatus(); return _createOrUpdateView_inlock( opCtx, viewName, viewOn, pipeline, std::move(collator.getValue())); }
Status ViewCatalog::modifyView(OperationContext* opCtx, const NamespaceString& viewName, const NamespaceString& viewOn, const BSONArray& pipeline) { stdx::lock_guard<stdx::mutex> lk(_mutex); if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); auto viewPtr = _lookup_inlock(opCtx, viewName.ns()); 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()); ViewDefinition savedDefinition = *viewPtr; opCtx->recoveryUnit()->onRollback([this, viewName, savedDefinition]() { this->_viewMap[viewName.ns()] = std::make_shared<ViewDefinition>(savedDefinition); }); return _createOrUpdateView_inlock( opCtx, viewName, viewOn, pipeline, CollatorInterface::cloneCollator(savedDefinition.defaultCollator())); }
StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn, const NamespaceString& nss) { stdx::lock_guard<stdx::mutex> lk(_mutex); const NamespaceString* resolvedNss = &nss; std::vector<BSONObj> resolvedPipeline; for (int i = 0; i < ViewGraph::kMaxViewDepth; i++) { auto view = _lookup_inlock(txn, resolvedNss->ns()); if (!view) return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline}); resolvedNss = &(view->viewOn()); // 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()) { return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline}); } } return {ErrorCodes::ViewDepthLimitExceeded, str::stream() << "View depth too deep or view cycle detected; maximum depth is " << ViewGraph::kMaxViewDepth}; }
Status ViewCatalog::modifyView(OperationContext* txn, const NamespaceString& viewName, const NamespaceString& viewOn, const BSONArray& pipeline) { stdx::lock_guard<stdx::mutex> lk(_mutex); if (serverGlobalParams.featureCompatibilityVersion.load() == ServerGlobalParams::FeatureCompatibilityVersion_32) { return Status(ErrorCodes::CommandNotSupported, "Cannot modify view when the featureCompatibilityVersion is 3.2. See " "http://dochub.mongodb.org/core/3.4-feature-compatibility."); } if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); auto viewPtr = _lookup_inlock(txn, viewName.ns()); 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()); ViewDefinition savedDefinition = *viewPtr; txn->recoveryUnit()->onRollback([this, txn, viewName, savedDefinition]() { this->_viewMap[viewName.ns()] = std::make_shared<ViewDefinition>(savedDefinition); }); return _createOrUpdateView_inlock(txn, viewName, viewOn, pipeline); }
Status ViewCatalog::createView(OperationContext* txn, const NamespaceString& viewName, const NamespaceString& viewOn, const BSONArray& pipeline) { stdx::lock_guard<stdx::mutex> lk(_mutex); if (serverGlobalParams.featureCompatibilityVersion.load() == ServerGlobalParams::FeatureCompatibilityVersion_32) { return Status(ErrorCodes::CommandNotSupported, "Cannot create view when the featureCompatibilityVersion is 3.2. See " "http://dochub.mongodb.org/core/3.4-feature-compatibility."); } if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); if (_lookup_inlock(txn, StringData(viewName.ns()))) return Status(ErrorCodes::NamespaceExists, "Namespace already exists"); if (!NamespaceString::validCollectionName(viewOn.coll())) return Status(ErrorCodes::InvalidNamespace, str::stream() << "invalid name for 'viewOn': " << viewOn.coll()); return _createOrUpdateView_inlock(txn, viewName, viewOn, pipeline); }
Status ViewCatalog::_validateCollation_inlock(OperationContext* opCtx, const ViewDefinition& view, const std::vector<NamespaceString>& refs) { for (auto&& potentialViewNss : refs) { auto otherView = _lookup_inlock(opCtx, potentialViewNss.ns()); 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(); }
StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* opCtx, const NamespaceString& nss) { stdx::lock_guard<stdx::mutex> lk(_mutex); const NamespaceString* resolvedNss = &nss; std::vector<BSONObj> resolvedPipeline; BSONObj collation; for (int i = 0; i < ViewGraph::kMaxViewDepth; i++) { auto view = _lookup_inlock(opCtx, resolvedNss->ns()); 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}; } return StatusWith<ResolvedView>( {*resolvedNss, std::move(resolvedPipeline), std::move(collation)}); } resolvedNss = &(view->viewOn()); 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()) { return StatusWith<ResolvedView>( {*resolvedNss, std::move(resolvedPipeline), std::move(collation)}); } } return {ErrorCodes::ViewDepthLimitExceeded, str::stream() << "View depth too deep or view cycle detected; maximum depth is " << ViewGraph::kMaxViewDepth}; }
Status ViewCatalog::modifyView(OperationContext* txn, const NamespaceString& viewName, const NamespaceString& viewOn, const BSONArray& pipeline) { stdx::lock_guard<stdx::mutex> lk(_mutex); if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); if (!_lookup_inlock(txn, StringData(viewName.ns()))) 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()); _createOrUpdateView_inlock(txn, viewName, viewOn, pipeline); return Status::OK(); }
StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* txn, const NamespaceString& nss) { stdx::lock_guard<stdx::mutex> lk(_mutex); const NamespaceString* resolvedNss = &nss; std::vector<BSONObj> resolvedPipeline; for (std::uint32_t i = 0; i < ViewCatalog::kMaxViewDepth; i++) { ViewDefinition* view = _lookup_inlock(txn, resolvedNss->ns()); if (!view) return StatusWith<ResolvedView>({*resolvedNss, resolvedPipeline}); resolvedNss = &(view->viewOn()); // 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()); } return {ErrorCodes::ViewDepthLimitExceeded, str::stream() << "View depth too deep or view cycle detected; maximum depth is " << kMaxViewDepth}; }
ViewDefinition* ViewCatalog::lookup(OperationContext* txn, StringData ns) { stdx::lock_guard<stdx::mutex> lk(_mutex); return _lookup_inlock(txn, ns); }
std::shared_ptr<ViewDefinition> ViewCatalog::lookup(OperationContext* opCtx, StringData ns) { stdx::lock_guard<stdx::mutex> lk(_mutex); return _lookup_inlock(opCtx, ns); }
StatusWith<ResolvedView> ViewCatalog::resolveView(OperationContext* opCtx, const NamespaceString& nss) { stdx::unique_lock<stdx::mutex> lock(_mutex); // Keep looping until the resolution completes. If the catalog is invalidated during the // resolution, we start over from the beginning. while (true) { // 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; int depth = 0; for (; depth < ViewGraph::kMaxViewDepth; depth++) { while (MONGO_FAIL_POINT(hangDuringViewResolution)) { log() << "Yielding mutex and hanging due to 'hangDuringViewResolution' failpoint"; lock.unlock(); sleepmillis(1000); lock.lock(); } // If the catalog has been invalidated, bail and restart. if (!_valid.load()) { uassertStatusOK(_reloadIfNeeded_inlock(opCtx)); break; } auto view = _lookup_inlock(opCtx, resolvedNss->ns()); 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}; } return StatusWith<ResolvedView>( {*resolvedNss, std::move(resolvedPipeline), std::move(collation.get())}); } resolvedNss = &view->viewOn(); if (!collation) { 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()) { 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; }