Add abstract classes for Accessor, VertexAccessor and EdgeAccessor.
This commit is contained in:
parent
da8aa68c86
commit
c64b607478
@ -52,27 +52,27 @@ class VertexAccessor;
|
|||||||
|
|
||||||
class EdgeAccessor final {
|
class EdgeAccessor final {
|
||||||
public:
|
public:
|
||||||
storage::EdgeAccessor impl_;
|
storage::EdgeAccessor *impl_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EdgeAccessor(storage::EdgeAccessor impl) : impl_(std::move(impl)) {}
|
explicit EdgeAccessor(storage::EdgeAccessor *impl) : impl_(impl) {}
|
||||||
|
|
||||||
bool IsVisible(storage::View view) const { return impl_.IsVisible(view); }
|
bool IsVisible(storage::View view) const { return impl_->IsVisible(view); }
|
||||||
|
|
||||||
storage::EdgeTypeId EdgeType() const { return impl_.EdgeType(); }
|
storage::EdgeTypeId EdgeType() const { return impl_->EdgeType(); }
|
||||||
|
|
||||||
auto Properties(storage::View view) const { return impl_.Properties(view); }
|
auto Properties(storage::View view) const { return impl_->Properties(view); }
|
||||||
|
|
||||||
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
||||||
return impl_.GetProperty(key, view);
|
return impl_->GetProperty(key, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||||
return impl_.SetProperty(key, value);
|
return impl_->SetProperty(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
||||||
return impl_.InitProperties(properties);
|
return impl_->InitProperties(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
||||||
@ -80,7 +80,7 @@ class EdgeAccessor final {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<std::map<storage::PropertyId, storage::PropertyValue>> ClearProperties() {
|
storage::Result<std::map<storage::PropertyId, storage::PropertyValue>> ClearProperties() {
|
||||||
return impl_.ClearProperties();
|
return impl_->ClearProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexAccessor To() const;
|
VertexAccessor To() const;
|
||||||
@ -89,9 +89,9 @@ class EdgeAccessor final {
|
|||||||
|
|
||||||
bool IsCycle() const;
|
bool IsCycle() const;
|
||||||
|
|
||||||
int64_t CypherId() const { return impl_.Gid().AsInt(); }
|
int64_t CypherId() const { return impl_->Gid().AsInt(); }
|
||||||
|
|
||||||
storage::Gid Gid() const noexcept { return impl_.Gid(); }
|
storage::Gid Gid() const noexcept { return impl_->Gid(); }
|
||||||
|
|
||||||
bool operator==(const EdgeAccessor &e) const noexcept { return impl_ == e.impl_; }
|
bool operator==(const EdgeAccessor &e) const noexcept { return impl_ == e.impl_; }
|
||||||
|
|
||||||
@ -100,37 +100,37 @@ class EdgeAccessor final {
|
|||||||
|
|
||||||
class VertexAccessor final {
|
class VertexAccessor final {
|
||||||
public:
|
public:
|
||||||
storage::VertexAccessor impl_;
|
storage::VertexAccessor *impl_;
|
||||||
|
|
||||||
static EdgeAccessor MakeEdgeAccessor(const storage::EdgeAccessor impl) { return EdgeAccessor(impl); }
|
static EdgeAccessor MakeEdgeAccessor(const storage::EdgeAccessor impl) { return EdgeAccessor(impl); }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit VertexAccessor(storage::VertexAccessor impl) : impl_(impl) {}
|
explicit VertexAccessor(storage::VertexAccessor *impl) : impl_(impl) {}
|
||||||
|
|
||||||
bool IsVisible(storage::View view) const { return impl_.IsVisible(view); }
|
bool IsVisible(storage::View view) const { return impl_->IsVisible(view); }
|
||||||
|
|
||||||
auto Labels(storage::View view) const { return impl_.Labels(view); }
|
auto Labels(storage::View view) const { return impl_->Labels(view); }
|
||||||
|
|
||||||
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_.AddLabel(label); }
|
storage::Result<bool> AddLabel(storage::LabelId label) { return impl_->AddLabel(label); }
|
||||||
|
|
||||||
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_.RemoveLabel(label); }
|
storage::Result<bool> RemoveLabel(storage::LabelId label) { return impl_->RemoveLabel(label); }
|
||||||
|
|
||||||
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
|
storage::Result<bool> HasLabel(storage::View view, storage::LabelId label) const {
|
||||||
return impl_.HasLabel(label, view);
|
return impl_->HasLabel(label, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Properties(storage::View view) const { return impl_.Properties(view); }
|
auto Properties(storage::View view) const { return impl_->Properties(view); }
|
||||||
|
|
||||||
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
storage::Result<storage::PropertyValue> GetProperty(storage::View view, storage::PropertyId key) const {
|
||||||
return impl_.GetProperty(key, view);
|
return impl_->GetProperty(key, view);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
storage::Result<storage::PropertyValue> SetProperty(storage::PropertyId key, const storage::PropertyValue &value) {
|
||||||
return impl_.SetProperty(key, value);
|
return impl_->SetProperty(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
storage::Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
||||||
return impl_.InitProperties(properties);
|
return impl_->InitProperties(properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
storage::Result<storage::PropertyValue> RemoveProperty(storage::PropertyId key) {
|
||||||
@ -138,12 +138,12 @@ class VertexAccessor final {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<std::map<storage::PropertyId, storage::PropertyValue>> ClearProperties() {
|
storage::Result<std::map<storage::PropertyId, storage::PropertyValue>> ClearProperties() {
|
||||||
return impl_.ClearProperties();
|
return impl_->ClearProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
||||||
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
|
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_->InEdges(view)))> {
|
||||||
auto maybe_edges = impl_.InEdges(view, edge_types);
|
auto maybe_edges = impl_->InEdges(view, edge_types);
|
||||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||||
}
|
}
|
||||||
@ -151,15 +151,15 @@ class VertexAccessor final {
|
|||||||
auto InEdges(storage::View view) const { return InEdges(view, {}); }
|
auto InEdges(storage::View view) const { return InEdges(view, {}); }
|
||||||
|
|
||||||
auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, const VertexAccessor &dest) const
|
auto InEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types, const VertexAccessor &dest) const
|
||||||
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.InEdges(view)))> {
|
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *(impl_->InEdges(view))))> {
|
||||||
auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_);
|
auto maybe_edges = impl_->InEdges(view, edge_types, dest.impl_);
|
||||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types) const
|
||||||
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *(impl_->OutEdges(view))))> {
|
||||||
auto maybe_edges = impl_.OutEdges(view, edge_types);
|
auto maybe_edges = impl_->OutEdges(view, edge_types);
|
||||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||||
}
|
}
|
||||||
@ -169,18 +169,18 @@ class VertexAccessor final {
|
|||||||
auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types,
|
auto OutEdges(storage::View view, const std::vector<storage::EdgeTypeId> &edge_types,
|
||||||
const VertexAccessor &dest) const
|
const VertexAccessor &dest) const
|
||||||
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
-> storage::Result<decltype(iter::imap(MakeEdgeAccessor, *impl_.OutEdges(view)))> {
|
||||||
auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_);
|
auto maybe_edges = impl_->OutEdges(view, edge_types, dest.impl_);
|
||||||
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
if (maybe_edges.HasError()) return maybe_edges.GetError();
|
||||||
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges));
|
||||||
}
|
}
|
||||||
|
|
||||||
storage::Result<size_t> InDegree(storage::View view) const { return impl_.InDegree(view); }
|
storage::Result<size_t> InDegree(storage::View view) const { return impl_->InDegree(view); }
|
||||||
|
|
||||||
storage::Result<size_t> OutDegree(storage::View view) const { return impl_.OutDegree(view); }
|
storage::Result<size_t> OutDegree(storage::View view) const { return impl_->OutDegree(view); }
|
||||||
|
|
||||||
int64_t CypherId() const { return impl_.Gid().AsInt(); }
|
int64_t CypherId() const { return impl_->Gid().AsInt(); }
|
||||||
|
|
||||||
storage::Gid Gid() const noexcept { return impl_.Gid(); }
|
storage::Gid Gid() const noexcept { return impl_->Gid(); }
|
||||||
|
|
||||||
bool operator==(const VertexAccessor &v) const noexcept {
|
bool operator==(const VertexAccessor &v) const noexcept {
|
||||||
static_assert(noexcept(impl_ == v.impl_));
|
static_assert(noexcept(impl_ == v.impl_));
|
||||||
@ -190,9 +190,9 @@ class VertexAccessor final {
|
|||||||
bool operator!=(const VertexAccessor &v) const noexcept { return !(*this == v); }
|
bool operator!=(const VertexAccessor &v) const noexcept { return !(*this == v); }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); }
|
inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_->ToVertex()); }
|
||||||
|
|
||||||
inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); }
|
inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_->FromVertex()); }
|
||||||
|
|
||||||
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
inline bool EdgeAccessor::IsCycle() const { return To() == From(); }
|
||||||
|
|
||||||
@ -241,12 +241,14 @@ namespace std {
|
|||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct hash<memgraph::query::VertexAccessor> {
|
struct hash<memgraph::query::VertexAccessor> {
|
||||||
size_t operator()(const memgraph::query::VertexAccessor &v) const { return std::hash<decltype(v.impl_)>{}(v.impl_); }
|
size_t operator()(const memgraph::query::VertexAccessor &v) const {
|
||||||
|
return std::hash<decltype(*v.impl_)>{}(*v.impl_);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
struct hash<memgraph::query::EdgeAccessor> {
|
struct hash<memgraph::query::EdgeAccessor> {
|
||||||
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(e.impl_)>{}(e.impl_); }
|
size_t operator()(const memgraph::query::EdgeAccessor &e) const { return std::hash<decltype(*e.impl_)>{}(*e.impl_); }
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
@ -6,11 +6,12 @@ set(storage_v2_src_files
|
|||||||
durability/serialization.cpp
|
durability/serialization.cpp
|
||||||
durability/snapshot.cpp
|
durability/snapshot.cpp
|
||||||
durability/wal.cpp
|
durability/wal.cpp
|
||||||
edge_accessor.cpp
|
|
||||||
indices.cpp
|
indices.cpp
|
||||||
|
inmemory/edge_accessor.cpp
|
||||||
|
inmemory/storage.cpp
|
||||||
|
inmemory/vertex_accessor.cpp
|
||||||
property_store.cpp
|
property_store.cpp
|
||||||
vertex_accessor.cpp
|
vertex_accessor.cpp)
|
||||||
storage.cpp)
|
|
||||||
|
|
||||||
|
|
||||||
set(storage_v2_src_files
|
set(storage_v2_src_files
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -19,6 +19,7 @@
|
|||||||
#include "storage/v2/edge_accessor.hpp"
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
#include "storage/v2/edge_ref.hpp"
|
#include "storage/v2/edge_ref.hpp"
|
||||||
#include "storage/v2/mvcc.hpp"
|
#include "storage/v2/mvcc.hpp"
|
||||||
|
#include "storage/v2/storage.hpp"
|
||||||
#include "storage/v2/vertex_accessor.hpp"
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
#include "utils/file_locker.hpp"
|
#include "utils/file_locker.hpp"
|
||||||
#include "utils/logging.hpp"
|
#include "utils/logging.hpp"
|
||||||
@ -713,11 +714,11 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
|||||||
// type and invalid from/to pointers because we don't know them here,
|
// type and invalid from/to pointers because we don't know them here,
|
||||||
// but that isn't an issue because we won't use that part of the API
|
// but that isn't an issue because we won't use that part of the API
|
||||||
// here.
|
// here.
|
||||||
auto ea =
|
auto ea = EdgeAccessor::Create(edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices,
|
||||||
EdgeAccessor{edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices, constraints, items};
|
constraints, items);
|
||||||
|
|
||||||
// Get edge data.
|
// Get edge data.
|
||||||
auto maybe_props = ea.Properties(View::OLD);
|
auto maybe_props = ea->Properties(View::OLD);
|
||||||
MG_ASSERT(maybe_props.HasValue(), "Invalid database state!");
|
MG_ASSERT(maybe_props.HasValue(), "Invalid database state!");
|
||||||
|
|
||||||
// Store the edge.
|
// Store the edge.
|
||||||
@ -776,14 +777,14 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
|||||||
snapshot.WriteUint(in_edges.size());
|
snapshot.WriteUint(in_edges.size());
|
||||||
for (const auto &item : in_edges) {
|
for (const auto &item : in_edges) {
|
||||||
snapshot.WriteUint(item.Gid().AsUint());
|
snapshot.WriteUint(item.Gid().AsUint());
|
||||||
snapshot.WriteUint(item.FromVertex().Gid().AsUint());
|
snapshot.WriteUint(item.FromVertex()->Gid().AsUint());
|
||||||
write_mapping(item.EdgeType());
|
write_mapping(item.EdgeType());
|
||||||
}
|
}
|
||||||
const auto &out_edges = maybe_out_edges.GetValue();
|
const auto &out_edges = maybe_out_edges.GetValue();
|
||||||
snapshot.WriteUint(out_edges.size());
|
snapshot.WriteUint(out_edges.size());
|
||||||
for (const auto &item : out_edges) {
|
for (const auto &item : out_edges) {
|
||||||
snapshot.WriteUint(item.Gid().AsUint());
|
snapshot.WriteUint(item.Gid().AsUint());
|
||||||
snapshot.WriteUint(item.ToVertex().Gid().AsUint());
|
snapshot.WriteUint(item.ToVertex()->Gid().AsUint());
|
||||||
write_mapping(item.EdgeType());
|
write_mapping(item.EdgeType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,247 +10,17 @@
|
|||||||
// licenses/APL.txt.
|
// licenses/APL.txt.
|
||||||
|
|
||||||
#include "storage/v2/edge_accessor.hpp"
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
#include "storage/v2/mvcc.hpp"
|
#include "storage/v2/inmemory/edge_accessor.hpp"
|
||||||
#include "storage/v2/property_value.hpp"
|
|
||||||
#include "storage/v2/vertex_accessor.hpp"
|
|
||||||
#include "utils/memory_tracker.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
bool EdgeAccessor::IsVisible(const View view) const {
|
std::unique_ptr<EdgeAccessor> EdgeAccessor::Create(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex,
|
||||||
bool exists = true;
|
Vertex *to_vertex, Transaction *transaction, Indices *indices,
|
||||||
bool deleted = true;
|
Constraints *constraints, Config::Items config,
|
||||||
// When edges don't have properties, their isolation level is still dictated by MVCC ->
|
bool for_deleted = false) {
|
||||||
// iterate over the deltas of the from_vertex_ and see which deltas can be applied on edges.
|
return std::make_unique<InMemoryEdgeAccessor>(edge, edge_type, from_vertex, to_vertex, transaction, indices,
|
||||||
if (!config_.properties_on_edges) {
|
constraints, config, for_deleted);
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(from_vertex_->lock);
|
|
||||||
// Initialize deleted by checking if out edges contain edge_
|
|
||||||
deleted = std::find_if(from_vertex_->out_edges.begin(), from_vertex_->out_edges.end(), [&](const auto &out_edge) {
|
|
||||||
return std::get<2>(out_edge) == edge_;
|
|
||||||
}) == from_vertex_->out_edges.end();
|
|
||||||
delta = from_vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::ADD_LABEL:
|
|
||||||
case Delta::Action::REMOVE_LABEL:
|
|
||||||
case Delta::Action::SET_PROPERTY:
|
|
||||||
case Delta::Action::REMOVE_IN_EDGE:
|
|
||||||
case Delta::Action::ADD_IN_EDGE:
|
|
||||||
case Delta::Action::RECREATE_OBJECT:
|
|
||||||
case Delta::Action::DELETE_OBJECT:
|
|
||||||
break;
|
|
||||||
case Delta::Action::ADD_OUT_EDGE: { // relevant for the from_vertex_ -> we just deleted the edge
|
|
||||||
if (delta.vertex_edge.edge == edge_) {
|
|
||||||
deleted = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::REMOVE_OUT_EDGE: { // also relevant for the from_vertex_ -> we just added the edge
|
|
||||||
if (delta.vertex_edge.edge == edge_) {
|
|
||||||
exists = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return exists && (for_deleted_ || !deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
|
||||||
deleted = edge_.ptr->deleted;
|
|
||||||
delta = edge_.ptr->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
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: {
|
|
||||||
deleted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return exists && (for_deleted_ || !deleted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexAccessor EdgeAccessor::FromVertex() const {
|
|
||||||
return VertexAccessor{from_vertex_, transaction_, indices_, constraints_, config_};
|
|
||||||
}
|
|
||||||
|
|
||||||
VertexAccessor EdgeAccessor::ToVertex() const {
|
|
||||||
return VertexAccessor{to_vertex_, transaction_, indices_, constraints_, config_};
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
|
||||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
|
||||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
|
||||||
|
|
||||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
auto current_value = edge_.ptr->properties.GetProperty(property);
|
|
||||||
// We could skip setting the value if the previous one is the same to the new
|
|
||||||
// one. This would save some memory as a delta would not be created as well as
|
|
||||||
// avoid copying the value. The reason we are not doing that is because the
|
|
||||||
// current code always follows the logical pattern of "create a delta" and
|
|
||||||
// "modify in-place". Additionally, the created delta will make other
|
|
||||||
// transactions get a SERIALIZATION_ERROR.
|
|
||||||
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, current_value);
|
|
||||||
edge_.ptr->properties.SetProperty(property, value);
|
|
||||||
|
|
||||||
return std::move(current_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
|
||||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
|
||||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
|
||||||
|
|
||||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
if (!edge_.ptr->properties.InitProperties(properties)) return false;
|
|
||||||
for (const auto &[property, _] : properties) {
|
|
||||||
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() {
|
|
||||||
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
|
||||||
|
|
||||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
auto properties = edge_.ptr->properties.Properties();
|
|
||||||
for (const auto &property : properties) {
|
|
||||||
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
edge_.ptr->properties.ClearProperties();
|
|
||||||
|
|
||||||
return std::move(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) const {
|
|
||||||
if (!config_.properties_on_edges) return PropertyValue();
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
PropertyValue value;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
|
||||||
deleted = edge_.ptr->deleted;
|
|
||||||
value = edge_.ptr->properties.GetProperty(property);
|
|
||||||
delta = edge_.ptr->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::SET_PROPERTY: {
|
|
||||||
if (delta.property.key == property) {
|
|
||||||
value = delta.property.value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::RECREATE_OBJECT: {
|
|
||||||
deleted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return std::move(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) const {
|
|
||||||
if (!config_.properties_on_edges) return std::map<PropertyId, PropertyValue>{};
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
std::map<PropertyId, PropertyValue> properties;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
|
||||||
deleted = edge_.ptr->deleted;
|
|
||||||
properties = edge_.ptr->properties.Properties();
|
|
||||||
delta = edge_.ptr->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::SET_PROPERTY: {
|
|
||||||
auto it = properties.find(delta.property.key);
|
|
||||||
if (it != properties.end()) {
|
|
||||||
if (delta.property.value.IsNull()) {
|
|
||||||
// remove the property
|
|
||||||
properties.erase(it);
|
|
||||||
} else {
|
|
||||||
// set the value
|
|
||||||
it->second = delta.property.value;
|
|
||||||
}
|
|
||||||
} else if (!delta.property.value.IsNull()) {
|
|
||||||
properties.emplace(delta.property.key, delta.property.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::RECREATE_OBJECT: {
|
|
||||||
deleted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return std::move(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::storage
|
} // namespace memgraph::storage
|
||||||
|
@ -11,91 +11,73 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "storage/v2/edge.hpp"
|
#include "storage/v2/edge.hpp"
|
||||||
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
#include "storage/v2/edge_ref.hpp"
|
#include "storage/v2/edge_ref.hpp"
|
||||||
|
|
||||||
#include "storage/v2/config.hpp"
|
#include "storage/v2/config.hpp"
|
||||||
#include "storage/v2/result.hpp"
|
#include "storage/v2/result.hpp"
|
||||||
#include "storage/v2/transaction.hpp"
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
#include "storage/v2/view.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
struct Vertex;
|
struct Transaction;
|
||||||
class VertexAccessor;
|
|
||||||
struct Indices;
|
|
||||||
struct Constraints;
|
|
||||||
|
|
||||||
class EdgeAccessor final {
|
class EdgeAccessor {
|
||||||
private:
|
private:
|
||||||
friend class Storage;
|
friend class Storage;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction,
|
EdgeAccessor(EdgeTypeId edge_type, Transaction *transaction, Config::Items config, bool for_deleted = false)
|
||||||
Indices *indices, Constraints *constraints, Config::Items config, bool for_deleted = false)
|
: edge_type_(edge_type), transaction_(transaction), config_(config), for_deleted_(for_deleted) {}
|
||||||
: edge_(edge),
|
|
||||||
edge_type_(edge_type),
|
virtual ~EdgeAccessor() {}
|
||||||
from_vertex_(from_vertex),
|
|
||||||
to_vertex_(to_vertex),
|
static std::unique_ptr<EdgeAccessor> Create(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex,
|
||||||
transaction_(transaction),
|
Vertex *to_vertex, Transaction *transaction, Indices *indices,
|
||||||
indices_(indices),
|
Constraints *constraints, Config::Items config, bool for_deleted = false);
|
||||||
constraints_(constraints),
|
|
||||||
config_(config),
|
|
||||||
for_deleted_(for_deleted) {}
|
|
||||||
|
|
||||||
/// @return true if the object is visible from the current transaction
|
/// @return true if the object is visible from the current transaction
|
||||||
bool IsVisible(View view) const;
|
virtual bool IsVisible(View view) const = 0;
|
||||||
|
|
||||||
VertexAccessor FromVertex() const;
|
virtual std::unique_ptr<VertexAccessor> FromVertex() const = 0;
|
||||||
|
|
||||||
VertexAccessor ToVertex() const;
|
virtual std::unique_ptr<VertexAccessor> ToVertex() const = 0;
|
||||||
|
|
||||||
EdgeTypeId EdgeType() const { return edge_type_; }
|
EdgeTypeId EdgeType() const { return edge_type_; }
|
||||||
|
|
||||||
/// Set a property value and return the old value.
|
/// Set a property value and return the old value.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<storage::PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
virtual Result<storage::PropertyValue> SetProperty(PropertyId property, const PropertyValue &value) = 0;
|
||||||
|
|
||||||
/// Set property values only if property store is empty. Returns `true` if successully set all values,
|
/// Set property values only if property store is empty. Returns `true` if successully set all values,
|
||||||
/// `false` otherwise.
|
/// `false` otherwise.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties);
|
virtual Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) = 0;
|
||||||
|
|
||||||
/// Remove all properties and return old values for each removed property.
|
/// Remove all properties and return old values for each removed property.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<std::map<PropertyId, PropertyValue>> ClearProperties();
|
virtual Result<std::map<PropertyId, PropertyValue>> ClearProperties() = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
|
virtual Result<PropertyValue> GetProperty(PropertyId property, View view) const = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
|
virtual Result<std::map<PropertyId, PropertyValue>> Properties(View view) const = 0;
|
||||||
|
|
||||||
Gid Gid() const noexcept {
|
virtual class Gid Gid() const noexcept = 0;
|
||||||
if (config_.properties_on_edges) {
|
|
||||||
return edge_.ptr->gid;
|
|
||||||
} else {
|
|
||||||
return edge_.gid;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsCycle() const { return from_vertex_ == to_vertex_; }
|
virtual bool IsCycle() const = 0;
|
||||||
|
|
||||||
bool operator==(const EdgeAccessor &other) const noexcept {
|
virtual bool operator==(const EdgeAccessor &other) const noexcept = 0;
|
||||||
return edge_ == other.edge_ && transaction_ == other.transaction_;
|
|
||||||
}
|
|
||||||
bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); }
|
bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); }
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
EdgeRef edge_;
|
|
||||||
EdgeTypeId edge_type_;
|
EdgeTypeId edge_type_;
|
||||||
Vertex *from_vertex_;
|
|
||||||
Vertex *to_vertex_;
|
|
||||||
Transaction *transaction_;
|
Transaction *transaction_;
|
||||||
Indices *indices_;
|
|
||||||
Constraints *constraints_;
|
|
||||||
Config::Items config_;
|
Config::Items config_;
|
||||||
|
|
||||||
// if the accessor was created for a deleted edge.
|
// if the accessor was created for a deleted edge.
|
||||||
@ -108,10 +90,3 @@ class EdgeAccessor final {
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace memgraph::storage
|
} // namespace memgraph::storage
|
||||||
|
|
||||||
namespace std {
|
|
||||||
template <>
|
|
||||||
struct hash<memgraph::storage::EdgeAccessor> {
|
|
||||||
size_t operator()(const memgraph::storage::EdgeAccessor &e) const { return e.Gid().AsUint(); }
|
|
||||||
};
|
|
||||||
} // namespace std
|
|
||||||
|
@ -327,10 +327,7 @@ void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||||
: self_(self),
|
: self_(self), index_iterator_(index_iterator), current_vertex_accessor_(nullptr), current_vertex_(nullptr) {
|
||||||
index_iterator_(index_iterator),
|
|
||||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_),
|
|
||||||
current_vertex_(nullptr) {
|
|
||||||
AdvanceUntilValid();
|
AdvanceUntilValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -347,8 +344,8 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
|
|||||||
}
|
}
|
||||||
if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) {
|
if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) {
|
||||||
current_vertex_ = index_iterator_->vertex;
|
current_vertex_ = index_iterator_->vertex;
|
||||||
current_vertex_accessor_ =
|
current_vertex_accessor_ = VertexAccessor::Create(current_vertex_, self_->transaction_, self_->indices_,
|
||||||
VertexAccessor{current_vertex_, self_->transaction_, self_->indices_, self_->constraints_, self_->config_};
|
self_->constraints_, self_->config_, self_->view_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -478,10 +475,7 @@ void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_time
|
|||||||
}
|
}
|
||||||
|
|
||||||
LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
|
||||||
: self_(self),
|
: self_(self), index_iterator_(index_iterator), current_vertex_accessor_(nullptr), current_vertex_(nullptr) {
|
||||||
index_iterator_(index_iterator),
|
|
||||||
current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_),
|
|
||||||
current_vertex_(nullptr) {
|
|
||||||
AdvanceUntilValid();
|
AdvanceUntilValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,8 +513,8 @@ void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() {
|
|||||||
if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_,
|
if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_,
|
||||||
index_iterator_->value, self_->transaction_, self_->view_)) {
|
index_iterator_->value, self_->transaction_, self_->view_)) {
|
||||||
current_vertex_ = index_iterator_->vertex;
|
current_vertex_ = index_iterator_->vertex;
|
||||||
current_vertex_accessor_ =
|
current_vertex_accessor_ = VertexAccessor::Create(current_vertex_, self_->transaction_, self_->indices_,
|
||||||
VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, self_->constraints_, self_->config_);
|
self_->constraints_, self_->config_, self_->view_);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ class LabelIndex {
|
|||||||
public:
|
public:
|
||||||
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
|
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
|
||||||
|
|
||||||
VertexAccessor operator*() const { return current_vertex_accessor_; }
|
VertexAccessor *operator*() const { return current_vertex_accessor_.get(); }
|
||||||
|
|
||||||
bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; }
|
bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; }
|
||||||
bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; }
|
bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; }
|
||||||
@ -90,7 +90,7 @@ class LabelIndex {
|
|||||||
|
|
||||||
Iterable *self_;
|
Iterable *self_;
|
||||||
utils::SkipList<Entry>::Iterator index_iterator_;
|
utils::SkipList<Entry>::Iterator index_iterator_;
|
||||||
VertexAccessor current_vertex_accessor_;
|
std::unique_ptr<VertexAccessor> current_vertex_accessor_;
|
||||||
Vertex *current_vertex_;
|
Vertex *current_vertex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -181,7 +181,7 @@ class LabelPropertyIndex {
|
|||||||
public:
|
public:
|
||||||
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
|
Iterator(Iterable *self, utils::SkipList<Entry>::Iterator index_iterator);
|
||||||
|
|
||||||
VertexAccessor operator*() const { return current_vertex_accessor_; }
|
VertexAccessor *operator*() const { return current_vertex_accessor_.get(); }
|
||||||
|
|
||||||
bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; }
|
bool operator==(const Iterator &other) const { return index_iterator_ == other.index_iterator_; }
|
||||||
bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; }
|
bool operator!=(const Iterator &other) const { return index_iterator_ != other.index_iterator_; }
|
||||||
@ -193,7 +193,7 @@ class LabelPropertyIndex {
|
|||||||
|
|
||||||
Iterable *self_;
|
Iterable *self_;
|
||||||
utils::SkipList<Entry>::Iterator index_iterator_;
|
utils::SkipList<Entry>::Iterator index_iterator_;
|
||||||
VertexAccessor current_vertex_accessor_;
|
std::unique_ptr<VertexAccessor> current_vertex_accessor_;
|
||||||
Vertex *current_vertex_;
|
Vertex *current_vertex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
258
src/storage/v2/inmemory/edge_accessor.cpp
Normal file
258
src/storage/v2/inmemory/edge_accessor.cpp
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#include "storage/v2/inmemory/edge_accessor.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||||
|
#include "storage/v2/mvcc.hpp"
|
||||||
|
#include "storage/v2/property_value.hpp"
|
||||||
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
|
#include "utils/memory_tracker.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::storage {
|
||||||
|
|
||||||
|
bool InMemoryEdgeAccessor::IsVisible(const View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = true;
|
||||||
|
// When edges don't have properties, their isolation level is still dictated by MVCC ->
|
||||||
|
// iterate over the deltas of the from_vertex_ and see which deltas can be applied on edges.
|
||||||
|
if (!config_.properties_on_edges) {
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(from_vertex_->lock);
|
||||||
|
// Initialize deleted by checking if out edges contain edge_
|
||||||
|
deleted = std::find_if(from_vertex_->out_edges.begin(), from_vertex_->out_edges.end(), [&](const auto &out_edge) {
|
||||||
|
return std::get<2>(out_edge) == edge_;
|
||||||
|
}) == from_vertex_->out_edges.end();
|
||||||
|
delta = from_vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_LABEL:
|
||||||
|
case Delta::Action::REMOVE_LABEL:
|
||||||
|
case Delta::Action::SET_PROPERTY:
|
||||||
|
case Delta::Action::REMOVE_IN_EDGE:
|
||||||
|
case Delta::Action::ADD_IN_EDGE:
|
||||||
|
case Delta::Action::RECREATE_OBJECT:
|
||||||
|
case Delta::Action::DELETE_OBJECT:
|
||||||
|
break;
|
||||||
|
case Delta::Action::ADD_OUT_EDGE: { // relevant for the from_vertex_ -> we just deleted the edge
|
||||||
|
if (delta.vertex_edge.edge == edge_) {
|
||||||
|
deleted = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::REMOVE_OUT_EDGE: { // also relevant for the from_vertex_ -> we just added the edge
|
||||||
|
if (delta.vertex_edge.edge == edge_) {
|
||||||
|
exists = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return exists && (for_deleted_ || !deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||||
|
deleted = edge_.ptr->deleted;
|
||||||
|
delta = edge_.ptr->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
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: {
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return exists && (for_deleted_ || !deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<VertexAccessor> InMemoryEdgeAccessor::FromVertex() const {
|
||||||
|
return std::make_unique<InMemoryVertexAccessor>(from_vertex_, transaction_, indices_, constraints_, config_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<VertexAccessor> InMemoryEdgeAccessor::ToVertex() const {
|
||||||
|
return std::make_unique<InMemoryVertexAccessor>(to_vertex_, transaction_, indices_, constraints_, config_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<storage::PropertyValue> InMemoryEdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||||
|
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||||
|
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||||
|
|
||||||
|
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
auto current_value = edge_.ptr->properties.GetProperty(property);
|
||||||
|
// We could skip setting the value if the previous one is the same to the new
|
||||||
|
// one. This would save some memory as a delta would not be created as well as
|
||||||
|
// avoid copying the value. The reason we are not doing that is because the
|
||||||
|
// current code always follows the logical pattern of "create a delta" and
|
||||||
|
// "modify in-place". Additionally, the created delta will make other
|
||||||
|
// transactions get a SERIALIZATION_ERROR.
|
||||||
|
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, current_value);
|
||||||
|
edge_.ptr->properties.SetProperty(property, value);
|
||||||
|
|
||||||
|
return std::move(current_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> InMemoryEdgeAccessor::InitProperties(
|
||||||
|
const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
||||||
|
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||||
|
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||||
|
|
||||||
|
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
if (!edge_.ptr->properties.InitProperties(properties)) return false;
|
||||||
|
for (const auto &[property, _] : properties) {
|
||||||
|
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property, PropertyValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> InMemoryEdgeAccessor::ClearProperties() {
|
||||||
|
if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED;
|
||||||
|
|
||||||
|
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (edge_.ptr->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
auto properties = edge_.ptr->properties.Properties();
|
||||||
|
for (const auto &property : properties) {
|
||||||
|
CreateAndLinkDelta(transaction_, edge_.ptr, Delta::SetPropertyTag(), property.first, property.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
edge_.ptr->properties.ClearProperties();
|
||||||
|
|
||||||
|
return std::move(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<PropertyValue> InMemoryEdgeAccessor::GetProperty(PropertyId property, View view) const {
|
||||||
|
if (!config_.properties_on_edges) return PropertyValue();
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
PropertyValue value;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||||
|
deleted = edge_.ptr->deleted;
|
||||||
|
value = edge_.ptr->properties.GetProperty(property);
|
||||||
|
delta = edge_.ptr->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::SET_PROPERTY: {
|
||||||
|
if (delta.property.key == property) {
|
||||||
|
value = delta.property.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> InMemoryEdgeAccessor::Properties(View view) const {
|
||||||
|
if (!config_.properties_on_edges) return std::map<PropertyId, PropertyValue>{};
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
std::map<PropertyId, PropertyValue> properties;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||||
|
deleted = edge_.ptr->deleted;
|
||||||
|
properties = edge_.ptr->properties.Properties();
|
||||||
|
delta = edge_.ptr->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::SET_PROPERTY: {
|
||||||
|
auto it = properties.find(delta.property.key);
|
||||||
|
if (it != properties.end()) {
|
||||||
|
if (delta.property.value.IsNull()) {
|
||||||
|
// remove the property
|
||||||
|
properties.erase(it);
|
||||||
|
} else {
|
||||||
|
// set the value
|
||||||
|
it->second = delta.property.value;
|
||||||
|
}
|
||||||
|
} else if (!delta.property.value.IsNull()) {
|
||||||
|
properties.emplace(delta.property.key, delta.property.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return std::move(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace memgraph::storage
|
106
src/storage/v2/inmemory/edge_accessor.hpp
Normal file
106
src/storage/v2/inmemory/edge_accessor.hpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "storage/v2/edge.hpp"
|
||||||
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
|
#include "storage/v2/edge_ref.hpp"
|
||||||
|
|
||||||
|
#include "storage/v2/config.hpp"
|
||||||
|
#include "storage/v2/id_types.hpp"
|
||||||
|
#include "storage/v2/result.hpp"
|
||||||
|
#include "storage/v2/transaction.hpp"
|
||||||
|
#include "storage/v2/view.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::storage {
|
||||||
|
|
||||||
|
struct Vertex;
|
||||||
|
class VertexAccessor;
|
||||||
|
struct Indices;
|
||||||
|
struct Constraints;
|
||||||
|
|
||||||
|
class InMemoryEdgeAccessor : public EdgeAccessor {
|
||||||
|
private:
|
||||||
|
friend class Storage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InMemoryEdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex,
|
||||||
|
Transaction *transaction, Indices *indices, Constraints *constraints, Config::Items config,
|
||||||
|
bool for_deleted = false)
|
||||||
|
: EdgeAccessor(edge_type, transaction, config, for_deleted),
|
||||||
|
edge_(edge),
|
||||||
|
from_vertex_(from_vertex),
|
||||||
|
to_vertex_(to_vertex),
|
||||||
|
indices_(indices),
|
||||||
|
constraints_(constraints) {}
|
||||||
|
|
||||||
|
/// @return true if the object is visible from the current transaction
|
||||||
|
bool IsVisible(View view) const override;
|
||||||
|
|
||||||
|
std::unique_ptr<VertexAccessor> FromVertex() const override;
|
||||||
|
|
||||||
|
std::unique_ptr<VertexAccessor> ToVertex() const override;
|
||||||
|
|
||||||
|
EdgeTypeId EdgeType() const { return edge_type_; }
|
||||||
|
|
||||||
|
/// Set a property value and return the old value.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<storage::PropertyValue> SetProperty(PropertyId property, const PropertyValue &value) override;
|
||||||
|
|
||||||
|
/// Set property values only if property store is empty. Returns `true` if successully set all values,
|
||||||
|
/// `false` otherwise.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) override;
|
||||||
|
|
||||||
|
/// Remove all properties and return old values for each removed property.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> ClearProperties() override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<PropertyValue> GetProperty(PropertyId property, View view) const override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const override;
|
||||||
|
|
||||||
|
class Gid Gid() const noexcept override {
|
||||||
|
if (config_.properties_on_edges) {
|
||||||
|
return edge_.ptr->gid;
|
||||||
|
} else {
|
||||||
|
return edge_.gid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsCycle() const override { return from_vertex_ == to_vertex_; }
|
||||||
|
|
||||||
|
bool operator==(const InMemoryEdgeAccessor &other) const noexcept {
|
||||||
|
return edge_ == other.edge_ && transaction_ == other.transaction_;
|
||||||
|
}
|
||||||
|
bool operator!=(const InMemoryEdgeAccessor &other) const noexcept { return !(*this == other); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
EdgeRef edge_;
|
||||||
|
Vertex *from_vertex_;
|
||||||
|
Vertex *to_vertex_;
|
||||||
|
Indices *indices_;
|
||||||
|
Constraints *constraints_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::storage
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct hash<memgraph::storage::InMemoryEdgeAccessor> {
|
||||||
|
size_t operator()(const memgraph::storage::InMemoryEdgeAccessor &e) const { return e.Gid().AsUint(); }
|
||||||
|
};
|
||||||
|
} // namespace std
|
@ -70,11 +70,11 @@ std::string RegisterReplicaErrorToString(Storage::RegisterReplicaError error) {
|
|||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end,
|
auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end,
|
||||||
std::optional<VertexAccessor> *vertex, Transaction *tx, View view, Indices *indices,
|
VertexAccessor *vertex, Transaction *tx, View view, Indices *indices,
|
||||||
Constraints *constraints, Config::Items config) {
|
Constraints *constraints, Config::Items config) {
|
||||||
while (it != end) {
|
while (it != end) {
|
||||||
*vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view);
|
vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view).get();
|
||||||
if (!*vertex) {
|
if (!vertex) {
|
||||||
++it;
|
++it;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
597
src/storage/v2/inmemory/storage.hpp
Normal file
597
src/storage/v2/inmemory/storage.hpp
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <span>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "io/network/endpoint.hpp"
|
||||||
|
#include "kvstore/kvstore.hpp"
|
||||||
|
#include "storage/v2/commit_log.hpp"
|
||||||
|
#include "storage/v2/config.hpp"
|
||||||
|
#include "storage/v2/constraints.hpp"
|
||||||
|
#include "storage/v2/durability/metadata.hpp"
|
||||||
|
#include "storage/v2/durability/wal.hpp"
|
||||||
|
#include "storage/v2/edge.hpp"
|
||||||
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
|
#include "storage/v2/indices.hpp"
|
||||||
|
#include "storage/v2/isolation_level.hpp"
|
||||||
|
#include "storage/v2/mvcc.hpp"
|
||||||
|
#include "storage/v2/name_id_mapper.hpp"
|
||||||
|
#include "storage/v2/result.hpp"
|
||||||
|
#include "storage/v2/storage.hpp"
|
||||||
|
#include "storage/v2/transaction.hpp"
|
||||||
|
#include "storage/v2/vertex.hpp"
|
||||||
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
|
#include "utils/file_locker.hpp"
|
||||||
|
#include "utils/on_scope_exit.hpp"
|
||||||
|
#include "utils/rw_lock.hpp"
|
||||||
|
#include "utils/scheduler.hpp"
|
||||||
|
#include "utils/skip_list.hpp"
|
||||||
|
#include "utils/synchronized.hpp"
|
||||||
|
#include "utils/uuid.hpp"
|
||||||
|
|
||||||
|
/// REPLICATION ///
|
||||||
|
#include "rpc/server.hpp"
|
||||||
|
#include "storage/v2/replication/config.hpp"
|
||||||
|
#include "storage/v2/replication/enums.hpp"
|
||||||
|
#include "storage/v2/replication/rpc.hpp"
|
||||||
|
#include "storage/v2/replication/serialization.hpp"
|
||||||
|
#include "storage/v2/storage_error.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::storage {
|
||||||
|
|
||||||
|
/// Structure used to return information about the storage.
|
||||||
|
struct StorageInfo {
|
||||||
|
uint64_t vertex_count;
|
||||||
|
uint64_t edge_count;
|
||||||
|
double average_degree;
|
||||||
|
uint64_t memory_usage;
|
||||||
|
uint64_t disk_usage;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
|
||||||
|
|
||||||
|
// The storage is based on this paper:
|
||||||
|
// https://db.in.tum.de/~muehlbau/papers/mvcc.pdf
|
||||||
|
// The paper implements a fully serializable storage, in our implementation we
|
||||||
|
// only implement snapshot isolation for transactions.
|
||||||
|
|
||||||
|
/// Iterable for iterating through all vertices of a Storage.
|
||||||
|
///
|
||||||
|
/// An instance of this will be usually be wrapped inside VerticesIterable for
|
||||||
|
/// generic, public use.
|
||||||
|
class AllVerticesIterable final {
|
||||||
|
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
||||||
|
Transaction *transaction_;
|
||||||
|
View view_;
|
||||||
|
Indices *indices_;
|
||||||
|
Constraints *constraints_;
|
||||||
|
Config::Items config_;
|
||||||
|
VertexAccessor *vertex_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
class Iterator final {
|
||||||
|
AllVerticesIterable *self_;
|
||||||
|
utils::SkipList<Vertex>::Iterator it_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it);
|
||||||
|
|
||||||
|
VertexAccessor *operator*() const;
|
||||||
|
|
||||||
|
Iterator &operator++();
|
||||||
|
|
||||||
|
bool operator==(const Iterator &other) const { return self_ == other.self_ && it_ == other.it_; }
|
||||||
|
|
||||||
|
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
||||||
|
};
|
||||||
|
|
||||||
|
AllVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view,
|
||||||
|
Indices *indices, Constraints *constraints, Config::Items config)
|
||||||
|
: vertices_accessor_(std::move(vertices_accessor)),
|
||||||
|
transaction_(transaction),
|
||||||
|
view_(view),
|
||||||
|
indices_(indices),
|
||||||
|
constraints_(constraints),
|
||||||
|
config_(config) {}
|
||||||
|
|
||||||
|
Iterator begin() { return Iterator(this, vertices_accessor_.begin()); }
|
||||||
|
Iterator end() { return Iterator(this, vertices_accessor_.end()); }
|
||||||
|
};
|
||||||
|
|
||||||
|
class InMemoryStorage final {
|
||||||
|
public:
|
||||||
|
/// @throw std::system_error
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
explicit InMemoryStorage(Config config = Config());
|
||||||
|
|
||||||
|
~InMemoryStorage();
|
||||||
|
|
||||||
|
class Accessor final {
|
||||||
|
private:
|
||||||
|
friend class InMemoryStorage;
|
||||||
|
|
||||||
|
explicit Accessor(InMemoryStorage *storage, IsolationLevel isolation_level);
|
||||||
|
|
||||||
|
public:
|
||||||
|
Accessor(const Accessor &) = delete;
|
||||||
|
Accessor &operator=(const Accessor &) = delete;
|
||||||
|
Accessor &operator=(Accessor &&other) = delete;
|
||||||
|
|
||||||
|
// NOTE: After the accessor is moved, all objects derived from it (accessors
|
||||||
|
// and iterators) are *invalid*. You have to get all derived objects again.
|
||||||
|
Accessor(Accessor &&other) noexcept;
|
||||||
|
|
||||||
|
~Accessor();
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
VertexAccessor CreateVertex();
|
||||||
|
|
||||||
|
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
|
||||||
|
|
||||||
|
VerticesIterable Vertices(View view) {
|
||||||
|
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
||||||
|
&storage_->indices_, &storage_->constraints_,
|
||||||
|
storage_->config_.items));
|
||||||
|
}
|
||||||
|
|
||||||
|
VerticesIterable Vertices(LabelId label, View view);
|
||||||
|
|
||||||
|
VerticesIterable Vertices(LabelId label, PropertyId property, View view);
|
||||||
|
|
||||||
|
VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view);
|
||||||
|
|
||||||
|
VerticesIterable Vertices(LabelId label, PropertyId property,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view);
|
||||||
|
|
||||||
|
/// Return approximate number of all vertices in the database.
|
||||||
|
/// Note that this is always an over-estimate and never an under-estimate.
|
||||||
|
int64_t ApproximateVertexCount() const { return storage_->vertices_.size(); }
|
||||||
|
|
||||||
|
/// Return approximate number of vertices with the given label.
|
||||||
|
/// Note that this is always an over-estimate and never an under-estimate.
|
||||||
|
int64_t ApproximateVertexCount(LabelId label) const {
|
||||||
|
return storage_->indices_.label_index.ApproximateVertexCount(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return approximate number of vertices with the given label and property.
|
||||||
|
/// Note that this is always an over-estimate and never an under-estimate.
|
||||||
|
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||||
|
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return approximate number of vertices with the given label and the given
|
||||||
|
/// value for the given property. Note that this is always an over-estimate
|
||||||
|
/// and never an under-estimate.
|
||||||
|
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const {
|
||||||
|
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return approximate number of vertices with the given label and value for
|
||||||
|
/// the given property in the range defined by provided upper and lower
|
||||||
|
/// bounds.
|
||||||
|
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &lower,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &upper) const {
|
||||||
|
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<storage::IndexStats> GetIndexStats(const storage::LabelId &label,
|
||||||
|
const storage::PropertyId &property) const {
|
||||||
|
return storage_->indices_.label_property_index.GetIndexStats(label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats() {
|
||||||
|
return storage_->indices_.label_property_index.ClearIndexStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStatsForLabels(const std::span<std::string> labels) {
|
||||||
|
std::vector<std::pair<LabelId, PropertyId>> deleted_indexes;
|
||||||
|
std::for_each(labels.begin(), labels.end(), [this, &deleted_indexes](const auto &label_str) {
|
||||||
|
std::vector<std::pair<LabelId, PropertyId>> loc_results =
|
||||||
|
storage_->indices_.label_property_index.DeleteIndexStatsForLabel(NameToLabel(label_str));
|
||||||
|
deleted_indexes.insert(deleted_indexes.end(), std::make_move_iterator(loc_results.begin()),
|
||||||
|
std::make_move_iterator(loc_results.end()));
|
||||||
|
});
|
||||||
|
return deleted_indexes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property, const IndexStats &stats) {
|
||||||
|
storage_->indices_.label_property_index.SetIndexStats(label, property, stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex);
|
||||||
|
|
||||||
|
/// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex(
|
||||||
|
VertexAccessor *vertex);
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type);
|
||||||
|
|
||||||
|
/// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
|
||||||
|
|
||||||
|
const std::string &LabelToName(LabelId label) const;
|
||||||
|
const std::string &PropertyToName(PropertyId property) const;
|
||||||
|
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
|
LabelId NameToLabel(std::string_view name);
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
|
PropertyId NameToProperty(std::string_view name);
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
|
EdgeTypeId NameToEdgeType(std::string_view name);
|
||||||
|
|
||||||
|
bool LabelIndexExists(LabelId label) const { return storage_->indices_.label_index.IndexExists(label); }
|
||||||
|
|
||||||
|
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const {
|
||||||
|
return storage_->indices_.label_property_index.IndexExists(label, property);
|
||||||
|
}
|
||||||
|
|
||||||
|
IndicesInfo ListAllIndices() const {
|
||||||
|
return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()};
|
||||||
|
}
|
||||||
|
|
||||||
|
ConstraintsInfo ListAllConstraints() const {
|
||||||
|
return {ListExistenceConstraints(storage_->constraints_),
|
||||||
|
storage_->constraints_.unique_constraints.ListConstraints()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void AdvanceCommand();
|
||||||
|
|
||||||
|
/// Returns void if the transaction has been committed.
|
||||||
|
/// Returns `StorageDataManipulationError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `ConstraintViolation`: the changes made by this transaction violate an existence or unique constraint. In this
|
||||||
|
/// case the transaction is automatically aborted.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
utils::BasicResult<StorageDataManipulationError, void> Commit(
|
||||||
|
std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
void Abort();
|
||||||
|
|
||||||
|
void FinalizeTransaction();
|
||||||
|
|
||||||
|
std::optional<uint64_t> GetTransactionId() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
VertexAccessor CreateVertex(storage::Gid gid);
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid);
|
||||||
|
|
||||||
|
InMemoryStorage *storage_;
|
||||||
|
std::shared_lock<utils::RWLock> storage_guard_;
|
||||||
|
Transaction transaction_;
|
||||||
|
std::optional<uint64_t> commit_timestamp_;
|
||||||
|
bool is_transaction_active_;
|
||||||
|
Config::Items config_;
|
||||||
|
};
|
||||||
|
|
||||||
|
Accessor Access(std::optional<IsolationLevel> override_isolation_level = {}) {
|
||||||
|
return Accessor{this, override_isolation_level.value_or(isolation_level_)};
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &LabelToName(LabelId label) const;
|
||||||
|
const std::string &PropertyToName(PropertyId property) const;
|
||||||
|
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
|
LabelId NameToLabel(std::string_view name);
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
|
PropertyId NameToProperty(std::string_view name);
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
|
EdgeTypeId NameToEdgeType(std::string_view name);
|
||||||
|
|
||||||
|
/// Create an index.
|
||||||
|
/// Returns void if the index has been created.
|
||||||
|
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
||||||
|
/// * `IndexDefinitionError`: the index already exists.
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
|
||||||
|
LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// Create an index.
|
||||||
|
/// Returns void if the index has been created.
|
||||||
|
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `IndexDefinitionError`: the index already exists.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
|
||||||
|
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// Drop an existing index.
|
||||||
|
/// Returns void if the index has been dropped.
|
||||||
|
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `IndexDefinitionError`: the index does not exist.
|
||||||
|
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
|
||||||
|
LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// Drop an existing index.
|
||||||
|
/// Returns void if the index has been dropped.
|
||||||
|
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `IndexDefinitionError`: the index does not exist.
|
||||||
|
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
|
||||||
|
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
IndicesInfo ListAllIndices() const;
|
||||||
|
|
||||||
|
/// Returns void if the existence constraint has been created.
|
||||||
|
/// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint.
|
||||||
|
/// * `ConstraintDefinitionError`: the constraint already exists.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
/// @throw std::length_error
|
||||||
|
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
|
||||||
|
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// Drop an existing existence constraint.
|
||||||
|
/// Returns void if the existence constraint has been dropped.
|
||||||
|
/// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `ConstraintDefinitionError`: the constraint did not exists.
|
||||||
|
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
|
||||||
|
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// Create an unique constraint.
|
||||||
|
/// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// * `ConstraintViolation`: there are already vertices violating the constraint.
|
||||||
|
/// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be:
|
||||||
|
/// * `SUCCESS` if the constraint was successfully created,
|
||||||
|
/// * `ALREADY_EXISTS` if the constraint already existed,
|
||||||
|
/// * `EMPTY_PROPERTIES` if the property set is empty, or
|
||||||
|
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
|
||||||
|
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
/// Removes an existing unique constraint.
|
||||||
|
/// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be:
|
||||||
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
|
/// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be:
|
||||||
|
/// * `SUCCESS` if constraint was successfully removed,
|
||||||
|
/// * `NOT_FOUND` if the specified constraint was not found,
|
||||||
|
/// * `EMPTY_PROPERTIES` if the property set is empty, or
|
||||||
|
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
|
||||||
|
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint(
|
||||||
|
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
ConstraintsInfo ListAllConstraints() const;
|
||||||
|
|
||||||
|
StorageInfo GetInfo() const;
|
||||||
|
|
||||||
|
bool LockPath();
|
||||||
|
bool UnlockPath();
|
||||||
|
|
||||||
|
bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {});
|
||||||
|
|
||||||
|
bool SetMainReplicationRole();
|
||||||
|
|
||||||
|
enum class RegisterReplicaError : uint8_t {
|
||||||
|
NAME_EXISTS,
|
||||||
|
END_POINT_EXISTS,
|
||||||
|
CONNECTION_FAILED,
|
||||||
|
COULD_NOT_BE_PERSISTED
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @pre The instance should have a MAIN role
|
||||||
|
/// @pre Timeout can only be set for SYNC replication
|
||||||
|
utils::BasicResult<RegisterReplicaError, void> RegisterReplica(
|
||||||
|
std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode,
|
||||||
|
replication::RegistrationMode registration_mode, const replication::ReplicationClientConfig &config = {});
|
||||||
|
/// @pre The instance should have a MAIN role
|
||||||
|
bool UnregisterReplica(const std::string &name);
|
||||||
|
|
||||||
|
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name);
|
||||||
|
|
||||||
|
ReplicationRole GetReplicationRole() const;
|
||||||
|
|
||||||
|
struct TimestampInfo {
|
||||||
|
uint64_t current_timestamp_of_replica;
|
||||||
|
uint64_t current_number_of_timestamp_behind_master;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ReplicaInfo {
|
||||||
|
std::string name;
|
||||||
|
replication::ReplicationMode mode;
|
||||||
|
io::network::Endpoint endpoint;
|
||||||
|
replication::ReplicaState state;
|
||||||
|
TimestampInfo timestamp_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ReplicaInfo> ReplicasInfo();
|
||||||
|
|
||||||
|
void FreeMemory();
|
||||||
|
|
||||||
|
void SetIsolationLevel(IsolationLevel isolation_level);
|
||||||
|
|
||||||
|
enum class CreateSnapshotError : uint8_t { DisabledForReplica };
|
||||||
|
|
||||||
|
utils::BasicResult<CreateSnapshotError> CreateSnapshot();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Transaction CreateTransaction(IsolationLevel isolation_level);
|
||||||
|
|
||||||
|
/// The force parameter determines the behaviour of the garbage collector.
|
||||||
|
/// If it's set to true, it will behave as a global operation, i.e. it can't
|
||||||
|
/// be part of a transaction, and no other transaction can be active at the same time.
|
||||||
|
/// This allows it to delete immediately vertices without worrying that some other
|
||||||
|
/// transaction is possibly using it. If there are active transactions when this method
|
||||||
|
/// is called with force set to true, it will fallback to the same method with the force
|
||||||
|
/// set to false.
|
||||||
|
/// If it's set to false, it will execute in parallel with other transactions, ensuring
|
||||||
|
/// that no object in use can be deleted.
|
||||||
|
/// @throw std::system_error
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
template <bool force>
|
||||||
|
void CollectGarbage();
|
||||||
|
|
||||||
|
bool InitializeWalFile();
|
||||||
|
void FinalizeWalFile();
|
||||||
|
|
||||||
|
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
|
||||||
|
[[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp);
|
||||||
|
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
|
||||||
|
[[nodiscard]] bool AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label,
|
||||||
|
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
|
||||||
|
|
||||||
|
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
|
||||||
|
|
||||||
|
void RestoreReplicas();
|
||||||
|
|
||||||
|
bool ShouldStoreAndRestoreReplicas() const;
|
||||||
|
|
||||||
|
// Main storage lock.
|
||||||
|
//
|
||||||
|
// Accessors take a shared lock when starting, so it is possible to block
|
||||||
|
// creation of new accessors by taking a unique lock. This is used when doing
|
||||||
|
// operations on storage that affect the global state, for example index
|
||||||
|
// creation.
|
||||||
|
mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE};
|
||||||
|
|
||||||
|
// 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};
|
||||||
|
// Even though the edge count is already kept in the `edges_` SkipList, the
|
||||||
|
// list is used only when properties are enabled for edges. Because of that we
|
||||||
|
// keep a separate count of edges that is always updated.
|
||||||
|
std::atomic<uint64_t> edge_count_{0};
|
||||||
|
|
||||||
|
NameIdMapper name_id_mapper_;
|
||||||
|
|
||||||
|
Constraints constraints_;
|
||||||
|
Indices indices_;
|
||||||
|
|
||||||
|
// Transaction engine
|
||||||
|
utils::SpinLock engine_lock_;
|
||||||
|
uint64_t timestamp_{kTimestampInitialId};
|
||||||
|
uint64_t transaction_id_{kTransactionInitialId};
|
||||||
|
// TODO: This isn't really a commit log, it doesn't even care if a
|
||||||
|
// transaction commited or aborted. We could probably combine this with
|
||||||
|
// `timestamp_` in a sensible unit, something like TransactionClock or
|
||||||
|
// whatever.
|
||||||
|
std::optional<CommitLog> commit_log_;
|
||||||
|
|
||||||
|
utils::Synchronized<std::list<Transaction>, utils::SpinLock> committed_transactions_;
|
||||||
|
IsolationLevel isolation_level_;
|
||||||
|
|
||||||
|
Config config_;
|
||||||
|
utils::Scheduler gc_runner_;
|
||||||
|
std::mutex gc_lock_;
|
||||||
|
|
||||||
|
// Undo buffers that were unlinked and now are waiting to be freed.
|
||||||
|
utils::Synchronized<std::list<std::pair<uint64_t, std::list<Delta>>>, utils::SpinLock> garbage_undo_buffers_;
|
||||||
|
|
||||||
|
// Vertices that are logically deleted but still have to be removed from
|
||||||
|
// indices before removing them from the main storage.
|
||||||
|
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_;
|
||||||
|
|
||||||
|
// Vertices that are logically deleted and removed from indices and now wait
|
||||||
|
// to be removed from the main storage.
|
||||||
|
std::list<std::pair<uint64_t, Gid>> garbage_vertices_;
|
||||||
|
|
||||||
|
// Edges that are logically deleted and wait to be removed from the main
|
||||||
|
// storage.
|
||||||
|
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_;
|
||||||
|
|
||||||
|
// Durability
|
||||||
|
std::filesystem::path snapshot_directory_;
|
||||||
|
std::filesystem::path wal_directory_;
|
||||||
|
std::filesystem::path lock_file_path_;
|
||||||
|
utils::OutputFile lock_file_handle_;
|
||||||
|
std::unique_ptr<kvstore::KVStore> storage_;
|
||||||
|
|
||||||
|
utils::Scheduler snapshot_runner_;
|
||||||
|
utils::SpinLock snapshot_lock_;
|
||||||
|
|
||||||
|
// UUID used to distinguish snapshots and to link snapshots to WALs
|
||||||
|
std::string uuid_;
|
||||||
|
// Sequence number used to keep track of the chain of WALs.
|
||||||
|
uint64_t wal_seq_num_{0};
|
||||||
|
|
||||||
|
// UUID to distinguish different main instance runs for replication process
|
||||||
|
// on SAME storage.
|
||||||
|
// Multiple instances can have same storage UUID and be MAIN at the same time.
|
||||||
|
// We cannot compare commit timestamps of those instances if one of them
|
||||||
|
// becomes the replica of the other so we use epoch_id_ as additional
|
||||||
|
// discriminating property.
|
||||||
|
// Example of this:
|
||||||
|
// We have 2 instances of the same storage, S1 and S2.
|
||||||
|
// S1 and S2 are MAIN and accept their own commits and write them to the WAL.
|
||||||
|
// At the moment when S1 commited a transaction with timestamp 20, and S2
|
||||||
|
// a different transaction with timestamp 15, we change S2's role to REPLICA
|
||||||
|
// and register it on S1.
|
||||||
|
// Without using the epoch_id, we don't know that S1 and S2 have completely
|
||||||
|
// different transactions, we think that the S2 is behind only by 5 commits.
|
||||||
|
std::string epoch_id_;
|
||||||
|
// History of the previous epoch ids.
|
||||||
|
// Each value consists of the epoch id along the last commit belonging to that
|
||||||
|
// epoch.
|
||||||
|
std::deque<std::pair<std::string, uint64_t>> epoch_history_;
|
||||||
|
|
||||||
|
std::optional<durability::WalFile> wal_file_;
|
||||||
|
uint64_t wal_unsynced_transactions_{0};
|
||||||
|
|
||||||
|
utils::FileRetainer file_retainer_;
|
||||||
|
|
||||||
|
// Global locker that is used for clients file locking
|
||||||
|
utils::FileRetainer::FileLocker global_locker_;
|
||||||
|
|
||||||
|
// Last commited timestamp
|
||||||
|
std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId};
|
||||||
|
|
||||||
|
class ReplicationServer;
|
||||||
|
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
|
||||||
|
|
||||||
|
class ReplicationClient;
|
||||||
|
// We create ReplicationClient using unique_ptr so we can move
|
||||||
|
// newly created client into the vector.
|
||||||
|
// We cannot move the client directly because it contains ThreadPool
|
||||||
|
// which cannot be moved. Also, the move is necessary because
|
||||||
|
// we don't want to create the client directly inside the vector
|
||||||
|
// because that would require the lock on the list putting all
|
||||||
|
// commits (they iterate list of clients) to halt.
|
||||||
|
// This way we can initialize client in main thread which means
|
||||||
|
// that we can immediately notify the user if the initialization
|
||||||
|
// failed.
|
||||||
|
using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>;
|
||||||
|
ReplicationClientList replication_clients_;
|
||||||
|
|
||||||
|
std::atomic<ReplicationRole> replication_role_{ReplicationRole::MAIN};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::storage
|
597
src/storage/v2/inmemory/vertex_accessor.cpp
Normal file
597
src/storage/v2/inmemory/vertex_accessor.cpp
Normal file
@ -0,0 +1,597 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "storage/v2/edge_accessor.hpp"
|
||||||
|
#include "storage/v2/id_types.hpp"
|
||||||
|
#include "storage/v2/indices.hpp"
|
||||||
|
#include "storage/v2/mvcc.hpp"
|
||||||
|
#include "storage/v2/property_value.hpp"
|
||||||
|
#include "utils/logging.hpp"
|
||||||
|
#include "utils/memory_tracker.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::storage {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
namespace {
|
||||||
|
std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View view) {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
||||||
|
deleted = vertex->deleted;
|
||||||
|
delta = vertex->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction, delta, view, [&](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
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: {
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {exists, deleted};
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
std::unique_ptr<InMemoryVertexAccessor> InMemoryVertexAccessor::Create(Vertex *vertex, Transaction *transaction,
|
||||||
|
Indices *indices, Constraints *constraints,
|
||||||
|
Config::Items config, View view) {
|
||||||
|
if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<InMemoryVertexAccessor>(vertex, transaction, indices, constraints, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InMemoryVertexAccessor::IsVisible(View view) const {
|
||||||
|
const auto [exists, deleted] = detail::IsVisible(vertex_, transaction_, view);
|
||||||
|
return exists && (for_deleted_ || !deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> InMemoryVertexAccessor::AddLabel(LabelId label) {
|
||||||
|
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false;
|
||||||
|
|
||||||
|
CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
|
||||||
|
|
||||||
|
vertex_->labels.push_back(label);
|
||||||
|
|
||||||
|
UpdateOnAddLabel(indices_, label, vertex_, *transaction_);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> InMemoryVertexAccessor::RemoveLabel(LabelId label) {
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label);
|
||||||
|
if (it == vertex_->labels.end()) return false;
|
||||||
|
|
||||||
|
CreateAndLinkDelta(transaction_, vertex_, Delta::AddLabelTag(), label);
|
||||||
|
|
||||||
|
std::swap(*it, *vertex_->labels.rbegin());
|
||||||
|
vertex_->labels.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> InMemoryVertexAccessor::HasLabel(LabelId label, View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
bool has_label = false;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
has_label = std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end();
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &has_label, label](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::REMOVE_LABEL: {
|
||||||
|
if (delta.label == label) {
|
||||||
|
MG_ASSERT(has_label, "Invalid database state!");
|
||||||
|
has_label = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::ADD_LABEL: {
|
||||||
|
if (delta.label == label) {
|
||||||
|
MG_ASSERT(!has_label, "Invalid database state!");
|
||||||
|
has_label = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
deleted = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return has_label;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::vector<LabelId>> InMemoryVertexAccessor::Labels(View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
std::vector<LabelId> labels;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
labels = vertex_->labels;
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &labels](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::REMOVE_LABEL: {
|
||||||
|
// Remove the label because we don't see the addition.
|
||||||
|
auto it = std::find(labels.begin(), labels.end(), delta.label);
|
||||||
|
MG_ASSERT(it != labels.end(), "Invalid database state!");
|
||||||
|
std::swap(*it, *labels.rbegin());
|
||||||
|
labels.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::ADD_LABEL: {
|
||||||
|
// Add the label because we don't see the removal.
|
||||||
|
auto it = std::find(labels.begin(), labels.end(), delta.label);
|
||||||
|
MG_ASSERT(it == labels.end(), "Invalid database state!");
|
||||||
|
labels.push_back(delta.label);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
deleted = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return std::move(labels);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<PropertyValue> InMemoryVertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
||||||
|
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
auto current_value = vertex_->properties.GetProperty(property);
|
||||||
|
// We could skip setting the value if the previous one is the same to the new
|
||||||
|
// one. This would save some memory as a delta would not be created as well as
|
||||||
|
// avoid copying the value. The reason we are not doing that is because the
|
||||||
|
// current code always follows the logical pattern of "create a delta" and
|
||||||
|
// "modify in-place". Additionally, the created delta will make other
|
||||||
|
// transactions get a SERIALIZATION_ERROR.
|
||||||
|
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value);
|
||||||
|
vertex_->properties.SetProperty(property, value);
|
||||||
|
|
||||||
|
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
||||||
|
|
||||||
|
return std::move(current_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<bool> InMemoryVertexAccessor::InitProperties(
|
||||||
|
const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
||||||
|
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
if (!vertex_->properties.InitProperties(properties)) return false;
|
||||||
|
for (const auto &[property, value] : properties) {
|
||||||
|
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, PropertyValue());
|
||||||
|
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> InMemoryVertexAccessor::ClearProperties() {
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
|
||||||
|
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
||||||
|
|
||||||
|
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
||||||
|
|
||||||
|
auto properties = vertex_->properties.Properties();
|
||||||
|
for (const auto &property : properties) {
|
||||||
|
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property.first, property.second);
|
||||||
|
UpdateOnSetProperty(indices_, property.first, PropertyValue(), vertex_, *transaction_);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_->properties.ClearProperties();
|
||||||
|
|
||||||
|
return std::move(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<PropertyValue> InMemoryVertexAccessor::GetProperty(PropertyId property, View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
PropertyValue value;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
value = vertex_->properties.GetProperty(property);
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::SET_PROPERTY: {
|
||||||
|
if (delta.property.key == property) {
|
||||||
|
value = delta.property.value;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return std::move(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> InMemoryVertexAccessor::Properties(View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
std::map<PropertyId, PropertyValue> properties;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
properties = vertex_->properties.Properties();
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::SET_PROPERTY: {
|
||||||
|
auto it = properties.find(delta.property.key);
|
||||||
|
if (it != properties.end()) {
|
||||||
|
if (delta.property.value.IsNull()) {
|
||||||
|
// remove the property
|
||||||
|
properties.erase(it);
|
||||||
|
} else {
|
||||||
|
// set the value
|
||||||
|
it->second = delta.property.value;
|
||||||
|
}
|
||||||
|
} else if (!delta.property.value.IsNull()) {
|
||||||
|
properties.emplace(delta.property.key, delta.property.value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::RECREATE_OBJECT: {
|
||||||
|
deleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return std::move(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::vector<EdgeAccessor>> InMemoryVertexAccessor::InEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||||
|
const InMemoryVertexAccessor *destination) const {
|
||||||
|
MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!");
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
if (edge_types.empty() && !destination) {
|
||||||
|
in_edges = vertex_->in_edges;
|
||||||
|
} else {
|
||||||
|
for (const auto &item : vertex_->in_edges) {
|
||||||
|
const auto &[edge_type, from_vertex, edge] = item;
|
||||||
|
if (destination && from_vertex != destination->vertex_) continue;
|
||||||
|
if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end())
|
||||||
|
continue;
|
||||||
|
in_edges.push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(
|
||||||
|
transaction_, delta, view, [&exists, &deleted, &in_edges, &edge_types, &destination](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_IN_EDGE: {
|
||||||
|
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||||
|
if (!edge_types.empty() &&
|
||||||
|
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||||
|
break;
|
||||||
|
// Add the edge because we don't see the removal.
|
||||||
|
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
||||||
|
MG_ASSERT(it == in_edges.end(), "Invalid database state!");
|
||||||
|
in_edges.push_back(link);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::REMOVE_IN_EDGE: {
|
||||||
|
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||||
|
if (!edge_types.empty() &&
|
||||||
|
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||||
|
break;
|
||||||
|
// Remove the label because we don't see the addition.
|
||||||
|
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
||||||
|
MG_ASSERT(it != in_edges.end(), "Invalid database state!");
|
||||||
|
std::swap(*it, *in_edges.rbegin());
|
||||||
|
in_edges.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (deleted) return Error::DELETED_OBJECT;
|
||||||
|
std::vector<EdgeAccessor> ret;
|
||||||
|
ret.reserve(in_edges.size());
|
||||||
|
for (const auto &item : in_edges) {
|
||||||
|
const auto &[edge_type, from_vertex, edge] = item;
|
||||||
|
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_);
|
||||||
|
}
|
||||||
|
return std::move(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<std::vector<EdgeAccessor>> InMemoryVertexAccessor::OutEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||||
|
const InMemoryVertexAccessor *destination) const {
|
||||||
|
MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!");
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
if (edge_types.empty() && !destination) {
|
||||||
|
out_edges = vertex_->out_edges;
|
||||||
|
} else {
|
||||||
|
for (const auto &item : vertex_->out_edges) {
|
||||||
|
const auto &[edge_type, to_vertex, edge] = item;
|
||||||
|
if (destination && to_vertex != destination->vertex_) continue;
|
||||||
|
if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end())
|
||||||
|
continue;
|
||||||
|
out_edges.push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(
|
||||||
|
transaction_, delta, view, [&exists, &deleted, &out_edges, &edge_types, &destination](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_OUT_EDGE: {
|
||||||
|
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||||
|
if (!edge_types.empty() &&
|
||||||
|
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||||
|
break;
|
||||||
|
// Add the edge because we don't see the removal.
|
||||||
|
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
||||||
|
MG_ASSERT(it == out_edges.end(), "Invalid database state!");
|
||||||
|
out_edges.push_back(link);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::REMOVE_OUT_EDGE: {
|
||||||
|
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
||||||
|
if (!edge_types.empty() &&
|
||||||
|
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
||||||
|
break;
|
||||||
|
// Remove the label because we don't see the addition.
|
||||||
|
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
||||||
|
MG_ASSERT(it != out_edges.end(), "Invalid database state!");
|
||||||
|
std::swap(*it, *out_edges.rbegin());
|
||||||
|
out_edges.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Delta::Action::DELETE_OBJECT: {
|
||||||
|
exists = false;
|
||||||
|
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (deleted) return Error::DELETED_OBJECT;
|
||||||
|
std::vector<EdgeAccessor> ret;
|
||||||
|
ret.reserve(out_edges.size());
|
||||||
|
for (const auto &item : out_edges) {
|
||||||
|
const auto &[edge_type, to_vertex, edge] = item;
|
||||||
|
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_);
|
||||||
|
}
|
||||||
|
return std::move(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<size_t> InMemoryVertexAccessor::InDegree(View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
size_t degree = 0;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
degree = vertex_->in_edges.size();
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, °ree](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_IN_EDGE:
|
||||||
|
++degree;
|
||||||
|
break;
|
||||||
|
case Delta::Action::REMOVE_IN_EDGE:
|
||||||
|
--degree;
|
||||||
|
break;
|
||||||
|
case Delta::Action::DELETE_OBJECT:
|
||||||
|
exists = false;
|
||||||
|
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return degree;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<size_t> InMemoryVertexAccessor::OutDegree(View view) const {
|
||||||
|
bool exists = true;
|
||||||
|
bool deleted = false;
|
||||||
|
size_t degree = 0;
|
||||||
|
Delta *delta = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||||
|
deleted = vertex_->deleted;
|
||||||
|
degree = vertex_->out_edges.size();
|
||||||
|
delta = vertex_->delta;
|
||||||
|
}
|
||||||
|
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, °ree](const Delta &delta) {
|
||||||
|
switch (delta.action) {
|
||||||
|
case Delta::Action::ADD_OUT_EDGE:
|
||||||
|
++degree;
|
||||||
|
break;
|
||||||
|
case Delta::Action::REMOVE_OUT_EDGE:
|
||||||
|
--degree;
|
||||||
|
break;
|
||||||
|
case Delta::Action::DELETE_OBJECT:
|
||||||
|
exists = false;
|
||||||
|
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
||||||
|
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
||||||
|
return degree;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace memgraph::storage
|
126
src/storage/v2/inmemory/vertex_accessor.hpp
Normal file
126
src/storage/v2/inmemory/vertex_accessor.hpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright 2023 Memgraph Ltd.
|
||||||
|
//
|
||||||
|
// Use of this software is governed by the Business Source License
|
||||||
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
|
// License, and you may not use this file except in compliance with the Business Source License.
|
||||||
|
//
|
||||||
|
// As of the Change Date specified in that file, in accordance with
|
||||||
|
// the Business Source License, use of this software will be governed
|
||||||
|
// by the Apache License, Version 2.0, included in the file
|
||||||
|
// licenses/APL.txt.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "storage/v2/vertex.hpp"
|
||||||
|
|
||||||
|
#include "storage/v2/config.hpp"
|
||||||
|
#include "storage/v2/result.hpp"
|
||||||
|
#include "storage/v2/transaction.hpp"
|
||||||
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
|
#include "storage/v2/view.hpp"
|
||||||
|
|
||||||
|
namespace memgraph::storage {
|
||||||
|
|
||||||
|
class EdgeAccessor;
|
||||||
|
class Storage;
|
||||||
|
struct Indices;
|
||||||
|
struct Constraints;
|
||||||
|
|
||||||
|
class InMemoryVertexAccessor : public VertexAccessor {
|
||||||
|
private:
|
||||||
|
friend class Storage;
|
||||||
|
|
||||||
|
public:
|
||||||
|
InMemoryVertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||||
|
Config::Items config, bool for_deleted = false)
|
||||||
|
: VertexAccessor(transaction, config, for_deleted),
|
||||||
|
vertex_(vertex),
|
||||||
|
indices_(indices),
|
||||||
|
constraints_(constraints) {}
|
||||||
|
|
||||||
|
static std::unique_ptr<InMemoryVertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||||
|
Constraints *constraints, Config::Items config, View view);
|
||||||
|
|
||||||
|
/// @return true if the object is visible from the current transaction
|
||||||
|
bool IsVisible(View view) const override;
|
||||||
|
|
||||||
|
/// Add a label and return `true` if insertion took place.
|
||||||
|
/// `false` is returned if the label already existed.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<bool> AddLabel(LabelId label) override;
|
||||||
|
|
||||||
|
/// Remove a label and return `true` if deletion took place.
|
||||||
|
/// `false` is returned if the vertex did not have a label already.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<bool> RemoveLabel(LabelId label) override;
|
||||||
|
|
||||||
|
Result<bool> HasLabel(LabelId label, View view) const override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
/// @throw std::length_error if the resulting vector exceeds
|
||||||
|
/// std::vector::max_size().
|
||||||
|
Result<std::vector<LabelId>> Labels(View view) const override;
|
||||||
|
|
||||||
|
/// Set a property value and return the old value.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value) override;
|
||||||
|
|
||||||
|
/// Set property values only if property store is empty. Returns `true` if successully set all values,
|
||||||
|
/// `false` otherwise.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) override;
|
||||||
|
|
||||||
|
/// Remove all properties and return the values of the removed properties.
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> ClearProperties() override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<PropertyValue> GetProperty(PropertyId property, View view) const override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
/// @throw std::length_error if the resulting vector exceeds
|
||||||
|
/// std::vector::max_size().
|
||||||
|
Result<std::vector<EdgeAccessor>> InEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||||
|
const VertexAccessor *destination) const override;
|
||||||
|
|
||||||
|
/// @throw std::bad_alloc
|
||||||
|
/// @throw std::length_error if the resulting vector exceeds
|
||||||
|
/// std::vector::max_size().
|
||||||
|
Result<std::vector<EdgeAccessor>> OutEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||||
|
const VertexAccessor *destination) const override;
|
||||||
|
|
||||||
|
Result<size_t> InDegree(View view) const override;
|
||||||
|
|
||||||
|
Result<size_t> OutDegree(View view) const override;
|
||||||
|
|
||||||
|
class Gid Gid() const noexcept override {
|
||||||
|
return vertex_->gid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const VertexAccessor &other) const noexcept override {
|
||||||
|
const auto *otherVertex = dynamic_cast<const InMemoryVertexAccessor *>(&other);
|
||||||
|
if (otherVertex == nullptr) return false;
|
||||||
|
return vertex_ == otherVertex->vertex_ && transaction_ == otherVertex->transaction_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Vertex *vertex_;
|
||||||
|
Indices *indices_;
|
||||||
|
Constraints *constraints_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace memgraph::storage
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template <>
|
||||||
|
struct hash<memgraph::storage::InMemoryVertexAccessor> {
|
||||||
|
size_t operator()(const memgraph::storage::InMemoryVertexAccessor &v) const noexcept { return v.Gid().AsUint(); }
|
||||||
|
};
|
||||||
|
} // namespace std
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -21,6 +21,7 @@
|
|||||||
#include "storage/v2/delta.hpp"
|
#include "storage/v2/delta.hpp"
|
||||||
#include "storage/v2/durability/wal.hpp"
|
#include "storage/v2/durability/wal.hpp"
|
||||||
#include "storage/v2/id_types.hpp"
|
#include "storage/v2/id_types.hpp"
|
||||||
|
#include "storage/v2/inmemory/storage.hpp"
|
||||||
#include "storage/v2/mvcc.hpp"
|
#include "storage/v2/mvcc.hpp"
|
||||||
#include "storage/v2/name_id_mapper.hpp"
|
#include "storage/v2/name_id_mapper.hpp"
|
||||||
#include "storage/v2/property_value.hpp"
|
#include "storage/v2/property_value.hpp"
|
||||||
@ -28,7 +29,6 @@
|
|||||||
#include "storage/v2/replication/enums.hpp"
|
#include "storage/v2/replication/enums.hpp"
|
||||||
#include "storage/v2/replication/rpc.hpp"
|
#include "storage/v2/replication/rpc.hpp"
|
||||||
#include "storage/v2/replication/serialization.hpp"
|
#include "storage/v2/replication/serialization.hpp"
|
||||||
#include "storage/v2/storage.hpp"
|
|
||||||
#include "utils/file.hpp"
|
#include "utils/file.hpp"
|
||||||
#include "utils/file_locker.hpp"
|
#include "utils/file_locker.hpp"
|
||||||
#include "utils/spin_lock.hpp"
|
#include "utils/spin_lock.hpp"
|
||||||
@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
class Storage::ReplicationClient {
|
class InMemoryStorage::ReplicationClient {
|
||||||
public:
|
public:
|
||||||
ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint,
|
ReplicationClient(std::string name, InMemoryStorage *storage, const io::network::Endpoint &endpoint,
|
||||||
replication::ReplicationMode mode, const replication::ReplicationClientConfig &config = {});
|
replication::ReplicationMode mode, const replication::ReplicationClientConfig &config = {});
|
||||||
|
|
||||||
// Handler used for transfering the current transaction.
|
// Handler used for transfering the current transaction.
|
||||||
@ -123,7 +123,7 @@ class Storage::ReplicationClient {
|
|||||||
|
|
||||||
const auto &Endpoint() const { return rpc_client_->Endpoint(); }
|
const auto &Endpoint() const { return rpc_client_->Endpoint(); }
|
||||||
|
|
||||||
Storage::TimestampInfo GetTimestampInfo();
|
InMemoryStorage::TimestampInfo GetTimestampInfo();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] bool FinalizeTransactionReplicationInternal();
|
[[nodiscard]] bool FinalizeTransactionReplicationInternal();
|
||||||
@ -150,7 +150,7 @@ class Storage::ReplicationClient {
|
|||||||
void HandleRpcFailure();
|
void HandleRpcFailure();
|
||||||
|
|
||||||
std::string name_;
|
std::string name_;
|
||||||
Storage *storage_;
|
InMemoryStorage *storage_;
|
||||||
std::optional<communication::ClientContext> rpc_context_;
|
std::optional<communication::ClientContext> rpc_context_;
|
||||||
std::optional<rpc::Client> rpc_client_;
|
std::optional<rpc::Client> rpc_client_;
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2022 Memgraph Ltd.
|
// Copyright 2023 Memgraph Ltd.
|
||||||
//
|
//
|
||||||
// Use of this software is governed by the Business Source License
|
// Use of this software is governed by the Business Source License
|
||||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||||
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
class Storage::ReplicationServer {
|
class InMemoryStorage::ReplicationServer {
|
||||||
public:
|
public:
|
||||||
explicit ReplicationServer(Storage *storage, io::network::Endpoint endpoint,
|
explicit ReplicationServer(Storage *storage, io::network::Endpoint endpoint,
|
||||||
const replication::ReplicationServerConfig &config);
|
const replication::ReplicationServerConfig &config);
|
||||||
@ -42,7 +42,7 @@ class Storage::ReplicationServer {
|
|||||||
std::optional<communication::ServerContext> rpc_server_context_;
|
std::optional<communication::ServerContext> rpc_server_context_;
|
||||||
std::optional<rpc::Server> rpc_server_;
|
std::optional<rpc::Server> rpc_server_;
|
||||||
|
|
||||||
Storage *storage_;
|
InMemoryStorage *storage_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace memgraph::storage
|
} // namespace memgraph::storage
|
||||||
|
@ -11,95 +11,33 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <atomic>
|
#include <set>
|
||||||
#include <cstdint>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <optional>
|
|
||||||
#include <shared_mutex>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
#include "io/network/endpoint.hpp"
|
|
||||||
#include "kvstore/kvstore.hpp"
|
|
||||||
#include "storage/v2/commit_log.hpp"
|
|
||||||
#include "storage/v2/config.hpp"
|
#include "storage/v2/config.hpp"
|
||||||
#include "storage/v2/constraints.hpp"
|
|
||||||
#include "storage/v2/durability/metadata.hpp"
|
|
||||||
#include "storage/v2/durability/wal.hpp"
|
|
||||||
#include "storage/v2/edge.hpp"
|
|
||||||
#include "storage/v2/edge_accessor.hpp"
|
|
||||||
#include "storage/v2/indices.hpp"
|
#include "storage/v2/indices.hpp"
|
||||||
#include "storage/v2/isolation_level.hpp"
|
|
||||||
#include "storage/v2/mvcc.hpp"
|
|
||||||
#include "storage/v2/name_id_mapper.hpp"
|
|
||||||
#include "storage/v2/result.hpp"
|
#include "storage/v2/result.hpp"
|
||||||
#include "storage/v2/transaction.hpp"
|
|
||||||
#include "storage/v2/vertex.hpp"
|
|
||||||
#include "storage/v2/vertex_accessor.hpp"
|
|
||||||
#include "utils/file_locker.hpp"
|
|
||||||
#include "utils/on_scope_exit.hpp"
|
|
||||||
#include "utils/rw_lock.hpp"
|
|
||||||
#include "utils/scheduler.hpp"
|
|
||||||
#include "utils/skip_list.hpp"
|
|
||||||
#include "utils/synchronized.hpp"
|
|
||||||
#include "utils/uuid.hpp"
|
|
||||||
|
|
||||||
/// REPLICATION ///
|
|
||||||
#include "rpc/server.hpp"
|
|
||||||
#include "storage/v2/replication/config.hpp"
|
|
||||||
#include "storage/v2/replication/enums.hpp"
|
|
||||||
#include "storage/v2/replication/rpc.hpp"
|
|
||||||
#include "storage/v2/replication/serialization.hpp"
|
|
||||||
#include "storage/v2/storage_error.hpp"
|
#include "storage/v2/storage_error.hpp"
|
||||||
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
|
#include "storage/v2/view.hpp"
|
||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
// The storage is based on this paper:
|
class VerticesIterableImpl;
|
||||||
// https://db.in.tum.de/~muehlbau/papers/mvcc.pdf
|
struct Transaction;
|
||||||
// The paper implements a fully serializable storage, in our implementation we
|
class EdgeAccessor;
|
||||||
// only implement snapshot isolation for transactions.
|
|
||||||
|
|
||||||
/// Iterable for iterating through all vertices of a Storage.
|
/// Structure used to return information about existing indices in the storage.
|
||||||
///
|
struct IndicesInfo {
|
||||||
/// An instance of this will be usually be wrapped inside VerticesIterable for
|
std::vector<LabelId> label;
|
||||||
/// generic, public use.
|
std::vector<std::pair<LabelId, PropertyId>> label_property;
|
||||||
class AllVerticesIterable final {
|
};
|
||||||
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
|
||||||
Transaction *transaction_;
|
|
||||||
View view_;
|
|
||||||
Indices *indices_;
|
|
||||||
Constraints *constraints_;
|
|
||||||
Config::Items config_;
|
|
||||||
std::optional<VertexAccessor> vertex_;
|
|
||||||
|
|
||||||
public:
|
/// Structure used to return information about existing constraints in the
|
||||||
class Iterator final {
|
/// storage.
|
||||||
AllVerticesIterable *self_;
|
struct ConstraintsInfo {
|
||||||
utils::SkipList<Vertex>::Iterator it_;
|
std::vector<std::pair<LabelId, PropertyId>> existence;
|
||||||
|
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
||||||
public:
|
|
||||||
Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it);
|
|
||||||
|
|
||||||
VertexAccessor operator*() const;
|
|
||||||
|
|
||||||
Iterator &operator++();
|
|
||||||
|
|
||||||
bool operator==(const Iterator &other) const { return self_ == other.self_ && it_ == other.it_; }
|
|
||||||
|
|
||||||
bool operator!=(const Iterator &other) const { return !(*this == other); }
|
|
||||||
};
|
|
||||||
|
|
||||||
AllVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view,
|
|
||||||
Indices *indices, Constraints *constraints, Config::Items config)
|
|
||||||
: vertices_accessor_(std::move(vertices_accessor)),
|
|
||||||
transaction_(transaction),
|
|
||||||
view_(view),
|
|
||||||
indices_(indices),
|
|
||||||
constraints_(constraints),
|
|
||||||
config_(config) {}
|
|
||||||
|
|
||||||
Iterator begin() { return Iterator(this, vertices_accessor_.begin()); }
|
|
||||||
Iterator end() { return Iterator(this, vertices_accessor_.end()); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Generic access to different kinds of vertex iterations.
|
/// Generic access to different kinds of vertex iterations.
|
||||||
@ -110,16 +48,10 @@ class VerticesIterable final {
|
|||||||
enum class Type { ALL, BY_LABEL, BY_LABEL_PROPERTY };
|
enum class Type { ALL, BY_LABEL, BY_LABEL_PROPERTY };
|
||||||
|
|
||||||
Type type_;
|
Type type_;
|
||||||
union {
|
VerticesIterableImpl *impl_;
|
||||||
AllVerticesIterable all_vertices_;
|
|
||||||
LabelIndex::Iterable vertices_by_label_;
|
|
||||||
LabelPropertyIndex::Iterable vertices_by_label_property_;
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit VerticesIterable(AllVerticesIterable);
|
explicit VerticesIterable(VerticesIterableImpl *);
|
||||||
explicit VerticesIterable(LabelIndex::Iterable);
|
|
||||||
explicit VerticesIterable(LabelPropertyIndex::Iterable);
|
|
||||||
|
|
||||||
VerticesIterable(const VerticesIterable &) = delete;
|
VerticesIterable(const VerticesIterable &) = delete;
|
||||||
VerticesIterable &operator=(const VerticesIterable &) = delete;
|
VerticesIterable &operator=(const VerticesIterable &) = delete;
|
||||||
@ -131,18 +63,12 @@ class VerticesIterable final {
|
|||||||
|
|
||||||
class Iterator final {
|
class Iterator final {
|
||||||
Type type_;
|
Type type_;
|
||||||
union {
|
VerticesIterable::Iterator *impl_;
|
||||||
AllVerticesIterable::Iterator all_it_;
|
|
||||||
LabelIndex::Iterable::Iterator by_label_it_;
|
|
||||||
LabelPropertyIndex::Iterable::Iterator by_label_property_it_;
|
|
||||||
};
|
|
||||||
|
|
||||||
void Destroy() noexcept;
|
void Destroy() noexcept;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Iterator(AllVerticesIterable::Iterator);
|
explicit Iterator(VerticesIterable::Iterator *);
|
||||||
explicit Iterator(LabelIndex::Iterable::Iterator);
|
|
||||||
explicit Iterator(LabelPropertyIndex::Iterable::Iterator);
|
|
||||||
|
|
||||||
Iterator(const Iterator &);
|
Iterator(const Iterator &);
|
||||||
Iterator &operator=(const Iterator &);
|
Iterator &operator=(const Iterator &);
|
||||||
@ -164,508 +90,124 @@ class VerticesIterable final {
|
|||||||
Iterator end();
|
Iterator end();
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Structure used to return information about existing indices in the storage.
|
class Accessor {
|
||||||
struct IndicesInfo {
|
|
||||||
std::vector<LabelId> label;
|
|
||||||
std::vector<std::pair<LabelId, PropertyId>> label_property;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Structure used to return information about existing constraints in the
|
|
||||||
/// storage.
|
|
||||||
struct ConstraintsInfo {
|
|
||||||
std::vector<std::pair<LabelId, PropertyId>> existence;
|
|
||||||
std::vector<std::pair<LabelId, std::set<PropertyId>>> unique;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Structure used to return information about the storage.
|
|
||||||
struct StorageInfo {
|
|
||||||
uint64_t vertex_count;
|
|
||||||
uint64_t edge_count;
|
|
||||||
double average_degree;
|
|
||||||
uint64_t memory_usage;
|
|
||||||
uint64_t disk_usage;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class ReplicationRole : uint8_t { MAIN, REPLICA };
|
|
||||||
|
|
||||||
class Storage final {
|
|
||||||
public:
|
public:
|
||||||
/// @throw std::system_error
|
Accessor(const Accessor &) = delete;
|
||||||
|
Accessor &operator=(const Accessor &) = delete;
|
||||||
|
Accessor &operator=(Accessor &&other) = delete;
|
||||||
|
|
||||||
|
// NOTE: After the accessor is moved, all objects derived from it (accessors
|
||||||
|
// and iterators) are *invalid*. You have to get all derived objects again.
|
||||||
|
Accessor(Accessor &&other) noexcept;
|
||||||
|
|
||||||
|
virtual ~Accessor();
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
explicit Storage(Config config = Config());
|
virtual VertexAccessor CreateVertex() = 0;
|
||||||
|
|
||||||
~Storage();
|
virtual std::optional<VertexAccessor> FindVertex(Gid gid, View view) = 0;
|
||||||
|
|
||||||
class Accessor final {
|
virtual VerticesIterable Vertices(View view) = 0;
|
||||||
private:
|
|
||||||
friend class Storage;
|
|
||||||
|
|
||||||
explicit Accessor(Storage *storage, IsolationLevel isolation_level);
|
virtual VerticesIterable Vertices(LabelId label, View view) = 0;
|
||||||
|
|
||||||
public:
|
virtual VerticesIterable Vertices(LabelId label, PropertyId property, View view) = 0;
|
||||||
Accessor(const Accessor &) = delete;
|
|
||||||
Accessor &operator=(const Accessor &) = delete;
|
|
||||||
Accessor &operator=(Accessor &&other) = delete;
|
|
||||||
|
|
||||||
// NOTE: After the accessor is moved, all objects derived from it (accessors
|
virtual VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) = 0;
|
||||||
// and iterators) are *invalid*. You have to get all derived objects again.
|
|
||||||
Accessor(Accessor &&other) noexcept;
|
|
||||||
|
|
||||||
~Accessor();
|
virtual VerticesIterable Vertices(LabelId label, PropertyId property,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// Return approximate number of all vertices in the database.
|
||||||
VertexAccessor CreateVertex();
|
/// Note that this is always an over-estimate and never an under-estimate.
|
||||||
|
virtual int64_t ApproximateVertexCount() const = 0;
|
||||||
|
|
||||||
std::optional<VertexAccessor> FindVertex(Gid gid, View view);
|
/// Return approximate number of vertices with the given label.
|
||||||
|
/// Note that this is always an over-estimate and never an under-estimate.
|
||||||
|
virtual int64_t ApproximateVertexCount(LabelId label) const = 0;
|
||||||
|
|
||||||
VerticesIterable Vertices(View view) {
|
/// Return approximate number of vertices with the given label and property.
|
||||||
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
/// Note that this is always an over-estimate and never an under-estimate.
|
||||||
&storage_->indices_, &storage_->constraints_,
|
virtual int64_t ApproximateVertexCount(LabelId label, PropertyId property) const = 0;
|
||||||
storage_->config_.items));
|
|
||||||
}
|
|
||||||
|
|
||||||
VerticesIterable Vertices(LabelId label, View view);
|
/// Return approximate number of vertices with the given label and the given
|
||||||
|
/// value for the given property. Note that this is always an over-estimate
|
||||||
|
/// and never an under-estimate.
|
||||||
|
virtual int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const = 0;
|
||||||
|
|
||||||
VerticesIterable Vertices(LabelId label, PropertyId property, View view);
|
/// Return approximate number of vertices with the given label and value for
|
||||||
|
/// the given property in the range defined by provided upper and lower
|
||||||
|
/// bounds.
|
||||||
|
virtual int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &lower,
|
||||||
|
const std::optional<utils::Bound<PropertyValue>> &upper) const = 0;
|
||||||
|
|
||||||
VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view);
|
virtual std::optional<storage::IndexStats> GetIndexStats(const storage::LabelId &label,
|
||||||
|
const storage::PropertyId &property) const = 0;
|
||||||
|
|
||||||
VerticesIterable Vertices(LabelId label, PropertyId property,
|
virtual std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats() = 0;
|
||||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
|
||||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view);
|
|
||||||
|
|
||||||
/// Return approximate number of all vertices in the database.
|
virtual std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStatsForLabels(
|
||||||
/// Note that this is always an over-estimate and never an under-estimate.
|
const std::span<std::string> labels) = 0;
|
||||||
int64_t ApproximateVertexCount() const { return storage_->vertices_.size(); }
|
|
||||||
|
|
||||||
/// Return approximate number of vertices with the given label.
|
virtual void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property,
|
||||||
/// Note that this is always an over-estimate and never an under-estimate.
|
const IndexStats &stats) = 0;
|
||||||
int64_t ApproximateVertexCount(LabelId label) const {
|
|
||||||
return storage_->indices_.label_index.ApproximateVertexCount(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return approximate number of vertices with the given label and property.
|
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
|
||||||
/// Note that this is always an over-estimate and never an under-estimate.
|
/// @throw std::bad_alloc
|
||||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const {
|
virtual Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex) = 0;
|
||||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return approximate number of vertices with the given label and the given
|
/// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise
|
||||||
/// value for the given property. Note that this is always an over-estimate
|
/// @throw std::bad_alloc
|
||||||
/// and never an under-estimate.
|
virtual Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex(
|
||||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const {
|
VertexAccessor *vertex) = 0;
|
||||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return approximate number of vertices with the given label and value for
|
/// @throw std::bad_alloc
|
||||||
/// the given property in the range defined by provided upper and lower
|
virtual Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) = 0;
|
||||||
/// bounds.
|
|
||||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
|
||||||
const std::optional<utils::Bound<PropertyValue>> &lower,
|
|
||||||
const std::optional<utils::Bound<PropertyValue>> &upper) const {
|
|
||||||
return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<storage::IndexStats> GetIndexStats(const storage::LabelId &label,
|
/// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise
|
||||||
const storage::PropertyId &property) const {
|
/// @throw std::bad_alloc
|
||||||
return storage_->indices_.label_property_index.GetIndexStats(label, property);
|
virtual Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge) = 0;
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats() {
|
virtual const std::string &LabelToName(LabelId label) const = 0;
|
||||||
return storage_->indices_.label_property_index.ClearIndexStats();
|
virtual const std::string &PropertyToName(PropertyId property) const = 0;
|
||||||
}
|
virtual const std::string &EdgeTypeToName(EdgeTypeId edge_type) const = 0;
|
||||||
|
|
||||||
std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStatsForLabels(const std::span<std::string> labels) {
|
|
||||||
std::vector<std::pair<LabelId, PropertyId>> deleted_indexes;
|
|
||||||
std::for_each(labels.begin(), labels.end(), [this, &deleted_indexes](const auto &label_str) {
|
|
||||||
std::vector<std::pair<LabelId, PropertyId>> loc_results =
|
|
||||||
storage_->indices_.label_property_index.DeleteIndexStatsForLabel(NameToLabel(label_str));
|
|
||||||
deleted_indexes.insert(deleted_indexes.end(), std::make_move_iterator(loc_results.begin()),
|
|
||||||
std::make_move_iterator(loc_results.end()));
|
|
||||||
});
|
|
||||||
return deleted_indexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property, const IndexStats &stats) {
|
|
||||||
storage_->indices_.label_property_index.SetIndexStats(label, property, stats);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
Result<std::optional<VertexAccessor>> DeleteVertex(VertexAccessor *vertex);
|
|
||||||
|
|
||||||
/// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
Result<std::optional<std::pair<VertexAccessor, std::vector<EdgeAccessor>>>> DetachDeleteVertex(
|
|
||||||
VertexAccessor *vertex);
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type);
|
|
||||||
|
|
||||||
/// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
Result<std::optional<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge);
|
|
||||||
|
|
||||||
const std::string &LabelToName(LabelId label) const;
|
|
||||||
const std::string &PropertyToName(PropertyId property) const;
|
|
||||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
|
||||||
LabelId NameToLabel(std::string_view name);
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
|
||||||
PropertyId NameToProperty(std::string_view name);
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
|
||||||
EdgeTypeId NameToEdgeType(std::string_view name);
|
|
||||||
|
|
||||||
bool LabelIndexExists(LabelId label) const { return storage_->indices_.label_index.IndexExists(label); }
|
|
||||||
|
|
||||||
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const {
|
|
||||||
return storage_->indices_.label_property_index.IndexExists(label, property);
|
|
||||||
}
|
|
||||||
|
|
||||||
IndicesInfo ListAllIndices() const {
|
|
||||||
return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()};
|
|
||||||
}
|
|
||||||
|
|
||||||
ConstraintsInfo ListAllConstraints() const {
|
|
||||||
return {ListExistenceConstraints(storage_->constraints_),
|
|
||||||
storage_->constraints_.unique_constraints.ListConstraints()};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AdvanceCommand();
|
|
||||||
|
|
||||||
/// Returns void if the transaction has been committed.
|
|
||||||
/// Returns `StorageDataManipulationError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// * `ConstraintViolation`: the changes made by this transaction violate an existence or unique constraint. In this
|
|
||||||
/// case the transaction is automatically aborted.
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
utils::BasicResult<StorageDataManipulationError, void> Commit(
|
|
||||||
std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
void Abort();
|
|
||||||
|
|
||||||
void FinalizeTransaction();
|
|
||||||
|
|
||||||
std::optional<uint64_t> GetTransactionId() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
VertexAccessor CreateVertex(storage::Gid gid);
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
Result<EdgeAccessor> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, storage::Gid gid);
|
|
||||||
|
|
||||||
Storage *storage_;
|
|
||||||
std::shared_lock<utils::RWLock> storage_guard_;
|
|
||||||
Transaction transaction_;
|
|
||||||
std::optional<uint64_t> commit_timestamp_;
|
|
||||||
bool is_transaction_active_;
|
|
||||||
Config::Items config_;
|
|
||||||
};
|
|
||||||
|
|
||||||
Accessor Access(std::optional<IsolationLevel> override_isolation_level = {}) {
|
|
||||||
return Accessor{this, override_isolation_level.value_or(isolation_level_)};
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::string &LabelToName(LabelId label) const;
|
|
||||||
const std::string &PropertyToName(PropertyId property) const;
|
|
||||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const;
|
|
||||||
|
|
||||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
LabelId NameToLabel(std::string_view name);
|
virtual LabelId NameToLabel(std::string_view name) = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
PropertyId NameToProperty(std::string_view name);
|
virtual PropertyId NameToProperty(std::string_view name) = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||||
EdgeTypeId NameToEdgeType(std::string_view name);
|
virtual EdgeTypeId NameToEdgeType(std::string_view name) = 0;
|
||||||
|
|
||||||
/// Create an index.
|
virtual bool LabelIndexExists(LabelId label) const = 0;
|
||||||
/// Returns void if the index has been created.
|
|
||||||
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
|
||||||
/// * `IndexDefinitionError`: the index already exists.
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
|
|
||||||
LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// Create an index.
|
virtual bool LabelPropertyIndexExists(LabelId label, PropertyId property) const = 0;
|
||||||
/// Returns void if the index has been created.
|
|
||||||
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// * `IndexDefinitionError`: the index already exists.
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
utils::BasicResult<StorageIndexDefinitionError, void> CreateIndex(
|
|
||||||
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// Drop an existing index.
|
virtual IndicesInfo ListAllIndices() const = 0;
|
||||||
/// Returns void if the index has been dropped.
|
|
||||||
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// * `IndexDefinitionError`: the index does not exist.
|
|
||||||
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
|
|
||||||
LabelId label, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// Drop an existing index.
|
virtual ConstraintsInfo ListAllConstraints() const = 0;
|
||||||
/// Returns void if the index has been dropped.
|
|
||||||
/// Returns `StorageIndexDefinitionError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// * `IndexDefinitionError`: the index does not exist.
|
|
||||||
utils::BasicResult<StorageIndexDefinitionError, void> DropIndex(
|
|
||||||
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
IndicesInfo ListAllIndices() const;
|
virtual void AdvanceCommand() = 0;
|
||||||
|
|
||||||
/// Returns void if the existence constraint has been created.
|
/// Returns void if the transaction has been committed.
|
||||||
/// Returns `StorageExistenceConstraintDefinitionError` if an error occures. Error can be:
|
/// Returns `StorageDataManipulationError` if an error occures. Error can be:
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
||||||
/// * `ConstraintViolation`: there is already a vertex existing that would break this new constraint.
|
/// * `ConstraintViolation`: the changes made by this transaction violate an existence or unique constraint. In this
|
||||||
/// * `ConstraintDefinitionError`: the constraint already exists.
|
/// case the transaction is automatically aborted.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
/// @throw std::length_error
|
virtual utils::BasicResult<StorageDataManipulationError, void> Commit(
|
||||||
utils::BasicResult<StorageExistenceConstraintDefinitionError, void> CreateExistenceConstraint(
|
std::optional<uint64_t> desired_commit_timestamp = {}) = 0;
|
||||||
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// Drop an existing existence constraint.
|
|
||||||
/// Returns void if the existence constraint has been dropped.
|
|
||||||
/// Returns `StorageExistenceConstraintDroppingError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// * `ConstraintDefinitionError`: the constraint did not exists.
|
|
||||||
utils::BasicResult<StorageExistenceConstraintDroppingError, void> DropExistenceConstraint(
|
|
||||||
LabelId label, PropertyId property, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// Create an unique constraint.
|
|
||||||
/// Returns `StorageUniqueConstraintDefinitionError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// * `ConstraintViolation`: there are already vertices violating the constraint.
|
|
||||||
/// Returns `UniqueConstraints::CreationStatus` otherwise. Value can be:
|
|
||||||
/// * `SUCCESS` if the constraint was successfully created,
|
|
||||||
/// * `ALREADY_EXISTS` if the constraint already existed,
|
|
||||||
/// * `EMPTY_PROPERTIES` if the property set is empty, or
|
|
||||||
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
utils::BasicResult<StorageUniqueConstraintDefinitionError, UniqueConstraints::CreationStatus> CreateUniqueConstraint(
|
virtual void Abort() = 0;
|
||||||
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
/// Removes an existing unique constraint.
|
virtual void FinalizeTransaction() = 0;
|
||||||
/// Returns `StorageUniqueConstraintDroppingError` if an error occures. Error can be:
|
|
||||||
/// * `ReplicationError`: there is at least one SYNC replica that has not confirmed receiving the transaction.
|
|
||||||
/// Returns `UniqueConstraints::DeletionStatus` otherwise. Value can be:
|
|
||||||
/// * `SUCCESS` if constraint was successfully removed,
|
|
||||||
/// * `NOT_FOUND` if the specified constraint was not found,
|
|
||||||
/// * `EMPTY_PROPERTIES` if the property set is empty, or
|
|
||||||
/// * `PROPERTIES_SIZE_LIMIT_EXCEEDED` if the property set exceeds the limit of maximum number of properties.
|
|
||||||
utils::BasicResult<StorageUniqueConstraintDroppingError, UniqueConstraints::DeletionStatus> DropUniqueConstraint(
|
|
||||||
LabelId label, const std::set<PropertyId> &properties, std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
ConstraintsInfo ListAllConstraints() const;
|
virtual std::optional<uint64_t> GetTransactionId() const = 0;
|
||||||
|
|
||||||
StorageInfo GetInfo() const;
|
|
||||||
|
|
||||||
bool LockPath();
|
|
||||||
bool UnlockPath();
|
|
||||||
|
|
||||||
bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {});
|
|
||||||
|
|
||||||
bool SetMainReplicationRole();
|
|
||||||
|
|
||||||
enum class RegisterReplicaError : uint8_t {
|
|
||||||
NAME_EXISTS,
|
|
||||||
END_POINT_EXISTS,
|
|
||||||
CONNECTION_FAILED,
|
|
||||||
COULD_NOT_BE_PERSISTED
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @pre The instance should have a MAIN role
|
|
||||||
/// @pre Timeout can only be set for SYNC replication
|
|
||||||
utils::BasicResult<RegisterReplicaError, void> RegisterReplica(
|
|
||||||
std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode,
|
|
||||||
replication::RegistrationMode registration_mode, const replication::ReplicationClientConfig &config = {});
|
|
||||||
/// @pre The instance should have a MAIN role
|
|
||||||
bool UnregisterReplica(const std::string &name);
|
|
||||||
|
|
||||||
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name);
|
|
||||||
|
|
||||||
ReplicationRole GetReplicationRole() const;
|
|
||||||
|
|
||||||
struct TimestampInfo {
|
|
||||||
uint64_t current_timestamp_of_replica;
|
|
||||||
uint64_t current_number_of_timestamp_behind_master;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ReplicaInfo {
|
|
||||||
std::string name;
|
|
||||||
replication::ReplicationMode mode;
|
|
||||||
io::network::Endpoint endpoint;
|
|
||||||
replication::ReplicaState state;
|
|
||||||
TimestampInfo timestamp_info;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<ReplicaInfo> ReplicasInfo();
|
|
||||||
|
|
||||||
void FreeMemory();
|
|
||||||
|
|
||||||
void SetIsolationLevel(IsolationLevel isolation_level);
|
|
||||||
|
|
||||||
enum class CreateSnapshotError : uint8_t { DisabledForReplica };
|
|
||||||
|
|
||||||
utils::BasicResult<CreateSnapshotError> CreateSnapshot();
|
|
||||||
|
|
||||||
private:
|
|
||||||
Transaction CreateTransaction(IsolationLevel isolation_level);
|
|
||||||
|
|
||||||
/// The force parameter determines the behaviour of the garbage collector.
|
|
||||||
/// If it's set to true, it will behave as a global operation, i.e. it can't
|
|
||||||
/// be part of a transaction, and no other transaction can be active at the same time.
|
|
||||||
/// This allows it to delete immediately vertices without worrying that some other
|
|
||||||
/// transaction is possibly using it. If there are active transactions when this method
|
|
||||||
/// is called with force set to true, it will fallback to the same method with the force
|
|
||||||
/// set to false.
|
|
||||||
/// If it's set to false, it will execute in parallel with other transactions, ensuring
|
|
||||||
/// that no object in use can be deleted.
|
|
||||||
/// @throw std::system_error
|
|
||||||
/// @throw std::bad_alloc
|
|
||||||
template <bool force>
|
|
||||||
void CollectGarbage();
|
|
||||||
|
|
||||||
bool InitializeWalFile();
|
|
||||||
void FinalizeWalFile();
|
|
||||||
|
|
||||||
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
|
|
||||||
[[nodiscard]] bool AppendToWalDataManipulation(const Transaction &transaction, uint64_t final_commit_timestamp);
|
|
||||||
/// Return true in all cases excepted if any sync replicas have not sent confirmation.
|
|
||||||
[[nodiscard]] bool AppendToWalDataDefinition(durability::StorageGlobalOperation operation, LabelId label,
|
|
||||||
const std::set<PropertyId> &properties, uint64_t final_commit_timestamp);
|
|
||||||
|
|
||||||
uint64_t CommitTimestamp(std::optional<uint64_t> desired_commit_timestamp = {});
|
|
||||||
|
|
||||||
void RestoreReplicas();
|
|
||||||
|
|
||||||
bool ShouldStoreAndRestoreReplicas() const;
|
|
||||||
|
|
||||||
// Main storage lock.
|
|
||||||
//
|
|
||||||
// Accessors take a shared lock when starting, so it is possible to block
|
|
||||||
// creation of new accessors by taking a unique lock. This is used when doing
|
|
||||||
// operations on storage that affect the global state, for example index
|
|
||||||
// creation.
|
|
||||||
mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE};
|
|
||||||
|
|
||||||
// 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};
|
|
||||||
// Even though the edge count is already kept in the `edges_` SkipList, the
|
|
||||||
// list is used only when properties are enabled for edges. Because of that we
|
|
||||||
// keep a separate count of edges that is always updated.
|
|
||||||
std::atomic<uint64_t> edge_count_{0};
|
|
||||||
|
|
||||||
NameIdMapper name_id_mapper_;
|
|
||||||
|
|
||||||
Constraints constraints_;
|
|
||||||
Indices indices_;
|
|
||||||
|
|
||||||
// Transaction engine
|
|
||||||
utils::SpinLock engine_lock_;
|
|
||||||
uint64_t timestamp_{kTimestampInitialId};
|
|
||||||
uint64_t transaction_id_{kTransactionInitialId};
|
|
||||||
// TODO: This isn't really a commit log, it doesn't even care if a
|
|
||||||
// transaction commited or aborted. We could probably combine this with
|
|
||||||
// `timestamp_` in a sensible unit, something like TransactionClock or
|
|
||||||
// whatever.
|
|
||||||
std::optional<CommitLog> commit_log_;
|
|
||||||
|
|
||||||
utils::Synchronized<std::list<Transaction>, utils::SpinLock> committed_transactions_;
|
|
||||||
IsolationLevel isolation_level_;
|
|
||||||
|
|
||||||
Config config_;
|
|
||||||
utils::Scheduler gc_runner_;
|
|
||||||
std::mutex gc_lock_;
|
|
||||||
|
|
||||||
// Undo buffers that were unlinked and now are waiting to be freed.
|
|
||||||
utils::Synchronized<std::list<std::pair<uint64_t, std::list<Delta>>>, utils::SpinLock> garbage_undo_buffers_;
|
|
||||||
|
|
||||||
// Vertices that are logically deleted but still have to be removed from
|
|
||||||
// indices before removing them from the main storage.
|
|
||||||
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_vertices_;
|
|
||||||
|
|
||||||
// Vertices that are logically deleted and removed from indices and now wait
|
|
||||||
// to be removed from the main storage.
|
|
||||||
std::list<std::pair<uint64_t, Gid>> garbage_vertices_;
|
|
||||||
|
|
||||||
// Edges that are logically deleted and wait to be removed from the main
|
|
||||||
// storage.
|
|
||||||
utils::Synchronized<std::list<Gid>, utils::SpinLock> deleted_edges_;
|
|
||||||
|
|
||||||
// Durability
|
|
||||||
std::filesystem::path snapshot_directory_;
|
|
||||||
std::filesystem::path wal_directory_;
|
|
||||||
std::filesystem::path lock_file_path_;
|
|
||||||
utils::OutputFile lock_file_handle_;
|
|
||||||
std::unique_ptr<kvstore::KVStore> storage_;
|
|
||||||
|
|
||||||
utils::Scheduler snapshot_runner_;
|
|
||||||
utils::SpinLock snapshot_lock_;
|
|
||||||
|
|
||||||
// UUID used to distinguish snapshots and to link snapshots to WALs
|
|
||||||
std::string uuid_;
|
|
||||||
// Sequence number used to keep track of the chain of WALs.
|
|
||||||
uint64_t wal_seq_num_{0};
|
|
||||||
|
|
||||||
// UUID to distinguish different main instance runs for replication process
|
|
||||||
// on SAME storage.
|
|
||||||
// Multiple instances can have same storage UUID and be MAIN at the same time.
|
|
||||||
// We cannot compare commit timestamps of those instances if one of them
|
|
||||||
// becomes the replica of the other so we use epoch_id_ as additional
|
|
||||||
// discriminating property.
|
|
||||||
// Example of this:
|
|
||||||
// We have 2 instances of the same storage, S1 and S2.
|
|
||||||
// S1 and S2 are MAIN and accept their own commits and write them to the WAL.
|
|
||||||
// At the moment when S1 commited a transaction with timestamp 20, and S2
|
|
||||||
// a different transaction with timestamp 15, we change S2's role to REPLICA
|
|
||||||
// and register it on S1.
|
|
||||||
// Without using the epoch_id, we don't know that S1 and S2 have completely
|
|
||||||
// different transactions, we think that the S2 is behind only by 5 commits.
|
|
||||||
std::string epoch_id_;
|
|
||||||
// History of the previous epoch ids.
|
|
||||||
// Each value consists of the epoch id along the last commit belonging to that
|
|
||||||
// epoch.
|
|
||||||
std::deque<std::pair<std::string, uint64_t>> epoch_history_;
|
|
||||||
|
|
||||||
std::optional<durability::WalFile> wal_file_;
|
|
||||||
uint64_t wal_unsynced_transactions_{0};
|
|
||||||
|
|
||||||
utils::FileRetainer file_retainer_;
|
|
||||||
|
|
||||||
// Global locker that is used for clients file locking
|
|
||||||
utils::FileRetainer::FileLocker global_locker_;
|
|
||||||
|
|
||||||
// Last commited timestamp
|
|
||||||
std::atomic<uint64_t> last_commit_timestamp_{kTimestampInitialId};
|
|
||||||
|
|
||||||
class ReplicationServer;
|
|
||||||
std::unique_ptr<ReplicationServer> replication_server_{nullptr};
|
|
||||||
|
|
||||||
class ReplicationClient;
|
|
||||||
// We create ReplicationClient using unique_ptr so we can move
|
|
||||||
// newly created client into the vector.
|
|
||||||
// We cannot move the client directly because it contains ThreadPool
|
|
||||||
// which cannot be moved. Also, the move is necessary because
|
|
||||||
// we don't want to create the client directly inside the vector
|
|
||||||
// because that would require the lock on the list putting all
|
|
||||||
// commits (they iterate list of clients) to halt.
|
|
||||||
// This way we can initialize client in main thread which means
|
|
||||||
// that we can immediately notify the user if the initialization
|
|
||||||
// failed.
|
|
||||||
using ReplicationClientList = utils::Synchronized<std::vector<std::unique_ptr<ReplicationClient>>, utils::SpinLock>;
|
|
||||||
ReplicationClientList replication_clients_;
|
|
||||||
|
|
||||||
std::atomic<ReplicationRole> replication_role_{ReplicationRole::MAIN};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace memgraph::storage
|
} // namespace memgraph::storage
|
||||||
|
@ -11,585 +11,17 @@
|
|||||||
|
|
||||||
#include "storage/v2/vertex_accessor.hpp"
|
#include "storage/v2/vertex_accessor.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include "storage/v2/inmemory/edge_accessor.hpp"
|
||||||
|
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||||
#include "storage/v2/edge_accessor.hpp"
|
|
||||||
#include "storage/v2/id_types.hpp"
|
|
||||||
#include "storage/v2/indices.hpp"
|
|
||||||
#include "storage/v2/mvcc.hpp"
|
|
||||||
#include "storage/v2/property_value.hpp"
|
|
||||||
#include "utils/logging.hpp"
|
|
||||||
#include "utils/memory_tracker.hpp"
|
|
||||||
|
|
||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
namespace detail {
|
std::unique_ptr<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||||
namespace {
|
Constraints *constraints, Config::Items config, View view) {
|
||||||
std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View view) {
|
return InMemoryVertexAccessor::Create(vertex, transaction, indices, constraints, config, view);
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex->lock);
|
|
||||||
deleted = vertex->deleted;
|
|
||||||
delta = vertex->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction, delta, view, [&](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
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: {
|
|
||||||
deleted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {exists, deleted};
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
} // namespace detail
|
|
||||||
|
|
||||||
std::optional<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
|
||||||
Constraints *constraints, Config::Items config, View view) {
|
|
||||||
if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
return VertexAccessor{vertex, transaction, indices, constraints, config};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VertexAccessor::IsVisible(View view) const {
|
Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view) const { return InEdges(view, {}, nullptr); }
|
||||||
const auto [exists, deleted] = detail::IsVisible(vertex_, transaction_, view);
|
Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view) const { return OutEdges(view, {}, nullptr); }
|
||||||
return exists && (for_deleted_ || !deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<bool> VertexAccessor::AddLabel(LabelId label) {
|
|
||||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false;
|
|
||||||
|
|
||||||
CreateAndLinkDelta(transaction_, vertex_, Delta::RemoveLabelTag(), label);
|
|
||||||
|
|
||||||
vertex_->labels.push_back(label);
|
|
||||||
|
|
||||||
UpdateOnAddLabel(indices_, label, vertex_, *transaction_);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<bool> VertexAccessor::RemoveLabel(LabelId label) {
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label);
|
|
||||||
if (it == vertex_->labels.end()) return false;
|
|
||||||
|
|
||||||
CreateAndLinkDelta(transaction_, vertex_, Delta::AddLabelTag(), label);
|
|
||||||
|
|
||||||
std::swap(*it, *vertex_->labels.rbegin());
|
|
||||||
vertex_->labels.pop_back();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const {
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
bool has_label = false;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
has_label = std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end();
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &has_label, label](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::REMOVE_LABEL: {
|
|
||||||
if (delta.label == label) {
|
|
||||||
MG_ASSERT(has_label, "Invalid database state!");
|
|
||||||
has_label = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::ADD_LABEL: {
|
|
||||||
if (delta.label == label) {
|
|
||||||
MG_ASSERT(!has_label, "Invalid database state!");
|
|
||||||
has_label = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::RECREATE_OBJECT: {
|
|
||||||
deleted = false;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return has_label;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const {
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
std::vector<LabelId> labels;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
labels = vertex_->labels;
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &labels](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::REMOVE_LABEL: {
|
|
||||||
// Remove the label because we don't see the addition.
|
|
||||||
auto it = std::find(labels.begin(), labels.end(), delta.label);
|
|
||||||
MG_ASSERT(it != labels.end(), "Invalid database state!");
|
|
||||||
std::swap(*it, *labels.rbegin());
|
|
||||||
labels.pop_back();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::ADD_LABEL: {
|
|
||||||
// Add the label because we don't see the removal.
|
|
||||||
auto it = std::find(labels.begin(), labels.end(), delta.label);
|
|
||||||
MG_ASSERT(it == labels.end(), "Invalid database state!");
|
|
||||||
labels.push_back(delta.label);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::RECREATE_OBJECT: {
|
|
||||||
deleted = false;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return std::move(labels);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) {
|
|
||||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
auto current_value = vertex_->properties.GetProperty(property);
|
|
||||||
// We could skip setting the value if the previous one is the same to the new
|
|
||||||
// one. This would save some memory as a delta would not be created as well as
|
|
||||||
// avoid copying the value. The reason we are not doing that is because the
|
|
||||||
// current code always follows the logical pattern of "create a delta" and
|
|
||||||
// "modify in-place". Additionally, the created delta will make other
|
|
||||||
// transactions get a SERIALIZATION_ERROR.
|
|
||||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, current_value);
|
|
||||||
vertex_->properties.SetProperty(property, value);
|
|
||||||
|
|
||||||
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
|
||||||
|
|
||||||
return std::move(current_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) {
|
|
||||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
if (!vertex_->properties.InitProperties(properties)) return false;
|
|
||||||
for (const auto &[property, value] : properties) {
|
|
||||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property, PropertyValue());
|
|
||||||
UpdateOnSetProperty(indices_, property, value, vertex_, *transaction_);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() {
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
|
|
||||||
if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR;
|
|
||||||
|
|
||||||
if (vertex_->deleted) return Error::DELETED_OBJECT;
|
|
||||||
|
|
||||||
auto properties = vertex_->properties.Properties();
|
|
||||||
for (const auto &property : properties) {
|
|
||||||
CreateAndLinkDelta(transaction_, vertex_, Delta::SetPropertyTag(), property.first, property.second);
|
|
||||||
UpdateOnSetProperty(indices_, property.first, PropertyValue(), vertex_, *transaction_);
|
|
||||||
}
|
|
||||||
|
|
||||||
vertex_->properties.ClearProperties();
|
|
||||||
|
|
||||||
return std::move(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view) const {
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
PropertyValue value;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
value = vertex_->properties.GetProperty(property);
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::SET_PROPERTY: {
|
|
||||||
if (delta.property.key == property) {
|
|
||||||
value = delta.property.value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::RECREATE_OBJECT: {
|
|
||||||
deleted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return std::move(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view) const {
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
std::map<PropertyId, PropertyValue> properties;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
properties = vertex_->properties.Properties();
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::SET_PROPERTY: {
|
|
||||||
auto it = properties.find(delta.property.key);
|
|
||||||
if (it != properties.end()) {
|
|
||||||
if (delta.property.value.IsNull()) {
|
|
||||||
// remove the property
|
|
||||||
properties.erase(it);
|
|
||||||
} else {
|
|
||||||
// set the value
|
|
||||||
it->second = delta.property.value;
|
|
||||||
}
|
|
||||||
} else if (!delta.property.value.IsNull()) {
|
|
||||||
properties.emplace(delta.property.key, delta.property.value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::RECREATE_OBJECT: {
|
|
||||||
deleted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return std::move(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::vector<EdgeAccessor>> VertexAccessor::InEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
|
||||||
const VertexAccessor *destination) const {
|
|
||||||
MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!");
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
if (edge_types.empty() && !destination) {
|
|
||||||
in_edges = vertex_->in_edges;
|
|
||||||
} else {
|
|
||||||
for (const auto &item : vertex_->in_edges) {
|
|
||||||
const auto &[edge_type, from_vertex, edge] = item;
|
|
||||||
if (destination && from_vertex != destination->vertex_) continue;
|
|
||||||
if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end())
|
|
||||||
continue;
|
|
||||||
in_edges.push_back(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(
|
|
||||||
transaction_, delta, view, [&exists, &deleted, &in_edges, &edge_types, &destination](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::ADD_IN_EDGE: {
|
|
||||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
|
||||||
if (!edge_types.empty() &&
|
|
||||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
|
||||||
break;
|
|
||||||
// Add the edge because we don't see the removal.
|
|
||||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
|
||||||
MG_ASSERT(it == in_edges.end(), "Invalid database state!");
|
|
||||||
in_edges.push_back(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::REMOVE_IN_EDGE: {
|
|
||||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
|
||||||
if (!edge_types.empty() &&
|
|
||||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
|
||||||
break;
|
|
||||||
// Remove the label because we don't see the addition.
|
|
||||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
|
||||||
MG_ASSERT(it != in_edges.end(), "Invalid database state!");
|
|
||||||
std::swap(*it, *in_edges.rbegin());
|
|
||||||
in_edges.pop_back();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (deleted) return Error::DELETED_OBJECT;
|
|
||||||
std::vector<EdgeAccessor> ret;
|
|
||||||
ret.reserve(in_edges.size());
|
|
||||||
for (const auto &item : in_edges) {
|
|
||||||
const auto &[edge_type, from_vertex, edge] = item;
|
|
||||||
ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_);
|
|
||||||
}
|
|
||||||
return std::move(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<std::vector<EdgeAccessor>> VertexAccessor::OutEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
|
||||||
const VertexAccessor *destination) const {
|
|
||||||
MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!");
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
if (edge_types.empty() && !destination) {
|
|
||||||
out_edges = vertex_->out_edges;
|
|
||||||
} else {
|
|
||||||
for (const auto &item : vertex_->out_edges) {
|
|
||||||
const auto &[edge_type, to_vertex, edge] = item;
|
|
||||||
if (destination && to_vertex != destination->vertex_) continue;
|
|
||||||
if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end())
|
|
||||||
continue;
|
|
||||||
out_edges.push_back(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(
|
|
||||||
transaction_, delta, view, [&exists, &deleted, &out_edges, &edge_types, &destination](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::ADD_OUT_EDGE: {
|
|
||||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
|
||||||
if (!edge_types.empty() &&
|
|
||||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
|
||||||
break;
|
|
||||||
// Add the edge because we don't see the removal.
|
|
||||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
|
||||||
MG_ASSERT(it == out_edges.end(), "Invalid database state!");
|
|
||||||
out_edges.push_back(link);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::REMOVE_OUT_EDGE: {
|
|
||||||
if (destination && delta.vertex_edge.vertex != destination->vertex_) break;
|
|
||||||
if (!edge_types.empty() &&
|
|
||||||
std::find(edge_types.begin(), edge_types.end(), delta.vertex_edge.edge_type) == edge_types.end())
|
|
||||||
break;
|
|
||||||
// Remove the label because we don't see the addition.
|
|
||||||
std::tuple<EdgeTypeId, Vertex *, EdgeRef> 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);
|
|
||||||
MG_ASSERT(it != out_edges.end(), "Invalid database state!");
|
|
||||||
std::swap(*it, *out_edges.rbegin());
|
|
||||||
out_edges.pop_back();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Delta::Action::DELETE_OBJECT: {
|
|
||||||
exists = false;
|
|
||||||
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (deleted) return Error::DELETED_OBJECT;
|
|
||||||
std::vector<EdgeAccessor> ret;
|
|
||||||
ret.reserve(out_edges.size());
|
|
||||||
for (const auto &item : out_edges) {
|
|
||||||
const auto &[edge_type, to_vertex, edge] = item;
|
|
||||||
ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_);
|
|
||||||
}
|
|
||||||
return std::move(ret);
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<size_t> VertexAccessor::InDegree(View view) const {
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
size_t degree = 0;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
degree = vertex_->in_edges.size();
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, °ree](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::ADD_IN_EDGE:
|
|
||||||
++degree;
|
|
||||||
break;
|
|
||||||
case Delta::Action::REMOVE_IN_EDGE:
|
|
||||||
--degree;
|
|
||||||
break;
|
|
||||||
case Delta::Action::DELETE_OBJECT:
|
|
||||||
exists = false;
|
|
||||||
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return degree;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result<size_t> VertexAccessor::OutDegree(View view) const {
|
|
||||||
bool exists = true;
|
|
||||||
bool deleted = false;
|
|
||||||
size_t degree = 0;
|
|
||||||
Delta *delta = nullptr;
|
|
||||||
{
|
|
||||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
|
||||||
deleted = vertex_->deleted;
|
|
||||||
degree = vertex_->out_edges.size();
|
|
||||||
delta = vertex_->delta;
|
|
||||||
}
|
|
||||||
ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, °ree](const Delta &delta) {
|
|
||||||
switch (delta.action) {
|
|
||||||
case Delta::Action::ADD_OUT_EDGE:
|
|
||||||
++degree;
|
|
||||||
break;
|
|
||||||
case Delta::Action::REMOVE_OUT_EDGE:
|
|
||||||
--degree;
|
|
||||||
break;
|
|
||||||
case Delta::Action::DELETE_OBJECT:
|
|
||||||
exists = false;
|
|
||||||
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 (!exists) return Error::NONEXISTENT_OBJECT;
|
|
||||||
if (!for_deleted_ && deleted) return Error::DELETED_OBJECT;
|
|
||||||
return degree;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace memgraph::storage
|
} // namespace memgraph::storage
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "storage/v2/vertex.hpp"
|
#include "storage/v2/vertex.hpp"
|
||||||
|
|
||||||
#include "storage/v2/config.hpp"
|
#include "storage/v2/config.hpp"
|
||||||
|
#include "storage/v2/constraints.hpp"
|
||||||
#include "storage/v2/result.hpp"
|
#include "storage/v2/result.hpp"
|
||||||
#include "storage/v2/transaction.hpp"
|
#include "storage/v2/transaction.hpp"
|
||||||
#include "storage/v2/view.hpp"
|
#include "storage/v2/view.hpp"
|
||||||
@ -23,94 +24,87 @@
|
|||||||
namespace memgraph::storage {
|
namespace memgraph::storage {
|
||||||
|
|
||||||
class EdgeAccessor;
|
class EdgeAccessor;
|
||||||
class Storage;
|
|
||||||
struct Indices;
|
struct Indices;
|
||||||
struct Constraints;
|
|
||||||
|
|
||||||
class VertexAccessor final {
|
class VertexAccessor {
|
||||||
private:
|
private:
|
||||||
friend class Storage;
|
friend class Storage;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints,
|
VertexAccessor(Transaction *transaction, Config::Items config, bool for_deleted = false)
|
||||||
Config::Items config, bool for_deleted = false)
|
: transaction_(transaction), config_(config), for_deleted_(for_deleted) {}
|
||||||
: vertex_(vertex),
|
|
||||||
transaction_(transaction),
|
|
||||||
indices_(indices),
|
|
||||||
constraints_(constraints),
|
|
||||||
config_(config),
|
|
||||||
for_deleted_(for_deleted) {}
|
|
||||||
|
|
||||||
static std::optional<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
virtual ~VertexAccessor() {}
|
||||||
Constraints *constraints, Config::Items config, View view);
|
|
||||||
|
static std::unique_ptr<VertexAccessor> Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||||
|
Constraints *constraints, Config::Items config, View view);
|
||||||
|
|
||||||
/// @return true if the object is visible from the current transaction
|
/// @return true if the object is visible from the current transaction
|
||||||
bool IsVisible(View view) const;
|
virtual bool IsVisible(View view) const = 0;
|
||||||
|
|
||||||
/// Add a label and return `true` if insertion took place.
|
/// Add a label and return `true` if insertion took place.
|
||||||
/// `false` is returned if the label already existed.
|
/// `false` is returned if the label already existed.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<bool> AddLabel(LabelId label);
|
virtual Result<bool> AddLabel(LabelId label) = 0;
|
||||||
|
|
||||||
/// Remove a label and return `true` if deletion took place.
|
/// Remove a label and return `true` if deletion took place.
|
||||||
/// `false` is returned if the vertex did not have a label already.
|
/// `false` is returned if the vertex did not have a label already.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<bool> RemoveLabel(LabelId label);
|
virtual Result<bool> RemoveLabel(LabelId label) = 0;
|
||||||
|
|
||||||
Result<bool> HasLabel(LabelId label, View view) const;
|
virtual Result<bool> HasLabel(LabelId label, View view) const = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
/// @throw std::length_error if the resulting vector exceeds
|
/// @throw std::length_error if the resulting vector exceeds
|
||||||
/// std::vector::max_size().
|
/// std::vector::max_size().
|
||||||
Result<std::vector<LabelId>> Labels(View view) const;
|
virtual Result<std::vector<LabelId>> Labels(View view) const = 0;
|
||||||
|
|
||||||
/// Set a property value and return the old value.
|
/// Set a property value and return the old value.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value);
|
virtual Result<PropertyValue> SetProperty(PropertyId property, const PropertyValue &value) = 0;
|
||||||
|
|
||||||
/// Set property values only if property store is empty. Returns `true` if successully set all values,
|
/// Set property values only if property store is empty. Returns `true` if successully set all values,
|
||||||
/// `false` otherwise.
|
/// `false` otherwise.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties);
|
virtual Result<bool> InitProperties(const std::map<storage::PropertyId, storage::PropertyValue> &properties) = 0;
|
||||||
|
|
||||||
/// Remove all properties and return the values of the removed properties.
|
/// Remove all properties and return the values of the removed properties.
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<std::map<PropertyId, PropertyValue>> ClearProperties();
|
virtual Result<std::map<PropertyId, PropertyValue>> ClearProperties() = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<PropertyValue> GetProperty(PropertyId property, View view) const;
|
virtual Result<PropertyValue> GetProperty(PropertyId property, View view) const = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
Result<std::map<PropertyId, PropertyValue>> Properties(View view) const;
|
virtual Result<std::map<PropertyId, PropertyValue>> Properties(View view) const = 0;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
/// @throw std::length_error if the resulting vector exceeds
|
/// @throw std::length_error if the resulting vector exceeds
|
||||||
/// std::vector::max_size().
|
/// std::vector::max_size().
|
||||||
Result<std::vector<EdgeAccessor>> InEdges(View view, const std::vector<EdgeTypeId> &edge_types = {},
|
virtual Result<std::vector<EdgeAccessor>> InEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||||
const VertexAccessor *destination = nullptr) const;
|
const VertexAccessor *destination) const = 0;
|
||||||
|
|
||||||
|
Result<std::vector<EdgeAccessor>> InEdges(View view) const;
|
||||||
|
|
||||||
/// @throw std::bad_alloc
|
/// @throw std::bad_alloc
|
||||||
/// @throw std::length_error if the resulting vector exceeds
|
/// @throw std::length_error if the resulting vector exceeds
|
||||||
/// std::vector::max_size().
|
/// std::vector::max_size().
|
||||||
Result<std::vector<EdgeAccessor>> OutEdges(View view, const std::vector<EdgeTypeId> &edge_types = {},
|
virtual Result<std::vector<EdgeAccessor>> OutEdges(View view, const std::vector<EdgeTypeId> &edge_types,
|
||||||
const VertexAccessor *destination = nullptr) const;
|
const VertexAccessor *destination) const = 0;
|
||||||
|
|
||||||
Result<size_t> InDegree(View view) const;
|
Result<std::vector<EdgeAccessor>> OutEdges(View view) const;
|
||||||
|
|
||||||
Result<size_t> OutDegree(View view) const;
|
virtual Result<size_t> InDegree(View view) const = 0;
|
||||||
|
|
||||||
Gid Gid() const noexcept { return vertex_->gid; }
|
virtual Result<size_t> OutDegree(View view) const = 0;
|
||||||
|
|
||||||
bool operator==(const VertexAccessor &other) const noexcept {
|
virtual Gid Gid() const noexcept = 0;
|
||||||
return vertex_ == other.vertex_ && transaction_ == other.transaction_;
|
|
||||||
}
|
virtual bool operator==(const VertexAccessor &other) const noexcept = 0;
|
||||||
bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); }
|
bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); }
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
Vertex *vertex_;
|
|
||||||
Transaction *transaction_;
|
Transaction *transaction_;
|
||||||
Indices *indices_;
|
|
||||||
Constraints *constraints_;
|
|
||||||
Config::Items config_;
|
Config::Items config_;
|
||||||
|
|
||||||
// if the accessor was created for a deleted vertex.
|
// if the accessor was created for a deleted vertex.
|
||||||
@ -124,10 +118,3 @@ class VertexAccessor final {
|
|||||||
};
|
};
|
||||||
|
|
||||||
} // namespace memgraph::storage
|
} // namespace memgraph::storage
|
||||||
|
|
||||||
namespace std {
|
|
||||||
template <>
|
|
||||||
struct hash<memgraph::storage::VertexAccessor> {
|
|
||||||
size_t operator()(const memgraph::storage::VertexAccessor &v) const noexcept { return v.Gid().AsUint(); }
|
|
||||||
};
|
|
||||||
} // namespace std
|
|
||||||
|
Loading…
Reference in New Issue
Block a user