diff options
author | Shin Yee Tan <shinyee.tan@mongodb.com> | 2022-04-20 17:50:36 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2023-01-25 16:45:20 +0000 |
commit | c58f4997c05d987a90a77c7870c0e2d742dda86a (patch) | |
tree | c5e248d2695cb2df8185f7b34795c6ac6c1c2843 | |
parent | 6659be6a462ee241f6b0a80a27ae9440bebf9216 (diff) | |
download | mongo-c58f4997c05d987a90a77c7870c0e2d742dda86a.tar.gz |
SERVER-53870 Improve view creation performance over time by avoiding reloading views from disk
(cherry picked from commit be752f7877f795faa42432be79039faf2b968660)
-rw-r--r-- | src/mongo/db/catalog/collection_catalog.cpp | 67 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_catalog.h | 19 | ||||
-rw-r--r-- | src/mongo/db/catalog/views_for_database.cpp | 97 | ||||
-rw-r--r-- | src/mongo/db/catalog/views_for_database.h | 15 | ||||
-rw-r--r-- | src/mongo/db/op_observer_impl.cpp | 10 | ||||
-rw-r--r-- | src/mongo/db/views/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/views/durable_view_catalog.cpp | 120 | ||||
-rw-r--r-- | src/mongo/db/views/durable_view_catalog.h | 8 | ||||
-rw-r--r-- | src/mongo/db/views/view_graph.cpp | 5 | ||||
-rw-r--r-- | src/mongo/db/views/view_graph.h | 17 |
10 files changed, 242 insertions, 117 deletions
diff --git a/src/mongo/db/catalog/collection_catalog.cpp b/src/mongo/db/catalog/collection_catalog.cpp index 25e79db00b1..5888c6211b9 100644 --- a/src/mongo/db/catalog/collection_catalog.cpp +++ b/src/mongo/db/catalog/collection_catalog.cpp @@ -469,13 +469,13 @@ void CollectionCatalog::write(OperationContext* opCtx, write(opCtx->getServiceContext(), std::move(job)); } -Status CollectionCatalog::createView( - OperationContext* opCtx, - const NamespaceString& viewName, - const NamespaceString& viewOn, - const BSONArray& pipeline, - const BSONObj& collation, - const ViewsForDatabase::PipelineValidatorFn& pipelineValidator) const { +Status CollectionCatalog::createView(OperationContext* opCtx, + const NamespaceString& viewName, + const NamespaceString& viewOn, + const BSONArray& pipeline, + const BSONObj& collation, + const ViewsForDatabase::PipelineValidatorFn& pipelineValidator, + const bool updateDurableViewCatalog) const { invariant(opCtx->lockState()->isCollectionLockedForMode(viewName, MODE_IX)); invariant(opCtx->lockState()->isCollectionLockedForMode( NamespaceString(viewName.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X)); @@ -483,6 +483,11 @@ Status CollectionCatalog::createView( invariant(_viewsForDatabase.contains(viewName.db())); const ViewsForDatabase& viewsForDb = *_getViewsForDatabase(opCtx, viewName.db()); + auto& uncommittedCatalogUpdates = UncommittedCatalogUpdates::get(opCtx); + if (uncommittedCatalogUpdates.shouldIgnoreExternalViewChanges(viewName.db())) { + return Status::OK(); + } + if (viewName.db() != viewOn.db()) return Status(ErrorCodes::BadValue, "View must be created on a view or collection in the same database"); @@ -508,7 +513,8 @@ Status CollectionCatalog::createView( pipeline, pipelineValidator, std::move(collator.getValue()), - ViewsForDatabase{viewsForDb}); + ViewsForDatabase{viewsForDb}, + ViewUpsertMode::kCreateView); } return result; @@ -550,7 +556,8 @@ Status CollectionCatalog::modifyView( pipeline, pipelineValidator, CollatorInterface::cloneCollator(viewPtr->defaultCollator()), - ViewsForDatabase{viewsForDb}); + ViewsForDatabase{viewsForDb}, + ViewUpsertMode::kUpdateView); } return result; @@ -1404,15 +1411,16 @@ Status CollectionCatalog::_createOrUpdateView( const BSONArray& pipeline, const ViewsForDatabase::PipelineValidatorFn& pipelineValidator, std::unique_ptr<CollatorInterface> collator, - ViewsForDatabase&& viewsForDb) const { + ViewsForDatabase&& viewsForDb, + ViewUpsertMode mode) const { invariant(opCtx->lockState()->isCollectionLockedForMode(viewName, MODE_IX)); invariant(opCtx->lockState()->isCollectionLockedForMode( NamespaceString(viewName.db(), NamespaceString::kSystemDotViewsCollectionName), MODE_X)); viewsForDb.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. + // Build the BSON definition for this view to be saved in the durable view catalog and/or to + // insert in the viewMap. If the collation is empty, omit it from the definition altogether. BSONObjBuilder viewDefBuilder; viewDefBuilder.append("_id", viewName.ns()); viewDefBuilder.append("viewOn", viewOn.coll()); @@ -1421,25 +1429,42 @@ Status CollectionCatalog::_createOrUpdateView( viewDefBuilder.append("collation", collator->getSpec().toBSON()); } + BSONObj viewDef = viewDefBuilder.obj(); BSONObj ownedPipeline = pipeline.getOwned(); - auto view = std::make_shared<ViewDefinition>( + ViewDefinition view( 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 = viewsForDb.upsertIntoGraph(opCtx, *(view.get()), pipelineValidator); + // If the view is already in the durable view catalog, we don't need to validate the graph. If + // we need to update the durable view catalog, we need to check that the resulting dependency + // graph is acyclic and within the maximum depth. + const bool viewGraphNeedsValidation = mode != ViewUpsertMode::kAlreadyDurableView; + Status graphStatus = + viewsForDb.upsertIntoGraph(opCtx, view, pipelineValidator, viewGraphNeedsValidation); if (!graphStatus.isOK()) { return graphStatus; } - viewsForDb.durable->upsert(opCtx, viewName, viewDefBuilder.obj()); + if (mode != ViewUpsertMode::kAlreadyDurableView) { + viewsForDb.durable->upsert(opCtx, viewName, viewDef); + } - viewsForDb.viewMap.clear(); viewsForDb.valid = false; - viewsForDb.viewGraphNeedsRefresh = true; - viewsForDb.stats = {}; + auto res = [&] { + switch (mode) { + case ViewUpsertMode::kCreateView: + case ViewUpsertMode::kAlreadyDurableView: + return viewsForDb.insert(opCtx, viewDef); + case ViewUpsertMode::kUpdateView: + viewsForDb.viewMap.clear(); + viewsForDb.viewGraphNeedsRefresh = true; + viewsForDb.stats = {}; + + // Reload the view catalog with the changes applied. + return viewsForDb.reload(opCtx); + } + MONGO_UNREACHABLE; + }(); - // Reload the view catalog with the changes applied. - auto res = viewsForDb.reload(opCtx); if (res.isOK()) { auto& uncommittedCatalogUpdates = UncommittedCatalogUpdates::get(opCtx); uncommittedCatalogUpdates.addView(opCtx, viewName); diff --git a/src/mongo/db/catalog/collection_catalog.h b/src/mongo/db/catalog/collection_catalog.h index 98239bcb8bd..a4b2891a1db 100644 --- a/src/mongo/db/catalog/collection_catalog.h +++ b/src/mongo/db/catalog/collection_catalog.h @@ -154,7 +154,8 @@ public: const NamespaceString& viewOn, const BSONArray& pipeline, const BSONObj& collation, - const ViewsForDatabase::PipelineValidatorFn& pipelineValidator) const; + const ViewsForDatabase::PipelineValidatorFn& pipelineValidator, + bool updateDurableViewCatalog = true) const; /** * Drop the view named 'viewName'. @@ -539,6 +540,19 @@ private: */ void _replaceViewsForDatabase(StringData dbName, ViewsForDatabase&& views); + enum class ViewUpsertMode { + // Insert all data for that view into the view map, view graph, and durable view catalog. + kCreateView, + + // Insert into the view map and view graph without reinserting the view into the durable + // view catalog. Skip view graph validation. + kAlreadyDurableView, + + // Reload the view map, insert into the view graph (flagging it as needing refresh), and + // update the durable view catalog. + kUpdateView, + }; + /** * Helper to take care of shared functionality for 'createView(...)' and 'modifyView(...)'. */ @@ -548,7 +562,8 @@ private: const BSONArray& pipeline, const ViewsForDatabase::PipelineValidatorFn& pipelineValidator, std::unique_ptr<CollatorInterface> collator, - ViewsForDatabase&& viewsForDb) const; + ViewsForDatabase&& viewsForDb, + ViewUpsertMode mode) const; /** * Returns true if this CollectionCatalog instance is part of an ongoing batched catalog write. diff --git a/src/mongo/db/catalog/views_for_database.cpp b/src/mongo/db/catalog/views_for_database.cpp index 776cf5e3266..404297c11a3 100644 --- a/src/mongo/db/catalog/views_for_database.cpp +++ b/src/mongo/db/catalog/views_for_database.cpp @@ -61,47 +61,8 @@ std::shared_ptr<const ViewDefinition> ViewsForDatabase::lookup(const NamespaceSt } Status ViewsForDatabase::reload(OperationContext* opCtx) { - 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()) { - stats.userTimeseries += 1; - } else { - stats.userViews += 1; - } - } else { - stats.internal += 1; - } - - viewMap[viewName.ns()] = std::move(viewDef); - return Status::OK(); - }; - try { - durable->iterate(opCtx, reloadCallback); + durable->iterate(opCtx, [&](const BSONObj& view) { return _insert(opCtx, view); }); } catch (const DBException& ex) { auto status = ex.toStatus(); LOGV2(22547, @@ -110,9 +71,60 @@ Status ViewsForDatabase::reload(OperationContext* opCtx) { "error"_attr = status); return status; } + valid = true; + return Status::OK(); +} + +Status ViewsForDatabase::insert(OperationContext* opCtx, const BSONObj& view) { + auto status = _insert(opCtx, view); + if (!status.isOK()) { + LOGV2(5387000, + "Could not insert view", + "db"_attr = durable->getName(), + "error"_attr = status); + return status; + } valid = true; + return Status::OK(); +}; + +Status ViewsForDatabase::_insert(OperationContext* opCtx, const BSONObj& view) { + 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()) { + stats.userTimeseries += 1; + } else { + stats.userViews += 1; + } + } else { + stats.internal += 1; + } + viewMap[viewName.ns()] = std::move(viewDef); return Status::OK(); } @@ -135,7 +147,8 @@ Status ViewsForDatabase::validateCollation(OperationContext* opCtx, Status ViewsForDatabase::upsertIntoGraph(OperationContext* opCtx, const ViewDefinition& viewDef, - const PipelineValidatorFn& validatePipeline) { + const PipelineValidatorFn& validatePipeline, + const bool needsValidation) { // Performs the insert into the graph. auto doInsert = [this, opCtx, &validatePipeline](const ViewDefinition& viewDef, bool needsValidation) -> Status { @@ -190,7 +203,7 @@ Status ViewsForDatabase::upsertIntoGraph(OperationContext* opCtx, // is simply a no-op. viewGraph.remove(viewDef.name()); - return doInsert(viewDef, true); + return doInsert(viewDef, needsValidation); } } // namespace mongo diff --git a/src/mongo/db/catalog/views_for_database.h b/src/mongo/db/catalog/views_for_database.h index 914adf60df7..ab4329bf9d9 100644 --- a/src/mongo/db/catalog/views_for_database.h +++ b/src/mongo/db/catalog/views_for_database.h @@ -93,6 +93,11 @@ public: Status reload(OperationContext* opCtx); /** + * Inserts the view into the view map. + */ + Status insert(OperationContext* opCtx, const BSONObj& view); + + /** * Returns Status::OK if each view namespace in 'refs' has the same default collation as * 'view'. Otherwise, returns ErrorCodes::OptionNotSupportedOnView. */ @@ -103,11 +108,17 @@ public: /** * 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. + * would be invalid. needsValidation can be set to false if the view already exists in the + * durable view catalog and skips checking that the resulting dependency graph is acyclic and + * within the maximum depth. */ Status upsertIntoGraph(OperationContext* opCtx, const ViewDefinition& viewDef, - const PipelineValidatorFn&); + const PipelineValidatorFn&, + bool needsValidation); + +private: + Status _insert(OperationContext* opCtx, const BSONObj& view); }; } // namespace mongo diff --git a/src/mongo/db/op_observer_impl.cpp b/src/mongo/db/op_observer_impl.cpp index 39c07f31b4c..f045cff94ff 100644 --- a/src/mongo/db/op_observer_impl.cpp +++ b/src/mongo/db/op_observer_impl.cpp @@ -611,7 +611,15 @@ void OpObserverImpl::onInserts(OperationContext* opCtx, if (nss.coll() == "system.js") { Scope::storedFuncMod(opCtx); } else if (nss.coll() == DurableViewCatalog::viewsCollectionName()) { - DurableViewCatalog::onExternalChange(opCtx, nss); + try { + for (auto it = first; it != last; it++) { + uassertStatusOK(DurableViewCatalog::onExternalInsert(opCtx, it->doc, nss)); + } + } catch (const DBException&) { + // If a previous operation left the view catalog in an invalid state, our inserts can + // fail even if all the definitions are valid. Reloading may help us reset the state. + DurableViewCatalog::onExternalChange(opCtx, nss); + } } else if (nss == NamespaceString::kSessionTransactionsTableNamespace && !lastOpTime.isNull()) { for (auto it = first; it != last; it++) { MongoDSessionCatalog::observeDirectWriteToConfigTransactions(opCtx, it->doc); diff --git a/src/mongo/db/views/SConscript b/src/mongo/db/views/SConscript index a16bf79890a..94d6b3edd39 100644 --- a/src/mongo/db/views/SConscript +++ b/src/mongo/db/views/SConscript @@ -17,6 +17,7 @@ env.Library( '$BUILD_DIR/mongo/db/audit', '$BUILD_DIR/mongo/db/catalog/database_holder', '$BUILD_DIR/mongo/db/multitenancy', + '$BUILD_DIR/mongo/db/views/view_catalog_helpers', ], ) diff --git a/src/mongo/db/views/durable_view_catalog.cpp b/src/mongo/db/views/durable_view_catalog.cpp index c96b7af6672..9faf0362a31 100644 --- a/src/mongo/db/views/durable_view_catalog.cpp +++ b/src/mongo/db/views/durable_view_catalog.cpp @@ -47,6 +47,7 @@ #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_helpers.h" #include "mongo/logv2/log.h" #include "mongo/stdx/unordered_set.h" #include "mongo/util/assert_util.h" @@ -54,6 +55,53 @@ namespace mongo { +namespace { +void validateViewDefinitionBSON(OperationContext* opCtx, + const BSONObj& viewDefinition, + StringData dbName) { + bool valid = true; + + for (const BSONElement& e : viewDefinition) { + std::string name(e.fieldName()); + valid &= name == "_id" || name == "viewOn" || name == "pipeline" || name == "collation" || + name == "timeseries"; + } + + const auto viewName = viewDefinition["_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() == dbName; + } + + valid &= NamespaceString::validCollectionName(viewDefinition["viewOn"].str()); + + const bool hasPipeline = viewDefinition.hasField("pipeline"); + valid &= hasPipeline; + if (hasPipeline) { + valid &= viewDefinition["pipeline"].type() == mongo::Array; + } + + valid &= (!viewDefinition.hasField("collation") || + viewDefinition["collation"].type() == BSONType::Object); + + valid &= !viewDefinition.hasField("timeseries") || + viewDefinition["timeseries"].type() == BSONType::Object; + + uassert(ErrorCodes::InvalidViewDefinition, + str::stream() << "found invalid view definition " << viewDefinition["_id"] + << " while reading '" + << NamespaceString(dbName, NamespaceString::kSystemDotViewsCollectionName) + << "'", + valid); +} +} // namespace + // DurableViewCatalog void DurableViewCatalog::onExternalChange(OperationContext* opCtx, const NamespaceString& name) { @@ -68,6 +116,33 @@ void DurableViewCatalog::onExternalChange(OperationContext* opCtx, const Namespa catalog->reloadViews(opCtx, name.db()).ignore(); } +Status DurableViewCatalog::onExternalInsert(OperationContext* opCtx, + const BSONObj& doc, + const NamespaceString& name) { + try { + validateViewDefinitionBSON(opCtx, doc, name.toString()); + } catch (const DBException& e) { + return e.toStatus(); + } + + auto catalog = CollectionCatalog::get(opCtx); + NamespaceString viewName(doc.getStringField("_id")); + NamespaceString viewOn(name.db(), doc.getStringField("viewOn")); + BSONArray pipeline(doc.getObjectField("pipeline")); + BSONObj collation(doc.getObjectField("collation")); + // Set updateDurableViewCatalog to false because the view has already been inserted into the + // durable view catalog. + const bool updateDurableViewCatalog = false; + + return catalog->createView(opCtx, + viewName, + viewOn, + pipeline, + collation, + view_catalog_helpers::validatePipeline, + updateDurableViewCatalog); +} + void DurableViewCatalog::onSystemViewsCollectionDrop(OperationContext* opCtx, const NamespaceString& name) { dassert(opCtx->lockState()->isDbLockedForMode(name.db(), MODE_IX)); @@ -127,9 +202,9 @@ void DurableViewCatalogImpl::_iterate(OperationContext* opCtx, try { viewDefinition = _validateViewDefinition(opCtx, record->data); uassertStatusOK(callback(viewDefinition)); - } catch (const ExceptionFor<ErrorCodes::InvalidViewDefinition>& ex) { + } catch (const ExceptionFor<ErrorCodes::InvalidViewDefinition>&) { if (lookupBehavior == ViewCatalogLookupBehavior::kValidateViews) { - throw ex; + throw; } } } @@ -142,46 +217,9 @@ BSONObj DurableViewCatalogImpl::_validateViewDefinition(OperationContext* opCtx, // decimal data even if decimal is disabled. fassert(40224, validateBSON(recordData.data(), recordData.size())); BSONObj viewDefinition = recordData.toBson(); + std::string dbName(_db->name().dbName()); - bool valid = true; - - for (const BSONElement& e : viewDefinition) { - std::string name(e.fieldName()); - valid &= name == "_id" || name == "viewOn" || name == "pipeline" || name == "collation" || - name == "timeseries"; - } - - const auto viewName = viewDefinition["_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().dbName(); - } - - valid &= NamespaceString::validCollectionName(viewDefinition["viewOn"].str()); - - const bool hasPipeline = viewDefinition.hasField("pipeline"); - valid &= hasPipeline; - if (hasPipeline) { - valid &= viewDefinition["pipeline"].type() == mongo::Array; - } - - valid &= (!viewDefinition.hasField("collation") || - viewDefinition["collation"].type() == BSONType::Object); - - valid &= !viewDefinition.hasField("timeseries") || - viewDefinition["timeseries"].type() == BSONType::Object; - - uassert(ErrorCodes::InvalidViewDefinition, - str::stream() << "found invalid view definition " << viewDefinition["_id"] - << " while reading '" << _db->getSystemViewsName() << "'", - valid); - + validateViewDefinitionBSON(opCtx, viewDefinition, dbName); return viewDefinition; } diff --git a/src/mongo/db/views/durable_view_catalog.h b/src/mongo/db/views/durable_view_catalog.h index 0546636225d..6b938c6f54c 100644 --- a/src/mongo/db/views/durable_view_catalog.h +++ b/src/mongo/db/views/durable_view_catalog.h @@ -68,6 +68,14 @@ public: static void onExternalChange(OperationContext* opCtx, const NamespaceString& name); /** + * Thread-safe method to insert into the in-memory view catalog when there is an insert to + * 'system.views'. + */ + static Status onExternalInsert(OperationContext* opCtx, + const BSONObj& doc, + const NamespaceString& name); + + /** * Thread-safe method to clear the in-memory state of the view catalog when the 'system.views' * collection is dropped. */ diff --git a/src/mongo/db/views/view_graph.cpp b/src/mongo/db/views/view_graph.cpp index 4282a0469a4..716dd4e9d32 100644 --- a/src/mongo/db/views/view_graph.cpp +++ b/src/mongo/db/views/view_graph.cpp @@ -126,10 +126,9 @@ void ViewGraph::insertWithoutValidating(const ViewDefinition& view, // pointers for its children. Node* node = &(_graph[nodeId]); invariant(node->children.empty()); - invariant(!static_cast<bool>(node->collator)); node->size = pipelineSize; - node->collator = view.defaultCollator(); + node->collator = CollatorInterface::cloneCollator(view.defaultCollator()); for (const NamespaceString& childNss : refs) { uint64_t childId = _getNodeId(childNss); @@ -165,7 +164,7 @@ void ViewGraph::remove(const NamespaceString& viewNss) { // This node no longer represents a view, so its children must be cleared and its collator // unset. node->children.clear(); - node->collator = boost::none; + node->collator = nullptr; // Only remove node if there are no remaining references to this node. if (node->parents.size() == 0) { diff --git a/src/mongo/db/views/view_graph.h b/src/mongo/db/views/view_graph.h index 2653f1b5aa1..98fefdb91dc 100644 --- a/src/mongo/db/views/view_graph.h +++ b/src/mongo/db/views/view_graph.h @@ -105,11 +105,18 @@ private: // This node represents a view namespace if and only if 'children' is nonempty and 'collator' is // set. struct Node { + Node() = default; + Node(const Node& other) + : ns(other.ns), children(other.children), parents(other.parents), size(other.size) { + if (other.collator) { + collator = CollatorInterface::cloneCollator(other.collator.get()); + } + } + /** * Returns true if this node represents a view. */ bool isView() const { - invariant(children.empty() == !static_cast<bool>(collator)); return !children.empty(); } @@ -124,10 +131,10 @@ private: // Represents the views that depend on this namespace. stdx::unordered_set<uint64_t> parents; - // When set, this is an unowned pointer to the view's collation, or nullptr if the view has - // the binary collation. When not set, this namespace is not a view and we don't care about - // its collator. - boost::optional<const CollatorInterface*> collator; + // When set to nullptr, the view either has the binary collation or this namespace is not a + // view and we don't care about its collator. Verify if view with isView. ViewGraph owns the + // collator in order to keep pointer alive after insertion. + std::unique_ptr<const CollatorInterface> collator; // The size of this view's "pipeline", in bytes. int size = 0; |