From 55f19129108bdd779b5c2d7107bc1287c767d9e1 Mon Sep 17 00:00:00 2001 From: florijan Date: Thu, 16 Feb 2017 15:47:55 +0100 Subject: [PATCH] Properties refactoring: STABLE STATE. Memgraph compiles. Properties are out. Tests are in progress. --- CMakeLists.txt | 1 + include/database/creation_exception.hpp | 7 +- include/database/graph_db.hpp | 3 + include/database/graph_db_accessor.hpp | 22 ++ include/mvcc/version_list.hpp | 1 + include/storage/edge_accessor.hpp | 26 +- include/storage/record_accessor.hpp | 111 +++++---- include/storage/typed_value_store.hpp | 1 - include/storage/vertex_accessor.hpp | 52 +++- src/database/graph_db_accessor.cpp | 65 +++-- src/storage/edge_accessor.cpp | 24 +- src/storage/record_accessor.cpp | 69 ++++++ src/storage/vertex_accessor.cpp | 43 +--- tests/unit/graph_db_accessor.cpp | 266 +++++++++++++++++++++ tests/unit/record_edge_vertex_accessor.cpp | 24 ++ 15 files changed, 580 insertions(+), 135 deletions(-) create mode 100644 src/storage/record_accessor.cpp create mode 100644 tests/unit/graph_db_accessor.cpp create mode 100644 tests/unit/record_edge_vertex_accessor.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ed7c03532..d4c100e43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,6 +316,7 @@ set(memgraph_src_files ${src_dir}/storage/typed_value.cpp ${src_dir}/storage/locking/record_lock.cpp # ${src_dir}/storage/garbage/garbage.cpp + ${src_dir}/storage/record_accessor.cpp ${src_dir}/storage/vertex_accessor.cpp ${src_dir}/storage/edge_accessor.cpp # ${src_dir}/storage/record_accessor.cpp diff --git a/include/database/creation_exception.hpp b/include/database/creation_exception.hpp index d10a39627..d05ffba17 100644 --- a/include/database/creation_exception.hpp +++ b/include/database/creation_exception.hpp @@ -7,11 +7,12 @@ #include "utils/exceptions/basic_exception.hpp" - +/** + * Thrown when something (Edge or a Vertex) can not + * be created. Typically due to database overload. + */ class CreationException : public BasicException { public: using BasicException::BasicException; }; - - diff --git a/include/database/graph_db.hpp b/include/database/graph_db.hpp index ddf141a47..e2387cc8c 100644 --- a/include/database/graph_db.hpp +++ b/include/database/graph_db.hpp @@ -21,6 +21,9 @@ class EdgeAccessor; /** * Main class which represents Database concept in code. + * This class is essentially a data structure. It exposes + * all the data publicly, and should therefore not be directly + * exposed to client functions. The GraphDbAccessor is used for that. */ class GraphDb { diff --git a/include/database/graph_db_accessor.hpp b/include/database/graph_db_accessor.hpp index 6d18fb6e1..8c9f27dfd 100644 --- a/include/database/graph_db_accessor.hpp +++ b/include/database/graph_db_accessor.hpp @@ -9,6 +9,14 @@ #include "transactions/transaction.hpp" +/** + * 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 { public: @@ -50,6 +58,13 @@ public: */ void detach_remove_vertex(VertexAccessor &vertex_accessor); + /** + * Returns accessors to all the vertices in the graph. + * TODO: switch to the Iterator library and map function. + * @return + */ + std::vector vertices(); + /** * Creates a new Edge and returns an accessor to it. * @@ -67,6 +82,13 @@ public: */ void remove_edge(EdgeAccessor& edge_accessor); + /** + * Returns accessors to all the edges in the graph. + * TODO: switch to the Iterator library and map function. + * @return + */ + std::vector edges(); + /** * Obtains the Label for the label's name. * @return See above. diff --git a/include/mvcc/version_list.hpp b/include/mvcc/version_list.hpp index a48bc7891..bba92333c 100644 --- a/include/mvcc/version_list.hpp +++ b/include/mvcc/version_list.hpp @@ -11,6 +11,7 @@ namespace mvcc { template class VersionList { + // TODO what is this Accessor? Dead code? friend class Accessor; public: diff --git a/include/storage/edge_accessor.hpp b/include/storage/edge_accessor.hpp index 0a4797e9c..ab085c879 100644 --- a/include/storage/edge_accessor.hpp +++ b/include/storage/edge_accessor.hpp @@ -6,18 +6,42 @@ #include "utils/reference_wrapper.hpp" #include "database/graph_db.hpp" +// forward declaring the VertexAccessor because it's returned +// by some functions class VertexAccessor; -class EdgeAccessor : public RecordAccessor { +/** + * Provides ways for the client programmer (i.e. code generated + * by the compiler) to interact with an Edge. + * + * This class indirectly inherits MVCC data structures and + * takes care of MVCC versioning. + */ +class EdgeAccessor : public RecordAccessor { public: using RecordAccessor::RecordAccessor; + /** + * Sets a new edge type. + * @param edge_type The new type. + */ void set_edge_type(GraphDb::EdgeType edge_type); + /** + * Returns the edge type. + * @return + */ GraphDb::EdgeType edge_type() const; + /** + * Returns an accessor to the originating Vertex of this edge. + * @return + */ VertexAccessor from() const; + /** + * Returns an accessor to the destination Vertex of this edge. + */ VertexAccessor to() const; // void remove(); diff --git a/include/storage/record_accessor.hpp b/include/storage/record_accessor.hpp index eea1773b3..e269c309c 100644 --- a/include/storage/record_accessor.hpp +++ b/include/storage/record_accessor.hpp @@ -6,7 +6,18 @@ #include "database/graph_db_accessor.hpp" #include "utils/pass_key.hpp" -template +#include "storage/typed_value_store.hpp" + +/** + * An accessor to a database record (an Edge or a Vertex). + * + * Exposes view and update functions to the client programmer. + * Assumes responsibility of doing all the relevant book-keeping + * (such as index updates etc). + * + * @tparam TRecord Type of record (MVCC Version) of the accessor. + */ +template class RecordAccessor { public: @@ -21,47 +32,64 @@ public: */ friend GraphDbAccessor; - RecordAccessor(mvcc::VersionList& vlist, - GraphDbAccessor& db_accessor) - : vlist_(vlist), record_(vlist_.find(db_accessor.transaction_)), db_accessor_(db_accessor) { - assert(record_ != nullptr); - } + /** + * @param vlist MVCC record that this accessor wraps. + * @param db_accessor The DB accessor that "owns" this record accessor. + */ + RecordAccessor(mvcc::VersionList& vlist, GraphDbAccessor& db_accessor); + /** + * @param vlist MVCC record that this accessor wraps. + * @param record MVCC version (that is viewable from this db_accessor.transaction) + * of the given record. Slightly more optimal then the constructor that does not + * accept an already found record. + * @param db_accessor The DB accessor that "owns" this record accessor. + */ RecordAccessor(mvcc::VersionList& vlist, TRecord& record, - GraphDbAccessor& db_accessor) - : vlist_(vlist), record_(&record), db_accessor_(db_accessor) { - assert(record_ != nullptr); - } + GraphDbAccessor& db_accessor); + /** + * Gets the property for the given key. + * @param key + * @return + */ + const TypedValue &PropsAt(GraphDb::Property key) const; + + /** + * Sets a value on the record for the given property. + * + * @tparam TValue Type of the value being set. + * @param key Property key. + * @param value The value to set. + */ template void PropsSet(GraphDb::Property key, TValue value) { - update().props_.set(key, value); + update().properties_.set(key, value); } - size_t PropsErase(GraphDb::Property key) { - return update().props_.erase(key); - } + /** + * Erases the property for the given key. + * + * @param key + * @return + */ + size_t PropsErase(GraphDb::Property key); - const TypedValueStore &Properties() const { - return view().properties_; - } + const TypedValueStore &Properties() const; void PropertiesAccept(std::function handler, - std::function finish = {}) const { - view().props_.Accept(handler, finish); - } + std::function finish = {}) const; // Assumes same transaction friend bool operator==(const RecordAccessor &a, const RecordAccessor &b) { // TODO consider the legitimacy of this comparison - return a.vlist_ == b.vlist_; + return &a.vlist_ == &b.vlist_; } - // Assumes same transaction friend bool operator!=(const RecordAccessor &a, const RecordAccessor &b) { // TODO consider the legitimacy of this comparison - return !(a == b); + return a != b; } /** @@ -69,18 +97,14 @@ public: * * @return See above. */ - GraphDbAccessor& db_accessor() { - return db_accessor_; - } + GraphDbAccessor& db_accessor(); /** - * Returns a const GraphDB accessor of this record accessor. - * - * @return See above. - */ - const GraphDbAccessor& db_accessor() const { - return db_accessor_; - } + * Returns a GraphDB accessor of this record accessor. + * + * @return See above. + */ + const GraphDbAccessor& db_accessor() const; protected: @@ -89,27 +113,14 @@ protected: * * @return See above. */ - TRecord& update() { - // TODO consider renaming this to something more indicative - // of the underlying MVCC functionality (like "new_version" or so) - if (!record_->is_visible_write(db_accessor_.transaction_)) - record_ = vlist_.update(record_, db_accessor_.transaction_); - - return *record_; - } + TRecord& update(); /** * Returns a version of the record that is only for viewing. * * @return See above. */ - const TRecord& view() const { - return *record_; - } - - // The record (edge or vertex) this accessor provides access to. - // Immutable, set in the constructor and never changed. - mvcc::VersionList& vlist_; + const TRecord& view() const; // The database accessor for which this record accessor is created // Provides means of getting to the transaction and database functions. @@ -117,6 +128,10 @@ protected: GraphDbAccessor& db_accessor_; private: + // The record (edge or vertex) this accessor provides access to. + // Immutable, set in the constructor and never changed. + mvcc::VersionList& vlist_; + /* The version of the record currently used in this transaction. Defaults to the * latest viewable version (set in the constructor). After the first update done * through this accessor a new, editable version, is created for this transaction, diff --git a/include/storage/typed_value_store.hpp b/include/storage/typed_value_store.hpp index ab8d4d4f7..bb336eff6 100644 --- a/include/storage/typed_value_store.hpp +++ b/include/storage/typed_value_store.hpp @@ -128,7 +128,6 @@ public: if (finish) finish(); - } private: diff --git a/include/storage/vertex_accessor.hpp b/include/storage/vertex_accessor.hpp index e3d7b2376..0335b113b 100644 --- a/include/storage/vertex_accessor.hpp +++ b/include/storage/vertex_accessor.hpp @@ -8,30 +8,70 @@ #include "utils/iterator/iterator.hpp" #include "database/graph_db.hpp" +// forward declaring the EdgeAccessor because it's returned +// by some functions class EdgeAccessor; -class VertexAccessor : public RecordAccessor { +/** + * Provides ways for the client programmer (i.e. code generated + * by the compiler) to interact with a Vertex. + * + * This class indirectly inherits MVCC data structures and + * takes care of MVCC versioning. + */ +class VertexAccessor : public RecordAccessor { public: using RecordAccessor::RecordAccessor; + /** + * Returns the number of outgoing edges. + * @return + */ size_t out_degree() const; + /** + * Returns the number of incoming edges. + * @return + */ size_t in_degree() const; + /** + * Adds a label to the Vertex. If the Vertex already + * has that label the call has no effect. + * @param label A label. + * @return If or not a new Label was set on this Vertex. + */ bool add_label(GraphDb::Label label); + /** + * Removes a label from the Vertex. + * @param label The label to remove. + * @return The number of removed labels (can be 0 or 1). + */ size_t remove_label(GraphDb::Label label); + /** + * Indicates if the Vertex has the given label. + * @param label A label. + * @return + */ bool has_label(GraphDb::Label label) const; + /** + * Returns all the Labels of the Vertex. + * @return + */ const std::set& labels() const; + /** + * Returns EdgeAccessors for all incoming edges. + * @return + */ std::vector in(); + /** + * Returns EdgeAccessors for all outgoing edges. + * @return + */ std::vector out(); - - // returns if remove was possible due to connections -// bool remove(); -// -// void detach_remove(); }; diff --git a/src/database/graph_db_accessor.cpp b/src/database/graph_db_accessor.cpp index 20da50450..e2d5ae496 100644 --- a/src/database/graph_db_accessor.cpp +++ b/src/database/graph_db_accessor.cpp @@ -1,4 +1,4 @@ -#include +#include "database/creation_exception.hpp" #include "database/graph_db_accessor.hpp" #include "storage/vertex.hpp" @@ -20,7 +20,7 @@ VertexAccessor GraphDbAccessor::insert_vertex() { Vertex *vertex = vertex_vlist->insert(transaction_); // insert the newly created record into the main storage - // TODO make the number of tries configurable configurable + // TODO make the number of tries configurable for (int i = 0; i < 5; ++i) { bool success = db_.vertices_.access().insert(vertex_vlist).second; if (success) @@ -37,9 +37,6 @@ bool GraphDbAccessor::remove_vertex(VertexAccessor &vertex_accessor) { return false; vertex_accessor.vlist_.remove(&vertex_accessor.update(), transaction_); - - // TODO remove the vertex from the main storage once it gets garbage collected - return true; } @@ -55,8 +52,22 @@ void GraphDbAccessor::detach_remove_vertex(VertexAccessor &vertex_accessor) { // mvcc removal of the vertex vertex_accessor.vlist_.remove(&vertex_accessor.update(), transaction_); +} - // TODO remove the vertex from the main storage once it gets garbage collected +std::vector GraphDbAccessor::vertices() { + auto sl_accessor = db_.vertices_.access(); + + std::vector accessors; + accessors.reserve(sl_accessor.size()); + + for (auto vlist : sl_accessor){ + auto record = vlist->find(transaction_); + if (record == nullptr) + continue; + accessors.emplace_back(*vlist, *record, *this); + } + + return accessors; } EdgeAccessor GraphDbAccessor::insert_edge( @@ -84,23 +95,39 @@ EdgeAccessor GraphDbAccessor::insert_edge( throw CreationException("Unable to create an Edge after 5 attempts"); } +/** + * Removes the given edge pointer from a vector of pointers. + * Does NOT maintain edge pointer ordering (for efficiency). + */ +void swap_out_edge(std::vector*> &edges, mvcc::VersionList *edge) { + auto found = std::find(edges.begin(), edges.end(), edge); + assert(found != edges.end()); + std::swap(*found, edges.back()); + edges.pop_back(); +} + void GraphDbAccessor::remove_edge(EdgeAccessor& edge_accessor) { - // remove this edge's reference from the "from" vertex - auto& vertex_from = edge_accessor.from().update(); - std::remove(vertex_from.out_.begin(), - vertex_from.out_.end(), - &edge_accessor.vlist_); - - // remove this edge's reference from the "to" vertex - auto& vertex_to = edge_accessor.to().update(); - std::remove(vertex_to.in_.begin(), - vertex_to.in_.end(), - &edge_accessor.vlist_); - - // remove this record from the database via MVCC + swap_out_edge(edge_accessor.from().update().out_, &edge_accessor.vlist_); + swap_out_edge(edge_accessor.to().update().in_, &edge_accessor.vlist_); edge_accessor.vlist_.remove(&edge_accessor.update(), transaction_); } +std::vector GraphDbAccessor::edges() { + auto sl_accessor = db_.edges_.access(); + + std::vector accessors; + accessors.reserve(sl_accessor.size()); + + for (auto vlist : sl_accessor){ + auto record = vlist->find(transaction_); + if (record == nullptr) + continue; + accessors.emplace_back(*vlist, *record, *this); + } + + return accessors; +} + GraphDb::Label GraphDbAccessor::label(const std::string& label_name) { return &(*db_.labels_.access().insert(label_name).first); } diff --git a/src/storage/edge_accessor.cpp b/src/storage/edge_accessor.cpp index c471ff414..408043f34 100644 --- a/src/storage/edge_accessor.cpp +++ b/src/storage/edge_accessor.cpp @@ -2,11 +2,11 @@ #include "storage/vertex_accessor.hpp" void EdgeAccessor::set_edge_type(GraphDb::EdgeType edge_type) { - this->update().edge_type_ = edge_type; + update().edge_type_ = edge_type; } GraphDb::EdgeType EdgeAccessor::edge_type() const { - return this->view().edge_type_; + return view().edge_type_; } VertexAccessor EdgeAccessor::from() const { @@ -14,23 +14,5 @@ VertexAccessor EdgeAccessor::from() const { } VertexAccessor EdgeAccessor::to() const { - return VertexAccessor(view().to_, db_accessor_); +return VertexAccessor(view().to_, db_accessor_); } - -//void EdgeAccessor::remove() { -// // remove this edge's reference from the "from" vertex -// auto& vertex_from = from().update(); -// std::remove(vertex_from.out_.begin(), -// vertex_from.out_.end(), -// vlist_); -// -// // remove this edge's reference from the "to" vertex -// auto& vertex_to = to().update(); -// std::remove(vertex_to.in_.begin(), -// vertex_to.in_.end(), -// vlist_); -// -// // remove this record from the database via MVCC -// vlist_.remove(&update(), db_accessor_.transaction_); -//} - diff --git a/src/storage/record_accessor.cpp b/src/storage/record_accessor.cpp new file mode 100644 index 000000000..b00d4b024 --- /dev/null +++ b/src/storage/record_accessor.cpp @@ -0,0 +1,69 @@ +#include "storage/record_accessor.hpp" +#include "storage/edge.hpp" +#include "storage/vertex.hpp" + +template +RecordAccessor::RecordAccessor(mvcc::VersionList& vlist, + GraphDbAccessor& db_accessor) + : vlist_(vlist), record_(vlist_.find(db_accessor.transaction_)), db_accessor_(db_accessor) { + assert(record_ != nullptr); +} + +template +RecordAccessor::RecordAccessor(mvcc::VersionList& vlist, + TRecord& record, + GraphDbAccessor& db_accessor) + : vlist_(vlist), record_(&record), db_accessor_(db_accessor) { + assert(record_ != nullptr); +} + +template +const TypedValue &RecordAccessor::PropsAt(GraphDb::Property key) const { + return view().properties_.at(key); +} + + +template +size_t RecordAccessor::PropsErase(GraphDb::Property key) { + return update().properties_.erase(key); +} + +template +const TypedValueStore &RecordAccessor::Properties() const { + return view().properties_; +} + +template +void RecordAccessor::PropertiesAccept( + std::function handler, + std::function finish) const { + view().properties_.Accept(handler, finish); +} + + +template +GraphDbAccessor &RecordAccessor::db_accessor() { + return db_accessor_; +} + +template +const GraphDbAccessor &RecordAccessor::db_accessor() const { + return db_accessor_; +} + +template +TRecord &RecordAccessor::update() { + if (!record_->is_visible_write(db_accessor_.transaction_)) + record_ = vlist_.update(db_accessor_.transaction_); + + return *record_; +} + +template +const TRecord &RecordAccessor::view() const { + return *record_; +} + +template class RecordAccessor; +template class RecordAccessor; + diff --git a/src/storage/vertex_accessor.cpp b/src/storage/vertex_accessor.cpp index fe5d51fec..c6480fa52 100644 --- a/src/storage/vertex_accessor.cpp +++ b/src/storage/vertex_accessor.cpp @@ -3,19 +3,19 @@ #include "storage/util.hpp" size_t VertexAccessor::out_degree() const { - return this->view().out_.size(); + return view().out_.size(); } size_t VertexAccessor::in_degree() const { - return this->view().in_.size(); + return view().in_.size(); } bool VertexAccessor::add_label(GraphDb::Label label) { - return this->update().labels_.emplace(label).second; + return update().labels_.emplace(label).second; } size_t VertexAccessor::remove_label(GraphDb::Label label) { - return this->update().labels_.erase(label); + return update().labels_.erase(label); } bool VertexAccessor::has_label(GraphDb::Label label) const { @@ -28,38 +28,9 @@ const std::set& VertexAccessor::labels() const { } std::vector VertexAccessor::in() { - const Vertex& record = view(); - std::vector in; - in.reserve(record.in_.size()); - for (auto edge_vlist_ptr : record.in_) - in.emplace_back(EdgeAccessor(*edge_vlist_ptr, db_accessor_)); - - return in; -} - -std::vector VertexAccessor::out() { return make_accessors(view().in_, db_accessor_); } - -//bool VertexAccessor::remove() { -// // TODO consider if this works well with MVCC -// if (out_degree() > 0 || in_degree() > 0) -// return false; -// -// vlist_.remove(&update(), db_accessor_.transaction_); -// return true; -//} -// -//void VertexAccessor::detach_remove() { -// // removing edges via accessors is both safe -// // and it should remove all the pointers in the relevant -// // vertices (including this one) -// for (auto edge_vlist : view().out_) -// EdgeAccessor(*edge_vlist, db_accessor_).remove(); -// -// for (auto edge_vlist : view().in_) -// EdgeAccessor(*edge_vlist, db_accessor_).remove(); -// -// vlist_.remove(&update(), db_accessor_.transaction_); -//} +std::vector VertexAccessor::out() { + return make_accessors(view().out_, db_accessor_); +} diff --git a/tests/unit/graph_db_accessor.cpp b/tests/unit/graph_db_accessor.cpp new file mode 100644 index 000000000..a42000887 --- /dev/null +++ b/tests/unit/graph_db_accessor.cpp @@ -0,0 +1,266 @@ +#include "gtest/gtest.h" + +#include "dbms/dbms.hpp" +#include "database/graph_db.hpp" +#include "database/graph_db_accessor.hpp" + +#include "storage/vertex_accessor.hpp" +#include "storage/edge_accessor.hpp" + + +size_t CountVertices(GraphDbAccessor &db_accessor) { + size_t r_val = 0; + for (auto va : db_accessor.vertices()) + r_val++; + + return r_val; +} + +size_t CountEdges(GraphDbAccessor &db_accessor) { + size_t r_val = 0; + for (auto va : db_accessor.edges()) + r_val++; + + return r_val; +} + +TEST(GraphDbAccessorTest, DbmsCreateDefault) { + Dbms dbms; + GraphDbAccessor accessor = dbms.active(); + EXPECT_EQ(accessor.name(), "default"); +} + +TEST(GraphDbAccessorTest, InsertVertex) { + Dbms dbms; + GraphDbAccessor accessor = dbms.active(); + + EXPECT_EQ(CountVertices(accessor), 0); + + accessor.insert_vertex(); + EXPECT_EQ(CountVertices(accessor), 1); + + accessor.insert_vertex(); + EXPECT_EQ(CountVertices(accessor), 2); +} + +TEST(GraphDbAccessorTest, RemoveVertexSameTransaction) { + Dbms dbms; + GraphDbAccessor accessor = dbms.active(); + + EXPECT_EQ(CountVertices(accessor), 0); + + auto va1 = accessor.insert_vertex(); + EXPECT_EQ(CountVertices(accessor), 1); + + EXPECT_TRUE(accessor.remove_vertex(va1)); + EXPECT_EQ(CountVertices(accessor), 0); +} + +TEST(GraphDbAccessorTest, RemoveVertexDifferentTransaction) { + Dbms dbms; + + // first transaction creates a vertex + GraphDbAccessor accessor1 = dbms.active(); + accessor1.insert_vertex(); + accessor1.transaction_.commit(); + + // second transaction checks that it sees it, and deletes it + GraphDbAccessor accessor2 = dbms.active(); + EXPECT_EQ(CountVertices(accessor2), 1); + for (auto vertex_accessor : accessor2.vertices()) + accessor2.remove_vertex(vertex_accessor); + accessor2.transaction_.commit(); + + // third transaction checks that it does not see the vertex + GraphDbAccessor accessor3 = dbms.active(); + EXPECT_EQ(CountVertices(accessor3), 0); +} + +TEST(GraphDbAccessorTest, InsertEdge) { + Dbms dbms; + GraphDbAccessor dba = dbms.active(); + + auto va1 = dba.insert_vertex(); + auto va2 = dba.insert_vertex(); + EXPECT_EQ(va1.in().size(), 0); + EXPECT_EQ(va1.out().size(), 0); + EXPECT_EQ(va2.in().size(), 0); + EXPECT_EQ(va2.out().size(), 0); + + // setup (v1) - [:likes] -> (v2) + dba.insert_edge(va1, va2, dba.edge_type("likes")); + EXPECT_EQ(CountEdges(dba), 1); + EXPECT_EQ(va1.out()[0].to(), va2); + EXPECT_EQ(va2.in()[0].from(), va1); + EXPECT_EQ(va1.in().size(), 0); + EXPECT_EQ(va1.out().size(), 1); + EXPECT_EQ(va2.in().size(), 1); + EXPECT_EQ(va2.out().size(), 0); + + // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) + auto va3 = dba.insert_vertex(); + dba.insert_edge(va3, va2, dba.edge_type("hates")); + EXPECT_EQ(CountEdges(dba), 2); + EXPECT_EQ(va3.out()[0].to(), va2); + EXPECT_EQ(va1.in().size(), 0); + EXPECT_EQ(va1.out().size(), 1); + EXPECT_EQ(va2.in().size(), 2); + EXPECT_EQ(va2.out().size(), 0); + EXPECT_EQ(va3.in().size(), 0); + EXPECT_EQ(va3.out().size(), 1); +} + +TEST(GraphDbAccessorTest, RemoveEdge) { + Dbms dbms; + GraphDbAccessor dba1 = dbms.active(); + + // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) + auto va1 = dba1.insert_vertex(); + auto va2 = dba1.insert_vertex(); + auto va3 = dba1.insert_vertex(); + dba1.insert_edge(va1, va2, dba1.edge_type("likes")); + dba1.insert_edge(va3, va2, dba1.edge_type("hates")); + EXPECT_EQ(CountEdges(dba1), 2); + + // remove all [:hates] edges + dba1.transaction_.commit(); + GraphDbAccessor dba2 = dbms.active(); + EXPECT_EQ(CountEdges(dba2), 2); + for (auto edge : dba2.edges()) + if (edge.edge_type() == dba2.edge_type("hates")) + dba2.remove_edge(edge); + + // current state: (v1) - [:likes] -> (v2), (v3) + dba2.transaction_.commit(); + GraphDbAccessor dba3 = dbms.active(); + EXPECT_EQ(CountEdges(dba3), 1); + EXPECT_EQ(CountVertices(dba3), 3); + for (auto edge : dba3.edges()) { + EXPECT_EQ(edge.edge_type(), dba3.edge_type("likes")); + auto v1 = edge.from(); + auto v2 = edge.to(); + + // ensure correct connectivity for all the vertices + for (auto vertex : dba3.vertices()) { + if (vertex == v1) { + EXPECT_EQ(vertex.in().size(), 0); + EXPECT_EQ(vertex.out().size(), 1); + } else if (vertex == v2) { + EXPECT_EQ(vertex.in().size(), 1); + EXPECT_EQ(vertex.out().size(), 0); + } else { + EXPECT_EQ(vertex.in().size(), 0); + EXPECT_EQ(vertex.out().size(), 0); + } + } + } +} + +TEST(GraphDbAccessorTest, DetachRemoveVertex) { + + Dbms dbms; + GraphDbAccessor dba1 = dbms.active(); + + // setup (v1) - [:likes] -> (v2) <- [:hates] - (v3) + auto va1 = dba1.insert_vertex(); + auto va2 = dba1.insert_vertex(); + auto va3 = dba1.insert_vertex(); + dba1.insert_edge(va1, va2, dba1.edge_type("likes")); + dba1.insert_edge(va1, va3, dba1.edge_type("likes")); + + // ensure that plain remove does NOT work + EXPECT_EQ(CountVertices(dba1), 3); + EXPECT_EQ(CountEdges(dba1), 2); + EXPECT_FALSE(dba1.remove_vertex(va1)); + EXPECT_FALSE(dba1.remove_vertex(va2)); + EXPECT_FALSE(dba1.remove_vertex(va3)); + EXPECT_EQ(CountVertices(dba1), 3); + EXPECT_EQ(CountEdges(dba1), 2); + + // make a new transaction because at the moment deletions + // in the same transaction are not visible + // DETACH REMOVE V3 + // new situation: (v1) - [:likes] -> (v2) + dba1.detach_remove_vertex(va3); + dba1.transaction_.commit(); + GraphDbAccessor dba2 = dbms.active(); + + EXPECT_EQ(CountVertices(dba2), 2); + EXPECT_EQ(CountEdges(dba2), 1); + for (auto va : dba2.vertices()) + EXPECT_FALSE(dba2.remove_vertex(va)); + + dba2.transaction_.commit(); + GraphDbAccessor dba3 = dbms.active(); + EXPECT_EQ(CountVertices(dba3), 2); + EXPECT_EQ(CountEdges(dba3), 1); + + for (auto va : dba3.vertices()) { + EXPECT_FALSE(dba3.remove_vertex(va)); + dba3.detach_remove_vertex(va); + break; + } + + dba3.transaction_.commit(); + GraphDbAccessor dba4 = dbms.active(); + EXPECT_EQ(CountVertices(dba4), 1); + EXPECT_EQ(CountEdges(dba4), 0); + + // remove the last vertex, it has no connections + // so that should work + for (auto va : dba4.vertices()) + EXPECT_TRUE(dba4.remove_vertex(va)); + + dba4.transaction_.commit(); + GraphDbAccessor dba5 = dbms.active(); + EXPECT_EQ(CountVertices(dba5), 0); + EXPECT_EQ(CountEdges(dba5), 0); +} + +TEST(GraphDbAccessorTest, Labels) { + Dbms dbms; + GraphDbAccessor dba1 = dbms.active(); + + GraphDb::Label label_friend = dba1.label("friend"); + EXPECT_EQ(label_friend, dba1.label("friend")); + EXPECT_NE(label_friend, dba1.label("friend2")); + EXPECT_EQ(dba1.label_name(label_friend), "friend"); + + // test that getting labels through a different accessor works + EXPECT_EQ(label_friend, dbms.active().label("friend")); + EXPECT_NE(label_friend, dbms.active().label("friend2")); +} + +TEST(GraphDbAccessorTest, EdgeTypes) { + Dbms dbms; + GraphDbAccessor dba1 = dbms.active(); + + GraphDb::EdgeType edge_type = dba1.edge_type("likes"); + EXPECT_EQ(edge_type, dba1.edge_type("likes")); + EXPECT_NE(edge_type, dba1.edge_type("hates")); + EXPECT_EQ(dba1.edge_type_name(edge_type), "likes"); + + // test that getting labels through a different accessor works + EXPECT_EQ(edge_type, dbms.active().edge_type("likes")); + EXPECT_NE(edge_type, dbms.active().edge_type("hates")); +} + +TEST(GraphDbAccessorTest, Properties) { + Dbms dbms; + GraphDbAccessor dba1 = dbms.active(); + + GraphDb::EdgeType prop = dba1.property("name"); + EXPECT_EQ(prop, dba1.property("name")); + EXPECT_NE(prop, dba1.property("surname")); + EXPECT_EQ(dba1.property_name(prop), "name"); + + // test that getting labels through a different accessor works + EXPECT_EQ(prop, dbms.active().property("name")); + EXPECT_NE(prop, dbms.active().property("surname")); +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/unit/record_edge_vertex_accessor.cpp b/tests/unit/record_edge_vertex_accessor.cpp new file mode 100644 index 000000000..cce389344 --- /dev/null +++ b/tests/unit/record_edge_vertex_accessor.cpp @@ -0,0 +1,24 @@ + +#include "gtest/gtest.h" + +#include "dbms/dbms.hpp" +#include "database/graph_db.hpp" +#include "database/graph_db_accessor.hpp" + +#include "storage/typed_value.hpp" +#include "storage/vertex_accessor.hpp" +#include "storage/edge_accessor.hpp" + + +TEST(RecordAccessor, PropertySet) { + + Dbms dbms; + GraphDbAccessor dba = dbms.active(); + + auto vertex = dba.insert_vertex(); + auto property = dba.property("PropName"); + + vertex.PropsSet(property, 42); + EXPECT_EQ(vertex.PropsAt(property).Value(), 42); + EXPECT_TRUE((vertex.PropsAt(dba.property("Other")) == TypedValue::Null).Value()); +}