Properties refactoring: STABLE STATE. Memgraph compiles. Properties are out. Tests are in progress.

This commit is contained in:
florijan 2017-02-16 15:47:55 +01:00
parent 70a8b93b0b
commit 55f1912910
15 changed files with 580 additions and 135 deletions

View File

@ -316,6 +316,7 @@ set(memgraph_src_files
${src_dir}/storage/typed_value.cpp ${src_dir}/storage/typed_value.cpp
${src_dir}/storage/locking/record_lock.cpp ${src_dir}/storage/locking/record_lock.cpp
# ${src_dir}/storage/garbage/garbage.cpp # ${src_dir}/storage/garbage/garbage.cpp
${src_dir}/storage/record_accessor.cpp
${src_dir}/storage/vertex_accessor.cpp ${src_dir}/storage/vertex_accessor.cpp
${src_dir}/storage/edge_accessor.cpp ${src_dir}/storage/edge_accessor.cpp
# ${src_dir}/storage/record_accessor.cpp # ${src_dir}/storage/record_accessor.cpp

View File

@ -7,11 +7,12 @@
#include "utils/exceptions/basic_exception.hpp" #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 { class CreationException : public BasicException {
public: public:
using BasicException::BasicException; using BasicException::BasicException;
}; };

View File

@ -21,6 +21,9 @@ class EdgeAccessor;
/** /**
* Main class which represents Database concept in code. * 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 { class GraphDb {

View File

@ -9,6 +9,14 @@
#include "transactions/transaction.hpp" #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 { class GraphDbAccessor {
public: public:
@ -50,6 +58,13 @@ public:
*/ */
void detach_remove_vertex(VertexAccessor &vertex_accessor); 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<VertexAccessor> vertices();
/** /**
* Creates a new Edge and returns an accessor to it. * Creates a new Edge and returns an accessor to it.
* *
@ -67,6 +82,13 @@ public:
*/ */
void remove_edge(EdgeAccessor& edge_accessor); 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<EdgeAccessor> edges();
/** /**
* Obtains the Label for the label's name. * Obtains the Label for the label's name.
* @return See above. * @return See above.

View File

@ -11,6 +11,7 @@ namespace mvcc {
template<class T> template<class T>
class VersionList { class VersionList {
// TODO what is this Accessor? Dead code?
friend class Accessor; friend class Accessor;
public: public:

View File

@ -6,18 +6,42 @@
#include "utils/reference_wrapper.hpp" #include "utils/reference_wrapper.hpp"
#include "database/graph_db.hpp" #include "database/graph_db.hpp"
// forward declaring the VertexAccessor because it's returned
// by some functions
class VertexAccessor; class VertexAccessor;
class EdgeAccessor : public RecordAccessor<Edge, EdgeAccessor> { /**
* 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<Edge> {
public: public:
using RecordAccessor::RecordAccessor; using RecordAccessor::RecordAccessor;
/**
* Sets a new edge type.
* @param edge_type The new type.
*/
void set_edge_type(GraphDb::EdgeType edge_type); void set_edge_type(GraphDb::EdgeType edge_type);
/**
* Returns the edge type.
* @return
*/
GraphDb::EdgeType edge_type() const; GraphDb::EdgeType edge_type() const;
/**
* Returns an accessor to the originating Vertex of this edge.
* @return
*/
VertexAccessor from() const; VertexAccessor from() const;
/**
* Returns an accessor to the destination Vertex of this edge.
*/
VertexAccessor to() const; VertexAccessor to() const;
// void remove(); // void remove();

View File

@ -6,7 +6,18 @@
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "utils/pass_key.hpp" #include "utils/pass_key.hpp"
template<typename TRecord, typename TDerived> #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<typename TRecord>
class RecordAccessor { class RecordAccessor {
public: public:
@ -21,47 +32,64 @@ public:
*/ */
friend GraphDbAccessor; friend GraphDbAccessor;
RecordAccessor(mvcc::VersionList<TRecord>& vlist, /**
GraphDbAccessor& db_accessor) * @param vlist MVCC record that this accessor wraps.
: vlist_(vlist), record_(vlist_.find(db_accessor.transaction_)), db_accessor_(db_accessor) { * @param db_accessor The DB accessor that "owns" this record accessor.
assert(record_ != nullptr); */
} RecordAccessor(mvcc::VersionList<TRecord>& 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<TRecord>& vlist, RecordAccessor(mvcc::VersionList<TRecord>& vlist,
TRecord& record, TRecord& record,
GraphDbAccessor& db_accessor) GraphDbAccessor& db_accessor);
: vlist_(vlist), record_(&record), db_accessor_(db_accessor) {
assert(record_ != nullptr);
}
/**
* 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<typename TValue> template<typename TValue>
void PropsSet(GraphDb::Property key, TValue value) { 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<GraphDb::Property> &Properties() const { const TypedValueStore<GraphDb::Property> &Properties() const;
return view().properties_;
}
void PropertiesAccept(std::function<void(const GraphDb::Property key, const TypedValue &prop)> handler, void PropertiesAccept(std::function<void(const GraphDb::Property key, const TypedValue &prop)> handler,
std::function<void()> finish = {}) const { std::function<void()> finish = {}) const;
view().props_.Accept(handler, finish);
}
// Assumes same transaction // Assumes same transaction
friend bool operator==(const RecordAccessor &a, const RecordAccessor &b) { friend bool operator==(const RecordAccessor &a, const RecordAccessor &b) {
// TODO consider the legitimacy of this comparison // 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) { friend bool operator!=(const RecordAccessor &a, const RecordAccessor &b) {
// TODO consider the legitimacy of this comparison // TODO consider the legitimacy of this comparison
return !(a == b); return a != b;
} }
/** /**
@ -69,18 +97,14 @@ public:
* *
* @return See above. * @return See above.
*/ */
GraphDbAccessor& db_accessor() { GraphDbAccessor& db_accessor();
return db_accessor_;
}
/** /**
* Returns a const GraphDB accessor of this record accessor. * Returns a GraphDB accessor of this record accessor.
* *
* @return See above. * @return See above.
*/ */
const GraphDbAccessor& db_accessor() const { const GraphDbAccessor& db_accessor() const;
return db_accessor_;
}
protected: protected:
@ -89,27 +113,14 @@ protected:
* *
* @return See above. * @return See above.
*/ */
TRecord& update() { 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_;
}
/** /**
* Returns a version of the record that is only for viewing. * Returns a version of the record that is only for viewing.
* *
* @return See above. * @return See above.
*/ */
const TRecord& view() const { 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<TRecord>& vlist_;
// The database accessor for which this record accessor is created // The database accessor for which this record accessor is created
// Provides means of getting to the transaction and database functions. // Provides means of getting to the transaction and database functions.
@ -117,6 +128,10 @@ protected:
GraphDbAccessor& db_accessor_; GraphDbAccessor& db_accessor_;
private: private:
// The record (edge or vertex) this accessor provides access to.
// Immutable, set in the constructor and never changed.
mvcc::VersionList<TRecord>& vlist_;
/* The version of the record currently used in this transaction. Defaults to the /* 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 * latest viewable version (set in the constructor). After the first update done
* through this accessor a new, editable version, is created for this transaction, * through this accessor a new, editable version, is created for this transaction,

View File

@ -128,7 +128,6 @@ public:
if (finish) if (finish)
finish(); finish();
} }
private: private:

View File

@ -8,30 +8,70 @@
#include "utils/iterator/iterator.hpp" #include "utils/iterator/iterator.hpp"
#include "database/graph_db.hpp" #include "database/graph_db.hpp"
// forward declaring the EdgeAccessor because it's returned
// by some functions
class EdgeAccessor; class EdgeAccessor;
class VertexAccessor : public RecordAccessor<Vertex, VertexAccessor> { /**
* 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<Vertex> {
public: public:
using RecordAccessor::RecordAccessor; using RecordAccessor::RecordAccessor;
/**
* Returns the number of outgoing edges.
* @return
*/
size_t out_degree() const; size_t out_degree() const;
/**
* Returns the number of incoming edges.
* @return
*/
size_t in_degree() const; 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); 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); 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; bool has_label(GraphDb::Label label) const;
/**
* Returns all the Labels of the Vertex.
* @return
*/
const std::set<GraphDb::Label>& labels() const; const std::set<GraphDb::Label>& labels() const;
/**
* Returns EdgeAccessors for all incoming edges.
* @return
*/
std::vector<EdgeAccessor> in(); std::vector<EdgeAccessor> in();
/**
* Returns EdgeAccessors for all outgoing edges.
* @return
*/
std::vector<EdgeAccessor> out(); std::vector<EdgeAccessor> out();
// returns if remove was possible due to connections
// bool remove();
//
// void detach_remove();
}; };

View File

@ -1,4 +1,4 @@
#include <database/creation_exception.hpp> #include "database/creation_exception.hpp"
#include "database/graph_db_accessor.hpp" #include "database/graph_db_accessor.hpp"
#include "storage/vertex.hpp" #include "storage/vertex.hpp"
@ -20,7 +20,7 @@ VertexAccessor GraphDbAccessor::insert_vertex() {
Vertex *vertex = vertex_vlist->insert(transaction_); Vertex *vertex = vertex_vlist->insert(transaction_);
// insert the newly created record into the main storage // 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) { for (int i = 0; i < 5; ++i) {
bool success = db_.vertices_.access().insert(vertex_vlist).second; bool success = db_.vertices_.access().insert(vertex_vlist).second;
if (success) if (success)
@ -37,9 +37,6 @@ bool GraphDbAccessor::remove_vertex(VertexAccessor &vertex_accessor) {
return false; return false;
vertex_accessor.vlist_.remove(&vertex_accessor.update(), transaction_); vertex_accessor.vlist_.remove(&vertex_accessor.update(), transaction_);
// TODO remove the vertex from the main storage once it gets garbage collected
return true; return true;
} }
@ -55,8 +52,22 @@ void GraphDbAccessor::detach_remove_vertex(VertexAccessor &vertex_accessor) {
// mvcc removal of the vertex // mvcc removal of the vertex
vertex_accessor.vlist_.remove(&vertex_accessor.update(), transaction_); vertex_accessor.vlist_.remove(&vertex_accessor.update(), transaction_);
}
// TODO remove the vertex from the main storage once it gets garbage collected std::vector<VertexAccessor> GraphDbAccessor::vertices() {
auto sl_accessor = db_.vertices_.access();
std::vector<VertexAccessor> 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( EdgeAccessor GraphDbAccessor::insert_edge(
@ -84,23 +95,39 @@ EdgeAccessor GraphDbAccessor::insert_edge(
throw CreationException("Unable to create an Edge after 5 attempts"); 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<mvcc::VersionList<Edge>*> &edges, mvcc::VersionList<Edge> *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) { void GraphDbAccessor::remove_edge(EdgeAccessor& edge_accessor) {
// remove this edge's reference from the "from" vertex swap_out_edge(edge_accessor.from().update().out_, &edge_accessor.vlist_);
auto& vertex_from = edge_accessor.from().update(); swap_out_edge(edge_accessor.to().update().in_, &edge_accessor.vlist_);
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
edge_accessor.vlist_.remove(&edge_accessor.update(), transaction_); edge_accessor.vlist_.remove(&edge_accessor.update(), transaction_);
} }
std::vector<EdgeAccessor> GraphDbAccessor::edges() {
auto sl_accessor = db_.edges_.access();
std::vector<EdgeAccessor> 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) { GraphDb::Label GraphDbAccessor::label(const std::string& label_name) {
return &(*db_.labels_.access().insert(label_name).first); return &(*db_.labels_.access().insert(label_name).first);
} }

View File

@ -2,11 +2,11 @@
#include "storage/vertex_accessor.hpp" #include "storage/vertex_accessor.hpp"
void EdgeAccessor::set_edge_type(GraphDb::EdgeType edge_type) { 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 { GraphDb::EdgeType EdgeAccessor::edge_type() const {
return this->view().edge_type_; return view().edge_type_;
} }
VertexAccessor EdgeAccessor::from() const { VertexAccessor EdgeAccessor::from() const {
@ -14,23 +14,5 @@ VertexAccessor EdgeAccessor::from() const {
} }
VertexAccessor EdgeAccessor::to() 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_);
//}

View File

@ -0,0 +1,69 @@
#include "storage/record_accessor.hpp"
#include "storage/edge.hpp"
#include "storage/vertex.hpp"
template<typename TRecord>
RecordAccessor<TRecord>::RecordAccessor(mvcc::VersionList<TRecord>& vlist,
GraphDbAccessor& db_accessor)
: vlist_(vlist), record_(vlist_.find(db_accessor.transaction_)), db_accessor_(db_accessor) {
assert(record_ != nullptr);
}
template<typename TRecord>
RecordAccessor<TRecord>::RecordAccessor(mvcc::VersionList<TRecord>& vlist,
TRecord& record,
GraphDbAccessor& db_accessor)
: vlist_(vlist), record_(&record), db_accessor_(db_accessor) {
assert(record_ != nullptr);
}
template<typename TRecord>
const TypedValue &RecordAccessor<TRecord>::PropsAt(GraphDb::Property key) const {
return view().properties_.at(key);
}
template<typename TRecord>
size_t RecordAccessor<TRecord>::PropsErase(GraphDb::Property key) {
return update().properties_.erase(key);
}
template<typename TRecord>
const TypedValueStore<GraphDb::Property> &RecordAccessor<TRecord>::Properties() const {
return view().properties_;
}
template<typename TRecord>
void RecordAccessor<TRecord>::PropertiesAccept(
std::function<void(const GraphDb::Property key, const TypedValue &prop)> handler,
std::function<void()> finish) const {
view().properties_.Accept(handler, finish);
}
template<typename TRecord>
GraphDbAccessor &RecordAccessor<TRecord>::db_accessor() {
return db_accessor_;
}
template<typename TRecord>
const GraphDbAccessor &RecordAccessor<TRecord>::db_accessor() const {
return db_accessor_;
}
template<typename TRecord>
TRecord &RecordAccessor<TRecord>::update() {
if (!record_->is_visible_write(db_accessor_.transaction_))
record_ = vlist_.update(db_accessor_.transaction_);
return *record_;
}
template<typename TRecord>
const TRecord &RecordAccessor<TRecord>::view() const {
return *record_;
}
template class RecordAccessor<Vertex>;
template class RecordAccessor<Edge>;

View File

@ -3,19 +3,19 @@
#include "storage/util.hpp" #include "storage/util.hpp"
size_t VertexAccessor::out_degree() const { size_t VertexAccessor::out_degree() const {
return this->view().out_.size(); return view().out_.size();
} }
size_t VertexAccessor::in_degree() const { size_t VertexAccessor::in_degree() const {
return this->view().in_.size(); return view().in_.size();
} }
bool VertexAccessor::add_label(GraphDb::Label label) { 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) { 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 { bool VertexAccessor::has_label(GraphDb::Label label) const {
@ -28,38 +28,9 @@ const std::set<GraphDb::Label>& VertexAccessor::labels() const {
} }
std::vector<EdgeAccessor> VertexAccessor::in() { std::vector<EdgeAccessor> VertexAccessor::in() {
const Vertex& record = view();
std::vector<EdgeAccessor> 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<EdgeAccessor> VertexAccessor::out() {
return make_accessors<EdgeAccessor>(view().in_, db_accessor_); return make_accessors<EdgeAccessor>(view().in_, db_accessor_);
} }
std::vector<EdgeAccessor> VertexAccessor::out() {
//bool VertexAccessor::remove() { return make_accessors<EdgeAccessor>(view().out_, db_accessor_);
// // 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_);
//}

View File

@ -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();
}

View File

@ -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<int>(), 42);
EXPECT_TRUE((vertex.PropsAt(dba.property("Other")) == TypedValue::Null).Value<bool>());
}