From 5b8e7ff43253242c82645e0f452653aef3ca1c72 Mon Sep 17 00:00:00 2001 From: Matej Ferencevic <matej.ferencevic@memgraph.io> Date: Mon, 8 Jul 2019 15:10:05 +0200 Subject: [PATCH] Implement edges in storage v2 Summary: This change implements full edges support in storage v2. Edges can be created and deleted. Support for detach-deleting vertices is added and regular vertex deletion verifies existance of edges. Reviewers: mtomic, teon.banek Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D2180 --- src/storage/v2/CMakeLists.txt | 1 + src/storage/v2/delta.hpp | 63 +- src/storage/v2/edge.hpp | 47 + src/storage/v2/edge_accessor.cpp | 18 + src/storage/v2/edge_accessor.hpp | 51 + src/storage/v2/mvcc.hpp | 49 +- src/storage/v2/result.hpp | 1 + src/storage/v2/storage.cpp | 268 +- src/storage/v2/storage.hpp | 12 + src/storage/v2/transaction.hpp | 2 + src/storage/v2/vertex.hpp | 6 + src/storage/v2/vertex_accessor.cpp | 165 + src/storage/v2/vertex_accessor.hpp | 16 +- tests/unit/CMakeLists.txt | 3 + tests/unit/storage_v2_edge.cpp | 4624 ++++++++++++++++++++++++++++ 15 files changed, 5304 insertions(+), 22 deletions(-) create mode 100644 src/storage/v2/edge.hpp create mode 100644 src/storage/v2/edge_accessor.cpp create mode 100644 src/storage/v2/edge_accessor.hpp create mode 100644 tests/unit/storage_v2_edge.cpp diff --git a/src/storage/v2/CMakeLists.txt b/src/storage/v2/CMakeLists.txt index bdc7207ae..6b7a2d3be 100644 --- a/src/storage/v2/CMakeLists.txt +++ b/src/storage/v2/CMakeLists.txt @@ -1,4 +1,5 @@ set(storage_v2_src_files + edge_accessor.cpp vertex_accessor.cpp storage.cpp) diff --git a/src/storage/v2/delta.hpp b/src/storage/v2/delta.hpp index 683f635e0..7c7a8cab1 100644 --- a/src/storage/v2/delta.hpp +++ b/src/storage/v2/delta.hpp @@ -6,21 +6,39 @@ namespace storage { +// Forward declarations because we only store pointers here. +struct Vertex; +struct Edge; + struct Delta { enum class Action { + // Used for both Vertex and Edge DELETE_OBJECT, RECREATE_OBJECT, + SET_PROPERTY, + + // Used only for Vertex ADD_LABEL, REMOVE_LABEL, - SET_PROPERTY, + ADD_IN_EDGE, + ADD_OUT_EDGE, + REMOVE_IN_EDGE, + REMOVE_OUT_EDGE, }; + // Used for both Vertex and Edge struct DeleteObjectTag {}; struct RecreateObjectTag {}; struct AddLabelTag {}; struct RemoveLabelTag {}; struct SetPropertyTag {}; + // Used only for Vertex + struct AddInEdgeTag {}; + struct AddOutEdgeTag {}; + struct RemoveInEdgeTag {}; + struct RemoveOutEdgeTag {}; + Delta(DeleteObjectTag, std::atomic<uint64_t> *timestamp, uint64_t command_id) : action(Action::DELETE_OBJECT), timestamp(timestamp), @@ -53,6 +71,34 @@ struct Delta { command_id(command_id), property({key, value}) {} + Delta(AddInEdgeTag, uint64_t edge_type, Vertex *vertex, Edge *edge, + std::atomic<uint64_t> *timestamp, uint64_t command_id) + : action(Action::ADD_IN_EDGE), + timestamp(timestamp), + command_id(command_id), + vertex_edge({edge_type, vertex, edge}) {} + + Delta(AddOutEdgeTag, uint64_t edge_type, Vertex *vertex, Edge *edge, + std::atomic<uint64_t> *timestamp, uint64_t command_id) + : action(Action::ADD_OUT_EDGE), + timestamp(timestamp), + command_id(command_id), + vertex_edge({edge_type, vertex, edge}) {} + + Delta(RemoveInEdgeTag, uint64_t edge_type, Vertex *vertex, Edge *edge, + std::atomic<uint64_t> *timestamp, uint64_t command_id) + : action(Action::REMOVE_IN_EDGE), + timestamp(timestamp), + command_id(command_id), + vertex_edge({edge_type, vertex, edge}) {} + + Delta(RemoveOutEdgeTag, uint64_t edge_type, Vertex *vertex, Edge *edge, + std::atomic<uint64_t> *timestamp, uint64_t command_id) + : action(Action::REMOVE_OUT_EDGE), + timestamp(timestamp), + command_id(command_id), + vertex_edge({edge_type, vertex, edge}) {} + Delta(Delta &&other) noexcept : action(other.action), timestamp(other.timestamp), @@ -71,6 +117,12 @@ struct Delta { property.key = other.property.key; new (&property.value) PropertyValue(std::move(other.property.value)); break; + case Action::ADD_IN_EDGE: + case Action::ADD_OUT_EDGE: + case Action::REMOVE_IN_EDGE: + case Action::REMOVE_OUT_EDGE: + vertex_edge = other.vertex_edge; + break; } // reset the action of other @@ -98,6 +150,11 @@ struct Delta { uint64_t key; storage::PropertyValue value; } property; + struct { + uint64_t edge_type; + Vertex *vertex; + Edge *edge; + } vertex_edge; }; private: @@ -107,6 +164,10 @@ struct Delta { case Action::RECREATE_OBJECT: case Action::ADD_LABEL: case Action::REMOVE_LABEL: + case Action::ADD_IN_EDGE: + case Action::ADD_OUT_EDGE: + case Action::REMOVE_IN_EDGE: + case Action::REMOVE_OUT_EDGE: break; case Action::SET_PROPERTY: property.value.~PropertyValue(); diff --git a/src/storage/v2/edge.hpp b/src/storage/v2/edge.hpp new file mode 100644 index 000000000..841cab886 --- /dev/null +++ b/src/storage/v2/edge.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <limits> +#include <unordered_map> + +#include "utils/spin_lock.hpp" + +#include "storage/v2/delta.hpp" +#include "storage/v2/gid.hpp" + +namespace storage { + +struct Vertex; + +struct Edge { + Edge(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) { + CHECK(delta->action == Delta::Action::DELETE_OBJECT) + << "Edge must be created with an initial DELETE_OBJECT delta!"; + } + + Gid gid; + + // TODO: add + // std::unordered_map<uint64_t, storage::PropertyValue> properties; + + utils::SpinLock lock; + bool deleted; + // uint8_t PAD; + // uint16_t PAD; + + Delta *delta; +}; + +inline bool operator==(const Edge &first, const Edge &second) { + return first.gid == second.gid; +} +inline bool operator<(const Edge &first, const Edge &second) { + return first.gid < second.gid; +} +inline bool operator==(const Edge &first, const Gid &second) { + return first.gid == second; +} +inline bool operator<(const Edge &first, const Gid &second) { + return first.gid < second; +} + +} // namespace storage diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp new file mode 100644 index 000000000..3cd753edc --- /dev/null +++ b/src/storage/v2/edge_accessor.cpp @@ -0,0 +1,18 @@ +#include "storage/v2/edge_accessor.hpp" + +#include <memory> + +#include "storage/v2/mvcc.hpp" +#include "storage/v2/vertex_accessor.hpp" + +namespace storage { + +VertexAccessor EdgeAccessor::FromVertex() { + return VertexAccessor{from_vertex_, transaction_}; +} + +VertexAccessor EdgeAccessor::ToVertex() { + return VertexAccessor{to_vertex_, transaction_}; +} + +} // namespace storage diff --git a/src/storage/v2/edge_accessor.hpp b/src/storage/v2/edge_accessor.hpp new file mode 100644 index 000000000..0f9d9e537 --- /dev/null +++ b/src/storage/v2/edge_accessor.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include <optional> + +#include "storage/v2/edge.hpp" + +#include "storage/v2/result.hpp" +#include "storage/v2/transaction.hpp" +#include "storage/v2/view.hpp" + +namespace storage { + +struct Vertex; +class VertexAccessor; +class Storage; + +class EdgeAccessor final { + private: + friend class Storage; + + public: + EdgeAccessor(Edge *edge, uint64_t edge_type, Vertex *from_vertex, + Vertex *to_vertex, Transaction *transaction) + : edge_(edge), + edge_type_(edge_type), + from_vertex_(from_vertex), + to_vertex_(to_vertex), + transaction_(transaction) {} + + VertexAccessor FromVertex(); + + VertexAccessor ToVertex(); + + uint64_t EdgeType() const { return edge_type_; } + + Gid Gid() const { return edge_->gid; } + + bool operator==(const EdgeAccessor &other) const { + return edge_ == other.edge_ && transaction_ == other.transaction_; + } + bool operator!=(const EdgeAccessor &other) const { return !(*this == other); } + + private: + Edge *edge_; + uint64_t edge_type_; + Vertex *from_vertex_; + Vertex *to_vertex_; + Transaction *transaction_; +}; + +} // namespace storage diff --git a/src/storage/v2/mvcc.hpp b/src/storage/v2/mvcc.hpp index a18567aba..1f75c8092 100644 --- a/src/storage/v2/mvcc.hpp +++ b/src/storage/v2/mvcc.hpp @@ -1,8 +1,12 @@ #pragma once +#include <type_traits> + #include "storage/v2/delta.hpp" +#include "storage/v2/edge.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/transaction.hpp" +#include "storage/v2/vertex.hpp" #include "storage/v2/view.hpp" namespace storage { @@ -47,15 +51,16 @@ inline void ApplyDeltasForRead(Transaction *transaction, Delta *delta, } } -/// This function prepares the Vertex object for a write. It checks whether -/// there are any serialization errors in the process (eg. the object can't be -/// written to from this transaction because it is being written to from another +/// This function prepares the object for a write. It checks whether there are +/// any serialization errors in the process (eg. the object can't be written to +/// from this transaction because it is being written to from another /// transaction) and returns a `bool` value indicating whether the caller can /// proceed with a write operation. -inline bool PrepareForWrite(Transaction *transaction, Vertex *vertex) { - if (vertex->delta == nullptr) return true; +template <typename TObj> +inline bool PrepareForWrite(Transaction *transaction, TObj *object) { + if (object->delta == nullptr) return true; - auto ts = vertex->delta->timestamp->load(std::memory_order_acquire); + auto ts = object->delta->timestamp->load(std::memory_order_acquire); if (ts == transaction->transaction_id || ts < transaction->start_timestamp) { return true; } @@ -74,25 +79,33 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) { transaction->command_id); } -/// This function creates a delta in the transaction for the Vertex object and -/// links the delta into the Vertex's delta list. It also adds the Vertex to the -/// transaction's modified vertices list. -template <class... Args> -inline void CreateAndLinkDelta(Transaction *transaction, Vertex *vertex, +/// This function creates a delta in the transaction for the object and links +/// the delta into the object's delta list. It also adds the object to the +/// transaction's modified objects list. +template <typename TObj, class... Args> +inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&... args) { auto delta = &transaction->deltas.emplace_back(std::forward<Args>(args)..., &transaction->commit_timestamp, transaction->command_id); - if (vertex->delta) { - vertex->delta->prev = delta; + if (object->delta) { + object->delta->prev = delta; } - delta->next.store(vertex->delta, std::memory_order_release); - vertex->delta = delta; + delta->next.store(object->delta, std::memory_order_release); + object->delta = delta; - if (transaction->modified_vertices.empty() || - transaction->modified_vertices.back() != vertex) { - transaction->modified_vertices.push_back(vertex); + if constexpr (std::is_same_v<TObj, Vertex>) { + if (transaction->modified_vertices.empty() || + transaction->modified_vertices.back() != object) { + transaction->modified_vertices.push_back(object); + } + } + if constexpr (std::is_same_v<TObj, Edge>) { + if (transaction->modified_edges.empty() || + transaction->modified_edges.back() != object) { + transaction->modified_edges.push_back(object); + } } } diff --git a/src/storage/v2/result.hpp b/src/storage/v2/result.hpp index 77d335cd9..dd51cddb0 100644 --- a/src/storage/v2/result.hpp +++ b/src/storage/v2/result.hpp @@ -9,6 +9,7 @@ namespace storage { enum class Error : uint8_t { SERIALIZATION_ERROR, DELETED_OBJECT, + VERTEX_HAS_EDGES, }; template <typename TReturn> diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index c34fa8953..82a843814 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -64,7 +64,7 @@ VertexAccessor Storage::Accessor::CreateVertex() { CHECK(inserted) << "The vertex must be inserted here!"; CHECK(it != acc.end()) << "Invalid Vertex accessor!"; transaction_->modified_vertices.push_back(&*it); - return VertexAccessor::Create(&*it, transaction_, View::NEW).value(); + return VertexAccessor{&*it, transaction_}; } std::optional<VertexAccessor> Storage::Accessor::FindVertex(Gid gid, @@ -88,13 +88,204 @@ Result<bool> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) { if (vertex_ptr->deleted) return Result<bool>{false}; - CreateAndLinkDelta(transaction_, vertex_ptr, Delta::RecreateObjectTag()); + if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) + return Result<bool>{Error::VERTEX_HAS_EDGES}; + CreateAndLinkDelta(transaction_, vertex_ptr, Delta::RecreateObjectTag()); vertex_ptr->deleted = true; return Result<bool>{true}; } +Result<bool> Storage::Accessor::DetachDeleteVertex(VertexAccessor *vertex) { + CHECK(vertex->transaction_ == transaction_) + << "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"; + auto vertex_ptr = vertex->vertex_; + + std::vector<std::tuple<uint64_t, Vertex *, Edge *>> in_edges; + std::vector<std::tuple<uint64_t, Vertex *, Edge *>> out_edges; + + { + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + if (!PrepareForWrite(transaction_, vertex_ptr)) + return Result<bool>{Error::SERIALIZATION_ERROR}; + + if (vertex_ptr->deleted) return Result<bool>{false}; + + in_edges = vertex_ptr->in_edges; + out_edges = vertex_ptr->out_edges; + } + + for (const auto &item : in_edges) { + auto [edge_type, from_vertex, edge] = item; + EdgeAccessor e{edge, edge_type, from_vertex, vertex_ptr, transaction_}; + auto ret = DeleteEdge(&e); + if (ret.IsError()) { + CHECK(ret.GetError() == Error::SERIALIZATION_ERROR) + << "Invalid database state!"; + return ret; + } + } + for (const auto &item : out_edges) { + auto [edge_type, to_vertex, edge] = item; + EdgeAccessor e{edge, edge_type, vertex_ptr, to_vertex, transaction_}; + auto ret = DeleteEdge(&e); + if (ret.IsError()) { + CHECK(ret.GetError() == Error::SERIALIZATION_ERROR) + << "Invalid database state!"; + return ret; + } + } + + std::lock_guard<utils::SpinLock> guard(vertex_ptr->lock); + + // We need to check again for serialization errors because we unlocked the + // vertex. Some other transaction could have modified the vertex in the + // meantime if we didn't have any edges to delete. + + if (!PrepareForWrite(transaction_, vertex_ptr)) + return Result<bool>{Error::SERIALIZATION_ERROR}; + + CHECK(!vertex_ptr->deleted) << "Invalid database state!"; + + CreateAndLinkDelta(transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + + return Result<bool>{true}; +} + +Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, + VertexAccessor *to, + uint64_t edge_type) { + CHECK(from->transaction_ == to->transaction_) + << "VertexAccessors must be from the same transaction when creating " + "an edge!"; + CHECK(from->transaction_ == transaction_) + << "VertexAccessors must be from the same transaction in when " + "creating an edge!"; + + auto from_vertex = from->vertex_; + auto to_vertex = to->vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, + std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(transaction_, from_vertex)) + return Result<EdgeAccessor>{Error::SERIALIZATION_ERROR}; + CHECK(!from_vertex->deleted) << "Invalid database state!"; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(transaction_, to_vertex)) + return Result<EdgeAccessor>{Error::SERIALIZATION_ERROR}; + CHECK(!to_vertex->deleted) << "Invalid database state!"; + } + + auto gid = storage_->edge_id_.fetch_add(1, std::memory_order_acq_rel); + auto acc = storage_->edges_.access(); + auto delta = CreateDeleteObjectDelta(transaction_); + auto [it, inserted] = acc.insert(Edge{storage::Gid::FromUint(gid), delta}); + CHECK(inserted) << "The edge must be inserted here!"; + CHECK(it != acc.end()) << "Invalid Edge accessor!"; + auto edge = &*it; + transaction_->modified_edges.push_back(edge); + + CreateAndLinkDelta(transaction_, from_vertex, Delta::RemoveOutEdgeTag(), + edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + + CreateAndLinkDelta(transaction_, to_vertex, Delta::RemoveInEdgeTag(), + edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + return Result<EdgeAccessor>{ + EdgeAccessor{edge, edge_type, from_vertex, to_vertex, transaction_}}; +} + +Result<bool> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) { + CHECK(edge->transaction_ == transaction_) + << "EdgeAccessor must be from the same transaction as the storage " + "accessor when deleting an edge!"; + auto edge_ptr = edge->edge_; + auto edge_type = edge->edge_type_; + + std::lock_guard<utils::SpinLock> guard(edge_ptr->lock); + + if (!PrepareForWrite(transaction_, edge_ptr)) + return Result<bool>{Error::SERIALIZATION_ERROR}; + + if (edge_ptr->deleted) return Result<bool>{false}; + + auto from_vertex = edge->from_vertex_; + auto to_vertex = edge->to_vertex_; + + // Obtain the locks by `gid` order to avoid lock cycles. + std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, + std::defer_lock); + std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); + if (from_vertex->gid < to_vertex->gid) { + guard_from.lock(); + guard_to.lock(); + } else if (from_vertex->gid > to_vertex->gid) { + guard_to.lock(); + guard_from.lock(); + } else { + // The vertices are the same vertex, only lock one. + guard_from.lock(); + } + + if (!PrepareForWrite(transaction_, from_vertex)) + return Result<bool>{Error::SERIALIZATION_ERROR}; + CHECK(!from_vertex->deleted) << "Invalid database state!"; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(transaction_, to_vertex)) + return Result<bool>{Error::SERIALIZATION_ERROR}; + CHECK(!to_vertex->deleted) << "Invalid database state!"; + } + + CreateAndLinkDelta(transaction_, edge_ptr, Delta::RecreateObjectTag()); + edge_ptr->deleted = true; + + CreateAndLinkDelta(transaction_, from_vertex, Delta::AddOutEdgeTag(), + edge_type, to_vertex, edge_ptr); + { + std::tuple<uint64_t, Vertex *, Edge *> link{edge_type, to_vertex, edge_ptr}; + auto it = std::find(from_vertex->out_edges.begin(), + from_vertex->out_edges.end(), link); + CHECK(it != from_vertex->out_edges.end()) << "Invalid database state!"; + std::swap(*it, *from_vertex->out_edges.rbegin()); + from_vertex->out_edges.pop_back(); + } + + CreateAndLinkDelta(transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, + from_vertex, edge_ptr); + { + std::tuple<uint64_t, Vertex *, Edge *> link{edge_type, from_vertex, + edge_ptr}; + auto it = + std::find(to_vertex->in_edges.begin(), to_vertex->in_edges.end(), link); + CHECK(it != to_vertex->in_edges.end()) << "Invalid database state!"; + std::swap(*it, *to_vertex->in_edges.rbegin()); + to_vertex->in_edges.pop_back(); + } + + return Result<bool>{true}; +} + void Storage::Accessor::AdvanceCommand() { ++transaction_->command_id; } void Storage::Accessor::Commit() { @@ -153,6 +344,48 @@ void Storage::Accessor::Abort() { } break; } + case Delta::Action::ADD_IN_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = + std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + CHECK(it == vertex->in_edges.end()) << "Invalid database state!"; + vertex->in_edges.push_back(link); + break; + } + case Delta::Action::ADD_OUT_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), + vertex->out_edges.end(), link); + CHECK(it == vertex->out_edges.end()) << "Invalid database state!"; + vertex->out_edges.push_back(link); + break; + } + case Delta::Action::REMOVE_IN_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = + std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + CHECK(it != vertex->in_edges.end()) << "Invalid database state!"; + std::swap(*it, *vertex->in_edges.rbegin()); + vertex->in_edges.pop_back(); + break; + } + case Delta::Action::REMOVE_OUT_EDGE: { + std::tuple<uint64_t, Vertex *, Edge *> link{ + current->vertex_edge.edge_type, current->vertex_edge.vertex, + current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), + vertex->out_edges.end(), link); + CHECK(it != vertex->out_edges.end()) << "Invalid database state!"; + std::swap(*it, *vertex->out_edges.rbegin()); + vertex->out_edges.pop_back(); + break; + } case Delta::Action::DELETE_OBJECT: { auto acc = storage_->vertices_.access(); CHECK(acc.remove(vertex->gid)) << "Invalid database state!"; @@ -167,6 +400,37 @@ void Storage::Accessor::Abort() { } vertex->delta = current; } + for (Edge *edge : transaction_->modified_edges) { + std::lock_guard<utils::SpinLock> guard(edge->lock); + Delta *current = edge->delta; + while (current != nullptr && + current->timestamp->load(std::memory_order_acquire) == + transaction_->transaction_id) { + switch (current->action) { + case Delta::Action::DELETE_OBJECT: { + auto acc = storage_->edges_.access(); + CHECK(acc.remove(edge->gid)) << "Invalid database state!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + edge->deleted = false; + break; + } + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_LABEL: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: { + LOG(FATAL) << "Invalid database state!"; + break; + } + } + current = current->next.load(std::memory_order_acquire); + } + edge->delta = current; + } transaction_->is_active = false; } diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp index 2bec6a135..34f3710de 100644 --- a/src/storage/v2/storage.hpp +++ b/src/storage/v2/storage.hpp @@ -4,6 +4,9 @@ #include "utils/skip_list.hpp" +#include "storage/v2/edge.hpp" +#include "storage/v2/edge_accessor.hpp" +#include "storage/v2/mvcc.hpp" #include "storage/v2/result.hpp" #include "storage/v2/transaction.hpp" #include "storage/v2/vertex.hpp" @@ -42,6 +45,13 @@ class Storage final { Result<bool> DeleteVertex(VertexAccessor *vertex); + Result<bool> DetachDeleteVertex(VertexAccessor *vertex); + + Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, + uint64_t edge_type); + + Result<bool> DeleteEdge(EdgeAccessor *edge); + void AdvanceCommand(); void Commit(); @@ -59,7 +69,9 @@ class Storage final { private: // Main object storage utils::SkipList<storage::Vertex> vertices_; + utils::SkipList<storage::Edge> edges_; std::atomic<uint64_t> vertex_id_{0}; + std::atomic<uint64_t> edge_id_{0}; // Transaction engine utils::SpinLock lock_; diff --git a/src/storage/v2/transaction.hpp b/src/storage/v2/transaction.hpp index e0fd8a4cd..963deba3b 100644 --- a/src/storage/v2/transaction.hpp +++ b/src/storage/v2/transaction.hpp @@ -7,6 +7,7 @@ #include "utils/skip_list.hpp" #include "storage/v2/delta.hpp" +#include "storage/v2/edge.hpp" #include "storage/v2/property_value.hpp" #include "storage/v2/vertex.hpp" #include "storage/v2/view.hpp" @@ -44,6 +45,7 @@ struct Transaction { uint64_t command_id; std::list<Delta> deltas; std::list<Vertex *> modified_vertices; + std::list<Edge *> modified_edges; bool is_active; bool must_abort; }; diff --git a/src/storage/v2/vertex.hpp b/src/storage/v2/vertex.hpp index bd19c4501..9cb4b18a8 100644 --- a/src/storage/v2/vertex.hpp +++ b/src/storage/v2/vertex.hpp @@ -1,12 +1,14 @@ #pragma once #include <limits> +#include <tuple> #include <unordered_map> #include <vector> #include "utils/spin_lock.hpp" #include "storage/v2/delta.hpp" +#include "storage/v2/edge.hpp" #include "storage/v2/gid.hpp" namespace storage { @@ -18,9 +20,13 @@ struct Vertex { } Gid gid; + std::vector<uint64_t> labels; std::unordered_map<uint64_t, storage::PropertyValue> properties; + std::vector<std::tuple<uint64_t, Vertex *, Edge *>> in_edges; + std::vector<std::tuple<uint64_t, Vertex *, Edge *>> out_edges; + utils::SpinLock lock; bool deleted; // uint8_t PAD; diff --git a/src/storage/v2/vertex_accessor.cpp b/src/storage/v2/vertex_accessor.cpp index 0d82aaf93..c08e690c8 100644 --- a/src/storage/v2/vertex_accessor.cpp +++ b/src/storage/v2/vertex_accessor.cpp @@ -2,6 +2,7 @@ #include <memory> +#include "storage/v2/edge_accessor.hpp" #include "storage/v2/mvcc.hpp" namespace storage { @@ -22,6 +23,10 @@ std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, case Delta::Action::ADD_LABEL: case Delta::Action::REMOVE_LABEL: case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: break; case Delta::Action::RECREATE_OBJECT: { is_visible = true; @@ -110,6 +115,10 @@ Result<bool> VertexAccessor::HasLabel(uint64_t label, View view) { break; } case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: break; } }); @@ -154,6 +163,10 @@ Result<std::vector<uint64_t>> VertexAccessor::Labels(View view) { break; } case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: break; } }); @@ -226,6 +239,10 @@ Result<PropertyValue> VertexAccessor::GetProperty(uint64_t property, } case Delta::Action::ADD_LABEL: case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: break; } }); @@ -272,6 +289,10 @@ Result<std::unordered_map<uint64_t, PropertyValue>> VertexAccessor::Properties( } case Delta::Action::ADD_LABEL: case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: break; } }); @@ -283,4 +304,148 @@ Result<std::unordered_map<uint64_t, PropertyValue>> VertexAccessor::Properties( std::move(properties)}; } +Result<std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>> +VertexAccessor::InEdges(const std::vector<uint64_t> &edge_types, View view) { + std::vector<std::tuple<uint64_t, Vertex *, Edge *>> in_edges; + bool deleted = false; + Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex_->lock); + deleted = vertex_->deleted; + in_edges = vertex_->in_edges; + delta = vertex_->delta; + } + ApplyDeltasForRead( + transaction_, delta, view, [&deleted, &in_edges](const Delta &delta) { + switch (delta.action) { + case Delta::Action::ADD_IN_EDGE: { + // Add the edge because we don't see the removal. + std::tuple<uint64_t, Vertex *, Edge *> link{ + delta.vertex_edge.edge_type, delta.vertex_edge.vertex, + delta.vertex_edge.edge}; + auto it = std::find(in_edges.begin(), in_edges.end(), link); + CHECK(it == in_edges.end()) << "Invalid database state!"; + in_edges.push_back(link); + break; + } + case Delta::Action::REMOVE_IN_EDGE: { + // Remove the label because we don't see the addition. + std::tuple<uint64_t, Vertex *, Edge *> link{ + delta.vertex_edge.edge_type, delta.vertex_edge.vertex, + delta.vertex_edge.edge}; + auto it = std::find(in_edges.begin(), in_edges.end(), link); + CHECK(it != in_edges.end()) << "Invalid database state!"; + std::swap(*it, *in_edges.rbegin()); + in_edges.pop_back(); + break; + } + case Delta::Action::DELETE_OBJECT: { + LOG(FATAL) << "Invalid accessor!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + break; + } + }); + if (deleted) { + return Result< + std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>>{ + Error::DELETED_OBJECT}; + } + std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>> ret; + ret.reserve(in_edges.size()); + for (const auto &item : in_edges) { + auto [edge_type, from_vertex, edge] = item; + if (edge_types.empty() || std::find(edge_types.begin(), edge_types.end(), + edge_type) != edge_types.end()) { + ret.emplace_back( + edge_type, VertexAccessor{from_vertex, transaction_}, + EdgeAccessor{edge, edge_type, from_vertex, vertex_, transaction_}); + } + } + return Result< + std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>>{ + std::move(ret)}; +} + +Result<std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>> +VertexAccessor::OutEdges(const std::vector<uint64_t> &edge_types, View view) { + std::vector<std::tuple<uint64_t, Vertex *, Edge *>> out_edges; + bool deleted = false; + Delta *delta = nullptr; + { + std::lock_guard<utils::SpinLock> guard(vertex_->lock); + deleted = vertex_->deleted; + out_edges = vertex_->out_edges; + delta = vertex_->delta; + } + ApplyDeltasForRead( + transaction_, delta, view, [&deleted, &out_edges](const Delta &delta) { + switch (delta.action) { + case Delta::Action::ADD_OUT_EDGE: { + // Add the edge because we don't see the removal. + std::tuple<uint64_t, Vertex *, Edge *> link{ + delta.vertex_edge.edge_type, delta.vertex_edge.vertex, + delta.vertex_edge.edge}; + auto it = std::find(out_edges.begin(), out_edges.end(), link); + CHECK(it == out_edges.end()) << "Invalid database state!"; + out_edges.push_back(link); + break; + } + case Delta::Action::REMOVE_OUT_EDGE: { + // Remove the label because we don't see the addition. + std::tuple<uint64_t, Vertex *, Edge *> link{ + delta.vertex_edge.edge_type, delta.vertex_edge.vertex, + delta.vertex_edge.edge}; + auto it = std::find(out_edges.begin(), out_edges.end(), link); + CHECK(it != out_edges.end()) << "Invalid database state!"; + std::swap(*it, *out_edges.rbegin()); + out_edges.pop_back(); + break; + } + case Delta::Action::DELETE_OBJECT: { + LOG(FATAL) << "Invalid accessor!"; + break; + } + case Delta::Action::RECREATE_OBJECT: { + deleted = false; + break; + } + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + break; + } + }); + if (deleted) { + return Result< + std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>>{ + Error::DELETED_OBJECT}; + } + std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>> ret; + ret.reserve(out_edges.size()); + for (const auto &item : out_edges) { + auto [edge_type, to_vertex, edge] = item; + if (edge_types.empty() || std::find(edge_types.begin(), edge_types.end(), + edge_type) != edge_types.end()) { + ret.emplace_back( + edge_type, VertexAccessor{to_vertex, transaction_}, + EdgeAccessor{edge, edge_type, vertex_, to_vertex, transaction_}); + } + } + return Result< + std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>>{ + std::move(ret)}; +} + } // namespace storage diff --git a/src/storage/v2/vertex_accessor.hpp b/src/storage/v2/vertex_accessor.hpp index 8b2f73698..e9540b112 100644 --- a/src/storage/v2/vertex_accessor.hpp +++ b/src/storage/v2/vertex_accessor.hpp @@ -10,16 +10,17 @@ namespace storage { +class EdgeAccessor; class Storage; class VertexAccessor final { private: friend class Storage; + public: VertexAccessor(Vertex *vertex, Transaction *transaction) : vertex_(vertex), transaction_(transaction) {} - public: static std::optional<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, View view); @@ -38,8 +39,21 @@ class VertexAccessor final { Result<std::unordered_map<uint64_t, PropertyValue>> Properties(View view); + Result<std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>> + InEdges(const std::vector<uint64_t> &edge_types, View view); + + Result<std::vector<std::tuple<uint64_t, VertexAccessor, EdgeAccessor>>> + OutEdges(const std::vector<uint64_t> &edge_types, View view); + Gid Gid() const { return vertex_->gid; } + bool operator==(const VertexAccessor &other) const { + return vertex_ == other.vertex_ && transaction_ == other.transaction_; + } + bool operator!=(const VertexAccessor &other) const { + return !(*this == other); + } + private: Vertex *vertex_; Transaction *transaction_; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index b5cd46045..208d2feaa 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -382,6 +382,9 @@ target_link_libraries(${test_prefix}auth mg-auth kvstore_lib) add_unit_test(property_value_v2.cpp) target_link_libraries(${test_prefix}property_value_v2 mg-utils) +add_unit_test(storage_v2_edge.cpp) +target_link_libraries(${test_prefix}storage_v2_edge mg-storage-v2) + add_unit_test(storage_v2.cpp) target_link_libraries(${test_prefix}storage_v2 mg-storage-v2) diff --git a/tests/unit/storage_v2_edge.cpp b/tests/unit/storage_v2_edge.cpp new file mode 100644 index 000000000..0a49674ca --- /dev/null +++ b/tests/unit/storage_v2_edge.cpp @@ -0,0 +1,4624 @@ +#include <gtest/gtest.h> + +#include <limits> + +#include "storage/v2/storage.hpp" + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeCreateFromSmallerCommit) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeCreateFromLargerCommit) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_to = acc.CreateVertex(); + auto vertex_from = acc.CreateVertex(); + gid_to = vertex_to.Gid(); + gid_from = vertex_from.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeCreateFromSameCommit) { + storage::Storage store; + storage::Gid gid_vertex = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid_vertex = vertex.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto res = acc.CreateEdge(&*vertex, &*vertex, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeCreateFromSmallerAbort) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + acc.Commit(); + } + + // Create edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeCreateFromLargerAbort) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_to = acc.CreateVertex(); + auto vertex_from = acc.CreateVertex(); + gid_to = vertex_to.Gid(); + gid_from = vertex_from.Gid(); + acc.Commit(); + } + + // Create edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeCreateFromSameAbort) { + storage::Storage store; + storage::Gid gid_vertex = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid_vertex = vertex.Gid(); + acc.Commit(); + } + + // Create edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto res = acc.CreateEdge(&*vertex, &*vertex, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto res = acc.CreateEdge(&*vertex, &*vertex, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeDeleteFromSmallerCommit) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto [edge_type, other_vertex, edge] = + vertex_from->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeDeleteFromLargerCommit) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_to = acc.CreateVertex(); + auto vertex_from = acc.CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto [edge_type, other_vertex, edge] = + vertex_from->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeDeleteFromSameCommit) { + storage::Storage store; + storage::Gid gid_vertex = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid_vertex = vertex.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto res = acc.CreateEdge(&*vertex, &*vertex, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete edge + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeDeleteFromSmallerAbort) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete the edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto [edge_type, other_vertex, edge] = + vertex_from->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete the edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto [edge_type, other_vertex, edge] = + vertex_from->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeDeleteFromLargerAbort) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertices + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(&*vertex_from, &*vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex_from); + ASSERT_EQ(edge.ToVertex(), *vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete the edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto [edge_type, other_vertex, edge] = + vertex_from->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete the edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto [edge_type, other_vertex, edge] = + vertex_from->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ( + vertex_from->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex_to->InEdges({2}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, EdgeDeleteFromSameAbort) { + storage::Storage store; + storage::Gid gid_vertex = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid_vertex = vertex.Gid(); + acc.Commit(); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto res = acc.CreateEdge(&*vertex, &*vertex, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), *vertex); + ASSERT_EQ(edge.ToVertex(), *vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete the edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + { + auto ret = vertex->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::NEW).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Delete the edge + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + auto [edge_type, other_vertex, edge] = + vertex->OutEdges({}, storage::View::NEW).GetReturn()[0]; + + auto res = acc.DeleteEdge(&edge); + ASSERT_TRUE(res.IsReturn()); + ASSERT_TRUE(res.GetReturn()); + + // Check edges without filters + { + auto ret = vertex->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + { + auto ret = vertex->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex); + ASSERT_EQ(e.ToVertex(), *vertex); + } + ASSERT_EQ(vertex->OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + // Check edges with filters + ASSERT_EQ(vertex->InEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + ASSERT_EQ(vertex->OutEdges({2}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({2, 5}, storage::View::OLD).GetReturn().size(), + 1); + + acc.Commit(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid_vertex, storage::View::NEW); + ASSERT_TRUE(vertex); + + // Check edges without filters + ASSERT_EQ(vertex->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex->OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + acc.Commit(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, VertexDetachDeleteSingleCommit) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + + auto res = acc.CreateEdge(&vertex_from, &vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), vertex_from); + ASSERT_EQ(edge.ToVertex(), vertex_to); + + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + + // Check edges + ASSERT_EQ(vertex_from.InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from.OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + { + auto ret = vertex_to.InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + ASSERT_EQ(vertex_to.OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + acc.Commit(); + } + + // Detach delete vertex + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.IsError()); + ASSERT_EQ(ret.GetError(), storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.IsReturn()); + ASSERT_TRUE(ret.GetReturn()); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_FALSE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, VertexDetachDeleteMultipleCommit) { + storage::Storage store; + storage::Gid gid_vertex1 = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_vertex2 = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store.Access(); + auto vertex1 = acc.CreateVertex(); + auto vertex2 = acc.CreateVertex(); + + gid_vertex1 = vertex1.Gid(); + gid_vertex2 = vertex2.Gid(); + + auto res1 = acc.CreateEdge(&vertex1, &vertex2, 10); + ASSERT_TRUE(res1.IsReturn()); + auto edge1 = res1.GetReturn(); + ASSERT_EQ(edge1.EdgeType(), 10); + ASSERT_EQ(edge1.FromVertex(), vertex1); + ASSERT_EQ(edge1.ToVertex(), vertex2); + + auto res2 = acc.CreateEdge(&vertex2, &vertex1, 20); + ASSERT_TRUE(res2.IsReturn()); + auto edge2 = res2.GetReturn(); + ASSERT_EQ(edge2.EdgeType(), 20); + ASSERT_EQ(edge2.FromVertex(), vertex2); + ASSERT_EQ(edge2.ToVertex(), vertex1); + + auto res3 = acc.CreateEdge(&vertex1, &vertex1, 30); + ASSERT_TRUE(res3.IsReturn()); + auto edge3 = res3.GetReturn(); + ASSERT_EQ(edge3.EdgeType(), 30); + ASSERT_EQ(edge3.FromVertex(), vertex1); + ASSERT_EQ(edge3.ToVertex(), vertex1); + + auto res4 = acc.CreateEdge(&vertex2, &vertex2, 40); + ASSERT_TRUE(res4.IsReturn()); + auto edge4 = res4.GetReturn(); + ASSERT_EQ(edge4.EdgeType(), 40); + ASSERT_EQ(edge4.FromVertex(), vertex2); + ASSERT_EQ(edge4.ToVertex(), vertex2); + + // Check edges + { + auto ret = vertex1.InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex1.OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex2.InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + { + auto ret = vertex2.OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + + acc.Commit(); + } + + // Detach delete vertex + { + auto acc = store.Access(); + auto vertex1 = acc.FindVertex(gid_vertex1, storage::View::NEW); + auto vertex2 = acc.FindVertex(gid_vertex2, storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&*vertex1); + ASSERT_TRUE(ret.IsError()); + ASSERT_EQ(ret.GetError(), storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&*vertex1); + ASSERT_TRUE(ret.IsReturn()); + ASSERT_TRUE(ret.GetReturn()); + } + + // Check edges + { + auto ret = vertex1->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->InEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex1->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->OutEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex2->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + + acc.Commit(); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex1 = acc.FindVertex(gid_vertex1, storage::View::NEW); + auto vertex2 = acc.FindVertex(gid_vertex2, storage::View::NEW); + ASSERT_FALSE(vertex1); + ASSERT_TRUE(vertex2); + + // Check edges + { + auto ret = vertex2->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, VertexDetachDeleteSingleAbort) { + storage::Storage store; + storage::Gid gid_from = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_to = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store.Access(); + auto vertex_from = acc.CreateVertex(); + auto vertex_to = acc.CreateVertex(); + + auto res = acc.CreateEdge(&vertex_from, &vertex_to, 5); + ASSERT_TRUE(res.IsReturn()); + auto edge = res.GetReturn(); + ASSERT_EQ(edge.EdgeType(), 5); + ASSERT_EQ(edge.FromVertex(), vertex_from); + ASSERT_EQ(edge.ToVertex(), vertex_to); + + gid_from = vertex_from.Gid(); + gid_to = vertex_to.Gid(); + + // Check edges + ASSERT_EQ(vertex_from.InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from.OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + { + auto ret = vertex_to.InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), vertex_from); + ASSERT_EQ(e.ToVertex(), vertex_to); + } + ASSERT_EQ(vertex_to.OutEdges({}, storage::View::NEW).GetReturn().size(), 0); + + acc.Commit(); + } + + // Detach delete vertex, but abort the transaction + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.IsError()); + ASSERT_EQ(ret.GetError(), storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.IsReturn()); + ASSERT_TRUE(ret.GetReturn()); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Abort(); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetReturn().size(), + 0); + { + auto ret = vertex_from->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + { + auto ret = vertex_to->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } + + // Detach delete vertex + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.IsError()); + ASSERT_EQ(ret.GetError(), storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&*vertex_from); + ASSERT_TRUE(ret.IsReturn()); + ASSERT_TRUE(ret.GetReturn()); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_from->InEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_to); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_from->OutEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 5); + ASSERT_EQ(v, *vertex_from); + ASSERT_EQ(e.EdgeType(), 5); + ASSERT_EQ(e.FromVertex(), *vertex_from); + ASSERT_EQ(e.ToVertex(), *vertex_to); + } + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + + acc.Commit(); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(gid_from, storage::View::NEW); + auto vertex_to = acc.FindVertex(gid_to, storage::View::NEW); + ASSERT_FALSE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges + ASSERT_EQ(vertex_to->InEdges({}, storage::View::OLD).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->InEdges({}, storage::View::NEW).GetReturn().size(), 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::OLD).GetReturn().size(), + 0); + ASSERT_EQ(vertex_to->OutEdges({}, storage::View::NEW).GetReturn().size(), + 0); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST(StorageV2, VertexDetachDeleteMultipleAbort) { + storage::Storage store; + storage::Gid gid_vertex1 = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + storage::Gid gid_vertex2 = + storage::Gid::FromUint(std::numeric_limits<uint64_t>::max()); + + // Create dataset + { + auto acc = store.Access(); + auto vertex1 = acc.CreateVertex(); + auto vertex2 = acc.CreateVertex(); + + gid_vertex1 = vertex1.Gid(); + gid_vertex2 = vertex2.Gid(); + + auto res1 = acc.CreateEdge(&vertex1, &vertex2, 10); + ASSERT_TRUE(res1.IsReturn()); + auto edge1 = res1.GetReturn(); + ASSERT_EQ(edge1.EdgeType(), 10); + ASSERT_EQ(edge1.FromVertex(), vertex1); + ASSERT_EQ(edge1.ToVertex(), vertex2); + + auto res2 = acc.CreateEdge(&vertex2, &vertex1, 20); + ASSERT_TRUE(res2.IsReturn()); + auto edge2 = res2.GetReturn(); + ASSERT_EQ(edge2.EdgeType(), 20); + ASSERT_EQ(edge2.FromVertex(), vertex2); + ASSERT_EQ(edge2.ToVertex(), vertex1); + + auto res3 = acc.CreateEdge(&vertex1, &vertex1, 30); + ASSERT_TRUE(res3.IsReturn()); + auto edge3 = res3.GetReturn(); + ASSERT_EQ(edge3.EdgeType(), 30); + ASSERT_EQ(edge3.FromVertex(), vertex1); + ASSERT_EQ(edge3.ToVertex(), vertex1); + + auto res4 = acc.CreateEdge(&vertex2, &vertex2, 40); + ASSERT_TRUE(res4.IsReturn()); + auto edge4 = res4.GetReturn(); + ASSERT_EQ(edge4.EdgeType(), 40); + ASSERT_EQ(edge4.FromVertex(), vertex2); + ASSERT_EQ(edge4.ToVertex(), vertex2); + + // Check edges + { + auto ret = vertex1.InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex1.OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex1); + } + } + { + auto ret = vertex2.InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), vertex1); + ASSERT_EQ(e.ToVertex(), vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + { + auto ret = vertex2.OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), vertex2); + ASSERT_EQ(e.ToVertex(), vertex2); + } + } + + acc.Commit(); + } + + // Detach delete vertex, but abort the transaction + { + auto acc = store.Access(); + auto vertex1 = acc.FindVertex(gid_vertex1, storage::View::NEW); + auto vertex2 = acc.FindVertex(gid_vertex2, storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&*vertex1); + ASSERT_TRUE(ret.IsError()); + ASSERT_EQ(ret.GetError(), storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&*vertex1); + ASSERT_TRUE(ret.IsReturn()); + ASSERT_TRUE(ret.GetReturn()); + } + + // Check edges + { + auto ret = vertex1->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->InEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex1->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->OutEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex2->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + + acc.Abort(); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex1 = acc.FindVertex(gid_vertex1, storage::View::NEW); + auto vertex2 = acc.FindVertex(gid_vertex2, storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + + // Check edges + { + auto ret = vertex1->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex1->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex1->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex1->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + { + auto ret = vertex2->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + + acc.Commit(); + } + + // Detach delete vertex + { + auto acc = store.Access(); + auto vertex1 = acc.FindVertex(gid_vertex1, storage::View::NEW); + auto vertex2 = acc.FindVertex(gid_vertex2, storage::View::NEW); + ASSERT_TRUE(vertex1); + ASSERT_TRUE(vertex2); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&*vertex1); + ASSERT_TRUE(ret.IsError()); + ASSERT_EQ(ret.GetError(), storage::Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&*vertex1); + ASSERT_TRUE(ret.IsReturn()); + ASSERT_TRUE(ret.GetReturn()); + } + + // Check edges + { + auto ret = vertex1->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->InEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex1->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 30); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 30); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + } + ASSERT_EQ(vertex1->OutEdges({}, storage::View::NEW).GetError(), + storage::Error::DELETED_OBJECT); + { + auto ret = vertex2->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 10); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 10); + ASSERT_EQ(e.FromVertex(), *vertex1); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { + return std::get<0>(a) < std::get<0>(b); + }); + ASSERT_EQ(edges.size(), 2); + { + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 20); + ASSERT_EQ(v, *vertex1); + ASSERT_EQ(e.EdgeType(), 20); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex1); + } + { + auto [et, v, e] = edges[1]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, *vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } + { + auto ret = vertex2->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + + acc.Commit(); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex1 = acc.FindVertex(gid_vertex1, storage::View::NEW); + auto vertex2 = acc.FindVertex(gid_vertex2, storage::View::NEW); + ASSERT_FALSE(vertex1); + ASSERT_TRUE(vertex2); + + // Check edges + { + auto ret = vertex2->InEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->InEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::OLD); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + { + auto ret = vertex2->OutEdges({}, storage::View::NEW); + ASSERT_TRUE(ret.IsReturn()); + auto edges = ret.GetReturn(); + ASSERT_EQ(edges.size(), 1); + auto [et, v, e] = edges[0]; + ASSERT_EQ(et, 40); + ASSERT_EQ(v, vertex2); + ASSERT_EQ(e.EdgeType(), 40); + ASSERT_EQ(e.FromVertex(), *vertex2); + ASSERT_EQ(e.ToVertex(), *vertex2); + } + } +}