backend/schema/graph/schema_graph_editor.h (179 lines of code) (raw):

// // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef THIRD_PARTY_CLOUD_SPANNER_EMULATOR_BACKEND_SCHEMA_GRAPH_SCHEMA_GRAPH_EDITOR_H_ #define THIRD_PARTY_CLOUD_SPANNER_EMULATOR_BACKEND_SCHEMA_GRAPH_SCHEMA_GRAPH_EDITOR_H_ #include <algorithm> #include <memory> #include <vector> #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "backend/common/case.h" #include "backend/schema/graph/schema_graph.h" #include "backend/schema/graph/schema_node.h" #include "backend/schema/graph/schema_objects_pool.h" #include "backend/schema/updater/schema_validation_context.h" #include "zetasql/base/ret_check.h" #include "absl/status/status.h" #include "zetasql/base/status_macros.h" namespace google { namespace spanner { namespace emulator { namespace backend { // SchemaGraphEditor clones a SchemaGraph of immutable SchemaNode(s) and applies // changes to it. // // Multiple nodes may be added to the schema graph or modified but only a // single node may be deleted at a time. Cascading deletes should be handled // by calling is_deleted()/MarkDeleted() on referenced/referencing nodes // respectively inside DeepClone(). // // Updates/deletions are made on clones of the nodes in the original graph and // the CanonicalizeGraph() method must be called to obtain a new graph with the // additions/updates/deletions applied. An instance of SchemaGraphEditor should // not be re-used after a call to CanonicalizeGraph(). Additions, Edits and // Deletions maintain the same relative order of nodes in the new graph as in // the original graph. // // During CanonicalizeGraph(), this class may call Validate() and // ValidateUpdate() on the SchemaNodes(s) in the new and old graph respectively. // Validate() and ValidateUpdate() are called in the same order in which the // nodes were added to the containing SchemaGraph. class SchemaGraphEditor { public: SchemaGraphEditor(const SchemaGraph* original_graph, SchemaValidationContext* context) : original_graph_(original_graph), context_(context), cloned_pool_(std::make_unique<SchemaObjectsPool>()) { context_->set_added_nodes(&added_nodes_); } template <typename T> using EditCallback = std::function<absl::Status(typename T::Editor*)>; // Creates a modifiable clone of 'node'. If the node is already cloned // for modifcation, re-uses the existing clone. The clone is modified through // a callback which is passed a T::Editor object. `T` should be a concrete // type implementing the SchemaNode interface and should provide for // construction of a T::Editor(T*) object that will be used to access the // internal state of the clone(of type T) for modifications. During editing, // the callback can signal validation or other errors by returning a non-OK // status. When the graph is canonicalized, the modified clone replaces all // references to the original node. template <typename T> absl::Status EditNode(const SchemaNode* node, const EditCallback<T>& edit_cb) { ZETASQL_RET_CHECK(deleted_nodes_.empty()) << "Graph has deleted nodes. It must be canonicalized before " << "making further changes."; // Get an editable node. T* editable = const_cast<SchemaNode*>(node)->As<T>(); ZETASQL_RET_CHECK_NE(editable, nullptr); // Clone the node if it already exists. if (IsOriginalNode(node)) { // Create a clone of the schema first. if (clone_map_.empty()) { ZETASQL_RETURN_IF_ERROR(InitCloneMap()); } // Edit the clone. const auto* clone = FindClone(node); ZETASQL_RET_CHECK_NE(clone, nullptr); editable = const_cast<SchemaNode*>(clone)->As<T>(); ZETASQL_RET_CHECK_NE(editable, nullptr); edited_clones_.insert(clone); } // Edit the node. typename T::Editor editor(editable); return edit_cb(&editor); } // Marks 'node' as deleted from the graph. The delete may result in // cascading deletes depending on how the different SchemaNode(s) // handle deletes of children in their respective DeepClone() // implementations. absl::Status DeleteNode(const SchemaNode* node); // Adds 'node' to the graph. absl::Status AddNode(std::unique_ptr<const SchemaNode> node); // Makes a new clone of the schema graph, fixing up node-relationships // after edits and calling validation on the node graph being edited. absl::StatusOr<std::unique_ptr<SchemaGraph>> CanonicalizeGraph(); // Deep-clones starting from the SchemaNode 'node' in schema graph. Any // nodes reachable from 'node' in the schema graph will also be cloned and // owned by the 'cloned_pool_'. As a result this method doesn't guarantee to // clone the entire schema graph, only the sub-graph reachable from 'node' and // ownership of the cloned sub-graph cannot be released from this class. // Callers should not directly call this method. absl::StatusOr<const SchemaNode*> Clone(const SchemaNode* node); // Clones an iterable container of nodes in-place. Erases deleted nodes. // Should only be used to clone a container of nodes on which there is no // dependency of the owning referencing node as deleted nodes in the container // will be erased, preventing the owning node from learning about their // deletion through the `is_deleted()` method. template <typename T, typename C> absl::Status CloneContainer(C* nodes) { for (auto it = nodes->begin(); it != nodes->end();) { ZETASQL_ASSIGN_OR_RETURN(const auto* schema_node, Clone(*it)); if (schema_node->is_deleted()) { it = nodes->erase(it); } else { *it = schema_node->template As<T>(); ++it; } } return absl::OkStatus(); } // Clones a vector of nodes in-place. Erases deleted nodes. template <typename T> absl::Status CloneVector(std::vector<const T*>* nodes) { return CloneContainer<T>(nodes); } template <typename T> absl::Status CloneOrDeleteSchemaObjectsNameMappings( std::vector<const T*>& schema_objects, CaseInsensitiveStringMap<const T*>& schema_objects_map) { for (auto it = schema_objects.begin(); it != schema_objects.end();) { ZETASQL_ASSIGN_OR_RETURN(const auto* schema_node, Clone(*it)); if (schema_node->is_deleted()) { schema_objects_map.erase((*it)->Name()); schema_objects.erase(it); } else { const T* cloned_object = schema_node->template As<const T>(); *it = cloned_object; schema_objects_map[cloned_object->Name()] = cloned_object; ++it; } } return absl::OkStatus(); } // Returns true if the graph has any modifications. bool HasModifications() const { return !deleted_nodes_.empty() || !edited_clones_.empty() || !added_nodes_.empty(); } // Returns a pointer to the original schema graph. const SchemaGraph* original_graph() const { return original_graph_; } private: const SchemaNode* FindClone(const SchemaNode* node) const { auto it = clone_map_.find(node); if (it == clone_map_.end()) { return nullptr; } return it->second; } enum NodeKind { kOriginal = 0, kAdded = 1, kEdited = 2, kDropped = 3, kCloned = 4, }; std::string NodeKindString(const SchemaNode* node) const { switch (GetNodeKind(node)) { case kOriginal: return "ORIGINAL"; case kAdded: return "ADDED"; case kEdited: return "EDITED"; case kDropped: return "DROPPED"; case kCloned: return "CLONED"; } } NodeKind GetNodeKind(const SchemaNode* node) const { if (IsOriginalNode(node)) { return kOriginal; } auto it = std::find_if( added_nodes_.begin(), added_nodes_.end(), [&node](const std::unique_ptr<const SchemaNode>& node_ptr) { return node_ptr.get() == node; }); if (it != added_nodes_.end()) { return kAdded; } auto it_deleted = std::find_if( deleted_nodes_.begin(), deleted_nodes_.end(), [&node](const SchemaNode* node_ptr) { return node_ptr == node; }); if (it_deleted != deleted_nodes_.end()) { return kDropped; } const auto* clone = FindClone(node); if (edited_clones_.contains(clone)) { return kEdited; } return kCloned; } int num_original_nodes() const { return original_graph_->GetSchemaNodes().size(); } // Clones the original schema and creates the mapping of // original nodes to clones. absl::Status InitCloneMap(); // Returns OK if 'node' is present in the original graph. bool IsOriginalNode(const SchemaNode* node) const; // Creates and registers a clone for 'node'. SchemaNode* MakeNewClone(const SchemaNode* node); // Updates the pointers to other SchemaNodes in the graph held by 'node' // to point to their canonicalized versions. Does not create any clones. absl::Status Fixup(const SchemaNode* node); absl::Status FixupInternal(const SchemaNode* original, SchemaNode* mutable_clone); // Canonicalizes the graph to process any pending edits/additions. absl::Status CanonicalizeEdits(); // Canonicalizes the graph to process any pending deletes. absl::Status CanonicalizeDeletion(); // The current depth of the cloning stack. int depth_ = 0; // The original graph. const SchemaGraph* original_graph_ = nullptr; // Validation context passed to Validate() and ValidateUpdate() methods for // SchemaNode. // This is also being used in SchemaUpdaterImpl. This is kept as a pointer // to ensure that updates to context in SchemaUpdaterImpl are reflected here. SchemaValidationContext* context_ = nullptr; // Canonicalization/cloning state. // ----------------------- // Number of deleted nodes. int trimmed_ = 0; // Mapping of original nodes to clones. absl::flat_hash_map<const SchemaNode*, const SchemaNode*> clone_map_; // If true, the SchemaGraph is being visited in the delete fixup phase. bool delete_fixup_ = false; // The set of nodes (cloned + newly added) that will constitue // the new SchemaGraph. std::vector<const SchemaNode*> new_nodes_; // The pool to store cloned schema objects in. std::unique_ptr<SchemaObjectsPool> cloned_pool_; // Editing state. // ----------------------- // The nodes being deleted. std::vector<const SchemaNode*> deleted_nodes_; // The nodes added to the graph. std::vector<std::unique_ptr<const SchemaNode>> added_nodes_; // Clones that were modified/edited. absl::flat_hash_set<const SchemaNode*> edited_clones_; }; } // namespace backend } // namespace emulator } // namespace spanner } // namespace google #endif // THIRD_PARTY_CLOUD_SPANNER_EMULATOR_BACKEND_SCHEMA_GRAPH_SCHEMA_GRAPH_EDITOR_H_