// // Copyright 2017 Memgraph // Created by Florijan Stamenkovic on 03.02.17. // #pragma once #include #include "cppitertools/filter.hpp" #include "cppitertools/imap.hpp" #include "graph_db.hpp" #include "storage/edge_accessor.hpp" #include "storage/vertex_accessor.hpp" #include "transactions/transaction.hpp" #include "utils/bound.hpp" /** Thrown when creating an index which already exists. */ class IndexExistsException : public utils::BasicException { using utils::BasicException::BasicException; }; /** * An accessor for the database object: exposes functions * for operating on the database. All the functions in * this class should be self-sufficient: for example the * function for creating * a new Vertex should take care of all the book-keeping around * the creation. */ class GraphDbAccessor { // We need to make friends with this guys since they need to access private // methods for updating indices. friend class RecordAccessor; friend class RecordAccessor; friend class VertexAccessor; friend class EdgeAccessor; public: /** * Creates an accessor for the given database. * * @param db The database */ GraphDbAccessor(GraphDb &db); ~GraphDbAccessor(); // the GraphDbAccessor can NOT be copied nor moved because // 1. it ensures transaction cleanup once it's destructed // 2. it will contain index and side-effect bookkeeping data // which is unique to the transaction (shared_ptr works but slower) GraphDbAccessor(const GraphDbAccessor &other) = delete; GraphDbAccessor(GraphDbAccessor &&other) = delete; GraphDbAccessor &operator=(const GraphDbAccessor &other) = delete; GraphDbAccessor &operator=(GraphDbAccessor &&other) = delete; /** * Returns the name of the database of this accessor. */ const std::string &name() const; /** * Creates a new Vertex and returns an accessor to it. * * @return See above. */ VertexAccessor insert_vertex(); /** * Removes the vertex of the given accessor. If the vertex has any outgoing * or incoming edges, it is not deleted. See `detach_remove_vertex` if you * want to remove a vertex regardless of connectivity. * * If the vertex has already been deleted by the current transaction+command, * this function will not do anything and will return true. * * @param vertex_accessor Accessor to vertex. * @return If or not the vertex was deleted. */ bool remove_vertex(VertexAccessor &vertex_accessor); /** * Removes the vertex of the given accessor along with all it's outgoing * and incoming connections. * * @param vertex_accessor Accessor to a vertex. */ void detach_remove_vertex(VertexAccessor &vertex_accessor); /** * Returns iterable over accessors to all the vertices in the graph * visible to the current transaction. * * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). */ auto vertices(bool current_state) { // wrap version lists into accessors, which will look for visible versions auto accessors = iter::imap([this](auto vlist) { return VertexAccessor(*vlist, *this); }, db_.vertices_.access()); // filter out the accessors not visible to the current transaction return iter::filter( [this, current_state](const VertexAccessor &accessor) { return (accessor.old_ && !(current_state && accessor.old_->is_deleted_by(*transaction_))) || (current_state && accessor.new_ && !accessor.new_->is_deleted_by(*transaction_)); }, std::move(accessors)); } /** * Return VertexAccessors which contain the current label for the current * transaction visibilty. * @param label - label for which to return VertexAccessors * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). * @return iterable collection */ auto vertices(const GraphDbTypes::Label &label, bool current_state) { return iter::imap( [this, current_state](auto vlist) { return VertexAccessor(*vlist, *this); }, db_.labels_index_.GetVlists(label, *transaction_, current_state)); } /** * Return VertexAccessors which contain the current label and property for the * given transaction visibility. * @param label - label for which to return VertexAccessors * @param property - property for which to return VertexAccessors * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). * @return iterable collection */ auto vertices(const GraphDbTypes::Label &label, const GraphDbTypes::Property &property, bool current_state) { debug_assert(db_.label_property_index_.IndexExists( LabelPropertyIndex::Key(label, property)), "Label+property index doesn't exist."); return iter::imap([this, current_state]( auto vlist) { return VertexAccessor(*vlist, *this); }, db_.label_property_index_.GetVlists( LabelPropertyIndex::Key(label, property), *transaction_, current_state)); } /** * Return VertexAccessors which contain the current label + property, and * those properties are equal to this 'value' for the given transaction * visibility. * @param label - label for which to return VertexAccessors * @param property - property for which to return VertexAccessors * @param value - property value for which to return VertexAccessors * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). * @return iterable collection */ auto vertices(const GraphDbTypes::Label &label, const GraphDbTypes::Property &property, const PropertyValue &value, bool current_state) { debug_assert(db_.label_property_index_.IndexExists( LabelPropertyIndex::Key(label, property)), "Label+property index doesn't exist."); debug_assert(value.type() != PropertyValue::Type::Null, "Can't query index for propery value type null."); return iter::imap([this, current_state]( auto vlist) { return VertexAccessor(*vlist, *this); }, db_.label_property_index_.GetVlists( LabelPropertyIndex::Key(label, property), value, *transaction_, current_state)); } /** * Return an iterable over VertexAccessors which contain the * given label and whose property value (for the given property) * falls within the given (lower, upper) @c Bound. * * The returned iterator will only contain * vertices/edges whose property value is comparable with the * given bounds (w.r.t. type). This has implications on Cypher * query execuction semantics which have not been resovled yet. * * At least one of the bounds must be specified. Bonds can't be * @c PropertyValue::Null. If both bounds are * specified, their PropertyValue elments must be of comparable * types. * * @param label - label for which to return VertexAccessors * @param property - property for which to return VertexAccessors * @param lower - Lower bound of the interval. * @param upper - Upper bound of the interval. * @param value - property value for which to return VertexAccessors * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). * @return iterable collection of record accessors * satisfy the bounds and are visible to the current transaction. */ auto vertices( const GraphDbTypes::Label &label, const GraphDbTypes::Property &property, const std::experimental::optional> lower, const std::experimental::optional> upper, bool current_state) { debug_assert(db_.label_property_index_.IndexExists( LabelPropertyIndex::Key(label, property)), "Label+property index doesn't exist."); return iter::imap([this, current_state]( auto vlist) { return VertexAccessor(*vlist, *this); }, db_.label_property_index_.GetVlists( LabelPropertyIndex::Key(label, property), lower, upper, *transaction_, current_state)); } /** * Creates a new Edge and returns an accessor to it. * * @param from The 'from' vertex. * @param to The 'to' vertex' * @param type Edge type. * @return An accessor to the edge. */ EdgeAccessor insert_edge(VertexAccessor &from, VertexAccessor &to, GraphDbTypes::EdgeType type); /** * Removes an edge from the graph. * * @param edge_accessor The accessor to an edge. */ void remove_edge(EdgeAccessor &edge_accessor); /** * Returns iterable over accessors to all the edges in the graph * visible to the current transaction. * * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). */ auto edges(bool current_state) { // wrap version lists into accessors, which will look for visible versions auto accessors = iter::imap([this](auto vlist) { return EdgeAccessor(*vlist, *this); }, db_.edges_.access()); // filter out the accessors not visible to the current transaction return iter::filter( [this, current_state](const EdgeAccessor &accessor) { return (accessor.old_ && !(current_state && accessor.old_->is_deleted_by(*transaction_))) || (current_state && accessor.new_ && !accessor.new_->is_deleted_by(*transaction_)); }, std::move(accessors)); } /** * Return EdgeAccessors which contain the edge_type for the current * transaction visibility. * @param edge_type - edge_type for which to return EdgeAccessors * @param current_state If true then the graph state for the * current transaction+command is returned (insertions, updates and * deletions performed in the current transaction+command are not * ignored). * @return iterable collection */ auto edges(const GraphDbTypes::EdgeType &edge_type, bool current_state) { return iter::imap([this, current_state]( auto vlist) { return EdgeAccessor(*vlist, *this); }, db_.edge_types_index_.GetVlists(edge_type, *transaction_, current_state)); } /** * Adds an index for the given (label, property) and populates * it with existing vertices that belong to it. * * You should never call BuildIndex on a GraphDbAccessor * (transaction) on which new vertices have been inserted or * existing ones updated. Do it in a new accessor instead. * * Build index throws if an index for the given * (label, property) already exists (even if it's being built * by a concurrent transaction and is not yet ready for use). * * @param label - label to build for * @param property - property to build for */ void BuildIndex(const GraphDbTypes::Label &label, const GraphDbTypes::Property &property) { const LabelPropertyIndex::Key key(label, property); if (db_.label_property_index_.CreateIndex(key) == false) { throw IndexExistsException( "Index is either being created by another transaction or already " "exists."); } // Everything that happens after the line above ended will be added to the // index automatically, but we still have to add to index everything that // happened earlier. We have to first wait for every transaction that // happend before, or a bit later than CreateIndex to end. { auto wait_transaction = db_.tx_engine_.Begin(); for (auto id : wait_transaction->snapshot()) { if (id == transaction_->id_) continue; while (wait_transaction->engine_.clog().is_active(id)) // TODO reconsider this constant, currently rule-of-thumb chosen std::this_thread::sleep_for(std::chrono::microseconds(100)); } wait_transaction->Commit(); } // This transaction surely sees everything that happened before CreateIndex. auto transaction = db_.tx_engine_.Begin(); for (auto vertex_vlist : db_.vertices_.access()) { auto vertex_record = vertex_vlist->find(*transaction); // Check if visible record exists, if it exists apply function on it. if (vertex_record == nullptr) continue; db_.label_property_index_.UpdateOnLabelProperty(vertex_vlist, vertex_record); } // Commit transaction as we finished applying method on newest visible // records. transaction->Commit(); // After these two operations we are certain that everything is contained in // the index under the assumption that this transaction contained no // vertex/edge insert/update before this method was invoked. db_.label_property_index_.IndexFinishedBuilding(key); } /** * @brief - Returns true if the given label+property index already exists and * is ready for use. */ bool LabelPropertyIndexExists(const GraphDbTypes::Label &label, const GraphDbTypes::Property &property) const { return db_.label_property_index_.IndexExists( LabelPropertyIndex::Key(label, property)); } /** * @brief - Returns vector of keys of label-property indices. */ std::vector GetIndicesKeys() { return db_.label_property_index_.GetIndicesKeys(); } /** * Return approximate number of all vertices in the database. * Note that this is always an over-estimate and never an under-estimate. */ int64_t vertices_count() const; /* * Return approximate number of all edges in the database. * Note that this is always an over-estimate and never an under-estimate. */ int64_t edges_count() const; /** * Return approximate number of vertices under indexes with the given label. * Note that this is always an over-estimate and never an under-estimate. * @param label - label to check for * @return number of vertices with the given label */ int64_t vertices_count(const GraphDbTypes::Label &label) const; /** * Return approximate number of vertices under indexes with the given label * and property. * Note that this is always an over-estimate and never an under-estimate. * @param label - label to check for * @param property - property to check for * @return number of vertices with the given label, fails if no such * label+property index exists. */ int64_t vertices_count(const GraphDbTypes::Label &label, const GraphDbTypes::Property &property) const; /** * Returns approximate number of vertices that have the given label * and the given value for the given property. * * Assumes that an index for that (label, property) exists. */ int64_t vertices_count(const GraphDbTypes::Label &label, const GraphDbTypes::Property &property, const PropertyValue &value) const; /** * Returns approximate number of vertices that have the given label * and whose vaue is in the range defined by upper and lower @c Bound. * * At least one bound must be specified. Neither can be * PropertyValue::Null. * * Assumes that an index for that (label, property) exists. */ int64_t vertices_count( const GraphDbTypes::Label &label, const GraphDbTypes::Property &property, const std::experimental::optional> lower, const std::experimental::optional> upper) const; /** * Return approximate number of edges under indexes with the given edge_type. * Note that this is always an over-estimate and never an under-estimate. * @param edge_type - edge_type to check for * @return number of edges with the given edge_type */ int64_t edges_count(const GraphDbTypes::EdgeType &edge_type) const; /** * Obtains the Label for the label's name. * @return See above. */ GraphDbTypes::Label label(const std::string &label_name); /** * Obtains the label name (a string) for the given label. * * @param label a Label. * @return See above. */ const std::string &label_name(const GraphDbTypes::Label label) const; /** * Obtains the EdgeType for it's name. * @return See above. */ GraphDbTypes::EdgeType edge_type(const std::string &edge_type_name); /** * Obtains the edge type name (a string) for the given edge type. * * @param edge_type an EdgeType. * @return See above. */ const std::string &edge_type_name( const GraphDbTypes::EdgeType edge_type) const; /** * Obtains the Property for it's name. * @return See above. */ GraphDbTypes::Property property(const std::string &property_name); /** * Obtains the property name (a string) for the given property. * * @param property a Property. * @return See above. */ const std::string &property_name(const GraphDbTypes::Property property) const; /** * Advances transaction's command id by 1. */ void advance_command(); /** * Commit transaction. */ void commit(); /** * Abort transaction. */ void abort(); /** * Return true if transaction is hinted to abort. */ bool should_abort() const; /** * Initializes the record pointers in the given accessor. * The old_ and new_ pointers need to be initialized * with appropriate values, and current_ set to old_ * if it exists and to new_ otherwise. * * @return True if accessor is valid after reconstruction. * This means that at least one record pointer was found * (either new_ or old_), possibly both. */ template bool Reconstruct(RecordAccessor &accessor) { accessor.vlist_->find_set_old_new(*transaction_, accessor.old_, accessor.new_); accessor.current_ = accessor.old_ ? accessor.old_ : accessor.new_; return accessor.old_ != nullptr || accessor.new_ != nullptr; // TODO review: we should never use a record accessor that // does not have either old_ or new_ (both are null), but // we can't assert that here because we construct such an accessor // and filter it out in GraphDbAccessor::[Vertices|Edges] // any ideas? } /** * Update accessor record with vlist. * * It is not legal * to call this function on a Vertex/Edge that has * been deleted in the current transaction+command. * * @args accessor whose record to update if possible. */ template void update(RecordAccessor &accessor) { // can't update a deleted record if: // - we only have old_ and it hasn't been deleted // - we have new_ and it hasn't been deleted if (!accessor.new_) { debug_assert( !accessor.old_->is_deleted_by(*transaction_), "Can't update a record deleted in the current transaction+command"); } else { debug_assert( !accessor.new_->is_deleted_by(*transaction_), "Can't update a record deleted in the current transaction+command"); } if (!accessor.new_) accessor.new_ = accessor.vlist_->update(*transaction_); } private: /** * Insert this vertex into corresponding label and label+property (if it * exists) index. * @param label - label with which to insert vertex label record * @param vertex_accessor - vertex_accessor to insert * @param vertex - vertex record to insert */ void update_label_indices(const GraphDbTypes::Label &label, const VertexAccessor &vertex_accessor, const Vertex *const vertex); /** * Insert this edge into corresponding edge_type index. * @param edge_type - edge_type index into which to insert record * @param edge_accessor - edge_accessor to insert * @param edge - edge record to insert */ void update_edge_type_index(const GraphDbTypes::EdgeType &edge_type, const EdgeAccessor &edge_accessor, const Edge *const edge); /** * Insert this vertex into corresponding any label + 'property' index. * @param property - vertex will be inserted into indexes which contain this * property * @param record_accessor - record_accessor to insert * @param vertex - vertex to insert */ void update_property_index(const GraphDbTypes::Property &property, const RecordAccessor &record_accessor, const Vertex *const vertex); GraphDb &db_; /** The current transaction */ tx::Transaction *const transaction_; bool commited_{false}; bool aborted_{false}; };