Vertex edges data structure introduced

Summary:
- The `Edges` data structure now handles common ops, including providing an iterator over edges whose "other" vertex is know.
- This should improve performance on dense_expand tests in the harness without other side-effects.
- query::plan::Expand operator modified not to check for existing-node stuff since that now gets handled by the `Edges` data structure.
- `Edges::Iterator` implemented only for const iterators since that suffices for now. Can implement non-const if the need arrises.

Reviewers: buda, mislav.bradac, teon.banek

Reviewed By: teon.banek

Subscribers: pullbot

Differential Revision: https://phabricator.memgraph.io/D763
This commit is contained in:
florijan 2017-09-08 11:29:54 +02:00
parent 9f8361dd27
commit 2c4966edfe
11 changed files with 406 additions and 113 deletions

View File

@ -218,11 +218,18 @@ bool GraphDbAccessor::RemoveVertex(VertexAccessor &vertex_accessor) {
void GraphDbAccessor::DetachRemoveVertex(VertexAccessor &vertex_accessor) {
debug_assert(!commited_ && !aborted_, "Accessor committed or aborted");
vertex_accessor.SwitchNew();
for (auto edge_accessor : vertex_accessor.in()) RemoveEdge(edge_accessor);
for (auto edge_accessor : vertex_accessor.in())
RemoveEdge(edge_accessor, true, false);
vertex_accessor.SwitchNew();
for (auto edge_accessor : vertex_accessor.out()) RemoveEdge(edge_accessor);
if (!RemoveVertex(vertex_accessor))
permanent_fail("Unable to remove vertex after all edges detached");
for (auto edge_accessor : vertex_accessor.out())
RemoveEdge(edge_accessor, false, true);
vertex_accessor.SwitchNew();
// it's possible the vertex was removed already in this transaction
// due to it getting matched multiple times by some patterns
// we can only delete it once, so check if it's already deleted
if (!vertex_accessor.current_->is_deleted_by(*transaction_))
vertex_accessor.vlist_->remove(vertex_accessor.current_, *transaction_);
}
EdgeAccessor GraphDbAccessor::InsertEdge(VertexAccessor &from,
@ -235,12 +242,12 @@ EdgeAccessor GraphDbAccessor::InsertEdge(VertexAccessor &from,
// ensure that the "from" accessor has the latest version
from.SwitchNew();
from.update().out_.emplace_back(edge_vlist);
from.update().out_.emplace(to.vlist_, edge_vlist, edge_type);
// ensure that the "to" accessor has the latest version
// WARNING: must do that after the above "from.update()" for cases when
// we are creating a cycle and "from" and "to" are the same vlist
to.SwitchNew();
to.update().in_.emplace_back(edge_vlist);
to.update().in_.emplace(from.vlist_, edge_vlist, edge_type);
bool success = db_.edges_.access().insert(edge_vlist).second;
const auto edge_accessor = EdgeAccessor(*edge_vlist, *this);
@ -272,27 +279,18 @@ int64_t GraphDbAccessor::EdgesCount(
return db_.edge_types_index_.Count(edge_type);
}
/**
* 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);
debug_assert(found != edges.end(), "Edge doesn't exist.");
std::swap(*found, edges.back());
edges.pop_back();
}
void GraphDbAccessor::RemoveEdge(EdgeAccessor &edge_accessor) {
void GraphDbAccessor::RemoveEdge(EdgeAccessor &edge_accessor,
bool remove_from_from, bool remove_from_to) {
debug_assert(!commited_ && !aborted_, "Accessor committed or aborted");
// it's possible the edge was removed already in this transaction
// due to it getting matched multiple times by some patterns
// we can only delete it once, so check if it's already deleted
edge_accessor.SwitchNew();
if (edge_accessor.current_->is_deleted_by(*transaction_)) return;
swap_out_edge(edge_accessor.from().update().out_, edge_accessor.vlist_);
swap_out_edge(edge_accessor.to().update().in_, edge_accessor.vlist_);
if (remove_from_from)
edge_accessor.from().update().out_.RemoveEdge(edge_accessor.vlist_);
if (remove_from_to)
edge_accessor.to().update().in_.RemoveEdge(edge_accessor.vlist_);
edge_accessor.vlist_->remove(edge_accessor.current_, *transaction_);
}

View File

@ -52,7 +52,7 @@ class GraphDbAccessor {
*
* @param db The database
*/
GraphDbAccessor(GraphDb &db);
explicit GraphDbAccessor(GraphDb &db);
~GraphDbAccessor();
// the GraphDbAccessor can NOT be copied nor moved because
@ -252,11 +252,19 @@ class GraphDbAccessor {
GraphDbTypes::EdgeType type);
/**
* Removes an edge from the graph.
* Removes an edge from the graph. Parameters can indicate if the edge should
* be removed from data structures in vertices it connects. When removing an
* edge both arguments should be `true`. `false` is only used when
* detach-deleting a vertex.
*
* @param edge_accessor The accessor to an edge.
* @param remove_from_from If the edge should be removed from the its origin
* side.
* @param remove_from_to If the edge should be removed from the its
* destination side.
*/
void RemoveEdge(EdgeAccessor &edge_accessor);
void RemoveEdge(EdgeAccessor &edge_accessor, bool remove_from = true,
bool remove_to = true);
/**
* Returns iterable over accessors to all the edges in the graph

View File

@ -312,10 +312,10 @@ std::unique_ptr<Cursor> ScanAllByLabelPropertyRange::MakeCursor(
ExpressionEvaluator evaluator(frame, symbol_table, db, graph_view_);
auto convert = [&evaluator](const auto &bound)
-> std::experimental::optional<utils::Bound<PropertyValue>> {
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
if (!bound) return std::experimental::nullopt;
return std::experimental::make_optional(utils::Bound<PropertyValue>(
bound.value().value()->Accept(evaluator), bound.value().type()));
};
return db.Vertices(label_, property_, convert(lower_bound()),
convert(upper_bound()), graph_view_ == GraphView::NEW);
};
@ -427,15 +427,47 @@ Expand::ExpandCursor::ExpandCursor(const Expand &self, GraphDbAccessor &db)
: self_(self), input_cursor_(self.input_->MakeCursor(db)), db_(db) {}
bool Expand::ExpandCursor::Pull(Frame &frame, const SymbolTable &symbol_table) {
// Helper function for handling existing-edge checking. Returns false only if
// existing_edge is true and the given new_edge is not equal to the existing
// one.
auto handle_existing_edge = [this, &frame](const EdgeAccessor &new_edge) {
if (self_.existing_edge_) {
TypedValue &old_edge_value = frame[self_.edge_symbol_];
// old_edge_value may be Null when using optional matching
if (old_edge_value.IsNull()) return false;
ExpectType(self_.edge_symbol_, old_edge_value, TypedValue::Type::Edge);
return old_edge_value.Value<EdgeAccessor>() == new_edge;
} else {
frame[self_.edge_symbol_] = new_edge;
return true;
}
};
// A helper function for expanding a node from an edge.
auto pull_node = [this, &frame](const EdgeAccessor &new_edge,
EdgeAtom::Direction direction) {
if (self_.existing_node_) return;
switch (direction) {
case EdgeAtom::Direction::IN:
frame[self_.node_symbol_] = new_edge.from();
break;
case EdgeAtom::Direction::OUT:
frame[self_.node_symbol_] = new_edge.to();
break;
case EdgeAtom::Direction::BOTH:
permanent_fail("Must indicate exact expansion direction here");
}
};
while (true) {
if (db_.should_abort()) throw HintedAbortError();
// attempt to get a value from the incoming edges
if (in_edges_ && *in_edges_it_ != in_edges_->end()) {
EdgeAccessor edge = *(*in_edges_it_)++;
if (HandleExistingEdge(edge, frame) &&
PullNode(edge, EdgeAtom::Direction::IN, frame))
if (handle_existing_edge(edge)) {
pull_node(edge, EdgeAtom::Direction::IN);
return true;
else
} else
continue;
}
@ -447,10 +479,10 @@ bool Expand::ExpandCursor::Pull(Frame &frame, const SymbolTable &symbol_table) {
// already done in the block above
if (self_.direction_ == EdgeAtom::Direction::BOTH && edge.is_cycle())
continue;
if (HandleExistingEdge(edge, frame) &&
PullNode(edge, EdgeAtom::Direction::OUT, frame))
if (handle_existing_edge(edge)) {
pull_node(edge, EdgeAtom::Direction::OUT);
return true;
else
} else
continue;
}
@ -506,13 +538,35 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame,
auto direction = self_.direction_;
if (direction == EdgeAtom::Direction::IN ||
direction == EdgeAtom::Direction::BOTH) {
in_edges_.emplace(vertex.in());
if (self_.existing_node_) {
TypedValue &existing_node = frame[self_.node_symbol_];
// old_node_value may be Null when using optional matching
if (!existing_node.IsNull()) {
ExpectType(self_.node_symbol_, existing_node,
TypedValue::Type::Vertex);
in_edges_.emplace(
vertex.in_with_destination(existing_node.ValueVertex()));
}
} else {
in_edges_.emplace(vertex.in());
}
in_edges_it_.emplace(in_edges_->begin());
}
if (direction == EdgeAtom::Direction::OUT ||
direction == EdgeAtom::Direction::BOTH) {
out_edges_.emplace(vertex.out());
if (self_.existing_node_) {
TypedValue &existing_node = frame[self_.node_symbol_];
// old_node_value may be Null when using optional matching
if (!existing_node.IsNull()) {
ExpectType(self_.node_symbol_, existing_node,
TypedValue::Type::Vertex);
out_edges_.emplace(
vertex.out_with_destination(existing_node.ValueVertex()));
}
} else {
out_edges_.emplace(vertex.out());
}
out_edges_it_.emplace(out_edges_->begin());
}
@ -525,19 +579,6 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame,
}
}
bool Expand::ExpandCursor::PullNode(const EdgeAccessor &new_edge,
EdgeAtom::Direction direction,
Frame &frame) {
switch (direction) {
case EdgeAtom::Direction::IN:
return self_.HandleExistingNode(new_edge.from(), frame);
case EdgeAtom::Direction::OUT:
return self_.HandleExistingNode(new_edge.to(), frame);
case EdgeAtom::Direction::BOTH:
permanent_fail("Must indicate exact expansion direction here");
}
}
ExpandVariable::ExpandVariable(Symbol node_symbol, Symbol edge_symbol,
EdgeAtom::Direction direction, bool is_reverse,
Expression *lower_bound, Expression *upper_bound,
@ -552,20 +593,6 @@ ExpandVariable::ExpandVariable(Symbol node_symbol, Symbol edge_symbol,
is_reverse_(is_reverse),
filter_(filter) {}
bool Expand::ExpandCursor::HandleExistingEdge(const EdgeAccessor &new_edge,
Frame &frame) const {
if (self_.existing_edge_) {
TypedValue &old_edge_value = frame[self_.edge_symbol_];
// old_edge_value may be Null when using optional matching
if (old_edge_value.IsNull()) return false;
ExpectType(self_.edge_symbol_, old_edge_value, TypedValue::Type::Edge);
return old_edge_value.Value<EdgeAccessor>() == new_edge;
} else {
frame[self_.edge_symbol_] = new_edge;
return true;
}
}
ACCEPT_WITH_INPUT(ExpandVariable)
namespace {

View File

@ -565,26 +565,6 @@ class Expand : public LogicalOperator, public ExpandCommon {
std::experimental::optional<OutEdgeIteratorT> out_edges_it_;
bool InitEdges(Frame &frame, const SymbolTable &symbol_table);
/**
* Expands a node for the given newly expanded edge.
*
* @return True if after this call a new node has been successfully
* expanded. Returns false only when matching an existing node and the
* new node does not qualify.
*/
bool PullNode(const EdgeAccessor &new_edge, EdgeAtom::Direction direction,
Frame &frame);
/**
* For a newly expanded edge handles existence checking and
* frame placement.
*
* @return If or not the given new_edge is a valid expansion. It is not
* valid only when matching and existing edge and new_edge does not match
* the old.
*/
bool HandleExistingEdge(const EdgeAccessor &new_edge, Frame &frame) const;
};
};

156
src/storage/edges.hpp Normal file
View File

@ -0,0 +1,156 @@
#pragma once
#include <utility>
#include <vector>
#include "database/graph_db_datatypes.hpp"
#include "mvcc/version_list.hpp"
// forward declare Vertex and Edge because they need this data structure
class Edge;
class Vertex;
/**
* A data stucture that holds a number of edges. This implementation assumes
* that separate Edges instances are used for incoming and outgoing edges in a
* vertex (and consequently that edge pointers are unique in it).
*/
class Edges {
using vertex_ptr_t = mvcc::VersionList<Vertex> *;
using edge_ptr_t = mvcc::VersionList<Edge> *;
struct Element {
vertex_ptr_t vertex;
edge_ptr_t edge;
GraphDbTypes::EdgeType edge_type;
};
/** Custom iterator that takes care of skipping edges when the destination
* vertex or edge type are known. */
class Iterator {
public:
/** Ctor that just sets the position. Used for normal iteration (that does
* not skip any edges), and for end-iterator creation in both normal and
* skipping iteration.
*
* @param iterator - Iterator in the underlying storage.
*/
explicit Iterator(std::vector<Element>::const_iterator iterator)
: position_(iterator) {}
/** Ctor used for creating the beginning iterator with known destionation
* vertex.
*
* @param iterator - Iterator in the underlying storage.
* @param end - End iterator in the underlying storage.
* @param vertex - The destination vertex vlist pointer.
*/
Iterator(std::vector<Element>::const_iterator position,
std::vector<Element>::const_iterator end, vertex_ptr_t vertex)
: position_(position), end_(end), vertex_(vertex) {
update_position();
}
/** Ctor used for creating the beginning iterator with known edge type.
*
* @param iterator - Iterator in the underlying storage.
* @param end - End iterator in the underlying storage.
* @param edge_type - The edge type that must be matched.
*/
Iterator(std::vector<Element>::const_iterator position,
std::vector<Element>::const_iterator end,
GraphDbTypes::EdgeType edge_type)
: position_(position), end_(end), edge_type_(edge_type) {
update_position();
}
Iterator &operator++() {
++position_;
update_position();
return *this;
}
const Element &operator*() const { return *position_; }
bool operator==(const Iterator &other) const {
return position_ == other.position_;
}
bool operator!=(const Iterator &other) const { return !(*this == other); }
private:
std::vector<Element>::const_iterator position_;
// end_ is used only in update_position() to limit find.
std::vector<Element>::const_iterator end_;
// Optional predicates. If set they define which edges are skipped by the
// iterator. Only one can be not-null in the current implementation.
vertex_ptr_t vertex_{nullptr};
GraphDbTypes::EdgeType edge_type_{nullptr};
/** Helper function that skips edges that don't satisfy the predicate
* present in this iterator. */
void update_position() {
if (vertex_)
position_ = std::find_if(
position_, end_,
[v = this->vertex_](const Element &e) { return e.vertex == v; });
else if (edge_type_)
position_ = std::find_if(position_, end_,
[et = this->edge_type_](const Element &e) {
return e.edge_type == et;
});
}
};
public:
/**
* Adds an edge to this structure.
*
* @param vertex - The destination vertex of the edge. That's the one opposite
* from the vertex that contains this `Edges` instance.
* @param edge - The edge.
* @param edge_type - Type of the edge.
*/
void emplace(vertex_ptr_t vertex, edge_ptr_t edge,
GraphDbTypes::EdgeType edge_type) {
storage_.emplace_back(Element{vertex, edge, edge_type});
}
/**
* Removes an edge from this structure.
*/
void RemoveEdge(edge_ptr_t edge) {
auto found = std::find_if(
storage_.begin(), storage_.end(),
[edge](const Element &element) { return edge == element.edge; });
debug_assert(found != storage_.end(),
"Removing an edge that is not present");
*found = std::move(storage_.back());
storage_.pop_back();
}
auto size() const { return storage_.size(); }
auto begin() const { return Iterator(storage_.begin()); }
auto end() const { return Iterator(storage_.end()); }
/**
* Creates a beginning iterator that will skip edges whose destination vertex
* is not equal to the given vertex.
*/
auto begin(vertex_ptr_t vertex) const {
return Iterator(storage_.begin(), storage_.end(), vertex);
}
/*
* Creates a beginning iterator that will skip edges whose edge type is not
* equal to the given. Relies on the fact that edge types are immutable during
* the whole edge lifetime.
*/
auto begin(GraphDbTypes::EdgeType edge_type) const {
return Iterator(storage_.begin(), storage_.end(), edge_type);
}
private:
std::vector<Element> storage_;
};

View File

@ -7,7 +7,7 @@
template <typename TRecord>
RecordAccessor<TRecord>::RecordAccessor(mvcc::VersionList<TRecord> &vlist,
GraphDbAccessor &db_accessor)
: db_accessor_(&db_accessor), vlist_(&vlist) {
: vlist_(&vlist), db_accessor_(&db_accessor) {
Reconstruct();
}

View File

@ -172,16 +172,16 @@ class RecordAccessor : public TotalOrdering<RecordAccessor<TRecord>> {
*/
const TRecord &current() const;
// The record (edge or vertex) this accessor provides access to.
// Immutable, set in the constructor and never changed.
mvcc::VersionList<TRecord> *vlist_;
private:
// The database accessor for which this record accessor is created
// Provides means of getting to the transaction and database functions.
// Immutable, set in the constructor and never changed.
GraphDbAccessor *db_accessor_;
// The record (edge or vertex) this accessor provides access to.
// Immutable, set in the constructor and never changed.
mvcc::VersionList<TRecord> *vlist_;
/**
* Latest version which is visible to the current transaction+command
* but has not been created nor modified by the current transaction+command.

View File

@ -3,25 +3,41 @@
#include <cppitertools/reversed.hpp>
#include "cppitertools/imap.hpp"
/**
* Converts a (beginning, end) pair of iterators into an iterable that can be
* passed on to itertools. */
template <typename TIterator>
class Iterable {
public:
Iterable(TIterator &&begin, TIterator &&end)
: begin_(std::forward<TIterator>(begin)),
end_(std::forward<TIterator>(end)) {}
auto begin() { return begin_; };
auto end() { return end_; };
private:
TIterator begin_;
TIterator end_;
};
/**
* Creates an iterator over record accessors (Edge or Vertex).
*
* @param begin Start iterator over (vertex_vlist_ptr, edge_vlist_ptr) pairs.
* @param end End iterator over (vertex_vlist_ptr, edge_vlist_ptr) pairs.
* @param db_accessor A database accessor to create the record accessors with.
*
* @tparam TAccessor The exact type of accessor.
* @tparam TIterable An iterable of pointers to version list objects.
*
* @param records An iterable of version list pointers for which accessors
* need to be created.
* @param db_accessor A database accessor to create the record accessors with.
*/
template <typename TAccessor, typename TIterable>
auto make_accessor_iterator(TIterable &&records, GraphDbAccessor &db_accessor) {
template <typename TAccessor, typename TIterator>
auto MakeAccessorIterator(TIterator &&begin, TIterator &&end,
GraphDbAccessor &db_accessor) {
return iter::imap(
[&db_accessor](auto vlist) {
return TAccessor(*vlist, db_accessor);
// note that here we iterate over records in REVERSED order
// this is necessary for DETACH DELETE (see GraphDbAccessor)
// which deletes items from relationship collections in a
// vertex accessor
[&db_accessor](auto &edges_element) {
return TAccessor(*edges_element.edge, db_accessor);
},
iter::reversed(std::forward<TIterable>(records)));
Iterable<TIterator>(std::forward<TIterator>(begin),
std::forward<TIterator>(end)));
}

View File

@ -1,10 +1,8 @@
#pragma once
#include <vector>
#include "database/graph_db_datatypes.hpp"
#include "mvcc/record.hpp"
#include "mvcc/version_list.hpp"
#include "storage/edges.hpp"
#include "storage/property_value_store.hpp"
// forward declare Edge because there is a circular usage Edge <-> Vertex
@ -12,8 +10,8 @@ class Edge;
class Vertex : public mvcc::Record<Vertex> {
public:
std::vector<mvcc::VersionList<Edge> *> out_;
std::vector<mvcc::VersionList<Edge> *> in_;
Edges out_;
Edges in_;
std::vector<GraphDbTypes::Label> labels_;
PropertyValueStore<GraphDbTypes::Property> properties_;
};

View File

@ -1,5 +1,6 @@
#pragma once
#include <limits>
#include <set>
#include <vector>
@ -67,14 +68,52 @@ class VertexAccessor : public RecordAccessor<Vertex> {
* Returns EdgeAccessors for all incoming edges.
*/
auto in() const {
return make_accessor_iterator<EdgeAccessor>(current().in_, db_accessor());
return MakeAccessorIterator<EdgeAccessor>(
current().in_.begin(), current().in_.end(), db_accessor());
}
/**
* Returns EdgeAccessors for all incoming edges whose destination is the given
* vertex.
*/
auto in_with_destination(const VertexAccessor &dest) const {
return MakeAccessorIterator<EdgeAccessor>(
current().in_.begin(dest.vlist_), current().in_.end(), db_accessor());
}
/**
* Returns EdgeAccessors for all incoming edges whose type is equal to the
* given.
*/
auto in_with_type(GraphDbTypes::EdgeType edge_type) const {
return MakeAccessorIterator<EdgeAccessor>(
current().in_.begin(edge_type), current().in_.end(), db_accessor());
}
/**
* Returns EdgeAccessors for all outgoing edges.
*/
auto out() const {
return make_accessor_iterator<EdgeAccessor>(current().out_, db_accessor());
return MakeAccessorIterator<EdgeAccessor>(
current().out_.begin(), current().out_.end(), db_accessor());
}
/**
* Returns EdgeAccessors for all outgoing edges whose destination is the given
* vertex.
*/
auto out_with_destination(const VertexAccessor &dest) const {
return MakeAccessorIterator<EdgeAccessor>(
current().out_.begin(dest.vlist_), current().out_.end(), db_accessor());
}
/**
* Returns EdgeAccessors for all outgoing edges whose type is equal to the
* given.
*/
auto out_with_type(GraphDbTypes::EdgeType edge_type) const {
return MakeAccessorIterator<EdgeAccessor>(
current().out_.begin(edge_type), current().out_.end(), db_accessor());
}
};
@ -82,9 +121,8 @@ std::ostream &operator<<(std::ostream &, const VertexAccessor &);
// hash function for the vertex accessor
namespace std {
template <> struct hash<VertexAccessor> {
size_t operator()(const VertexAccessor &v) const {
return v.temporary_id();
};
};
template <>
struct hash<VertexAccessor> {
size_t operator()(const VertexAccessor &v) const { return v.temporary_id(); };
};
}

View File

@ -236,3 +236,75 @@ TEST(RecordAccessor, VertexEdgeConnections) {
for (auto e : v1.out()) EXPECT_EQ(edge, e);
for (auto e : v2.in()) EXPECT_EQ(edge, e);
}
#define TEST_EDGE_ITERABLE(iterable, ...) \
{ \
std::vector<EdgeAccessor> edge_accessors; \
auto expected_vec = std::vector<EdgeAccessor>(__VA_ARGS__); \
for (const auto &ea : iterable) edge_accessors.emplace_back(ea); \
ASSERT_EQ(edge_accessors.size(), expected_vec.size()); \
EXPECT_TRUE(std::is_permutation( \
edge_accessors.begin(), edge_accessors.end(), expected_vec.begin())); \
}
TEST(RecordAccessor, VertexEdgeConnectionsWithExistingVertex) {
Dbms dbms;
auto dba = dbms.active();
auto v1 = dba->InsertVertex();
auto v2 = dba->InsertVertex();
auto v3 = dba->InsertVertex();
auto edge_type = dba->EdgeType("edge type");
auto e12 = dba->InsertEdge(v1, v2, edge_type);
auto e22 = dba->InsertEdge(v2, v2, edge_type);
auto e23a = dba->InsertEdge(v2, v3, edge_type);
auto e23b = dba->InsertEdge(v2, v3, edge_type);
auto e32 = dba->InsertEdge(v3, v2, edge_type);
dba->AdvanceCommand();
TEST_EDGE_ITERABLE(v1.out_with_destination(v1));
TEST_EDGE_ITERABLE(v1.out_with_destination(v2), {e12});
TEST_EDGE_ITERABLE(v1.out_with_destination(v3));
TEST_EDGE_ITERABLE(v2.out_with_destination(v1));
TEST_EDGE_ITERABLE(v2.out_with_destination(v2), {e22});
TEST_EDGE_ITERABLE(v2.out_with_destination(v3), {e23a, e23b});
TEST_EDGE_ITERABLE(v3.out_with_destination(v1));
TEST_EDGE_ITERABLE(v3.out_with_destination(v2), {e32});
TEST_EDGE_ITERABLE(v3.out_with_destination(v3));
TEST_EDGE_ITERABLE(v1.in_with_destination(v1));
TEST_EDGE_ITERABLE(v1.in_with_destination(v2));
TEST_EDGE_ITERABLE(v1.in_with_destination(v3));
TEST_EDGE_ITERABLE(v2.in_with_destination(v1), {e12});
TEST_EDGE_ITERABLE(v2.in_with_destination(v2), {e22});
TEST_EDGE_ITERABLE(v2.in_with_destination(v3), {e32});
TEST_EDGE_ITERABLE(v3.in_with_destination(v1));
TEST_EDGE_ITERABLE(v3.in_with_destination(v2), {e23a, e23b});
TEST_EDGE_ITERABLE(v3.in_with_destination(v3));
}
TEST(RecordAccessor, VertexEdgeConnectionsWithEdgeType) {
Dbms dbms;
auto dba = dbms.active();
auto v1 = dba->InsertVertex();
auto v2 = dba->InsertVertex();
auto a = dba->EdgeType("a");
auto b = dba->EdgeType("b");
auto ea = dba->InsertEdge(v1, v2, a);
auto eb_1 = dba->InsertEdge(v2, v1, b);
auto eb_2 = dba->InsertEdge(v2, v1, b);
dba->AdvanceCommand();
TEST_EDGE_ITERABLE(v1.in(), {eb_1, eb_2});
TEST_EDGE_ITERABLE(v2.in(), {ea});
TEST_EDGE_ITERABLE(v1.in_with_type(a));
TEST_EDGE_ITERABLE(v1.in_with_type(b), {eb_1, eb_2});
TEST_EDGE_ITERABLE(v1.out_with_type(a), {ea});
TEST_EDGE_ITERABLE(v1.out_with_type(b));
TEST_EDGE_ITERABLE(v2.in_with_type(a), {ea});
TEST_EDGE_ITERABLE(v2.in_with_type(b));
TEST_EDGE_ITERABLE(v2.out_with_type(a));
TEST_EDGE_ITERABLE(v2.out_with_type(b), {eb_1, eb_2});
}
#undef TEST_EDGE_ITERABLE