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:
parent
9f8361dd27
commit
2c4966edfe
@ -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_);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
156
src/storage/edges.hpp
Normal 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_;
|
||||
};
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -172,16 +172,16 @@ class RecordAccessor : public TotalOrdering<RecordAccessor<TRecord>> {
|
||||
*/
|
||||
const TRecord ¤t() 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.
|
||||
|
@ -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)));
|
||||
}
|
||||
|
@ -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_;
|
||||
};
|
||||
|
@ -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(); };
|
||||
};
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user