Compare commits
58 Commits
local_shar
...
disk-stora
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
153222a2a7 | ||
|
|
cbe23f7efa | ||
|
|
95c119999f | ||
|
|
bd0801dc1f | ||
|
|
04261f7cce | ||
|
|
7bf38442aa | ||
|
|
43fd6906b6 | ||
|
|
697bed4348 | ||
|
|
e45decfae8 | ||
|
|
6a5ec391d9 | ||
|
|
243e716812 | ||
|
|
7ed38621ce | ||
|
|
5f1b05337f | ||
|
|
7a4d910282 | ||
|
|
d6b9309618 | ||
|
|
0505f29b52 | ||
|
|
e4225c4ac1 | ||
|
|
c960d253dd | ||
|
|
f3c4b4cea1 | ||
|
|
30328832f4 | ||
|
|
6ffbade6d8 | ||
|
|
d37842e523 | ||
|
|
ead0f9db22 | ||
|
|
9e2dc55750 | ||
|
|
245f2a34e1 | ||
|
|
e51f82db1d | ||
|
|
225922206c | ||
|
|
26f56352e4 | ||
|
|
39c14746ad | ||
|
|
955d2f1322 | ||
|
|
69778d6ca9 | ||
|
|
c4b8d7d568 | ||
|
|
c2ebd511ab | ||
|
|
9ce31bbab1 | ||
|
|
797fb78910 | ||
|
|
a1bf90a687 | ||
|
|
3a96db481e | ||
|
|
6d959d07d3 | ||
|
|
9b3bdb5c84 | ||
|
|
177d9dabf5 | ||
|
|
c819012a52 | ||
|
|
53623da2b7 | ||
|
|
148f6e1498 | ||
|
|
bc6f8e31fc | ||
|
|
b34044459e | ||
|
|
a22b011736 | ||
|
|
876359f4b6 | ||
|
|
c441b7de0b | ||
|
|
b6beffa9e2 | ||
|
|
1bed62da4a | ||
|
|
2019f5cc72 | ||
|
|
f070329e77 | ||
|
|
b8d956294b | ||
|
|
72371cc8ee | ||
|
|
e193d4036e | ||
|
|
ceb3e67f8e | ||
|
|
f7c1923ab2 | ||
|
|
7503b304f2 |
@@ -53,6 +53,8 @@
|
||||
#include "query/procedure/module.hpp"
|
||||
#include "query/procedure/py_module.hpp"
|
||||
#include "requests/requests.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/inmemory/storage.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
@@ -195,7 +197,7 @@ DEFINE_bool(allow_load_csv, true, "Controls whether LOAD CSV clause is allowed i
|
||||
|
||||
// Storage flags.
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 30, "Storage garbage collector interval (in seconds).",
|
||||
DEFINE_VALIDATED_uint64(storage_gc_cycle_sec, 60, "Storage garbage collector interval (in seconds).",
|
||||
FLAG_IN_RANGE(1, 24 * 3600));
|
||||
// NOTE: The `storage_properties_on_edges` flag must be the same here and in
|
||||
// `mg_import_csv`. If you change it, make sure to change it there as well.
|
||||
@@ -877,6 +879,9 @@ int main(int argc, char **argv) {
|
||||
// End enterprise features initialization
|
||||
#endif
|
||||
|
||||
// auto type = memgraph::storage::Config::StorageMode::Type::IN_MEMORY;
|
||||
auto type = memgraph::storage::Config::StorageMode::Type::PERSISTENT;
|
||||
|
||||
// Main storage and execution engines initialization
|
||||
memgraph::storage::Config db_config{
|
||||
.gc = {.type = memgraph::storage::Config::Gc::Type::PERIODIC,
|
||||
@@ -889,7 +894,8 @@ int main(int argc, char **argv) {
|
||||
.wal_file_flush_every_n_tx = FLAGS_storage_wal_file_flush_every_n_tx,
|
||||
.snapshot_on_exit = FLAGS_storage_snapshot_on_exit,
|
||||
.restore_replicas_on_startup = true},
|
||||
.transaction = {.isolation_level = ParseIsolationLevel()}};
|
||||
.transaction = {.isolation_level = ParseIsolationLevel()},
|
||||
.storage_mode = {.type = type}};
|
||||
if (FLAGS_storage_snapshot_interval_sec == 0) {
|
||||
if (FLAGS_storage_wal_enabled) {
|
||||
LOG_FATAL(
|
||||
@@ -908,7 +914,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
db_config.durability.snapshot_interval = std::chrono::seconds(FLAGS_storage_snapshot_interval_sec);
|
||||
}
|
||||
auto db = std::unique_ptr<memgraph::storage::Storage>(new memgraph::storage::InMemoryStorage(db_config));
|
||||
// auto db = std::unique_ptr<memgraph::storage::Storage>(new memgraph::storage::InMemoryStorage(db_config));
|
||||
auto db = std::unique_ptr<memgraph::storage::Storage>(new memgraph::storage::DiskStorage(db_config));
|
||||
|
||||
memgraph::query::InterpreterContext interpreter_context{
|
||||
db.get(),
|
||||
|
||||
@@ -372,6 +372,12 @@ class DbAccessor final {
|
||||
|
||||
VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); }
|
||||
|
||||
/// TODO(andi): Change return result to Result<bool>
|
||||
void PrefetchInEdges(const VertexAccessor &vertex_acc) const { accessor_->PrefetchInEdges(*vertex_acc.impl_); }
|
||||
|
||||
/// TODO(andi): Change return result to Result<bool>
|
||||
void PrefetchOutEdges(const VertexAccessor &vertex_acc) const { accessor_->PrefetchOutEdges(*vertex_acc.impl_); }
|
||||
|
||||
storage::Result<EdgeAccessor> InsertEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
const storage::EdgeTypeId &edge_type) {
|
||||
auto maybe_edge = accessor_->CreateEdge(from->impl_.get(), to->impl_.get(), edge_type);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -482,6 +482,7 @@ PullPlanDump::PullChunk PullPlanDump::CreateEdgePullChunk() {
|
||||
// If we have a saved iterable from a previous pull
|
||||
// we need to use the same iterable
|
||||
if (!maybe_edge_iterable) {
|
||||
dba_->PrefetchOutEdges(vertex);
|
||||
maybe_edge_iterable = std::make_shared<EdgeAccessorIterable>(vertex.OutEdges(storage::View::OLD));
|
||||
}
|
||||
auto &maybe_edges = *maybe_edge_iterable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -467,6 +467,8 @@ TypedValue Degree(const TypedValue *args, int64_t nargs, const FunctionContext &
|
||||
FType<Or<Null, Vertex>>("degree", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
const auto &vertex = args[0].ValueVertex();
|
||||
ctx.db_accessor->PrefetchInEdges(vertex);
|
||||
ctx.db_accessor->PrefetchOutEdges(vertex);
|
||||
size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
|
||||
size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
|
||||
return TypedValue(static_cast<int64_t>(out_degree + in_degree), ctx.memory);
|
||||
@@ -476,6 +478,7 @@ TypedValue InDegree(const TypedValue *args, int64_t nargs, const FunctionContext
|
||||
FType<Or<Null, Vertex>>("inDegree", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
const auto &vertex = args[0].ValueVertex();
|
||||
ctx.db_accessor->PrefetchInEdges(vertex);
|
||||
size_t in_degree = UnwrapDegreeResult(vertex.InDegree(ctx.view));
|
||||
return TypedValue(static_cast<int64_t>(in_degree), ctx.memory);
|
||||
}
|
||||
@@ -484,6 +487,7 @@ TypedValue OutDegree(const TypedValue *args, int64_t nargs, const FunctionContex
|
||||
FType<Or<Null, Vertex>>("outDegree", args, nargs);
|
||||
if (args[0].IsNull()) return TypedValue(ctx.memory);
|
||||
const auto &vertex = args[0].ValueVertex();
|
||||
ctx.db_accessor->PrefetchOutEdges(vertex);
|
||||
size_t out_degree = UnwrapDegreeResult(vertex.OutDegree(ctx.view));
|
||||
return TypedValue(static_cast<int64_t>(out_degree), ctx.memory);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "query/stream/common.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/edge.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "query/stream/streams.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
@@ -493,7 +493,8 @@ UniqueCursorPtr ScanAll::MakeCursor(utils::MemoryResource *mem) const {
|
||||
|
||||
auto vertices = [this](Frame &, ExecutionContext &context) {
|
||||
auto *db = context.db_accessor;
|
||||
return std::make_optional(db->Vertices(view_));
|
||||
auto vertices = std::make_optional(db->Vertices(view_));
|
||||
return vertices;
|
||||
};
|
||||
return MakeUniqueCursorPtr<ScanAllCursor<decltype(vertices)>>(mem, output_symbol_, input_->MakeCursor(mem), view_,
|
||||
std::move(vertices), "ScanAll");
|
||||
@@ -811,10 +812,12 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, ExecutionContext &context) {
|
||||
// old_node_value may be Null when using optional matching
|
||||
if (!existing_node.IsNull()) {
|
||||
ExpectType(self_.common_.node_symbol, existing_node, TypedValue::Type::Vertex);
|
||||
context.db_accessor->PrefetchInEdges(vertex);
|
||||
in_edges_.emplace(
|
||||
UnwrapEdgesResult(vertex.InEdges(self_.view_, self_.common_.edge_types, existing_node.ValueVertex())));
|
||||
}
|
||||
} else {
|
||||
context.db_accessor->PrefetchInEdges(vertex);
|
||||
in_edges_.emplace(UnwrapEdgesResult(vertex.InEdges(self_.view_, self_.common_.edge_types)));
|
||||
}
|
||||
if (in_edges_) {
|
||||
@@ -828,10 +831,12 @@ bool Expand::ExpandCursor::InitEdges(Frame &frame, ExecutionContext &context) {
|
||||
// old_node_value may be Null when using optional matching
|
||||
if (!existing_node.IsNull()) {
|
||||
ExpectType(self_.common_.node_symbol, existing_node, TypedValue::Type::Vertex);
|
||||
context.db_accessor->PrefetchOutEdges(vertex);
|
||||
out_edges_.emplace(
|
||||
UnwrapEdgesResult(vertex.OutEdges(self_.view_, self_.common_.edge_types, existing_node.ValueVertex())));
|
||||
}
|
||||
} else {
|
||||
context.db_accessor->PrefetchOutEdges(vertex);
|
||||
out_edges_.emplace(UnwrapEdgesResult(vertex.OutEdges(self_.view_, self_.common_.edge_types)));
|
||||
}
|
||||
if (out_edges_) {
|
||||
@@ -889,7 +894,8 @@ namespace {
|
||||
* @return See above.
|
||||
*/
|
||||
auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction direction,
|
||||
const std::vector<storage::EdgeTypeId> &edge_types, utils::MemoryResource *memory) {
|
||||
const std::vector<storage::EdgeTypeId> &edge_types, utils::MemoryResource *memory,
|
||||
DbAccessor *db_accessor) {
|
||||
// wraps an EdgeAccessor into a pair <accessor, direction>
|
||||
auto wrapper = [](EdgeAtom::Direction direction, auto &&edges) {
|
||||
return iter::imap([direction](const auto &edge) { return std::make_pair(edge, direction); },
|
||||
@@ -900,6 +906,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio
|
||||
utils::pmr::vector<decltype(wrapper(direction, *vertex.InEdges(view, edge_types)))> chain_elements(memory);
|
||||
|
||||
if (direction != EdgeAtom::Direction::OUT) {
|
||||
db_accessor->PrefetchInEdges(vertex);
|
||||
auto edges = UnwrapEdgesResult(vertex.InEdges(view, edge_types));
|
||||
if (edges.begin() != edges.end()) {
|
||||
chain_elements.emplace_back(wrapper(EdgeAtom::Direction::IN, std::move(edges)));
|
||||
@@ -907,6 +914,7 @@ auto ExpandFromVertex(const VertexAccessor &vertex, EdgeAtom::Direction directio
|
||||
}
|
||||
|
||||
if (direction != EdgeAtom::Direction::IN) {
|
||||
db_accessor->PrefetchOutEdges(vertex);
|
||||
auto edges = UnwrapEdgesResult(vertex.OutEdges(view, edge_types));
|
||||
if (edges.begin() != edges.end()) {
|
||||
chain_elements.emplace_back(wrapper(EdgeAtom::Direction::OUT, std::move(edges)));
|
||||
@@ -972,8 +980,9 @@ class ExpandVariableCursor : public Cursor {
|
||||
|
||||
// a stack of edge iterables corresponding to the level/depth of
|
||||
// the expansion currently being Pulled
|
||||
using ExpandEdges = decltype(ExpandFromVertex(std::declval<VertexAccessor>(), EdgeAtom::Direction::IN,
|
||||
self_.common_.edge_types, utils::NewDeleteResource()));
|
||||
using ExpandEdges =
|
||||
decltype(ExpandFromVertex(std::declval<VertexAccessor>(), EdgeAtom::Direction::IN, self_.common_.edge_types,
|
||||
utils::NewDeleteResource(), std::declval<DbAccessor *>()));
|
||||
|
||||
utils::pmr::vector<ExpandEdges> edges_;
|
||||
// an iterator indicating the position in the corresponding edges_ element
|
||||
@@ -1014,7 +1023,8 @@ class ExpandVariableCursor : public Cursor {
|
||||
|
||||
if (upper_bound_ > 0) {
|
||||
auto *memory = edges_.get_allocator().GetMemoryResource();
|
||||
edges_.emplace_back(ExpandFromVertex(vertex, self_.common_.direction, self_.common_.edge_types, memory));
|
||||
edges_.emplace_back(
|
||||
ExpandFromVertex(vertex, self_.common_.direction, self_.common_.edge_types, memory, context.db_accessor));
|
||||
edges_it_.emplace_back(edges_.back().begin());
|
||||
}
|
||||
|
||||
@@ -1120,8 +1130,8 @@ class ExpandVariableCursor : public Cursor {
|
||||
// edge's expansions onto the stack, if we should continue to expand
|
||||
if (upper_bound_ > static_cast<int64_t>(edges_.size())) {
|
||||
auto *memory = edges_.get_allocator().GetMemoryResource();
|
||||
edges_.emplace_back(
|
||||
ExpandFromVertex(current_vertex, self_.common_.direction, self_.common_.edge_types, memory));
|
||||
edges_.emplace_back(ExpandFromVertex(current_vertex, self_.common_.direction, self_.common_.edge_types, memory,
|
||||
context.db_accessor));
|
||||
edges_it_.emplace_back(edges_.back().begin());
|
||||
}
|
||||
|
||||
@@ -1264,6 +1274,7 @@ class STShortestPathCursor : public query::plan::Cursor {
|
||||
|
||||
for (const auto &vertex : source_frontier) {
|
||||
if (self_.common_.direction != EdgeAtom::Direction::IN) {
|
||||
context.db_accessor->PrefetchOutEdges(vertex);
|
||||
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : out_edges) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -1290,6 +1301,7 @@ class STShortestPathCursor : public query::plan::Cursor {
|
||||
}
|
||||
}
|
||||
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
|
||||
dba.PrefetchInEdges(vertex);
|
||||
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : in_edges) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -1330,6 +1342,7 @@ class STShortestPathCursor : public query::plan::Cursor {
|
||||
// reversed.
|
||||
for (const auto &vertex : sink_frontier) {
|
||||
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
|
||||
context.db_accessor->PrefetchOutEdges(vertex);
|
||||
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : out_edges) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -1355,6 +1368,7 @@ class STShortestPathCursor : public query::plan::Cursor {
|
||||
}
|
||||
}
|
||||
if (self_.common_.direction != EdgeAtom::Direction::IN) {
|
||||
dba.PrefetchInEdges(vertex);
|
||||
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : in_edges) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -1644,15 +1658,17 @@ class ExpandWeightedShortestPathCursor : public query::plan::Cursor {
|
||||
// Populates the priority queue structure with expansions
|
||||
// from the given vertex. skips expansions that don't satisfy
|
||||
// the "where" condition.
|
||||
auto expand_from_vertex = [this, &expand_pair](const VertexAccessor &vertex, const TypedValue &weight,
|
||||
int64_t depth) {
|
||||
auto expand_from_vertex = [this, &expand_pair, &context](const VertexAccessor &vertex, const TypedValue &weight,
|
||||
int64_t depth) {
|
||||
if (self_.common_.direction != EdgeAtom::Direction::IN) {
|
||||
context.db_accessor->PrefetchOutEdges(vertex);
|
||||
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : out_edges) {
|
||||
expand_pair(edge, edge.To(), weight, depth);
|
||||
}
|
||||
}
|
||||
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
|
||||
context.db_accessor->PrefetchInEdges(vertex);
|
||||
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : in_edges) {
|
||||
expand_pair(edge, edge.From(), weight, depth);
|
||||
@@ -1911,6 +1927,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
auto expand_from_vertex = [this, &expand_vertex, &context](const VertexAccessor &vertex, const TypedValue &weight,
|
||||
int64_t depth) {
|
||||
if (self_.common_.direction != EdgeAtom::Direction::IN) {
|
||||
context.db_accessor->PrefetchOutEdges(vertex);
|
||||
auto out_edges = UnwrapEdgesResult(vertex.OutEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : out_edges) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
@@ -1925,6 +1942,7 @@ class ExpandAllShortestPathsCursor : public query::plan::Cursor {
|
||||
}
|
||||
}
|
||||
if (self_.common_.direction != EdgeAtom::Direction::OUT) {
|
||||
context.db_accessor->PrefetchInEdges(vertex);
|
||||
auto in_edges = UnwrapEdgesResult(vertex.InEdges(storage::View::OLD, self_.common_.edge_types));
|
||||
for (const auto &edge : in_edges) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/plan/preprocess.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
|
||||
DECLARE_int64(query_vertex_count_to_expand_existing);
|
||||
|
||||
|
||||
@@ -845,7 +845,8 @@ bool SharedLibraryModule::Load(const std::filesystem::path &file_path) {
|
||||
file_path_ = file_path;
|
||||
dlerror(); // Clear any existing error.
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
|
||||
// handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL | RTLD_DEEPBIND);
|
||||
handle_ = dlopen(file_path.c_str(), RTLD_NOW | RTLD_LOCAL);
|
||||
if (!handle_) {
|
||||
spdlog::error(
|
||||
utils::MessageWithLink("Unable to load module {}; {}.", file_path, dlerror(), "https://memgr.ph/modules"));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -302,6 +302,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) {
|
||||
if (!maybe_from_vertex) {
|
||||
continue;
|
||||
}
|
||||
accessor->PrefetchOutEdges(*maybe_from_vertex);
|
||||
auto maybe_out_edges = maybe_from_vertex->OutEdges(storage::View::OLD);
|
||||
MG_ASSERT(maybe_out_edges.HasValue());
|
||||
const auto edge_gid = created_edge.object.Gid();
|
||||
|
||||
@@ -7,13 +7,19 @@ set(storage_v2_src_files
|
||||
durability/snapshot.cpp
|
||||
durability/wal.cpp
|
||||
edge_accessor.cpp
|
||||
indices.cpp
|
||||
inmemory/indices.cpp
|
||||
inmemory/edge_accessor.cpp
|
||||
inmemory/storage.cpp
|
||||
inmemory/vertex_accessor.cpp
|
||||
property_store.cpp
|
||||
vertex_accessor.cpp
|
||||
storage.cpp
|
||||
vertex_accessor.cpp)
|
||||
disk/compaction_filter.cpp
|
||||
disk/indices.cpp
|
||||
disk/storage.cpp
|
||||
disk/rocksdb_storage.cpp
|
||||
disk/vertex_accessor.cpp
|
||||
disk/edge_accessor.cpp)
|
||||
|
||||
|
||||
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
|
||||
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
|
||||
@@ -55,6 +55,12 @@ struct Config {
|
||||
struct Transaction {
|
||||
IsolationLevel isolation_level{IsolationLevel::SNAPSHOT_ISOLATION};
|
||||
} transaction;
|
||||
|
||||
struct StorageMode {
|
||||
enum class Type { IN_MEMORY, PERSISTENT };
|
||||
|
||||
Type type{Type::IN_MEMORY};
|
||||
} storage_mode;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
|
||||
@@ -84,6 +84,7 @@ bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, c
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
@@ -198,6 +199,7 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std::
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -12,6 +12,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
#include "storage/v2/edge_ref.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
@@ -123,6 +124,7 @@ inline bool operator!=(const PreviousPtr::Pointer &a, const PreviousPtr::Pointer
|
||||
struct Delta {
|
||||
enum class Action {
|
||||
// Used for both Vertex and Edge
|
||||
DELETE_DESERIALIZED_OBJECT,
|
||||
DELETE_OBJECT,
|
||||
RECREATE_OBJECT,
|
||||
SET_PROPERTY,
|
||||
@@ -137,6 +139,7 @@ struct Delta {
|
||||
};
|
||||
|
||||
// Used for both Vertex and Edge
|
||||
struct DeleteDeserializedObjectTag {};
|
||||
struct DeleteObjectTag {};
|
||||
struct RecreateObjectTag {};
|
||||
struct SetPropertyTag {};
|
||||
@@ -149,6 +152,9 @@ struct Delta {
|
||||
struct RemoveInEdgeTag {};
|
||||
struct RemoveOutEdgeTag {};
|
||||
|
||||
Delta(DeleteDeserializedObjectTag, uint64_t timestamp)
|
||||
: action(Action::DELETE_DESERIALIZED_OBJECT), timestamp(new std::atomic<uint64_t>(timestamp)) {}
|
||||
|
||||
Delta(DeleteObjectTag, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
||||
: action(Action::DELETE_OBJECT), timestamp(timestamp), command_id(command_id) {}
|
||||
|
||||
@@ -212,6 +218,9 @@ struct Delta {
|
||||
case Action::SET_PROPERTY:
|
||||
property.value.~PropertyValue();
|
||||
break;
|
||||
case Action::DELETE_DESERIALIZED_OBJECT:
|
||||
delete timestamp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
src/storage/v2/disk/compaction_filter.cpp
Normal file
27
src/storage/v2/disk/compaction_filter.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// 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 <rocksdb/cache.h>
|
||||
#include <rocksdb/compaction_filter.h>
|
||||
#include <rocksdb/options.h>
|
||||
#include <rocksdb/slice_transform.h>
|
||||
|
||||
class TimestampCompactionFilter : public rocksdb::CompactionFilter {
|
||||
public:
|
||||
const char *Name() const override { return "TimestampCompactionFilter"; }
|
||||
|
||||
/// Return true if the key-value pair should be removed from the database during compaction.
|
||||
/// Filters KV entries that are older than the specified timestamp.
|
||||
bool Filter(int level, const rocksdb::Slice &key, const rocksdb::Slice &existing_value, std::string *new_value,
|
||||
bool *value_changed) const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
34
src/storage/v2/disk/disk_edge.hpp
Normal file
34
src/storage/v2/disk/disk_edge.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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 "storage/v2/edge.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
struct DiskEdge : public Edge {
|
||||
/// Create a new edge with the given GID and delta.
|
||||
/// Use this constructor when creating a new edge in the transaction.
|
||||
DiskEdge(Gid gid, Delta *delta) : Edge(gid, delta) {}
|
||||
|
||||
/// Create a new edge with the given GID, delta and modification timestamp.
|
||||
/// Use this constructor when loading an edge from disk.
|
||||
DiskEdge(Gid gid, Delta *delta, uint64_t modification_ts) : Edge(gid, delta), modification_ts(modification_ts) {}
|
||||
|
||||
/// modification timestamp of the last transaction that modified this vertex.
|
||||
uint64_t modification_ts;
|
||||
};
|
||||
|
||||
const auto disk_edge_cmp = [](DiskEdge *first, DiskEdge *second) {
|
||||
return first->modification_ts < second->modification_ts;
|
||||
};
|
||||
} // namespace memgraph::storage
|
||||
32
src/storage/v2/disk/disk_vertex.hpp
Normal file
32
src/storage/v2/disk/disk_vertex.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 <cstdint>
|
||||
#include "storage/v2/vertex.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
struct DiskVertex : Vertex {
|
||||
/// Create a new vertex with the given GID and delta.
|
||||
/// Use this constructor when creating a new vertex in the transaction.
|
||||
DiskVertex(Gid gid, Delta *delta) : Vertex(gid, delta) {}
|
||||
|
||||
/// Modification timestamp of the last transaction that modified this vertex.
|
||||
uint64_t modification_ts;
|
||||
};
|
||||
|
||||
const auto disk_vertex_cmp = [](DiskVertex *first, DiskVertex *second) {
|
||||
return first->modification_ts < second->modification_ts;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
291
src/storage/v2/disk/edge_accessor.cpp
Normal file
291
src/storage/v2/disk/edge_accessor.cpp
Normal file
@@ -0,0 +1,291 @@
|
||||
// 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/disk/edge_accessor.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
|
||||
#include "storage/v2/disk/vertex_accessor.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
bool DiskEdgeAccessor::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_DESERIALIZED_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_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
return exists && (for_deleted_ || !deleted);
|
||||
}
|
||||
|
||||
void DiskEdgeAccessor::InitializeDeserializedEdge(EdgeTypeId edge_type_id, std::string_view property_store) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
// TODO(andi): What if config properties on edges are disablesd and can we make it work without lock?
|
||||
std::lock_guard<utils::SpinLock> guard(edge_.ptr->lock);
|
||||
edge_type_ = edge_type_id;
|
||||
SetPropertyStore(property_store);
|
||||
}
|
||||
|
||||
std::unique_ptr<VertexAccessor> DiskEdgeAccessor::FromVertex() const {
|
||||
// TODO(andi): Revisit this GID caching thing once again
|
||||
return std::make_unique<DiskVertexAccessor>(from_vertex_, transaction_, indices_, constraints_, config_,
|
||||
from_vertex_->gid);
|
||||
}
|
||||
|
||||
std::unique_ptr<VertexAccessor> DiskEdgeAccessor::ToVertex() const {
|
||||
// TODO(andi): Revisit this GID caching thing once again
|
||||
return std::make_unique<DiskVertexAccessor>(to_vertex_, transaction_, indices_, constraints_, config_,
|
||||
to_vertex_->gid);
|
||||
}
|
||||
|
||||
Result<storage::PropertyValue> DiskEdgeAccessor::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> DiskEdgeAccessor::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>> DiskEdgeAccessor::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> DiskEdgeAccessor::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_DESERIALIZED_OBJECT:
|
||||
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>> DiskEdgeAccessor::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_DESERIALIZED_OBJECT:
|
||||
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);
|
||||
}
|
||||
|
||||
bool DiskEdgeAccessor::SetPropertyStore(std::string_view buffer) const {
|
||||
if (config_.properties_on_edges) {
|
||||
edge_.ptr->properties.SetBuffer(buffer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::string> DiskEdgeAccessor::PropertyStore() const {
|
||||
if (config_.properties_on_edges) {
|
||||
return edge_.ptr->properties.StringBuffer();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DiskEdgeAccessor::UpdateModificationTimestamp(uint64_t modification_ts) { modification_ts_ = modification_ts; }
|
||||
|
||||
} // namespace memgraph::storage
|
||||
116
src/storage/v2/disk/edge_accessor.hpp
Normal file
116
src/storage/v2/disk/edge_accessor.hpp
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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 <cstdint>
|
||||
#include <optional>
|
||||
|
||||
#include "storage/v2/disk/indices.hpp"
|
||||
#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 DiskVertex;
|
||||
class VertexAccessor;
|
||||
struct Indices;
|
||||
struct Constraints;
|
||||
|
||||
class DiskEdgeAccessor final : public EdgeAccessor {
|
||||
private:
|
||||
friend class DiskStorage;
|
||||
|
||||
public:
|
||||
DiskEdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, DiskVertex *from_vertex, DiskVertex *to_vertex,
|
||||
Transaction *transaction, DiskIndices *indices, Constraints *constraints, Config::Items config,
|
||||
storage::Gid gid, 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),
|
||||
gid_(gid) {}
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
bool IsVisible(View view) const override;
|
||||
|
||||
/// Initializes deserialized edge from the disk.
|
||||
void InitializeDeserializedEdge(EdgeTypeId edge_type_id, std::string_view property_store);
|
||||
|
||||
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;
|
||||
|
||||
storage::Gid Gid() const noexcept override {
|
||||
if (config_.properties_on_edges) {
|
||||
return edge_.ptr->gid;
|
||||
}
|
||||
return edge_.gid;
|
||||
}
|
||||
|
||||
std::optional<std::string> PropertyStore() const override;
|
||||
|
||||
bool SetPropertyStore(std::string_view buffer) const override;
|
||||
|
||||
void UpdateModificationTimestamp(uint64_t modification_ts);
|
||||
|
||||
std::unique_ptr<EdgeAccessor> Copy() const override { return std::make_unique<DiskEdgeAccessor>(*this); }
|
||||
|
||||
bool IsCycle() const override { return from_vertex_ == to_vertex_; }
|
||||
|
||||
bool operator==(const EdgeAccessor &other) const noexcept override {
|
||||
const auto *otherEdge = dynamic_cast<const DiskEdgeAccessor *>(&other);
|
||||
if (otherEdge == nullptr) return false;
|
||||
return edge_ == otherEdge->edge_ && transaction_ == otherEdge->transaction_;
|
||||
}
|
||||
bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); }
|
||||
|
||||
private:
|
||||
EdgeRef edge_;
|
||||
DiskVertex *from_vertex_;
|
||||
DiskVertex *to_vertex_;
|
||||
DiskIndices *indices_;
|
||||
Constraints *constraints_;
|
||||
storage::Gid gid_;
|
||||
uint64_t modification_ts_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
390
src/storage/v2/disk/helper_storage.hpp
Normal file
390
src/storage/v2/disk/helper_storage.hpp
Normal file
@@ -0,0 +1,390 @@
|
||||
// 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 <rocksdb/db.h>
|
||||
#include <rocksdb/iterator.h>
|
||||
#include <rocksdb/options.h>
|
||||
#include <rocksdb/status.h>
|
||||
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::storage::rocks {
|
||||
|
||||
constexpr const char *vertexHandle = "vertex";
|
||||
constexpr const char *edgeHandle = "edge";
|
||||
constexpr const char *outEdgeDirection = "0";
|
||||
constexpr const char *inEdgeDirection = "1";
|
||||
|
||||
// /// Use it for operations that must successfully finish.
|
||||
inline void AssertRocksDBStatus(const rocksdb::Status &status) {
|
||||
MG_ASSERT(status.ok(), "rocksdb: {}", status.ToString());
|
||||
}
|
||||
|
||||
// inline bool CheckRocksDBStatus(const rocksdb::Status &status) {
|
||||
// if (!status.ok()) [[unlikely]] {
|
||||
// spdlog::error("rocksdb: {}", status.ToString());
|
||||
// }
|
||||
// return status.ok();
|
||||
// }
|
||||
|
||||
class RocksDBStorage {
|
||||
public:
|
||||
explicit RocksDBStorage() {
|
||||
options_.create_if_missing = true;
|
||||
// options_.OptimizeLevelStyleCompaction();
|
||||
std::filesystem::path rocksdb_path = "./rocks_experiment_unit";
|
||||
MG_ASSERT(utils::EnsureDir(rocksdb_path), "Unable to create storage folder on the disk.");
|
||||
AssertRocksDBStatus(rocksdb::DB::Open(options_, rocksdb_path, &db_));
|
||||
AssertRocksDBStatus(db_->CreateColumnFamily(rocksdb::ColumnFamilyOptions(), vertexHandle, &vertex_chandle));
|
||||
AssertRocksDBStatus(db_->CreateColumnFamily(rocksdb::ColumnFamilyOptions(), edgeHandle, &edge_chandle));
|
||||
}
|
||||
|
||||
RocksDBStorage(const RocksDBStorage &) = delete;
|
||||
RocksDBStorage &operator=(const RocksDBStorage &) = delete;
|
||||
RocksDBStorage &operator=(RocksDBStorage &&) = delete;
|
||||
RocksDBStorage(RocksDBStorage &&) = delete;
|
||||
|
||||
~RocksDBStorage() {
|
||||
AssertRocksDBStatus(db_->DropColumnFamily(vertex_chandle));
|
||||
AssertRocksDBStatus(db_->DropColumnFamily(edge_chandle));
|
||||
AssertRocksDBStatus(db_->DestroyColumnFamilyHandle(vertex_chandle));
|
||||
AssertRocksDBStatus(db_->DestroyColumnFamilyHandle(edge_chandle));
|
||||
AssertRocksDBStatus(db_->Close());
|
||||
delete db_;
|
||||
}
|
||||
|
||||
// // EDGE ACCESSOR FUNCTIONALITIES
|
||||
// // -----------------------------------------------------------
|
||||
|
||||
// /// fetch the edge's source vertex by its GID
|
||||
// std::optional<query::VertexAccessor> FromVertex(const query::EdgeAccessor &edge_acc, query::DbAccessor &dba) {
|
||||
// return FindVertex(SerializeIdType(edge_acc.From().Gid()), dba);
|
||||
// }
|
||||
|
||||
// /// fetch the edge's destination vertex by its GID
|
||||
// std::optional<query::VertexAccessor> ToVertex(const query::EdgeAccessor &edge_acc, query::DbAccessor &dba) {
|
||||
// return FindVertex(SerializeIdType(edge_acc.To().Gid()), dba);
|
||||
// }
|
||||
|
||||
// /// VERTEX ACCESSOR FUNCTIONALITIES
|
||||
// /// ------------------------------------------------------------
|
||||
|
||||
// /// The VertexAccessor's out edge with gid src_gid has the following format in the RocksDB:
|
||||
// /// src_gid | other_vertex_gid | 0 | ...
|
||||
// /// other_vertex_gid | src_gid | 1 | ...
|
||||
// /// We use the firt way since this should be possible to optimize using Bloom filters and prefix search
|
||||
// std::vector<query::EdgeAccessor> OutEdges(const query::VertexAccessor &vertex_acc, query::DbAccessor &dba) {
|
||||
// const auto vertex_acc_gid = SerializeIdType(vertex_acc.Gid());
|
||||
// std::vector<query::EdgeAccessor> out_edges;
|
||||
// auto it = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions(), edge_chandle));
|
||||
// for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
// const std::string_view key = it->key().ToStringView();
|
||||
// const auto vertex_parts = utils::Split(key, "|");
|
||||
// if (vertex_parts[0] == vertex_acc_gid && vertex_parts[2] == outEdgeDirection) {
|
||||
// out_edges.push_back(DeserializeEdge(key, it->value().ToStringView(), dba));
|
||||
// }
|
||||
// }
|
||||
// return out_edges;
|
||||
// }
|
||||
|
||||
// /// The VertexAccessor's out edge with gid src_gid has the following format in the RocksDB:
|
||||
// /// other_vertex_gid | dest_gid | 0 | ...
|
||||
// /// dest_gid | other_verte_gid | 1 | ...
|
||||
// /// we use the second way since this should be possible to optimize using Bloom filters and prefix search.
|
||||
// std::vector<query::EdgeAccessor> InEdges(const query::VertexAccessor &vertex_acc, query::DbAccessor &dba) {
|
||||
// const auto vertex_acc_gid = SerializeIdType(vertex_acc.Gid());
|
||||
// std::vector<query::EdgeAccessor> in_edges;
|
||||
// auto it = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions(), edge_chandle));
|
||||
// for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
// const std::string_view key = it->key().ToStringView();
|
||||
// const auto vertex_parts = utils::Split(key, "|");
|
||||
// if (vertex_parts[0] == vertex_acc_gid && vertex_parts[2] == inEdgeDirection) {
|
||||
// in_edges.push_back(DeserializeEdge(key, it->value().ToStringView(), dba));
|
||||
// }
|
||||
// }
|
||||
// return in_edges;
|
||||
// }
|
||||
|
||||
// /// TODO: how will we handle new vertex creation
|
||||
|
||||
// /// STORAGE ACCESSOR FUNCTIONALITIES
|
||||
// /// -----------------------------------------------------------
|
||||
|
||||
// /// TODO: how will we handle new edge creation
|
||||
|
||||
// /// @return Accessor to the deleted edge if a deletion took place, std::nullopt otherwise.
|
||||
// /// Delete two edge entries since on edge is represented on a two-fold level.
|
||||
// /// Edges are deleted from logical partition containing edges.
|
||||
// std::optional<query::EdgeAccessor> DeleteEdge(const query::EdgeAccessor &edge_acc) {
|
||||
// auto [src_dest_key, dest_src_key] = SerializeEdge(edge_acc);
|
||||
// if (!CheckRocksDBStatus(db_->Delete(rocksdb::WriteOptions(), edge_chandle, src_dest_key)) ||
|
||||
// !CheckRocksDBStatus(db_->Delete(rocksdb::WriteOptions(), edge_chandle, dest_src_key))) {
|
||||
// return std::nullopt;
|
||||
// }
|
||||
// return edge_acc;
|
||||
// }
|
||||
|
||||
// /// Helper function, not used in the real accessor.
|
||||
// std::optional<std::vector<query::EdgeAccessor>> DeleteEdges(const auto &edge_accessors) {
|
||||
// std::vector<query::EdgeAccessor> edge_accs;
|
||||
// for (auto &&it : edge_accessors) {
|
||||
// if (const auto deleted_edge_res = DeleteEdge(it); !deleted_edge_res.has_value()) {
|
||||
// return std::nullopt;
|
||||
// }
|
||||
// edge_accs.push_back(it);
|
||||
// }
|
||||
// return edge_accs;
|
||||
// }
|
||||
|
||||
// /// @return A reference to the deleted vertex accessor if deleted, otherwise std::nullopt.
|
||||
// /// Delete vertex from logical partition containing vertices.
|
||||
// std::optional<query::VertexAccessor> DeleteVertex(const query::VertexAccessor &vertex_acc) {
|
||||
// if (!CheckRocksDBStatus(db_->Delete(rocksdb::WriteOptions(), vertex_chandle, SerializeVertex(vertex_acc)))) {
|
||||
// return std::nullopt;
|
||||
// }
|
||||
// return vertex_acc;
|
||||
// }
|
||||
|
||||
// /// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise.
|
||||
// /// Delete vertex from logical partition containing vertices.
|
||||
// /// For each edge delete two key-value entries from logical partition containing edges.
|
||||
// std::optional<std::pair<query::VertexAccessor, std::vector<query::EdgeAccessor>>> DetachDeleteVertex(
|
||||
// const query::VertexAccessor &vertex_acc) {
|
||||
// auto del_vertex = DeleteVertex(vertex_acc);
|
||||
// if (!del_vertex.has_value()) {
|
||||
// return std::nullopt;
|
||||
// }
|
||||
// auto out_edges = vertex_acc.OutEdges(storage::View::OLD);
|
||||
// auto in_edges = vertex_acc.InEdges(storage::View::OLD);
|
||||
// if (out_edges.HasError() || in_edges.HasError()) {
|
||||
// return std::nullopt;
|
||||
// }
|
||||
// if (auto del_edges = DeleteEdges(*out_edges), del_in_edges = DeleteEdges(*in_edges);
|
||||
// del_edges.has_value() && del_in_edges.has_value()) {
|
||||
// del_edges->insert(del_in_edges->end(), std::make_move_iterator(del_in_edges->begin()),
|
||||
// std::make_move_iterator(del_in_edges->end()));
|
||||
// return std::make_pair(*del_vertex, *del_edges);
|
||||
// }
|
||||
// return std::nullopt;
|
||||
// }
|
||||
|
||||
// /// STORING
|
||||
// /// -----------------------------------------------------------
|
||||
|
||||
// /// Serialize and store in-memory vertex to the disk.
|
||||
// /// Properties are serialized as the value
|
||||
// void StoreVertex(const query::VertexAccessor &vertex_acc) {
|
||||
// AssertRocksDBStatus(db_->Put(rocksdb::WriteOptions(), vertex_chandle, SerializeVertex(vertex_acc),
|
||||
// SerializeProperties(vertex_acc.PropertyStore())));
|
||||
// }
|
||||
|
||||
// /// Store edge as two key-value entries in the RocksDB.
|
||||
// void StoreEdge(const query::EdgeAccessor &edge_acc) {
|
||||
// auto [src_dest_key, dest_src_key] = SerializeEdge(edge_acc);
|
||||
// const std::string value = SerializeProperties(edge_acc.PropertyStore());
|
||||
// AssertRocksDBStatus(db_->Put(rocksdb::WriteOptions(), edge_chandle, src_dest_key, value));
|
||||
// AssertRocksDBStatus(db_->Put(rocksdb::WriteOptions(), edge_chandle, dest_src_key, value));
|
||||
// }
|
||||
|
||||
// /// UPDATE PART
|
||||
// /// -----------------------------------------------------------
|
||||
|
||||
// /// Clear all entries from the database.
|
||||
// /// TODO: check if this deletes all entries, or you also need to specify handle here
|
||||
// /// TODO: This will not be needed in the production code and can possibly removed in testing
|
||||
// void Clear() {
|
||||
// auto it = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions()));
|
||||
// for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
// db_->Delete(rocksdb::WriteOptions(), it->key().ToString());
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// READ PART
|
||||
// /// -----------------------------------------------------------
|
||||
|
||||
// /// TODO: if the need comes for using also a GID object, use std::variant
|
||||
// /// This should again be changed when we have mulitple same vertices
|
||||
// std::optional<query::VertexAccessor> FindVertex(const std::string_view gid, query::DbAccessor &dba) {
|
||||
// auto it = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions(), vertex_chandle));
|
||||
// std::optional<query::VertexAccessor> result = {};
|
||||
// for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
// const auto &key = it->key().ToString();
|
||||
// if (const auto vertex_parts = utils::Split(key, "|"); vertex_parts[1] == gid) {
|
||||
// result = DeserializeVertex(key, it->value().ToStringView(), dba);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /// Get all vertices by a label.
|
||||
// std::vector<query::VertexAccessor> Vertices(query::DbAccessor &dba, const storage::LabelId &label_id) {
|
||||
// return Vertices(dba, [label_id](const auto &vertex) {
|
||||
// const auto res = vertex.HasLabel(storage::View::OLD, label_id);
|
||||
// return !res.HasError() && *res;
|
||||
// });
|
||||
// }
|
||||
|
||||
// /// Read all vertices stored in the database by a property
|
||||
// std::vector<query::VertexAccessor> Vertices(query::DbAccessor &dba, const storage::PropertyId &property_id,
|
||||
// const storage::PropertyValue &property_value) {
|
||||
// return Vertices(dba, [property_id, property_value](const auto &vertex) {
|
||||
// const auto res = vertex.GetProperty(storage::View::OLD, property_id);
|
||||
// return !res.HasError() && *res == property_value;
|
||||
// });
|
||||
// }
|
||||
|
||||
// /// Get all vertices.
|
||||
// std::vector<query::VertexAccessor> Vertices(query::DbAccessor &dba) {
|
||||
// return Vertices(dba, [](const auto & /*vertex*/) { return true; });
|
||||
// }
|
||||
|
||||
// /// Read all vertices stored in the database and filter them by a lambda function.
|
||||
// std::vector<query::VertexAccessor> Vertices(query::DbAccessor &dba, const auto &vertex_filter) {
|
||||
// std::vector<query::VertexAccessor> vertices;
|
||||
// auto it = std::unique_ptr<rocksdb::Iterator>(db_->NewIterator(rocksdb::ReadOptions(), vertex_chandle));
|
||||
// for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
// auto vertex = DeserializeVertex(it->key().ToStringView(), it->value().ToStringView(), dba);
|
||||
// if (vertex_filter(vertex)) {
|
||||
// vertices.push_back(vertex);
|
||||
// }
|
||||
// }
|
||||
// return vertices;
|
||||
// }
|
||||
|
||||
// private:
|
||||
// /// Serialization of properties is done by saving the property store buffer
|
||||
// /// If the data is stored in the local buffer of the property store, data from the buffer is copied to the string
|
||||
// /// If the data is stored in some external buffer, the data is read from that location and copied to the string
|
||||
// inline std::string SerializeProperties(const auto &&properties) { return properties; }
|
||||
|
||||
// /// Serialize labels delimitied by | to string
|
||||
// std::string SerializeLabels(const auto &&labels) {
|
||||
// if (labels.HasError() || (*labels).empty()) {
|
||||
// return "";
|
||||
// }
|
||||
// std::string result = std::to_string((*labels)[0].AsUint());
|
||||
// std::string ser_labels = std::accumulate(
|
||||
// std::next((*labels).begin()), (*labels).end(), result,
|
||||
// [](const std::string &join, const auto &label_id) { return join + "," + std::to_string(label_id.AsUint());
|
||||
// });
|
||||
// return ser_labels;
|
||||
// }
|
||||
|
||||
// /// Serializes id type to string
|
||||
// inline std::string SerializeIdType(const auto &id) { return std::to_string(id.AsUint()); }
|
||||
|
||||
// /// Serialize vertex to string
|
||||
// /// The format: | label1,label2,label3 | gid
|
||||
// std::string SerializeVertex(const query::VertexAccessor &vertex_acc) {
|
||||
// std::string result = SerializeLabels(vertex_acc.Labels(storage::View::OLD)) + "|";
|
||||
// result += SerializeIdType(vertex_acc.Gid());
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /// Deserialize vertex from string
|
||||
// /// Properties are read from value and set to the vertex later
|
||||
// query::VertexAccessor DeserializeVertex(const std::string_view key, const std::string_view value,
|
||||
// query::DbAccessor &dba) {
|
||||
// /// Create vertex
|
||||
// auto impl = dba.InsertVertex();
|
||||
// spdlog::info("Key to deserialize: {}", key);
|
||||
// const auto vertex_parts = utils::Split(key, "|");
|
||||
// // Deserialize labels
|
||||
// if (!vertex_parts[0].empty()) {
|
||||
// const auto labels = utils::Split(vertex_parts[0], ",");
|
||||
// for (const auto &label : labels) {
|
||||
// const storage::LabelId label_id = storage::LabelId::FromUint(std::stoull(label));
|
||||
// auto maybe_error = impl.AddLabel(label_id);
|
||||
// if (maybe_error.HasError()) {
|
||||
// switch (maybe_error.GetError()) {
|
||||
// case storage::Error::SERIALIZATION_ERROR:
|
||||
// throw utils::BasicException("Serialization");
|
||||
// case storage::Error::DELETED_OBJECT:
|
||||
// throw utils::BasicException("Trying to set a label on a deleted node.");
|
||||
// case storage::Error::VERTEX_HAS_EDGES:
|
||||
// case storage::Error::PROPERTIES_DISABLED:
|
||||
// case storage::Error::NONEXISTENT_OBJECT:
|
||||
// throw utils::BasicException("Unexpected error when setting a label.");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// impl.SetGid(storage::Gid::FromUint(std::stoull(vertex_parts[1])));
|
||||
// impl.SetPropertyStore(value);
|
||||
// return impl;
|
||||
// }
|
||||
|
||||
// /// Serializes edge accessor to obtain a key for the key-value store.
|
||||
// /// @return two strings because there will be two keys since edge is stored in both directions.
|
||||
// // | from_gid | to_gid | direction | edge_type | edge_gid
|
||||
// std::pair<std::string, std::string> SerializeEdge(const query::EdgeAccessor &edge_acc) {
|
||||
// // Serialized objects
|
||||
// auto from_gid = SerializeIdType(edge_acc.From().Gid());
|
||||
// auto to_gid = SerializeIdType(edge_acc.To().Gid());
|
||||
// auto edge_type = SerializeIdType(edge_acc.EdgeType());
|
||||
// auto edge_gid = SerializeIdType(edge_acc.Gid());
|
||||
// // source->destination key
|
||||
// std::string src_dest_key = from_gid + "|";
|
||||
// src_dest_key += to_gid + "|";
|
||||
// src_dest_key += outEdgeDirection;
|
||||
// src_dest_key += "|" + edge_type + "|";
|
||||
// src_dest_key += edge_gid;
|
||||
// // destination->source key
|
||||
// std::string dest_src_key = to_gid + "|";
|
||||
// dest_src_key += from_gid + "|";
|
||||
// dest_src_key += inEdgeDirection;
|
||||
// dest_src_key += "|" + edge_type + "|";
|
||||
// dest_src_key += edge_gid;
|
||||
// return {src_dest_key, dest_src_key};
|
||||
// }
|
||||
|
||||
// /// Deserialize edge from the given key-value.
|
||||
// /// Properties are read from value and set to the edge later.
|
||||
// ///
|
||||
// query::EdgeAccessor DeserializeEdge(const std::string_view key, const std::string_view value,
|
||||
// query::DbAccessor &dba) {
|
||||
// const auto edge_parts = utils::Split(key, "|");
|
||||
// auto [from_gid, to_gid] = std::invoke(
|
||||
// [&](const auto &edge_parts) {
|
||||
// if (edge_parts[2] == "0") { // out edge
|
||||
// return std::make_pair(edge_parts[0], edge_parts[1]);
|
||||
// }
|
||||
// // in edge
|
||||
// return std::make_pair(edge_parts[1], edge_parts[0]);
|
||||
// },
|
||||
// edge_parts);
|
||||
// // load vertex accessors
|
||||
// auto from_acc = FindVertex(from_gid, dba);
|
||||
// auto to_acc = FindVertex(to_gid, dba);
|
||||
// if (!from_acc.has_value() || !to_acc.has_value()) {
|
||||
// throw utils::BasicException("Non-existing vertices during edge deserialization");
|
||||
// }
|
||||
// const auto edge_type_id = storage::EdgeTypeId::FromUint(std::stoull(edge_parts[3]));
|
||||
// auto maybe_edge = dba.InsertEdge(&*from_acc, &*to_acc, edge_type_id);
|
||||
// MG_ASSERT(maybe_edge.HasValue());
|
||||
// // in the new storage API, setting gid must be done atomically
|
||||
// maybe_edge->SetGid(storage::Gid::FromUint(std::stoull(edge_parts[4])));
|
||||
// maybe_edge->SetPropertyStore(value);
|
||||
// return *maybe_edge;
|
||||
// }
|
||||
|
||||
rocksdb::Options options_;
|
||||
rocksdb::DB *db_;
|
||||
rocksdb::ColumnFamilyHandle *vertex_chandle = nullptr;
|
||||
rocksdb::ColumnFamilyHandle *edge_chandle = nullptr;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage::rocks
|
||||
150
src/storage/v2/disk/indices.cpp
Normal file
150
src/storage/v2/disk/indices.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
// 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.
|
||||
|
||||
// LABEL INDEX
|
||||
|
||||
#include "storage/v2/disk/indices.hpp"
|
||||
#include "storage/v2/delta.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/rocksdb.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
namespace {
|
||||
constexpr const char *label_index_path = "rocksdb_label_index";
|
||||
constexpr const char *label_property_index_path = "rocksdb_label_property_index";
|
||||
|
||||
} // namespace
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// LABEL_DISK_INDEX METHODS
|
||||
|
||||
LabelDiskIndex::LabelDiskIndex(DiskIndices *indices, Config config) : indices_(indices), config_(config) {
|
||||
std::filesystem::path rocksdb_path = label_index_path;
|
||||
kvstore_ = std::make_unique<RocksDBStorage>();
|
||||
utils::EnsureDirOrDie(rocksdb_path);
|
||||
kvstore_->options_.create_if_missing = true;
|
||||
// kvstore_->options_.comparator = new ComparatorWithU64TsImpl();
|
||||
logging::AssertRocksDBStatus(rocksdb::DB::Open(kvstore_->options_, rocksdb_path, &kvstore_->db_));
|
||||
}
|
||||
|
||||
AllDiskVerticesIterable LabelDiskIndex::Vertices(LabelId label, View view, Transaction *transaction) {
|
||||
/// TODO: (andi): How to solve issue with garbage collection of vertices?
|
||||
auto acc = vertices_.access();
|
||||
rocksdb::ReadOptions ro;
|
||||
rocksdb::Slice ts = utils::StringTimestamp(std::numeric_limits<uint64_t>::max());
|
||||
ro.timestamp = &ts;
|
||||
auto it = std::unique_ptr<rocksdb::Iterator>(kvstore_->db_->NewIterator(ro));
|
||||
/// TODO: andi: No need to save all labels in the RocksDB, we can save only the first one and apply Bloom filter on
|
||||
/// it. Or do some kind of optimization.
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
const auto &key = it->key().ToString();
|
||||
const auto vertex_parts = utils::Split(key, "|");
|
||||
if (const auto labels = utils::Split(vertex_parts[0], ",");
|
||||
// TODO: (andi): When you decouple SerializeIdType, modify this to_string call to use SerializeIdType.
|
||||
std::find(labels.begin(), labels.end(), std::to_string(label.AsUint())) != labels.end()) {
|
||||
auto gid = storage::Gid::FromUint(std::stoull(vertex_parts[1]));
|
||||
auto vertex_commit_ts = utils::ExtractTimestampFromDeserializedUserKey(key);
|
||||
auto delta = CreateDeleteDeserializedObjectDelta(transaction, vertex_commit_ts);
|
||||
spdlog::debug("Found vertex with gid {} and commit_ts {} in index", vertex_parts[1], vertex_commit_ts);
|
||||
auto [vertex_it, inserted] = acc.insert(DiskVertex{gid, delta});
|
||||
std::vector<LabelId> label_ids;
|
||||
if (!vertex_parts[0].empty()) {
|
||||
auto labels = utils::Split(vertex_parts[0], ",");
|
||||
std::transform(labels.begin(), labels.end(), std::back_inserter(label_ids),
|
||||
[](const auto &label) { return storage::LabelId::FromUint(std::stoull(label)); });
|
||||
}
|
||||
vertex_it->labels = std::move(label_ids);
|
||||
vertex_it->properties.SetBuffer(it->value().ToStringView());
|
||||
|
||||
/// if the vertex with the given gid doesn't exist on the disk, it must be inserted here.
|
||||
// MG_ASSERT(inserted, "The vertex must be inserted here!");
|
||||
// MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!");
|
||||
}
|
||||
}
|
||||
return {vertices_.access(), transaction, view, indices_, nullptr, config_};
|
||||
}
|
||||
|
||||
/// TODO: andi: No need to save all labels in the RocksDB, we can save only the first one and apply Bloom filter on it.
|
||||
bool LabelDiskIndex::CreateIndex(LabelId label,
|
||||
const std::vector<std::tuple<std::string, std::string, uint64_t>> &vertices) {
|
||||
index_.emplace_back(label);
|
||||
// Serialize vertices with the same timestamp (latest commit), they have been serialized with at the main storage.
|
||||
for (const auto &[key, value, commit_ts] : vertices) {
|
||||
rocksdb::WriteOptions write_options;
|
||||
rocksdb::Slice ts = utils::StringTimestamp(commit_ts);
|
||||
write_options.timestamp = &ts;
|
||||
logging::AssertRocksDBStatus(kvstore_->db_->Put(write_options, key, value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabelDiskIndex::DropIndex(LabelId label) { throw utils::NotYetImplemented("LabelIndex::DropIndex"); }
|
||||
|
||||
bool LabelDiskIndex::IndexExists(LabelId label) const {
|
||||
return std::find(index_.begin(), index_.end(), label) != index_.end();
|
||||
}
|
||||
|
||||
std::vector<LabelId> LabelDiskIndex::ListIndices() const { throw utils::NotYetImplemented("LabelIndex::ListIndices"); }
|
||||
|
||||
int64_t LabelDiskIndex::ApproximateVertexCount(LabelId label) const { return 1; }
|
||||
|
||||
/// Clear all indexed vertices from the disk
|
||||
void LabelDiskIndex::Clear() { throw utils::NotYetImplemented("LabelIndex::Clear"); }
|
||||
|
||||
/// TODO: Maybe we can remove completely interaction with garbage collector
|
||||
void LabelDiskIndex::RunGC() { throw utils::NotYetImplemented("LabelIndex::RunGC"); }
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// LABEL_PROPERTY_DISK_INDEX METHODS
|
||||
|
||||
LabelPropertyDiskIndex::LabelPropertyDiskIndex(DiskIndices *indices, Config config)
|
||||
: indices_(indices), config_(config) {
|
||||
std::filesystem::path rocksdb_path = label_property_index_path;
|
||||
kvstore_ = std::make_unique<RocksDBStorage>();
|
||||
utils::EnsureDirOrDie(rocksdb_path);
|
||||
kvstore_->options_.create_if_missing = true;
|
||||
// kvstore_->options_.comparator = new ComparatorWithU64TsImpl();
|
||||
logging::AssertRocksDBStatus(rocksdb::DB::Open(kvstore_->options_, rocksdb_path, &kvstore_->db_));
|
||||
}
|
||||
|
||||
bool LabelPropertyDiskIndex::CreateIndex(LabelId label, PropertyId property) {
|
||||
throw utils::NotYetImplemented("LabelPropertyIndex::CreateIndex");
|
||||
}
|
||||
|
||||
bool LabelPropertyDiskIndex::DropIndex(LabelId label, PropertyId property) {
|
||||
throw utils::NotYetImplemented("LabelPropertyIndex::DropIndex");
|
||||
}
|
||||
|
||||
bool LabelPropertyDiskIndex::IndexExists(LabelId label, PropertyId property) const {
|
||||
throw utils::NotYetImplemented("LabelPropertyIndex::IndexExists");
|
||||
}
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> LabelPropertyDiskIndex::ListIndices() const {
|
||||
throw utils::NotYetImplemented("LabelPropertyIndex::ListIndices");
|
||||
}
|
||||
|
||||
int64_t LabelPropertyDiskIndex::ApproximateVertexCount(LabelId label, PropertyId property) const {
|
||||
throw utils::NotYetImplemented("LabelPropertyIndex::ApproximateVertexCount");
|
||||
}
|
||||
|
||||
/// Clear all indexed vertices from the disk
|
||||
void LabelPropertyDiskIndex::Clear() { throw utils::NotYetImplemented("LabelPropertyIndex::Clear"); }
|
||||
|
||||
/// TODO: Maybe we can remove completely interaction with garbage collector
|
||||
void LabelPropertyDiskIndex::RunGC() { throw utils::NotYetImplemented("LabelPropertyIndex::RunGC"); }
|
||||
|
||||
} // namespace memgraph::storage
|
||||
147
src/storage/v2/disk/indices.hpp
Normal file
147
src/storage/v2/disk/indices.hpp
Normal file
@@ -0,0 +1,147 @@
|
||||
// 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 <tuple>
|
||||
#include <utility>
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/constraints.hpp"
|
||||
#include "storage/v2/disk/disk_vertex.hpp"
|
||||
#include "storage/v2/disk/rocksdb_storage.hpp"
|
||||
#include "storage/v2/disk/vertices_iterable.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
struct DiskIndices;
|
||||
|
||||
/// Immovable implementation of LabelDiskIndex for on-disk storage.
|
||||
class LabelDiskIndex {
|
||||
public:
|
||||
explicit LabelDiskIndex(DiskIndices *indices, Config config);
|
||||
LabelDiskIndex() = delete;
|
||||
|
||||
LabelDiskIndex(const LabelDiskIndex &) = delete;
|
||||
LabelDiskIndex &operator=(const LabelDiskIndex &) = delete;
|
||||
|
||||
LabelDiskIndex(LabelDiskIndex &&) = delete;
|
||||
LabelDiskIndex &operator=(LabelDiskIndex &&) = delete;
|
||||
|
||||
~LabelDiskIndex() = default;
|
||||
|
||||
/// TODO(andi): If there are no other usages of constaints_ and config_ maybe we can remove
|
||||
/// them from here
|
||||
AllDiskVerticesIterable Vertices(LabelId label, View view, Transaction *transaction);
|
||||
|
||||
/// Stores all vertices in the RocksDB instances. Vertices are intentionally transferred as pure strings to avoid
|
||||
/// unnecessary deserialization and serialization.
|
||||
/// @tparam vertices is a vector of tuples where each tuple contains key, value and the timestamp it has been saved
|
||||
/// with
|
||||
bool CreateIndex(LabelId label, const std::vector<std::tuple<std::string, std::string, uint64_t>> &vertices);
|
||||
|
||||
bool DropIndex(LabelId label);
|
||||
|
||||
bool IndexExists(LabelId label) const;
|
||||
|
||||
std::vector<LabelId> ListIndices() const;
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label) const;
|
||||
|
||||
/// Clear all indexed vertices from the disk
|
||||
void Clear();
|
||||
|
||||
/// TODO: Maybe we can remove completely interaction with garbage collector
|
||||
void RunGC();
|
||||
|
||||
private:
|
||||
std::vector<LabelId> index_;
|
||||
DiskIndices *indices_;
|
||||
Config config_;
|
||||
std::unique_ptr<RocksDBStorage> kvstore_;
|
||||
utils::SkipList<Vertex> vertices_;
|
||||
};
|
||||
|
||||
/// Immovable implementation of LabelPropertyDiskIndex for on-disk storage.
|
||||
class LabelPropertyDiskIndex {
|
||||
public:
|
||||
explicit LabelPropertyDiskIndex(DiskIndices *indices, Config config);
|
||||
LabelPropertyDiskIndex() = delete;
|
||||
|
||||
LabelPropertyDiskIndex(const LabelPropertyDiskIndex &) = delete;
|
||||
LabelPropertyDiskIndex &operator=(const LabelPropertyDiskIndex &) = delete;
|
||||
|
||||
LabelPropertyDiskIndex(LabelPropertyDiskIndex &&) = delete;
|
||||
LabelPropertyDiskIndex &operator=(LabelPropertyDiskIndex &&) = delete;
|
||||
|
||||
~LabelPropertyDiskIndex() = default;
|
||||
|
||||
/// TODO(andi): If there are no other usages of constaints_ and config_ maybe we can remove
|
||||
/// them from here
|
||||
// VerticesIterable Vertices(LabelId label, PropertyId property,
|
||||
// const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
// const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
||||
// Transaction *transaction) {
|
||||
// utils::SkipList<Vertex> vertices;
|
||||
// // return VerticesIterable(AllMemoryVerticesIterable(vertices.access(), transaction, view, indices_,
|
||||
// constraints_,
|
||||
// // config_));
|
||||
// throw utils::NotYetImplemented("LabelPropertyIndex::Vertices");
|
||||
// }
|
||||
|
||||
bool CreateIndex(LabelId label, PropertyId property);
|
||||
|
||||
bool DropIndex(LabelId label, PropertyId property);
|
||||
|
||||
bool IndexExists(LabelId label, PropertyId property) const;
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> ListIndices() const;
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const;
|
||||
|
||||
/// Clear all indexed vertices from the disk
|
||||
void Clear();
|
||||
|
||||
/// TODO: Maybe we can remove completely interaction with garbage collector
|
||||
void RunGC();
|
||||
|
||||
private:
|
||||
std::vector<std::pair<LabelId, PropertyId>> index_;
|
||||
DiskIndices *indices_;
|
||||
Config config_;
|
||||
std::unique_ptr<RocksDBStorage> kvstore_;
|
||||
};
|
||||
|
||||
/// Immovable implementation of disk indices. Stored with the help of RocksDB in LSM trees.
|
||||
struct DiskIndices {
|
||||
explicit DiskIndices(Config config) : label_index(this, config), label_property_index(this, config) {}
|
||||
DiskIndices() = delete;
|
||||
|
||||
DiskIndices(const DiskIndices &) = delete;
|
||||
DiskIndices &operator=(const DiskIndices &) = delete;
|
||||
|
||||
DiskIndices(DiskIndices &&) = delete;
|
||||
DiskIndices &operator=(DiskIndices &&) = delete;
|
||||
|
||||
~DiskIndices() = default;
|
||||
|
||||
LabelDiskIndex label_index;
|
||||
LabelPropertyDiskIndex label_property_index;
|
||||
};
|
||||
|
||||
void RemoveObsoleteEntries(DiskIndices *indices, uint64_t oldest_active_start_timestamp);
|
||||
|
||||
} // namespace memgraph::storage
|
||||
72
src/storage/v2/disk/rocksdb_storage.cpp
Normal file
72
src/storage/v2/disk/rocksdb_storage.cpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// 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 "rocksdb_storage.hpp"
|
||||
#include "utils/rocksdb.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
namespace {
|
||||
|
||||
inline rocksdb::Slice StripTimestampFromUserKey(const rocksdb::Slice &user_key, size_t ts_sz) {
|
||||
rocksdb::Slice ret = user_key;
|
||||
ret.remove_suffix(ts_sz);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline rocksdb::Slice ExtractTimestampFromUserKey(const rocksdb::Slice &user_key) {
|
||||
assert(user_key.size() >= sizeof(uint64_t));
|
||||
return rocksdb::Slice(user_key.data() + user_key.size() - sizeof(uint64_t), sizeof(uint64_t));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ComparatorWithU64TsImpl::ComparatorWithU64TsImpl()
|
||||
: Comparator(/*ts_sz=*/sizeof(uint64_t)), cmp_without_ts_(rocksdb::BytewiseComparator()) {
|
||||
assert(cmp_without_ts_->timestamp_size() == 0);
|
||||
}
|
||||
|
||||
int ComparatorWithU64TsImpl::Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const {
|
||||
int ret = CompareWithoutTimestamp(a, b);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
// Compare timestamp.
|
||||
// For the same user key with different timestamps, larger (newer) timestamp
|
||||
// comes first.
|
||||
return -CompareTimestamp(ExtractTimestampFromUserKey(a), ExtractTimestampFromUserKey(b));
|
||||
}
|
||||
|
||||
int ComparatorWithU64TsImpl::CompareWithoutTimestamp(const rocksdb::Slice &a, bool a_has_ts, const rocksdb::Slice &b,
|
||||
bool b_has_ts) const {
|
||||
const size_t ts_sz = timestamp_size();
|
||||
assert(!a_has_ts || a.size() >= ts_sz);
|
||||
assert(!b_has_ts || b.size() >= ts_sz);
|
||||
rocksdb::Slice lhs = a_has_ts ? StripTimestampFromUserKey(a, ts_sz) : a;
|
||||
rocksdb::Slice rhs = b_has_ts ? StripTimestampFromUserKey(b, ts_sz) : b;
|
||||
return cmp_without_ts_->Compare(lhs, rhs);
|
||||
}
|
||||
|
||||
int ComparatorWithU64TsImpl::CompareTimestamp(const rocksdb::Slice &ts1, const rocksdb::Slice &ts2) const {
|
||||
assert(ts1.size() == sizeof(uint64_t));
|
||||
assert(ts2.size() == sizeof(uint64_t));
|
||||
uint64_t lhs = utils::DecodeFixed64(ts1.data());
|
||||
uint64_t rhs = utils::DecodeFixed64(ts2.data());
|
||||
if (lhs < rhs) {
|
||||
return -1;
|
||||
}
|
||||
if (lhs > rhs) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
63
src/storage/v2/disk/rocksdb_storage.hpp
Normal file
63
src/storage/v2/disk/rocksdb_storage.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 <rocksdb/comparator.h>
|
||||
#include <rocksdb/db.h>
|
||||
#include <rocksdb/iterator.h>
|
||||
#include <rocksdb/options.h>
|
||||
#include <rocksdb/status.h>
|
||||
|
||||
#include "utils/logging.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
/// Wraps RocksDB objects inside a struct. Vertex_chandle and edge_chandle are column family handles that may be
|
||||
/// nullptr. In that case client should take care about them.
|
||||
struct RocksDBStorage {
|
||||
/// TODO: (andi) Revisit special methods if this struct
|
||||
|
||||
~RocksDBStorage() {
|
||||
logging::AssertRocksDBStatus(db_->Close());
|
||||
delete options_.comparator;
|
||||
}
|
||||
|
||||
rocksdb::Options options_;
|
||||
rocksdb::DB *db_;
|
||||
rocksdb::ColumnFamilyHandle *vertex_chandle = nullptr;
|
||||
rocksdb::ColumnFamilyHandle *edge_chandle = nullptr;
|
||||
};
|
||||
|
||||
class ComparatorWithU64TsImpl : public rocksdb::Comparator {
|
||||
public:
|
||||
explicit ComparatorWithU64TsImpl();
|
||||
|
||||
static const char *kClassName() { return "be"; }
|
||||
|
||||
const char *Name() const override { return kClassName(); }
|
||||
|
||||
void FindShortSuccessor(std::string *) const override {}
|
||||
void FindShortestSeparator(std::string *, const rocksdb::Slice &) const override {}
|
||||
|
||||
int Compare(const rocksdb::Slice &a, const rocksdb::Slice &b) const override;
|
||||
|
||||
using Comparator::CompareWithoutTimestamp;
|
||||
int CompareWithoutTimestamp(const rocksdb::Slice &a, bool a_has_ts, const rocksdb::Slice &b,
|
||||
bool b_has_ts) const override;
|
||||
|
||||
int CompareTimestamp(const rocksdb::Slice &ts1, const rocksdb::Slice &ts2) const override;
|
||||
|
||||
private:
|
||||
const Comparator *cmp_without_ts_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
1860
src/storage/v2/disk/storage.cpp
Normal file
1860
src/storage/v2/disk/storage.cpp
Normal file
File diff suppressed because it is too large
Load Diff
580
src/storage/v2/disk/storage.hpp
Normal file
580
src/storage/v2/disk/storage.hpp
Normal file
@@ -0,0 +1,580 @@
|
||||
// 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/disk/disk_edge.hpp"
|
||||
#include "storage/v2/disk/disk_vertex.hpp"
|
||||
#include "storage/v2/disk/indices.hpp"
|
||||
#include "storage/v2/disk/rocksdb_storage.hpp"
|
||||
#include "storage/v2/disk/vertex_accessor.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/id_types.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
#include "storage/v2/property_store.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/exceptions.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"
|
||||
|
||||
/// ROCKSDB
|
||||
#include <rocksdb/db.h>
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
class DiskStorage final : public Storage {
|
||||
public:
|
||||
/// @throw std::system_error
|
||||
/// @throw std::bad_alloc
|
||||
explicit DiskStorage(Config config = Config());
|
||||
|
||||
~DiskStorage() override;
|
||||
|
||||
class DiskAccessor final : public Storage::Accessor {
|
||||
private:
|
||||
friend class DiskStorage;
|
||||
|
||||
explicit DiskAccessor(DiskStorage *storage, IsolationLevel isolation_level);
|
||||
|
||||
public:
|
||||
DiskAccessor(const DiskAccessor &) = delete;
|
||||
DiskAccessor &operator=(const DiskAccessor &) = delete;
|
||||
DiskAccessor &operator=(DiskAccessor &&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.
|
||||
DiskAccessor(DiskAccessor &&other) noexcept;
|
||||
|
||||
~DiskAccessor() override;
|
||||
|
||||
std::unique_ptr<VertexAccessor> CreateVertex() override;
|
||||
|
||||
/// Checks whether the vertex with the given `gid` exists in the vertices_. If it does, it returns a
|
||||
/// VertexAccessor to it. If it doesn't, it fetches vertex from the RocksDB. If it doesn't exist in the RocksDB
|
||||
/// either, it returns nullptr. If the vertex is fetched from the RocksDB, it is inserted into the vertices_ and
|
||||
/// lru_vertices_. Check whether the vertex is in the memory cache (vertices_) is done in O(logK) where K is the
|
||||
/// size of the cache.
|
||||
std::unique_ptr<VertexAccessor> FindVertex(Gid gid, View view) override;
|
||||
|
||||
/// Utility method to load all vertices from the underlying KV storage.
|
||||
VerticesIterable Vertices(View view) override;
|
||||
|
||||
/// Utility method to load all vertices from the underlying KV storage with label `label`.
|
||||
VerticesIterable Vertices(LabelId label, View view) override;
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property, View view) override;
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) override;
|
||||
|
||||
VerticesIterable Vertices(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view) override;
|
||||
|
||||
int64_t ApproximateVertexCount() const override;
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label) const override {
|
||||
return storage_->indices_.label_index.ApproximateVertexCount(label);
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property) const override {
|
||||
throw utils::NotYetImplemented("ApproximateVertexCount(label, property) is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const override {
|
||||
throw utils::NotYetImplemented(
|
||||
"ApproximateVertexCount(label, property, value) is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
int64_t ApproximateVertexCount(LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper) const override {
|
||||
throw utils::NotYetImplemented(
|
||||
"ApproximateVertexCount(label, property, lower, upper) is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
std::optional<storage::IndexStats> GetIndexStats(const storage::LabelId &label,
|
||||
const storage::PropertyId &property) const override {
|
||||
throw utils::NotYetImplemented("GetIndexStats() is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> ClearIndexStats() override {
|
||||
throw utils::NotYetImplemented("ClearIndexStats() is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
std::vector<std::pair<LabelId, PropertyId>> DeleteIndexStatsForLabels(
|
||||
const std::span<std::string> labels) override {
|
||||
throw utils::NotYetImplemented("DeleteIndexStatsForLabels(labels) is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
void SetIndexStats(const storage::LabelId &label, const storage::PropertyId &property,
|
||||
const IndexStats &stats) override {
|
||||
throw utils::NotYetImplemented("SetIndexStats(stats) is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
/// Deletes vertex only from the cache if it was created in the same transaction.
|
||||
/// If the vertex was fetched from the RocksDB, it is deleted from the RocksDB.
|
||||
/// It is impossible that the object isn't in the cache because of generated query plan.
|
||||
Result<std::unique_ptr<VertexAccessor>> DeleteVertex(VertexAccessor *vertex) override;
|
||||
|
||||
Result<std::optional<std::pair<std::unique_ptr<VertexAccessor>, std::vector<std::unique_ptr<EdgeAccessor>>>>>
|
||||
DetachDeleteVertex(VertexAccessor *vertex) override;
|
||||
|
||||
void PrefetchInEdges(const VertexAccessor &vertex_acc) override;
|
||||
|
||||
void PrefetchOutEdges(const VertexAccessor &vertex_acc) override;
|
||||
|
||||
Result<std::unique_ptr<EdgeAccessor>> CreateEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
EdgeTypeId edge_type) override;
|
||||
|
||||
Result<std::unique_ptr<EdgeAccessor>> DeleteEdge(EdgeAccessor *edge) override;
|
||||
|
||||
const std::string &LabelToName(LabelId label) const override;
|
||||
const std::string &PropertyToName(PropertyId property) const override;
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const override;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
LabelId NameToLabel(std::string_view name) override;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
PropertyId NameToProperty(std::string_view name) override;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
EdgeTypeId NameToEdgeType(std::string_view name) override;
|
||||
|
||||
bool LabelIndexExists(LabelId label) const override { return storage_->indices_.label_index.IndexExists(label); }
|
||||
|
||||
bool LabelPropertyIndexExists(LabelId label, PropertyId property) const override {
|
||||
return storage_->indices_.label_property_index.IndexExists(label, property);
|
||||
}
|
||||
|
||||
IndicesInfo ListAllIndices() const override {
|
||||
return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()};
|
||||
}
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const override {
|
||||
throw utils::NotYetImplemented("ListAllConstraints() is not implemented for DiskStorage.");
|
||||
}
|
||||
|
||||
void AdvanceCommand() override;
|
||||
|
||||
utils::BasicResult<StorageDataManipulationError, void> Commit(
|
||||
std::optional<uint64_t> desired_commit_timestamp = {}) override;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// Currently, it does everything the same as in-memory version.
|
||||
void Abort() override;
|
||||
|
||||
/// Currently, it does everything the same as in-memory version.
|
||||
void FinalizeTransaction() override;
|
||||
|
||||
std::optional<uint64_t> GetTransactionId() const override;
|
||||
|
||||
private:
|
||||
/// TODO(andi): Consolidate this vertex creation methods and find from in-memory version where are they used.
|
||||
/// Used for deserialization of vertices and edges from KV store.
|
||||
/// @throw std::bad_alloc
|
||||
std::unique_ptr<VertexAccessor> CreateVertex(storage::Gid gid);
|
||||
|
||||
/// TODO(andi): Consolidate this vertex creation methods and find from in-memory version where are they used.
|
||||
std::unique_ptr<VertexAccessor> CreateVertex(storage::Gid gid, uint64_t vertex_commit_ts);
|
||||
|
||||
void PrefetchEdges(const auto &prefetch_edge_filter);
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// TODO(andi): Consolidate this vertex creation methods and find from in-memory version where are they used.
|
||||
Result<std::unique_ptr<EdgeAccessor>> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type,
|
||||
storage::Gid gid);
|
||||
|
||||
/// TODO(andi): Consolidate this vertex creation methods and find from in-memory version where are they used.
|
||||
Result<std::unique_ptr<EdgeAccessor>> CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type,
|
||||
storage::Gid gid, uint64_t edge_commit_ts);
|
||||
|
||||
// (De)serialization utility methods
|
||||
|
||||
/// Serialize types defined with STORAGE_DEFINE_ID_TYPE
|
||||
std::string SerializeIdType(const auto &id) const;
|
||||
|
||||
/// Deserialize types defined with STORAGE_DEFINE_ID_TYPE
|
||||
static auto DeserializeIdType(const std::string &str);
|
||||
|
||||
/// Serialize timestamp to string
|
||||
static std::string SerializeTimestamp(uint64_t ts);
|
||||
|
||||
/// Serialize labels to string
|
||||
static std::string SerializeLabels(const std::vector<LabelId> &labels);
|
||||
|
||||
/// Uses the PropertyStore buffer to serialize properties to string.
|
||||
static std::string SerializeProperties(PropertyStore &properties);
|
||||
|
||||
/// Serialize vertex to string as a key in KV store
|
||||
/// label1, label2 | GID | commit_timestamp
|
||||
std::string SerializeVertex(const Result<std::vector<LabelId>> &labels, Gid gid) const;
|
||||
|
||||
/// Serialize vertex to string as a key in KV store
|
||||
/// label1, label2 | GID | commit_timestamp
|
||||
std::string SerializeVertex(const Vertex &vertex) const;
|
||||
|
||||
/// Serialize edge as two KV entries
|
||||
/// vertex_gid_1 | vertex_gid_2 | direction | edge_type | GID | commit_timestamp
|
||||
std::pair<std::string, std::string> SerializeEdge(EdgeAccessor *edge_acc) const;
|
||||
|
||||
/// Serialize edge as two KV entries
|
||||
/// vertex_gid_1 | vertex_gid_2 | direction | edge_type | GID | commit_timestamp
|
||||
/// @tparam src_vertex_gid, dest_vertex_gid: Gid of the source and destination vertices
|
||||
/// @tparam edge: Edge to be serialized
|
||||
/// @tparam edge_type_id: EdgeTypeId of the edge
|
||||
std::pair<std::string, std::string> SerializeEdge(Gid src_vertex_gid, Gid dest_vertex_gid, EdgeTypeId edge_type_id,
|
||||
const Edge *edge) const;
|
||||
|
||||
/// Deserializes vertex from the string key and stores it into the vertices_ and lru_vertices_.
|
||||
/// Properties are deserialized from the value.
|
||||
/// The method should be called only when the vertex is not in the cache.
|
||||
std::unique_ptr<VertexAccessor> DeserializeVertex(const rocksdb::Slice &key, const rocksdb::Slice &value);
|
||||
|
||||
/// Deserializes edge from the string key and stores it into the edges_ cache.
|
||||
/// Properties are deserialized from the value.
|
||||
/// The method should be called only when the edge is not in the cache.
|
||||
std::unique_ptr<EdgeAccessor> DeserializeEdge(const rocksdb::Slice &key, const rocksdb::Slice &value);
|
||||
|
||||
/// Flushes vertices and edges to the disk with the commit timestamp.
|
||||
/// At the time of calling, the commit_timestamp_ must already exist.
|
||||
/// After this method, the vertex and edge caches are cleared.
|
||||
void FlushCache();
|
||||
|
||||
DiskStorage *storage_;
|
||||
|
||||
std::shared_lock<utils::RWLock> storage_guard_;
|
||||
Transaction transaction_;
|
||||
std::vector<std::string> edges_to_delete_;
|
||||
std::vector<std::string> vertices_to_delete_;
|
||||
std::optional<uint64_t> commit_timestamp_;
|
||||
bool is_transaction_active_;
|
||||
Config::Items config_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Storage::Accessor> Access(std::optional<IsolationLevel> override_isolation_level) override {
|
||||
return std::unique_ptr<DiskAccessor>(new DiskAccessor{this, override_isolation_level.value_or(isolation_level_)});
|
||||
}
|
||||
|
||||
const std::string &LabelToName(LabelId label) const override;
|
||||
const std::string &PropertyToName(PropertyId property) const override;
|
||||
const std::string &EdgeTypeToName(EdgeTypeId edge_type) const override;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
LabelId NameToLabel(std::string_view name) override;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
PropertyId NameToProperty(std::string_view name) override;
|
||||
|
||||
/// @throw std::bad_alloc if unable to insert a new mapping
|
||||
EdgeTypeId NameToEdgeType(std::string_view name) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
IndicesInfo ListAllIndices() const override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
/// 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) override;
|
||||
|
||||
ConstraintsInfo ListAllConstraints() const override;
|
||||
|
||||
StorageInfo GetInfo() const override;
|
||||
|
||||
bool LockPath() override;
|
||||
bool UnlockPath() override;
|
||||
|
||||
bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config) override;
|
||||
|
||||
bool SetMainReplicationRole() override;
|
||||
|
||||
/// @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) override;
|
||||
/// @pre The instance should have a MAIN role
|
||||
bool UnregisterReplica(const std::string &name) override;
|
||||
|
||||
std::optional<replication::ReplicaState> GetReplicaState(std::string_view name) override;
|
||||
|
||||
ReplicationRole GetReplicationRole() const override;
|
||||
|
||||
std::vector<ReplicaInfo> ReplicasInfo() override;
|
||||
|
||||
void FreeMemory() override;
|
||||
|
||||
void SetIsolationLevel(IsolationLevel isolation_level) override;
|
||||
|
||||
utils::BasicResult<CreateSnapshotError> CreateSnapshot() override;
|
||||
|
||||
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};
|
||||
|
||||
utils::SkipList<storage::Vertex> vertices_;
|
||||
utils::SkipList<storage::Edge> edges_;
|
||||
std::set<storage::DiskVertex *, decltype(storage::disk_vertex_cmp)> lru_vertices_;
|
||||
std::set<storage::DiskEdge *, decltype(storage::disk_edge_cmp)> lru_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_;
|
||||
DiskIndices 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};
|
||||
|
||||
std::unique_ptr<RocksDBStorage> kvstore_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
638
src/storage/v2/disk/vertex_accessor.cpp
Normal file
638
src/storage/v2/disk/vertex_accessor.cpp
Normal file
@@ -0,0 +1,638 @@
|
||||
// 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/disk/vertex_accessor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#include "storage/v2/disk/edge_accessor.hpp"
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/inmemory/indices.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/exceptions.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_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {exists, deleted};
|
||||
}
|
||||
} // namespace
|
||||
} // namespace detail
|
||||
|
||||
void DiskVertexAccessor::InitializeDeserializedVertex(const std::vector<LabelId> &label_ids,
|
||||
const std::string_view property_store) {
|
||||
utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception;
|
||||
std::lock_guard<utils::SpinLock> guard(vertex_->lock);
|
||||
|
||||
std::for_each(label_ids.begin(), label_ids.end(),
|
||||
[this](const LabelId &label_id) { vertex_->labels.push_back(label_id); });
|
||||
SetPropertyStore(property_store);
|
||||
}
|
||||
|
||||
std::unique_ptr<DiskVertexAccessor> DiskVertexAccessor::Create(Vertex *vertex, Transaction *transaction,
|
||||
DiskIndices *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<DiskVertexAccessor>(static_cast<DiskVertex *>(vertex), transaction, indices, constraints,
|
||||
config, vertex->gid);
|
||||
}
|
||||
|
||||
bool DiskVertexAccessor::IsVisible(View view) const {
|
||||
// const auto [exists, deleted] = detail::IsVisible(vertex_, transaction_, view);
|
||||
// return exists && (for_deleted_ || !deleted);
|
||||
throw utils::NotYetImplemented("DiskVertexAccessor::IsVisible");
|
||||
}
|
||||
|
||||
Result<bool> DiskVertexAccessor::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> DiskVertexAccessor::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> DiskVertexAccessor::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_DESERIALIZED_OBJECT:
|
||||
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>> DiskVertexAccessor::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_DESERIALIZED_OBJECT:
|
||||
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> DiskVertexAccessor::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> DiskVertexAccessor::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>> DiskVertexAccessor::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);
|
||||
throw utils::NotYetImplemented("DiskVertexAccessor::ClearProperties");
|
||||
}
|
||||
|
||||
Result<PropertyValue> DiskVertexAccessor::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_DESERIALIZED_OBJECT:
|
||||
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>> DiskVertexAccessor::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_DESERIALIZED_OBJECT:
|
||||
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<std::unique_ptr<EdgeAccessor>>> DiskVertexAccessor::InEdges(
|
||||
View view, const std::vector<EdgeTypeId> &edge_types, const VertexAccessor *destination) const {
|
||||
const auto *destVA = static_cast<const DiskVertexAccessor *>(destination);
|
||||
MG_ASSERT(!destination || destVA, "Target VertexAccessor must be from the same storage as the storage accessor!");
|
||||
MG_ASSERT(!destVA || destVA->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() && !destVA) {
|
||||
in_edges = vertex_->in_edges;
|
||||
} else {
|
||||
for (const auto &item : vertex_->in_edges) {
|
||||
const auto &[edge_type, from_vertex, edge] = item;
|
||||
if (destVA && from_vertex != destVA->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, &destVA](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_IN_EDGE: {
|
||||
if (destVA && delta.vertex_edge.vertex != destVA->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 (destVA && delta.vertex_edge.vertex != destVA->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_DESERIALIZED_OBJECT:
|
||||
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<std::unique_ptr<EdgeAccessor>> ret;
|
||||
ret.reserve(in_edges.size());
|
||||
for (const auto &item : in_edges) {
|
||||
const auto &[edge_type, from_vertex, edge] = item;
|
||||
// TODO(andi): GID handling should be done in a different manner
|
||||
ret.emplace_back(std::make_unique<DiskEdgeAccessor>(edge, edge_type, static_cast<DiskVertex *>(from_vertex),
|
||||
vertex_, transaction_, indices_, constraints_, config_,
|
||||
edge.gid));
|
||||
}
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
Result<std::vector<std::unique_ptr<EdgeAccessor>>> DiskVertexAccessor::OutEdges(
|
||||
View view, const std::vector<EdgeTypeId> &edge_types, const VertexAccessor *destination) const {
|
||||
const auto *destVA = static_cast<const DiskVertexAccessor *>(destination);
|
||||
MG_ASSERT(!destination || destVA, "Target VertexAccessor must be from the same storage as the storage accessor!");
|
||||
MG_ASSERT(!destVA || destVA->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() && !destVA) {
|
||||
out_edges = vertex_->out_edges;
|
||||
} else {
|
||||
for (const auto &item : vertex_->out_edges) {
|
||||
const auto &[edge_type, to_vertex, edge] = item;
|
||||
if (destVA && to_vertex != destVA->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, &destVA](const Delta &delta) {
|
||||
switch (delta.action) {
|
||||
case Delta::Action::ADD_OUT_EDGE: {
|
||||
if (destVA && delta.vertex_edge.vertex != destVA->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 (destVA && delta.vertex_edge.vertex != destVA->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_DESERIALIZED_OBJECT:
|
||||
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<std::unique_ptr<EdgeAccessor>> ret;
|
||||
ret.reserve(out_edges.size());
|
||||
for (const auto &item : out_edges) {
|
||||
const auto &[edge_type, to_vertex, edge] = item;
|
||||
ret.emplace_back(std::make_unique<DiskEdgeAccessor>(edge, edge_type, vertex_, static_cast<DiskVertex *>(to_vertex),
|
||||
transaction_, indices_, constraints_, config_, edge.gid));
|
||||
}
|
||||
return std::move(ret);
|
||||
}
|
||||
|
||||
Result<size_t> DiskVertexAccessor::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;
|
||||
*/
|
||||
throw utils::NotYetImplemented("DiskVertexAccessor::InDegree");
|
||||
}
|
||||
|
||||
Result<size_t> DiskVertexAccessor::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_DESERIALIZED_OBJECT:
|
||||
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;
|
||||
throw utils::NotYetImplemented("DiskVertexAccessor::OutDegree");
|
||||
}
|
||||
|
||||
std::string DiskVertexAccessor::PropertyStore() const { return vertex_->properties.StringBuffer(); }
|
||||
|
||||
void DiskVertexAccessor::SetPropertyStore(std::string_view buffer) const { vertex_->properties.SetBuffer(buffer); }
|
||||
|
||||
void DiskVertexAccessor::UpdateModificationTimestamp(uint64_t modification_ts) { modification_ts_ = modification_ts; }
|
||||
|
||||
} // namespace memgraph::storage
|
||||
135
src/storage/v2/disk/vertex_accessor.hpp
Normal file
135
src/storage/v2/disk/vertex_accessor.hpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// 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 <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "storage/v2/disk/disk_vertex.hpp"
|
||||
#include "storage/v2/disk/indices.hpp"
|
||||
#include "storage/v2/id_types.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 DiskVertexAccessor final : public VertexAccessor {
|
||||
private:
|
||||
friend class DiskStorage;
|
||||
|
||||
public:
|
||||
DiskVertexAccessor(DiskVertex *vertex, Transaction *transaction, DiskIndices *indices, Constraints *constraints,
|
||||
Config::Items config, storage::Gid gid, bool for_deleted = false)
|
||||
: VertexAccessor(transaction, config, for_deleted),
|
||||
vertex_(vertex),
|
||||
indices_(indices),
|
||||
constraints_(constraints),
|
||||
gid_(gid) {}
|
||||
|
||||
static std::unique_ptr<DiskVertexAccessor> Create(Vertex *vertex, Transaction *transaction, DiskIndices *indices,
|
||||
Constraints *constraints, Config::Items config, View view);
|
||||
|
||||
/// The method initializes the vertex from the disk.
|
||||
void InitializeDeserializedVertex(const std::vector<LabelId> &label_ids, std::string_view property_store);
|
||||
|
||||
/// @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<std::unique_ptr<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<std::unique_ptr<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;
|
||||
|
||||
storage::Gid Gid() const noexcept override { return vertex_->gid; }
|
||||
|
||||
std::string PropertyStore() const override;
|
||||
|
||||
void SetPropertyStore(std::string_view buffer) const override;
|
||||
|
||||
void UpdateModificationTimestamp(uint64_t modification_ts);
|
||||
|
||||
std::unique_ptr<VertexAccessor> Copy() const override { return std::make_unique<DiskVertexAccessor>(*this); }
|
||||
|
||||
bool operator==(const VertexAccessor &other) const noexcept override {
|
||||
const auto *otherVertex = dynamic_cast<const DiskVertexAccessor *>(&other);
|
||||
if (otherVertex == nullptr) return false;
|
||||
return vertex_ == otherVertex->vertex_ && transaction_ == otherVertex->transaction_;
|
||||
}
|
||||
|
||||
bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); }
|
||||
|
||||
private:
|
||||
DiskVertex *vertex_;
|
||||
DiskIndices *indices_;
|
||||
Constraints *constraints_;
|
||||
// cached from DiskVertex
|
||||
storage::Gid gid_;
|
||||
uint64_t modification_ts_;
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
63
src/storage/v2/disk/vertices_iterable.hpp
Normal file
63
src/storage/v2/disk/vertices_iterable.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
// 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 "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
struct Transaction;
|
||||
class EdgeAccessor;
|
||||
struct DiskIndices;
|
||||
|
||||
/// Implementation provided in storage/v2/storage.cpp
|
||||
class AllDiskVerticesIterable {
|
||||
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
||||
Transaction *transaction_;
|
||||
View view_;
|
||||
DiskIndices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config config_;
|
||||
std::unique_ptr<VertexAccessor> vertex_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
AllDiskVerticesIterable *self_;
|
||||
utils::SkipList<Vertex>::Iterator it_;
|
||||
|
||||
public:
|
||||
Iterator(AllDiskVerticesIterable *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); }
|
||||
};
|
||||
|
||||
AllDiskVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view,
|
||||
DiskIndices *indices, Constraints *constraints, Config 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()); }
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "storage/v2/durability/metadata.hpp"
|
||||
#include "storage/v2/durability/wal.hpp"
|
||||
#include "storage/v2/edge.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/inmemory/indices.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "storage/v2/durability/wal.hpp"
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/edge_ref.hpp"
|
||||
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
@@ -629,7 +630,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
|
||||
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
|
||||
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid,
|
||||
Indices *indices, Constraints *constraints, Config items, const std::string &uuid,
|
||||
const std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
utils::FileRetainer *file_retainer) {
|
||||
// Ensure that the storage directory exists.
|
||||
@@ -674,7 +675,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
||||
};
|
||||
|
||||
// Store all edges.
|
||||
if (items.properties_on_edges) {
|
||||
if (items.items.properties_on_edges) {
|
||||
offset_edges = snapshot.GetPosition();
|
||||
auto acc = edges->access();
|
||||
for (auto &edge : acc) {
|
||||
@@ -701,6 +702,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
||||
is_visible = true;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
is_visible = false;
|
||||
break;
|
||||
@@ -743,7 +745,8 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
||||
auto acc = vertices->access();
|
||||
for (auto &vertex : acc) {
|
||||
// The visibility check is implemented for vertices so we use it here.
|
||||
auto va = VertexAccessor::Create(&vertex, transaction, indices, constraints, items, View::OLD);
|
||||
/// TODO: Here we need to create a vertex accessor dependent on the storage.
|
||||
auto va = InMemoryVertexAccessor::Create(&vertex, transaction, indices, constraints, items.items, View::OLD);
|
||||
if (!va) continue;
|
||||
|
||||
// Get vertex data.
|
||||
@@ -753,9 +756,9 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps
|
||||
MG_ASSERT(maybe_labels.HasValue(), "Invalid database state!");
|
||||
auto maybe_props = va->Properties(View::OLD);
|
||||
MG_ASSERT(maybe_props.HasValue(), "Invalid database state!");
|
||||
auto maybe_in_edges = va->InEdges(View::OLD);
|
||||
auto maybe_in_edges = va->InEdges(View::OLD, {}, nullptr);
|
||||
MG_ASSERT(maybe_in_edges.HasValue(), "Invalid database state!");
|
||||
auto maybe_out_edges = va->OutEdges(View::OLD);
|
||||
auto maybe_out_edges = va->OutEdges(View::OLD, {}, nullptr);
|
||||
MG_ASSERT(maybe_out_edges.HasValue(), "Invalid database state!");
|
||||
|
||||
// Store the vertex.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -19,7 +19,7 @@
|
||||
#include "storage/v2/constraints.hpp"
|
||||
#include "storage/v2/durability/metadata.hpp"
|
||||
#include "storage/v2/edge.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/inmemory/indices.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
@@ -68,7 +68,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis
|
||||
void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory,
|
||||
const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count,
|
||||
utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper,
|
||||
Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid,
|
||||
Indices *indices, Constraints *constraints, Config items, const std::string &uuid,
|
||||
std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history,
|
||||
utils::FileRetainer *file_retainer);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -101,6 +101,7 @@ Marker VertexActionToMarker(Delta::Action action) {
|
||||
// because the Delta's represent undo actions and we want to store redo
|
||||
// actions.
|
||||
switch (action) {
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
return Marker::DELTA_VERTEX_CREATE;
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
@@ -491,6 +492,7 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite
|
||||
encoder->WriteUint(timestamp);
|
||||
std::lock_guard<utils::SpinLock> guard(vertex.lock);
|
||||
switch (delta.action) {
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT: {
|
||||
encoder->WriteMarker(VertexActionToMarker(delta.action));
|
||||
@@ -558,6 +560,7 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta
|
||||
encoder->WritePropertyValue(edge.properties.GetProperty(delta.property.key));
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
// These actions are already encoded in vertex *_OUT_EDGE actions. Also,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -25,7 +25,8 @@ struct Vertex;
|
||||
|
||||
struct Edge {
|
||||
Edge(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT ||
|
||||
delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT,
|
||||
"Edge must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
/// TODO(andi): Change this
|
||||
std::unique_ptr<EdgeAccessor> EdgeAccessor::Create(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex,
|
||||
Vertex *to_vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, bool for_deleted) {
|
||||
Constraints *constraints, Config config, bool for_deleted) {
|
||||
return std::make_unique<InMemoryEdgeAccessor>(edge, edge_type, from_vertex, to_vertex, transaction, indices,
|
||||
constraints, config, for_deleted);
|
||||
constraints, config.items, for_deleted);
|
||||
}
|
||||
|
||||
bool operator==(const std::unique_ptr<EdgeAccessor> &ea1, const std::unique_ptr<EdgeAccessor> &ea2) noexcept {
|
||||
|
||||
@@ -38,7 +38,7 @@ class EdgeAccessor {
|
||||
|
||||
static std::unique_ptr<EdgeAccessor> Create(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex,
|
||||
Vertex *to_vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, bool for_deleted = false);
|
||||
Constraints *constraints, Config config, bool for_deleted = false);
|
||||
|
||||
/// @return true if the object is visible from the current transaction
|
||||
virtual bool IsVisible(View view) const = 0;
|
||||
@@ -74,6 +74,10 @@ class EdgeAccessor {
|
||||
|
||||
virtual std::unique_ptr<EdgeAccessor> Copy() const = 0;
|
||||
|
||||
virtual std::optional<std::string> PropertyStore() const = 0;
|
||||
|
||||
virtual bool SetPropertyStore(std::string_view buffer) const = 0;
|
||||
|
||||
virtual bool operator==(const EdgeAccessor &other) const noexcept = 0;
|
||||
bool operator!=(const EdgeAccessor &other) const noexcept { return !(*this == other); }
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
|
||||
@@ -45,6 +46,7 @@ bool InMemoryEdgeAccessor::IsVisible(const View view) const {
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
case Delta::Action::ADD_IN_EDGE:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
break;
|
||||
case Delta::Action::ADD_OUT_EDGE: { // relevant for the from_vertex_ -> we just deleted the edge
|
||||
@@ -84,6 +86,7 @@ bool InMemoryEdgeAccessor::IsVisible(const View view) const {
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -182,6 +185,7 @@ Result<PropertyValue> InMemoryEdgeAccessor::GetProperty(PropertyId property, Vie
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -233,6 +237,7 @@ Result<std::map<PropertyId, PropertyValue>> InMemoryEdgeAccessor::Properties(Vie
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -255,4 +260,19 @@ Result<std::map<PropertyId, PropertyValue>> InMemoryEdgeAccessor::Properties(Vie
|
||||
return std::move(properties);
|
||||
}
|
||||
|
||||
bool InMemoryEdgeAccessor::SetPropertyStore(std::string_view buffer) const {
|
||||
if (config_.properties_on_edges) {
|
||||
edge_.ptr->properties.SetBuffer(buffer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<std::string> InMemoryEdgeAccessor::PropertyStore() const {
|
||||
if (config_.properties_on_edges) {
|
||||
return edge_.ptr->properties.StringBuffer();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
|
||||
@@ -83,6 +83,10 @@ class InMemoryEdgeAccessor final : public EdgeAccessor {
|
||||
|
||||
bool IsCycle() const override { return from_vertex_ == to_vertex_; }
|
||||
|
||||
std::optional<std::string> PropertyStore() const override;
|
||||
|
||||
bool SetPropertyStore(std::string_view buffer) const override;
|
||||
|
||||
std::unique_ptr<EdgeAccessor> Copy() const override { return std::make_unique<InMemoryEdgeAccessor>(*this); }
|
||||
|
||||
bool operator==(const EdgeAccessor &other) const noexcept override {
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/bound.hpp"
|
||||
@@ -79,6 +80,7 @@ bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp)
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
@@ -141,6 +143,7 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
@@ -185,6 +188,7 @@ bool CurrentVersionHasLabel(const Vertex &vertex, LabelId label, Transaction *tr
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
@@ -231,6 +235,7 @@ bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, Propert
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
MG_ASSERT(!deleted, "Invalid database state!");
|
||||
deleted = true;
|
||||
@@ -344,16 +349,17 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
}
|
||||
if (CurrentVersionHasLabel(*index_iterator_->vertex, self_->label_, self_->transaction_, self_->view_)) {
|
||||
current_vertex_ = index_iterator_->vertex;
|
||||
current_vertex_accessor_ = VertexAccessor::Create(current_vertex_, self_->transaction_, self_->indices_,
|
||||
self_->constraints_, self_->config_, self_->view_);
|
||||
/// TODO: Here we need to create a vertex accessor dependent on the storage.
|
||||
current_vertex_accessor_ =
|
||||
InMemoryVertexAccessor::Create(current_vertex_, self_->transaction_, self_->indices_, self_->constraints_,
|
||||
self_->config_.items, self_->view_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LabelIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view,
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config)
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints, Config config)
|
||||
: index_accessor_(std::move(index_accessor)),
|
||||
label_(label),
|
||||
view_(view),
|
||||
@@ -513,8 +519,10 @@ void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() {
|
||||
if (CurrentVersionHasLabelProperty(*index_iterator_->vertex, self_->label_, self_->property_,
|
||||
index_iterator_->value, self_->transaction_, self_->view_)) {
|
||||
current_vertex_ = index_iterator_->vertex;
|
||||
current_vertex_accessor_ = VertexAccessor::Create(current_vertex_, self_->transaction_, self_->indices_,
|
||||
self_->constraints_, self_->config_, self_->view_);
|
||||
/// TODO: Here we need to create a vertex accessor dependent on the storage.
|
||||
current_vertex_accessor_ =
|
||||
InMemoryVertexAccessor::Create(current_vertex_, self_->transaction_, self_->indices_, self_->constraints_,
|
||||
self_->config_.items, self_->view_);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -537,7 +545,7 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList<Entry>::Accessor index_ac
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view,
|
||||
Transaction *transaction, Indices *indices, Constraints *constraints,
|
||||
Config::Items config)
|
||||
Config config)
|
||||
: index_accessor_(std::move(index_accessor)),
|
||||
label_(label),
|
||||
property_(property),
|
||||
@@ -51,7 +51,7 @@ class LabelIndex {
|
||||
};
|
||||
|
||||
public:
|
||||
LabelIndex(Indices *indices, Constraints *constraints, Config::Items config)
|
||||
LabelIndex(Indices *indices, Constraints *constraints, Config config)
|
||||
: indices_(indices), constraints_(constraints), config_(config) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
@@ -72,7 +72,7 @@ class LabelIndex {
|
||||
class Iterable {
|
||||
public:
|
||||
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, View view, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config);
|
||||
Indices *indices, Constraints *constraints, Config config);
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
@@ -112,7 +112,7 @@ class LabelIndex {
|
||||
Transaction *transaction_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
Config config_;
|
||||
};
|
||||
|
||||
/// Returns an self with vertices visible from the given transaction.
|
||||
@@ -136,7 +136,7 @@ class LabelIndex {
|
||||
std::map<LabelId, utils::SkipList<Entry>> index_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
Config config_;
|
||||
};
|
||||
|
||||
struct IndexStats {
|
||||
@@ -158,7 +158,7 @@ class LabelPropertyIndex {
|
||||
};
|
||||
|
||||
public:
|
||||
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config)
|
||||
LabelPropertyIndex(Indices *indices, Constraints *constraints, Config config)
|
||||
: indices_(indices), constraints_(constraints), config_(config) {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
@@ -183,7 +183,7 @@ class LabelPropertyIndex {
|
||||
Iterable(utils::SkipList<Entry>::Accessor index_accessor, LabelId label, PropertyId property,
|
||||
const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
const std::optional<utils::Bound<PropertyValue>> &upper_bound, View view, Transaction *transaction,
|
||||
Indices *indices, Constraints *constraints, Config::Items config);
|
||||
Indices *indices, Constraints *constraints, Config config);
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
@@ -227,7 +227,7 @@ class LabelPropertyIndex {
|
||||
Transaction *transaction_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
Config config_;
|
||||
};
|
||||
|
||||
Iterable Vertices(LabelId label, PropertyId property, const std::optional<utils::Bound<PropertyValue>> &lower_bound,
|
||||
@@ -276,11 +276,11 @@ class LabelPropertyIndex {
|
||||
std::map<std::pair<LabelId, PropertyId>, storage::IndexStats> stats_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config::Items config_;
|
||||
Config config_;
|
||||
};
|
||||
|
||||
struct Indices {
|
||||
Indices(Constraints *constraints, Config::Items config)
|
||||
Indices(Constraints *constraints, Config config)
|
||||
: label_index(this, constraints, config), label_property_index(this, constraints, config) {}
|
||||
|
||||
// Disable copy and move because members hold pointer to `this`.
|
||||
@@ -26,8 +26,8 @@
|
||||
#include "storage/v2/durability/snapshot.hpp"
|
||||
#include "storage/v2/durability/wal.hpp"
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/inmemory/edge_accessor.hpp"
|
||||
#include "storage/v2/inmemory/indices.hpp"
|
||||
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/replication/config.hpp"
|
||||
@@ -72,7 +72,7 @@ std::string RegisterReplicaErrorToString(InMemoryStorage::RegisterReplicaError e
|
||||
} // namespace
|
||||
|
||||
InMemoryStorage::InMemoryStorage(Config config)
|
||||
: indices_(&constraints_, config.items),
|
||||
: indices_(&constraints_, config),
|
||||
isolation_level_(config.transaction.isolation_level),
|
||||
config_(config),
|
||||
snapshot_directory_(config_.durability.storage_directory / durability::kSnapshotDirectory),
|
||||
@@ -244,6 +244,22 @@ InMemoryStorage::InMemoryAccessor::~InMemoryAccessor() {
|
||||
FinalizeTransaction();
|
||||
}
|
||||
|
||||
utils::SkipList<Vertex>::Iterator AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it,
|
||||
utils::SkipList<Vertex>::Iterator end,
|
||||
std::unique_ptr<VertexAccessor> &vertex, Transaction *tx,
|
||||
View view, Indices *indices, Constraints *constraints,
|
||||
Config config) {
|
||||
while (it != end) {
|
||||
vertex = InMemoryVertexAccessor::Create(&*it, tx, indices, constraints, config.items, view);
|
||||
if (!vertex) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
std::unique_ptr<VertexAccessor> InMemoryStorage::InMemoryAccessor::CreateVertex() {
|
||||
OOMExceptionEnabler oom_exception;
|
||||
auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel);
|
||||
@@ -281,7 +297,8 @@ std::unique_ptr<VertexAccessor> InMemoryStorage::InMemoryAccessor::FindVertex(st
|
||||
auto acc = storage_->vertices_.access();
|
||||
auto it = acc.find(gid);
|
||||
if (it == acc.end()) return {};
|
||||
return VertexAccessor::Create(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, view);
|
||||
return InMemoryVertexAccessor::Create(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_,
|
||||
view);
|
||||
}
|
||||
|
||||
// TODO: Think about moving from raw pointer to unique_ptr!
|
||||
@@ -842,6 +859,7 @@ void InMemoryStorage::InMemoryAccessor::Abort() {
|
||||
storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel);
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
vertex->deleted = true;
|
||||
my_deleted_vertices.push_back(vertex->gid);
|
||||
@@ -872,6 +890,7 @@ void InMemoryStorage::InMemoryAccessor::Abort() {
|
||||
edge->properties.SetProperty(current->property.key, current->property.value);
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
edge->deleted = true;
|
||||
my_deleted_edges.push_back(edge->gid);
|
||||
@@ -1551,6 +1570,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
|
||||
if (prev.type != PreviousPtr::Type::VERTEX) continue;
|
||||
find_and_apply_deltas(&delta, *prev.vertex, [](auto action) {
|
||||
switch (action) {
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_LABEL:
|
||||
@@ -1576,6 +1596,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
return true;
|
||||
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
@@ -1598,6 +1619,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
return true;
|
||||
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
case Delta::Action::ADD_LABEL:
|
||||
@@ -1620,6 +1642,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
|
||||
case Delta::Action::ADD_OUT_EDGE:
|
||||
return true;
|
||||
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
@@ -1642,6 +1665,7 @@ bool InMemoryStorage::AppendToWalDataManipulation(const Transaction &transaction
|
||||
case Delta::Action::RECREATE_OBJECT:
|
||||
return true;
|
||||
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
case Delta::Action::SET_PROPERTY:
|
||||
case Delta::Action::ADD_LABEL:
|
||||
@@ -1721,8 +1745,7 @@ utils::BasicResult<InMemoryStorage::CreateSnapshotError> InMemoryStorage::Create
|
||||
// Create snapshot.
|
||||
durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_,
|
||||
config_.durability.snapshot_retention_count, &vertices_, &edges_, &name_id_mapper_,
|
||||
&indices_, &constraints_, config_.items, uuid_, epoch_id_, epoch_history_,
|
||||
&file_retainer_);
|
||||
&indices_, &constraints_, config_, uuid_, epoch_id_, epoch_history_, &file_retainer_);
|
||||
|
||||
// Finalize snapshot transaction.
|
||||
commit_log_->MarkFinished(transaction.start_timestamp);
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#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/inmemory/indices.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/name_id_mapper.hpp"
|
||||
@@ -91,9 +91,9 @@ class InMemoryStorage final : public Storage {
|
||||
std::unique_ptr<VertexAccessor> FindVertex(Gid gid, View view) override;
|
||||
|
||||
VerticesIterable Vertices(View view) override {
|
||||
return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
||||
&storage_->indices_, &storage_->constraints_,
|
||||
storage_->config_.items));
|
||||
return VerticesIterable(AllMemoryVerticesIterable(storage_->vertices_.access(), &transaction_, view,
|
||||
&storage_->indices_, &storage_->constraints_,
|
||||
storage_->config_));
|
||||
}
|
||||
|
||||
VerticesIterable Vertices(LabelId label, View view) override;
|
||||
@@ -173,6 +173,10 @@ class InMemoryStorage final : public Storage {
|
||||
Result<std::optional<std::pair<std::unique_ptr<VertexAccessor>, std::vector<std::unique_ptr<EdgeAccessor>>>>>
|
||||
DetachDeleteVertex(VertexAccessor *vertex) override;
|
||||
|
||||
void PrefetchInEdges(const VertexAccessor &vertex_acc) override {}
|
||||
|
||||
void PrefetchOutEdges(const VertexAccessor &vertex_acc) override {}
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
Result<std::unique_ptr<EdgeAccessor>> CreateEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
EdgeTypeId edge_type) override;
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
#include "storage/v2/edge_accessor.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/inmemory/edge_accessor.hpp"
|
||||
#include "storage/v2/inmemory/indices.hpp"
|
||||
#include "storage/v2/mvcc.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
@@ -49,6 +49,7 @@ std::pair<bool, bool> IsVisible(Vertex *vertex, Transaction *transaction, View v
|
||||
deleted = false;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -139,6 +140,7 @@ Result<bool> InMemoryVertexAccessor::HasLabel(LabelId label, View view) const {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -188,6 +190,7 @@ Result<std::vector<LabelId>> InMemoryVertexAccessor::Labels(View view) const {
|
||||
labels.push_back(delta.label);
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -287,6 +290,7 @@ Result<PropertyValue> InMemoryVertexAccessor::GetProperty(PropertyId property, V
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -337,6 +341,7 @@ Result<std::map<PropertyId, PropertyValue>> InMemoryVertexAccessor::Properties(V
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -414,6 +419,7 @@ Result<std::vector<std::unique_ptr<EdgeAccessor>>> InMemoryVertexAccessor::InEdg
|
||||
in_edges.pop_back();
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -497,6 +503,7 @@ Result<std::vector<std::unique_ptr<EdgeAccessor>>> InMemoryVertexAccessor::OutEd
|
||||
out_edges.pop_back();
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
exists = false;
|
||||
break;
|
||||
@@ -544,6 +551,7 @@ Result<size_t> InMemoryVertexAccessor::InDegree(View view) const {
|
||||
case Delta::Action::REMOVE_IN_EDGE:
|
||||
--degree;
|
||||
break;
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
exists = false;
|
||||
break;
|
||||
@@ -582,6 +590,7 @@ Result<size_t> InMemoryVertexAccessor::OutDegree(View view) const {
|
||||
case Delta::Action::REMOVE_OUT_EDGE:
|
||||
--degree;
|
||||
break;
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT:
|
||||
exists = false;
|
||||
break;
|
||||
@@ -601,4 +610,8 @@ Result<size_t> InMemoryVertexAccessor::OutDegree(View view) const {
|
||||
return degree;
|
||||
}
|
||||
|
||||
std::string InMemoryVertexAccessor::PropertyStore() const { return vertex_->properties.StringBuffer(); }
|
||||
|
||||
void InMemoryVertexAccessor::SetPropertyStore(std::string_view buffer) const { vertex_->properties.SetBuffer(buffer); }
|
||||
|
||||
} // namespace memgraph::storage
|
||||
|
||||
@@ -100,6 +100,10 @@ class InMemoryVertexAccessor final : public VertexAccessor {
|
||||
|
||||
storage::Gid Gid() const noexcept override { return vertex_->gid; }
|
||||
|
||||
std::string PropertyStore() const override;
|
||||
|
||||
void SetPropertyStore(std::string_view buffer) const override;
|
||||
|
||||
std::unique_ptr<VertexAccessor> Copy() const override { return std::make_unique<InMemoryVertexAccessor>(*this); }
|
||||
|
||||
bool operator==(const VertexAccessor &other) const noexcept override {
|
||||
|
||||
62
src/storage/v2/inmemory/vertices_iterable.hpp
Normal file
62
src/storage/v2/inmemory/vertices_iterable.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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 "storage/v2/vertex_accessor.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
struct Transaction;
|
||||
class EdgeAccessor;
|
||||
|
||||
/// Implementation provided in storage/v2/storage.cpp
|
||||
class AllMemoryVerticesIterable final {
|
||||
utils::SkipList<Vertex>::Accessor vertices_accessor_;
|
||||
Transaction *transaction_;
|
||||
View view_;
|
||||
Indices *indices_;
|
||||
Constraints *constraints_;
|
||||
Config config_;
|
||||
std::unique_ptr<VertexAccessor> vertex_;
|
||||
|
||||
public:
|
||||
class Iterator final {
|
||||
AllMemoryVerticesIterable *self_;
|
||||
utils::SkipList<Vertex>::Iterator it_;
|
||||
|
||||
public:
|
||||
Iterator(AllMemoryVerticesIterable *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); }
|
||||
};
|
||||
|
||||
AllMemoryVerticesIterable(utils::SkipList<Vertex>::Accessor vertices_accessor, Transaction *transaction, View view,
|
||||
Indices *indices, Constraints *constraints, Config 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()); }
|
||||
};
|
||||
|
||||
} // namespace memgraph::storage
|
||||
@@ -32,6 +32,7 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
|
||||
const auto commit_timestamp = transaction->commit_timestamp
|
||||
? transaction->commit_timestamp->load(std::memory_order_acquire)
|
||||
: transaction->transaction_id.load(std::memory_order_acquire);
|
||||
spdlog::debug("Delta's commit timestamp: {}", delta->timestamp->load(std::memory_order_acquire));
|
||||
while (delta != nullptr) {
|
||||
auto ts = delta->timestamp->load(std::memory_order_acquire);
|
||||
auto cid = delta->command_id;
|
||||
@@ -79,8 +80,9 @@ inline void ApplyDeltasForRead(Transaction *transaction, const Delta *delta, Vie
|
||||
template <typename TObj>
|
||||
inline bool PrepareForWrite(Transaction *transaction, TObj *object) {
|
||||
if (object->delta == nullptr) return true;
|
||||
|
||||
auto ts = object->delta->timestamp->load(std::memory_order_acquire);
|
||||
spdlog::debug("Delta: {} Ts: {} TX id {} TX start {}", object->delta->action, ts,
|
||||
transaction->transaction_id.load(std::memory_order_acquire), transaction->start_timestamp);
|
||||
if (ts == transaction->transaction_id.load(std::memory_order_acquire) || ts < transaction->start_timestamp) {
|
||||
return true;
|
||||
}
|
||||
@@ -100,6 +102,13 @@ inline Delta *CreateDeleteObjectDelta(Transaction *transaction) {
|
||||
transaction->command_id);
|
||||
}
|
||||
|
||||
/// TODO(andi): Add docs for this function.
|
||||
/// Command id will be ignored in the case of dealing with deserialized deltas since on the disk, we are operating
|
||||
/// in SnapshotIsolation mode.
|
||||
inline Delta *CreateDeleteDeserializedObjectDelta(Transaction *transaction, uint64_t timestamp) {
|
||||
return &transaction->deltas.emplace_back(Delta::DeleteDeserializedObjectTag(), timestamp);
|
||||
}
|
||||
|
||||
/// This function creates a delta in the transaction for the object and links
|
||||
/// the delta into the object's delta list.
|
||||
/// @throw std::bad_alloc
|
||||
|
||||
@@ -11,9 +11,12 @@
|
||||
|
||||
#include "storage/v2/property_store.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
@@ -1219,4 +1222,37 @@ bool PropertyStore::ClearProperties() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string PropertyStore::StringBuffer() {
|
||||
uint64_t size = 0;
|
||||
uint8_t *data = nullptr;
|
||||
std::tie(size, data) = GetSizeData(buffer_);
|
||||
if (size % 8 != 0) { // We are storing the data in the local buffer.
|
||||
size = sizeof(buffer_) - 1;
|
||||
data = &buffer_[1];
|
||||
}
|
||||
std::string arr(size, ' ');
|
||||
for (uint i = 0; i < size; ++i) {
|
||||
arr[i] = static_cast<char>(data[i]);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
void PropertyStore::SetBuffer(const std::string_view buffer) {
|
||||
uint64_t size = 0;
|
||||
uint8_t *data = nullptr;
|
||||
if (buffer.size() == sizeof(buffer_) - 1) { // use local buffer
|
||||
buffer_[0] = kUseLocalBuffer;
|
||||
size = buffer.size() - 1;
|
||||
data = &buffer_[1];
|
||||
} else {
|
||||
size = buffer.size();
|
||||
data = new uint8_t[size];
|
||||
SetSizeData(buffer_, size, data);
|
||||
}
|
||||
|
||||
for (uint i = 0; i < size; ++i) {
|
||||
data[i] = static_cast<uint8_t>(buffer[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace memgraph::storage
|
||||
|
||||
@@ -71,6 +71,12 @@ class PropertyStore {
|
||||
/// @throw std::bad_alloc
|
||||
bool ClearProperties();
|
||||
|
||||
/// Return property buffer as a string
|
||||
std::string StringBuffer();
|
||||
|
||||
/// Sets buffer
|
||||
void SetBuffer(std::string_view buffer);
|
||||
|
||||
private:
|
||||
uint8_t buffer_[sizeof(uint64_t) + sizeof(uint8_t *)];
|
||||
};
|
||||
|
||||
@@ -167,9 +167,9 @@ void InMemoryStorage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader
|
||||
storage_->edges_.clear();
|
||||
|
||||
storage_->constraints_ = Constraints();
|
||||
storage_->indices_.label_index = LabelIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items);
|
||||
storage_->indices_.label_index = LabelIndex(&storage_->indices_, &storage_->constraints_, storage_->config_);
|
||||
storage_->indices_.label_property_index =
|
||||
LabelPropertyIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items);
|
||||
LabelPropertyIndex(&storage_->indices_, &storage_->constraints_, storage_->config_);
|
||||
try {
|
||||
spdlog::debug("Loading snapshot");
|
||||
auto recovered_snapshot = durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_,
|
||||
@@ -458,6 +458,7 @@ uint64_t InMemoryStorage::ReplicationServer::ReadAndApplyDelta(durability::BaseD
|
||||
is_visible = true;
|
||||
break;
|
||||
}
|
||||
case Delta::Action::DELETE_DESERIALIZED_OBJECT:
|
||||
case Delta::Action::DELETE_OBJECT: {
|
||||
is_visible = false;
|
||||
break;
|
||||
|
||||
@@ -11,15 +11,18 @@
|
||||
|
||||
#include "storage/v2/storage.hpp"
|
||||
|
||||
#include "storage/v2/disk/vertex_accessor.hpp"
|
||||
#include "storage/v2/inmemory/storage.hpp"
|
||||
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end,
|
||||
std::unique_ptr<VertexAccessor> &vertex, Transaction *tx, View view, Indices *indices,
|
||||
Constraints *constraints, Config::Items config) {
|
||||
Constraints *constraints, Config config) {
|
||||
while (it != end) {
|
||||
vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view);
|
||||
/// TODO:(andi) Here we need to create a vertex accessor dependent on the storage.
|
||||
vertex = InMemoryVertexAccessor::Create(&*it, tx, indices, constraints, config.items, view);
|
||||
if (!vertex) {
|
||||
++it;
|
||||
continue;
|
||||
@@ -29,22 +32,57 @@ auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipLis
|
||||
return it;
|
||||
}
|
||||
|
||||
AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::Iterator it)
|
||||
/// TODO: (andi): Templatize
|
||||
auto AdvanceToVisibleVertex(utils::SkipList<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end,
|
||||
std::unique_ptr<VertexAccessor> &vertex, Transaction *tx, View view, DiskIndices *indices,
|
||||
Constraints *constraints, Config config) {
|
||||
while (it != end) {
|
||||
/// TODO:(andi) Here we need to create a vertex accessor dependent on the storage.
|
||||
vertex = DiskVertexAccessor::Create(&*it, tx, indices, constraints, config.items, view);
|
||||
if (!vertex) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
AllMemoryVerticesIterable::Iterator::Iterator(AllMemoryVerticesIterable *self, utils::SkipList<Vertex>::Iterator it)
|
||||
: self_(self),
|
||||
it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), self->vertex_, self->transaction_, self->view_,
|
||||
self->indices_, self_->constraints_, self->config_)) {}
|
||||
|
||||
VertexAccessor *AllVerticesIterable::Iterator::operator*() const { return self_->vertex_.get(); }
|
||||
AllDiskVerticesIterable::Iterator::Iterator(AllDiskVerticesIterable *self, utils::SkipList<Vertex>::Iterator it)
|
||||
: self_(self),
|
||||
it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), self->vertex_, self->transaction_, self->view_,
|
||||
self->indices_, self_->constraints_, self->config_)) {}
|
||||
|
||||
AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() {
|
||||
VertexAccessor *AllMemoryVerticesIterable::Iterator::operator*() const { return self_->vertex_.get(); }
|
||||
|
||||
VertexAccessor *AllDiskVerticesIterable::Iterator::operator*() const { return self_->vertex_.get(); }
|
||||
|
||||
AllMemoryVerticesIterable::Iterator &AllMemoryVerticesIterable::Iterator::operator++() {
|
||||
++it_;
|
||||
it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), self_->vertex_, self_->transaction_, self_->view_,
|
||||
self_->indices_, self_->constraints_, self_->config_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
VerticesIterable::VerticesIterable(AllVerticesIterable vertices) : type_(Type::ALL) {
|
||||
new (&all_vertices_) AllVerticesIterable(std::move(vertices));
|
||||
AllDiskVerticesIterable::Iterator &AllDiskVerticesIterable::Iterator::operator++() {
|
||||
++it_;
|
||||
/// TODO(andi): Check what is happening
|
||||
it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), self_->vertex_, self_->transaction_, self_->view_,
|
||||
self_->indices_, self_->constraints_, self_->config_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
VerticesIterable::VerticesIterable(AllMemoryVerticesIterable vertices) : type_(Type::MEMORY_ALL) {
|
||||
new (&all_memory_vertices_) AllMemoryVerticesIterable(std::move(vertices));
|
||||
}
|
||||
|
||||
VerticesIterable::VerticesIterable(AllDiskVerticesIterable vertices) : type_(Type::DISK_ALL) {
|
||||
new (&all_disk_vertices_) AllDiskVerticesIterable(std::move(vertices));
|
||||
}
|
||||
|
||||
VerticesIterable::VerticesIterable(LabelIndex::Iterable vertices) : type_(Type::BY_LABEL) {
|
||||
@@ -57,8 +95,11 @@ VerticesIterable::VerticesIterable(LabelPropertyIndex::Iterable vertices) : type
|
||||
|
||||
VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(other.type_) {
|
||||
switch (other.type_) {
|
||||
case Type::ALL:
|
||||
new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_));
|
||||
case Type::MEMORY_ALL:
|
||||
new (&all_memory_vertices_) AllMemoryVerticesIterable(std::move(other.all_memory_vertices_));
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
new (&all_disk_vertices_) AllDiskVerticesIterable(std::move(other.all_disk_vertices_));
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_));
|
||||
@@ -71,8 +112,11 @@ VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(ot
|
||||
|
||||
VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
all_vertices_.AllVerticesIterable::~AllVerticesIterable();
|
||||
case Type::MEMORY_ALL:
|
||||
all_memory_vertices_.AllMemoryVerticesIterable::~AllMemoryVerticesIterable();
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
all_disk_vertices_.AllDiskVerticesIterable::~AllDiskVerticesIterable();
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
vertices_by_label_.LabelIndex::Iterable::~Iterable();
|
||||
@@ -83,8 +127,11 @@ VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept
|
||||
}
|
||||
type_ = other.type_;
|
||||
switch (other.type_) {
|
||||
case Type::ALL:
|
||||
new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_));
|
||||
case Type::MEMORY_ALL:
|
||||
new (&all_memory_vertices_) AllMemoryVerticesIterable(std::move(other.all_memory_vertices_));
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
new (&all_disk_vertices_) AllDiskVerticesIterable(std::move(other.all_disk_vertices_));
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_));
|
||||
@@ -98,8 +145,11 @@ VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept
|
||||
|
||||
VerticesIterable::~VerticesIterable() {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
all_vertices_.AllVerticesIterable::~AllVerticesIterable();
|
||||
case Type::MEMORY_ALL:
|
||||
all_memory_vertices_.AllMemoryVerticesIterable::~AllMemoryVerticesIterable();
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
all_disk_vertices_.AllDiskVerticesIterable::~AllDiskVerticesIterable();
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
vertices_by_label_.LabelIndex::Iterable::~Iterable();
|
||||
@@ -112,8 +162,10 @@ VerticesIterable::~VerticesIterable() {
|
||||
|
||||
VerticesIterable::Iterator VerticesIterable::begin() {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
return Iterator(all_vertices_.begin());
|
||||
case Type::MEMORY_ALL:
|
||||
return Iterator(all_memory_vertices_.begin());
|
||||
case Type::DISK_ALL:
|
||||
return Iterator(all_disk_vertices_.begin());
|
||||
case Type::BY_LABEL:
|
||||
return Iterator(vertices_by_label_.begin());
|
||||
case Type::BY_LABEL_PROPERTY:
|
||||
@@ -123,8 +175,10 @@ VerticesIterable::Iterator VerticesIterable::begin() {
|
||||
|
||||
VerticesIterable::Iterator VerticesIterable::end() {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
return Iterator(all_vertices_.end());
|
||||
case Type::MEMORY_ALL:
|
||||
return Iterator(all_memory_vertices_.end());
|
||||
case Type::DISK_ALL:
|
||||
return Iterator(all_disk_vertices_.end());
|
||||
case Type::BY_LABEL:
|
||||
return Iterator(vertices_by_label_.end());
|
||||
case Type::BY_LABEL_PROPERTY:
|
||||
@@ -132,8 +186,12 @@ VerticesIterable::Iterator VerticesIterable::end() {
|
||||
}
|
||||
}
|
||||
|
||||
VerticesIterable::Iterator::Iterator(AllVerticesIterable::Iterator it) : type_(Type::ALL) {
|
||||
new (&all_it_) AllVerticesIterable::Iterator(std::move(it));
|
||||
VerticesIterable::Iterator::Iterator(AllMemoryVerticesIterable::Iterator it) : type_(Type::MEMORY_ALL) {
|
||||
new (&all_memory_it_) AllMemoryVerticesIterable::Iterator(std::move(it));
|
||||
}
|
||||
|
||||
VerticesIterable::Iterator::Iterator(AllDiskVerticesIterable::Iterator it) : type_(Type::DISK_ALL) {
|
||||
new (&all_disk_it_) AllDiskVerticesIterable::Iterator(std::move(it));
|
||||
}
|
||||
|
||||
VerticesIterable::Iterator::Iterator(LabelIndex::Iterable::Iterator it) : type_(Type::BY_LABEL) {
|
||||
@@ -146,8 +204,11 @@ VerticesIterable::Iterator::Iterator(LabelPropertyIndex::Iterable::Iterator it)
|
||||
|
||||
VerticesIterable::Iterator::Iterator(const VerticesIterable::Iterator &other) : type_(other.type_) {
|
||||
switch (other.type_) {
|
||||
case Type::ALL:
|
||||
new (&all_it_) AllVerticesIterable::Iterator(other.all_it_);
|
||||
case Type::MEMORY_ALL:
|
||||
new (&all_memory_it_) AllMemoryVerticesIterable::Iterator(other.all_memory_it_);
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
new (&all_disk_it_) AllDiskVerticesIterable::Iterator(other.all_disk_it_);
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_);
|
||||
@@ -162,8 +223,11 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const Vertices
|
||||
Destroy();
|
||||
type_ = other.type_;
|
||||
switch (other.type_) {
|
||||
case Type::ALL:
|
||||
new (&all_it_) AllVerticesIterable::Iterator(other.all_it_);
|
||||
case Type::MEMORY_ALL:
|
||||
new (&all_memory_it_) AllMemoryVerticesIterable::Iterator(other.all_memory_it_);
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
new (&all_disk_it_) AllDiskVerticesIterable::Iterator(other.all_disk_it_);
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_);
|
||||
@@ -177,8 +241,11 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const Vertices
|
||||
|
||||
VerticesIterable::Iterator::Iterator(VerticesIterable::Iterator &&other) noexcept : type_(other.type_) {
|
||||
switch (other.type_) {
|
||||
case Type::ALL:
|
||||
new (&all_it_) AllVerticesIterable::Iterator(std::move(other.all_it_));
|
||||
case Type::MEMORY_ALL:
|
||||
new (&all_memory_it_) AllMemoryVerticesIterable::Iterator(std::move(other.all_memory_it_));
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
new (&all_disk_it_) AllDiskVerticesIterable::Iterator(std::move(other.all_disk_it_));
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
new (&by_label_it_) LabelIndex::Iterable::Iterator(std::move(other.by_label_it_));
|
||||
@@ -193,8 +260,11 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(VerticesIterab
|
||||
Destroy();
|
||||
type_ = other.type_;
|
||||
switch (other.type_) {
|
||||
case Type::ALL:
|
||||
new (&all_it_) AllVerticesIterable::Iterator(std::move(other.all_it_));
|
||||
case Type::MEMORY_ALL:
|
||||
new (&all_memory_it_) AllMemoryVerticesIterable::Iterator(std::move(other.all_memory_it_));
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
new (&all_disk_it_) AllDiskVerticesIterable::Iterator(std::move(other.all_disk_it_));
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
new (&by_label_it_) LabelIndex::Iterable::Iterator(std::move(other.by_label_it_));
|
||||
@@ -210,8 +280,11 @@ VerticesIterable::Iterator::~Iterator() { Destroy(); }
|
||||
|
||||
void VerticesIterable::Iterator::Destroy() noexcept {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
all_it_.AllVerticesIterable::Iterator::~Iterator();
|
||||
case Type::MEMORY_ALL:
|
||||
all_memory_it_.AllMemoryVerticesIterable::Iterator::~Iterator();
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
all_disk_it_.AllDiskVerticesIterable::Iterator::~Iterator();
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
by_label_it_.LabelIndex::Iterable::Iterator::~Iterator();
|
||||
@@ -224,8 +297,10 @@ void VerticesIterable::Iterator::Destroy() noexcept {
|
||||
|
||||
VertexAccessor *VerticesIterable::Iterator::operator*() const {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
return *all_it_;
|
||||
case Type::MEMORY_ALL:
|
||||
return *all_memory_it_;
|
||||
case Type::DISK_ALL:
|
||||
return *all_disk_it_;
|
||||
case Type::BY_LABEL:
|
||||
return *by_label_it_;
|
||||
case Type::BY_LABEL_PROPERTY:
|
||||
@@ -235,8 +310,11 @@ VertexAccessor *VerticesIterable::Iterator::operator*() const {
|
||||
|
||||
VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
++all_it_;
|
||||
case Type::MEMORY_ALL:
|
||||
++all_memory_it_;
|
||||
break;
|
||||
case Type::DISK_ALL:
|
||||
++all_disk_it_;
|
||||
break;
|
||||
case Type::BY_LABEL:
|
||||
++by_label_it_;
|
||||
@@ -250,8 +328,10 @@ VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() {
|
||||
|
||||
bool VerticesIterable::Iterator::operator==(const Iterator &other) const {
|
||||
switch (type_) {
|
||||
case Type::ALL:
|
||||
return all_it_ == other.all_it_;
|
||||
case Type::MEMORY_ALL:
|
||||
return all_memory_it_ == other.all_memory_it_;
|
||||
case Type::DISK_ALL:
|
||||
return all_disk_it_ == other.all_disk_it_;
|
||||
case Type::BY_LABEL:
|
||||
return by_label_it_ == other.by_label_it_;
|
||||
case Type::BY_LABEL_PROPERTY:
|
||||
|
||||
@@ -16,15 +16,17 @@
|
||||
|
||||
#include "io/network/endpoint.hpp"
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/indices.hpp"
|
||||
#include "storage/v2/disk/indices.hpp"
|
||||
#include "storage/v2/disk/vertices_iterable.hpp"
|
||||
#include "storage/v2/inmemory/indices.hpp"
|
||||
#include "storage/v2/inmemory/vertices_iterable.hpp"
|
||||
#include "storage/v2/replication/config.hpp"
|
||||
#include "storage/v2/replication/enums.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "storage/v2/storage_error.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
|
||||
#include "storage/v2/replication/config.hpp"
|
||||
#include "storage/v2/replication/enums.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
struct Transaction;
|
||||
@@ -32,70 +34,24 @@ class EdgeAccessor;
|
||||
|
||||
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_;
|
||||
std::unique_ptr<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()); }
|
||||
};
|
||||
|
||||
/// Generic access to different kinds of vertex iterations.
|
||||
///
|
||||
/// This class should be the primary type used by the client code to iterate
|
||||
/// over vertices inside a Storage instance.
|
||||
class VerticesIterable final {
|
||||
enum class Type { ALL, BY_LABEL, BY_LABEL_PROPERTY };
|
||||
enum class Type { MEMORY_ALL, DISK_ALL, BY_LABEL, BY_LABEL_PROPERTY };
|
||||
|
||||
Type type_;
|
||||
union {
|
||||
AllVerticesIterable all_vertices_;
|
||||
AllMemoryVerticesIterable all_memory_vertices_;
|
||||
AllDiskVerticesIterable all_disk_vertices_;
|
||||
LabelIndex::Iterable vertices_by_label_;
|
||||
LabelPropertyIndex::Iterable vertices_by_label_property_;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit VerticesIterable(AllVerticesIterable);
|
||||
explicit VerticesIterable(AllMemoryVerticesIterable);
|
||||
explicit VerticesIterable(AllDiskVerticesIterable);
|
||||
explicit VerticesIterable(LabelIndex::Iterable);
|
||||
explicit VerticesIterable(LabelPropertyIndex::Iterable);
|
||||
|
||||
@@ -110,7 +66,8 @@ class VerticesIterable final {
|
||||
class Iterator final {
|
||||
Type type_;
|
||||
union {
|
||||
AllVerticesIterable::Iterator all_it_;
|
||||
AllMemoryVerticesIterable::Iterator all_memory_it_;
|
||||
AllDiskVerticesIterable::Iterator all_disk_it_;
|
||||
LabelIndex::Iterable::Iterator by_label_it_;
|
||||
LabelPropertyIndex::Iterable::Iterator by_label_property_it_;
|
||||
};
|
||||
@@ -118,7 +75,8 @@ class VerticesIterable final {
|
||||
void Destroy() noexcept;
|
||||
|
||||
public:
|
||||
explicit Iterator(AllVerticesIterable::Iterator);
|
||||
explicit Iterator(AllMemoryVerticesIterable::Iterator);
|
||||
explicit Iterator(AllDiskVerticesIterable::Iterator);
|
||||
explicit Iterator(LabelIndex::Iterable::Iterator);
|
||||
explicit Iterator(LabelPropertyIndex::Iterable::Iterator);
|
||||
|
||||
@@ -243,6 +201,10 @@ class Storage {
|
||||
std::optional<std::pair<std::unique_ptr<VertexAccessor>, std::vector<std::unique_ptr<EdgeAccessor>>>>>
|
||||
DetachDeleteVertex(VertexAccessor *vertex) = 0;
|
||||
|
||||
virtual void PrefetchInEdges(const VertexAccessor &vertex_acc) = 0;
|
||||
|
||||
virtual void PrefetchOutEdges(const VertexAccessor &vertex_acc) = 0;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
virtual Result<std::unique_ptr<EdgeAccessor>> CreateEdge(VertexAccessor *from, VertexAccessor *to,
|
||||
EdgeTypeId edge_type) = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -25,7 +25,8 @@ namespace memgraph::storage {
|
||||
|
||||
struct Vertex {
|
||||
Vertex(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) {
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT,
|
||||
MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT ||
|
||||
delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT,
|
||||
"Vertex must be created with an initial DELETE_OBJECT delta!");
|
||||
}
|
||||
|
||||
|
||||
@@ -11,16 +11,12 @@
|
||||
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
|
||||
#include "storage/v2/disk/vertex_accessor.hpp"
|
||||
#include "storage/v2/inmemory/edge_accessor.hpp"
|
||||
#include "storage/v2/inmemory/vertex_accessor.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
std::unique_ptr<VertexAccessor> VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices,
|
||||
Constraints *constraints, Config::Items config, View view) {
|
||||
return InMemoryVertexAccessor::Create(vertex, transaction, indices, constraints, config, view);
|
||||
}
|
||||
|
||||
Result<std::vector<std::unique_ptr<EdgeAccessor>>> VertexAccessor::InEdges(
|
||||
View view, const std::vector<EdgeTypeId> &edge_types) const {
|
||||
return InEdges(view, edge_types, nullptr);
|
||||
|
||||
@@ -12,14 +12,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/vertex.hpp"
|
||||
|
||||
#include "storage/v2/config.hpp"
|
||||
#include "storage/v2/constraints.hpp"
|
||||
#include "storage/v2/result.hpp"
|
||||
#include "storage/v2/transaction.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
|
||||
namespace memgraph::storage {
|
||||
|
||||
@@ -38,9 +39,6 @@ class VertexAccessor {
|
||||
|
||||
virtual ~VertexAccessor() {}
|
||||
|
||||
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
|
||||
virtual bool IsVisible(View view) const = 0;
|
||||
|
||||
@@ -80,6 +78,10 @@ class VertexAccessor {
|
||||
/// @throw std::bad_alloc
|
||||
virtual Result<std::map<PropertyId, PropertyValue>> Properties(View view) const = 0;
|
||||
|
||||
virtual std::string PropertyStore() const = 0;
|
||||
|
||||
virtual void SetPropertyStore(std::string_view buffer) const = 0;
|
||||
|
||||
/// @throw std::bad_alloc
|
||||
/// @throw std::length_error if the resulting vector exceeds
|
||||
/// std::vector::max_size().
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -75,4 +75,15 @@ void Fatal(const char *msg, const Args &...msg_args) {
|
||||
#endif
|
||||
|
||||
inline void RedirectToStderr() { spdlog::set_default_logger(spdlog::stderr_color_mt("stderr")); }
|
||||
|
||||
// /// Use it for operations that must successfully finish.
|
||||
inline void AssertRocksDBStatus(const auto &status) { MG_ASSERT(status.ok(), "rocksdb: {}", status.ToString()); }
|
||||
|
||||
inline bool CheckRocksDBStatus(const auto &status) {
|
||||
if (!status.ok()) [[unlikely]] {
|
||||
spdlog::error("rocksdb: {}", status.ToString());
|
||||
}
|
||||
return status.ok();
|
||||
}
|
||||
|
||||
} // namespace memgraph::logging
|
||||
|
||||
@@ -81,11 +81,9 @@ bool LessThanDecimal(T a, T b) {
|
||||
return (b - a) > std::numeric_limits<T>::epsilon();
|
||||
}
|
||||
|
||||
/*
|
||||
* return 0 if a == b
|
||||
* return 1 if a > b
|
||||
* return -1 if a < b
|
||||
*/
|
||||
/// @return 0 if a == b
|
||||
/// @return 1 if a > b
|
||||
/// @return -1 if a < b
|
||||
template <FloatingPoint T>
|
||||
int CompareDecimal(T a, T b) {
|
||||
if (ApproxEqualDecimal(a, b)) return 0;
|
||||
|
||||
43
src/utils/rocksdb.hpp
Normal file
43
src/utils/rocksdb.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <rocksdb/db.h>
|
||||
|
||||
namespace memgraph::utils {
|
||||
|
||||
/// TODO: (andi): This can potentially be a problem on big-endian machines.
|
||||
inline void PutFixed64(std::string *dst, uint64_t value) {
|
||||
dst->append(const_cast<const char *>(reinterpret_cast<char *>(&value)), sizeof(value));
|
||||
}
|
||||
|
||||
inline uint64_t DecodeFixed64(const char *ptr) {
|
||||
// Load the raw bytes
|
||||
uint64_t result;
|
||||
memcpy(&result, ptr, sizeof(result)); // gcc optimizes this to a plain load
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::string StringTimestamp(uint64_t ts) {
|
||||
std::string ret;
|
||||
PutFixed64(&ret, ts);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline uint64_t ExtractTimestampFromDeserializedUserKey(const rocksdb::Slice &user_key) {
|
||||
return DecodeFixed64(user_key.data_ + user_key.size_);
|
||||
}
|
||||
|
||||
} // namespace memgraph::utils
|
||||
@@ -303,6 +303,9 @@ target_link_libraries(${test_prefix}storage_v2_decoder_encoder mg-storage-v2)
|
||||
add_unit_test(storage_v2_durability.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v2_durability mg-storage-v2)
|
||||
|
||||
add_unit_test(storage_rocks.cpp)
|
||||
target_link_libraries(${test_prefix}storage_rocks mg-storage-v2)
|
||||
|
||||
add_unit_test(storage_v2_edge.cpp)
|
||||
target_link_libraries(${test_prefix}storage_v2_edge mg-storage-v2)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 Memgraph Ltd.
|
||||
// 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
|
||||
@@ -219,6 +219,7 @@ auto CountIterable(TIterable &&iterable) {
|
||||
inline uint64_t CountEdges(memgraph::query::DbAccessor *dba, memgraph::storage::View view) {
|
||||
uint64_t count = 0;
|
||||
for (auto vertex : dba->Vertices(view)) {
|
||||
dba->PrefetchOutEdges();
|
||||
auto maybe_edges = vertex.OutEdges(view);
|
||||
MG_ASSERT(maybe_edges.HasValue());
|
||||
count += CountIterable(*maybe_edges);
|
||||
|
||||
@@ -292,6 +292,7 @@ TEST(QueryPlan, CreateExpand) {
|
||||
}
|
||||
|
||||
for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) {
|
||||
dba.PrefetchOutEdges();
|
||||
auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD);
|
||||
MG_ASSERT(maybe_edges.HasValue());
|
||||
for (auto edge : *maybe_edges) {
|
||||
@@ -1131,6 +1132,7 @@ TEST(QueryPlan, SetProperty) {
|
||||
|
||||
EXPECT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 2);
|
||||
for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) {
|
||||
dba.PrefetchOutEdges();
|
||||
auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD);
|
||||
ASSERT_TRUE(maybe_edges.HasValue());
|
||||
for (auto edge : *maybe_edges) {
|
||||
@@ -1187,6 +1189,7 @@ TEST(QueryPlan, SetProperties) {
|
||||
|
||||
EXPECT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 1);
|
||||
for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) {
|
||||
dba.PrefetchOutEdges();
|
||||
auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD);
|
||||
ASSERT_TRUE(maybe_edges.HasValue());
|
||||
for (auto edge : *maybe_edges) {
|
||||
@@ -1375,6 +1378,7 @@ TEST(QueryPlan, RemoveProperty) {
|
||||
|
||||
EXPECT_EQ(CountEdges(&dba, memgraph::storage::View::OLD), 2);
|
||||
for (auto vertex : dba.Vertices(memgraph::storage::View::OLD)) {
|
||||
dba.PrefetchOutEdges();
|
||||
auto maybe_edges = vertex.OutEdges(memgraph::storage::View::OLD);
|
||||
ASSERT_TRUE(maybe_edges.HasValue());
|
||||
for (auto edge : *maybe_edges) {
|
||||
|
||||
@@ -46,6 +46,8 @@ TEST(QueryPlan, CreateNodeWithAttributes) {
|
||||
const auto &v = node_value.ValueVertex();
|
||||
EXPECT_TRUE(*v.HasLabel(memgraph::storage::View::NEW, label));
|
||||
EXPECT_EQ(v.GetProperty(memgraph::storage::View::NEW, property)->ValueInt(), 42);
|
||||
dba->PrefetchInEdges();
|
||||
dba->PrefetchOutEdges();
|
||||
EXPECT_EQ(CountIterable(*v.InEdges(memgraph::storage::View::NEW)), 0);
|
||||
EXPECT_EQ(CountIterable(*v.OutEdges(memgraph::storage::View::NEW)), 0);
|
||||
// Invokes LOG(FATAL) instead of erroring out.
|
||||
|
||||
323
tests/unit/storage_rocks.cpp
Normal file
323
tests/unit/storage_rocks.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
// 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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <exception>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "query/common.hpp"
|
||||
#include "query/db_accessor.hpp"
|
||||
#include "storage/v2/delta.hpp"
|
||||
#include "storage/v2/disk/storage.hpp"
|
||||
#include "storage/v2/id_types.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "storage/v2/vertex_accessor.hpp"
|
||||
#include "storage/v2/view.hpp"
|
||||
|
||||
class RocksDBStorageTest : public ::testing::TestWithParam<bool> {
|
||||
public:
|
||||
~RocksDBStorageTest() { db.Clear(); }
|
||||
|
||||
protected:
|
||||
memgraph::storage::rocks::RocksDBStorage db;
|
||||
memgraph::storage::Storage storage;
|
||||
};
|
||||
|
||||
TEST_F(RocksDBStorageTest, SerializeVertexGID) {
|
||||
// empty vertices, only gid is serialized
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
std::unordered_set<uint64_t> gids;
|
||||
for (uint64_t i = 0; i < 5; ++i) {
|
||||
gids.insert(i);
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.SetGid(memgraph::storage::Gid::FromUint(i));
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// load vertices from disk
|
||||
auto loaded_vertices = db.Vertices(dba);
|
||||
ASSERT_EQ(loaded_vertices.size(), 5);
|
||||
for (const auto &vertex_acc : loaded_vertices) {
|
||||
ASSERT_TRUE(gids.contains(vertex_acc.Gid().AsUint()));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, SerializeVertexGIDLabels) {
|
||||
// serialize vertex's gid with its single label
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
// save vertices on disk
|
||||
std::unordered_set<uint64_t> gids;
|
||||
std::vector<memgraph::storage::LabelId> label_ids{dba.NameToLabel("Player"), dba.NameToLabel("Person"),
|
||||
dba.NameToLabel("Ball")};
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
gids.insert(i);
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.SetGid(memgraph::storage::Gid::FromUint(i));
|
||||
impl.AddLabel(label_ids[i % 3]);
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// load vertices from disk
|
||||
auto loaded_vertices = db.Vertices(dba);
|
||||
ASSERT_EQ(loaded_vertices.size(), 5);
|
||||
for (const auto &vertex_acc : loaded_vertices) {
|
||||
ASSERT_TRUE(gids.contains(vertex_acc.Gid().AsUint()));
|
||||
auto labels = vertex_acc.Labels(memgraph::storage::View::OLD);
|
||||
ASSERT_EQ(labels->size(), 1);
|
||||
ASSERT_TRUE(std::all_of(labels->begin(), labels->end(), [&label_ids](const auto &label_id) {
|
||||
return std::find(label_ids.begin(), label_ids.end(), label_id) != label_ids.end();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, SerializeVertexGIDMutlipleLabels) {
|
||||
// serialize vertex's gid with multiple labels it contains
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
// save vertices on disk
|
||||
std::unordered_set<uint64_t> gids;
|
||||
std::vector<memgraph::storage::LabelId> label_ids{dba.NameToLabel("Player"), dba.NameToLabel("Person"),
|
||||
dba.NameToLabel("Ball")};
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
gids.insert(i);
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.SetGid(memgraph::storage::Gid::FromUint(i));
|
||||
impl.AddLabel(label_ids[i % 3]);
|
||||
impl.AddLabel(label_ids[(i + 1) % 3]);
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// load vertices from disk
|
||||
auto loaded_vertices = db.Vertices(dba);
|
||||
ASSERT_EQ(loaded_vertices.size(), 5);
|
||||
for (const auto &vertex_acc : loaded_vertices) {
|
||||
ASSERT_TRUE(gids.contains(vertex_acc.Gid().AsUint()));
|
||||
auto labels = vertex_acc.Labels(memgraph::storage::View::OLD);
|
||||
ASSERT_EQ(labels->size(), 2);
|
||||
ASSERT_TRUE(std::all_of(labels->begin(), labels->end(), [&label_ids](const auto &label_id) {
|
||||
return std::find(label_ids.begin(), label_ids.end(), label_id) != label_ids.end();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, GetVerticesByLabel) {
|
||||
// search vertices by label
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
// prepare labels
|
||||
std::vector<memgraph::storage::LabelId> label_ids{dba.NameToLabel("Player"), dba.NameToLabel("Player"),
|
||||
dba.NameToLabel("Ball")};
|
||||
// insert vertices
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.AddLabel(label_ids[i % 3]);
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// load vertices from disk
|
||||
auto player_vertices = db.Vertices(dba, dba.NameToLabel("Player"));
|
||||
auto ball_vertices = db.Vertices(dba, dba.NameToLabel("Ball"));
|
||||
ASSERT_EQ(player_vertices.size(), 4);
|
||||
ASSERT_EQ(ball_vertices.size(), 1);
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, GetVerticesByProperty) {
|
||||
// search vertices by property value
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
// prepare ssd properties
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> ssd_properties_1;
|
||||
ssd_properties_1.emplace(dba.NameToProperty("price"), memgraph::storage::PropertyValue(225.84));
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> ssd_properties_2;
|
||||
ssd_properties_2.emplace(dba.NameToProperty("price"), memgraph::storage::PropertyValue(226.84));
|
||||
// prepare hdd properties
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> hdd_properties_1;
|
||||
hdd_properties_1.emplace(dba.NameToProperty("price"), memgraph::storage::PropertyValue(125.84));
|
||||
std::vector properties{ssd_properties_1, ssd_properties_2, hdd_properties_1, hdd_properties_1};
|
||||
// insert vertices
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
auto impl = dba.InsertVertex();
|
||||
memgraph::query::MultiPropsInitChecked(&impl, properties[i]);
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// load vertices from disk
|
||||
auto ssd_vertices_1 = db.Vertices(dba, dba.NameToProperty("price"), memgraph::storage::PropertyValue(225.84));
|
||||
auto hdd_vertices = db.Vertices(dba, dba.NameToProperty("price"), memgraph::storage::PropertyValue(125.84));
|
||||
auto hdd_vertices_non_existing =
|
||||
db.Vertices(dba, dba.NameToProperty("price"), memgraph::storage::PropertyValue(125.81));
|
||||
ASSERT_EQ(ssd_vertices_1.size(), 1);
|
||||
ASSERT_EQ(hdd_vertices.size(), 2);
|
||||
ASSERT_EQ(hdd_vertices_non_existing.size(), 0);
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, DeleteVertex) {
|
||||
// auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTE);
|
||||
auto storage_dba = storage.Access();
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> properties;
|
||||
// samo 1 property stane
|
||||
properties.emplace(dba.NameToProperty("sum"), memgraph::storage::PropertyValue("2TB"));
|
||||
properties.emplace(dba.NameToProperty("same_type"), memgraph::storage::PropertyValue(true));
|
||||
// properties.emplace(dba.NameToProperty("cluster_price"), memgraph::storage::PropertyValue(2000.42));
|
||||
// create vertex
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.AddLabel(dba.NameToLabel("Player"));
|
||||
memgraph::query::MultiPropsInitChecked(&impl, properties);
|
||||
db.StoreVertex(impl);
|
||||
// find vertex should work now
|
||||
ASSERT_TRUE(db.FindVertex(std::to_string(impl.Gid().AsUint()), dba).has_value());
|
||||
db.FindVertex(std::to_string(impl.Gid().AsUint()), dba);
|
||||
// RocksDB doesn't physically delete entry so deletion will pass two times
|
||||
ASSERT_TRUE(db.DeleteVertex(impl).has_value());
|
||||
ASSERT_TRUE(db.DeleteVertex(impl).has_value());
|
||||
// second time you shouldn't be able to find the vertex
|
||||
ASSERT_FALSE(db.FindVertex(std::to_string(impl.Gid().AsUint()), dba).has_value());
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, SerializeVertexGIDProperties) {
|
||||
// serializes vertex's gid, multiple labels and properties
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
// prepare labels
|
||||
std::vector<memgraph::storage::LabelId> label_ids{dba.NameToLabel("Player"), dba.NameToLabel("Person"),
|
||||
dba.NameToLabel("Ball")};
|
||||
// prepare properties
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> properties;
|
||||
properties.emplace(dba.NameToProperty("name"), memgraph::storage::PropertyValue("disk"));
|
||||
properties.emplace(dba.NameToProperty("memory"), memgraph::storage::PropertyValue("1TB"));
|
||||
properties.emplace(dba.NameToProperty("price"), memgraph::storage::PropertyValue(1000.21));
|
||||
properties.emplace(dba.NameToProperty("price2"), memgraph::storage::PropertyValue(1000.212));
|
||||
// gids
|
||||
std::unordered_set<uint64_t> gids;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
gids.insert(i);
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.SetGid(memgraph::storage::Gid::FromUint(i));
|
||||
impl.AddLabel(label_ids[i % 3]);
|
||||
impl.AddLabel(label_ids[(i + 1) % 3]);
|
||||
memgraph::query::MultiPropsInitChecked(&impl, properties);
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// load vertices from disk
|
||||
auto loaded_vertices = db.Vertices(dba);
|
||||
ASSERT_EQ(loaded_vertices.size(), 5);
|
||||
for (const auto &vertex_acc : loaded_vertices) {
|
||||
ASSERT_TRUE(gids.contains(vertex_acc.Gid().AsUint()));
|
||||
// labels
|
||||
auto labels = vertex_acc.Labels(memgraph::storage::View::OLD);
|
||||
ASSERT_EQ(labels->size(), 2);
|
||||
ASSERT_TRUE(std::all_of(labels->begin(), labels->end(), [&label_ids](const auto &label_id) {
|
||||
return std::find(label_ids.begin(), label_ids.end(), label_id) != label_ids.end();
|
||||
}));
|
||||
// check properties
|
||||
auto props = vertex_acc.Properties(memgraph::storage::View::OLD);
|
||||
ASSERT_FALSE(props.HasError());
|
||||
auto prop_name = vertex_acc.GetProperty(memgraph::storage::View::OLD, dba.NameToProperty("name"));
|
||||
auto prop_memory = vertex_acc.GetProperty(memgraph::storage::View::OLD, dba.NameToProperty("memory"));
|
||||
auto prop_price = vertex_acc.GetProperty(memgraph::storage::View::OLD, dba.NameToProperty("price"));
|
||||
auto prop_unexisting = vertex_acc.GetProperty(memgraph::storage::View::OLD, dba.NameToProperty("random"));
|
||||
ASSERT_TRUE(prop_name->IsString());
|
||||
ASSERT_EQ(prop_name->ValueString(), "disk");
|
||||
ASSERT_TRUE(prop_memory->IsString());
|
||||
ASSERT_EQ(prop_memory->ValueString(), "1TB");
|
||||
ASSERT_TRUE(prop_price->IsDouble());
|
||||
ASSERT_DOUBLE_EQ(prop_price->ValueDouble(), 1000.21);
|
||||
ASSERT_TRUE(prop_unexisting->IsNull());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(RocksDBStorageTest, SerializeEdge) {
|
||||
// create two vertices and edge between them
|
||||
// search by one of the vertices, return edge
|
||||
// check deserialization for both vertices and edge
|
||||
auto storage_dba = storage.Access(memgraph::storage::IsolationLevel::READ_UNCOMMITTED);
|
||||
memgraph::query::DbAccessor dba(&storage_dba);
|
||||
std::vector<memgraph::storage::LabelId> label_ids{dba.NameToLabel("Player"), dba.NameToLabel("Referee")};
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> properties_1;
|
||||
properties_1.emplace(dba.NameToProperty("price"), memgraph::storage::PropertyValue(221.84));
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> properties_2;
|
||||
properties_2.emplace(dba.NameToProperty("price"), memgraph::storage::PropertyValue(222.84));
|
||||
std::vector properties{properties_1, properties_2};
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
auto impl = dba.InsertVertex();
|
||||
impl.AddLabel(label_ids[i]);
|
||||
memgraph::query::MultiPropsInitChecked(&impl, properties[i]);
|
||||
db.StoreVertex(impl);
|
||||
}
|
||||
// prepare edge properties
|
||||
std::map<memgraph::storage::PropertyId, memgraph::storage::PropertyValue> edge_properties;
|
||||
edge_properties.emplace(dba.NameToProperty("sum"), memgraph::storage::PropertyValue("2TB"));
|
||||
edge_properties.emplace(dba.NameToProperty("same_type"), memgraph::storage::PropertyValue(true));
|
||||
edge_properties.emplace(dba.NameToProperty("cluster_price"), memgraph::storage::PropertyValue(2000.42));
|
||||
// Before inserting edge, find two vertices
|
||||
// find source vertex by the property
|
||||
auto src_vertices = db.Vertices(dba, dba.NameToProperty("price"), memgraph::storage::PropertyValue(221.84));
|
||||
ASSERT_EQ(src_vertices.size(), 1);
|
||||
auto src_vertex = src_vertices[0];
|
||||
// find destination vertex by the property
|
||||
auto dest_vertices = db.Vertices(dba, dba.NameToProperty("price"), memgraph::storage::PropertyValue(222.84));
|
||||
ASSERT_EQ(dest_vertices.size(), 1);
|
||||
auto dest_vertex = dest_vertices[0];
|
||||
// insert the edge
|
||||
uint64_t edge_gid = 2;
|
||||
auto edge_type_id = "CONNECTION";
|
||||
auto impl_edge = dba.InsertEdge(&src_vertex, &dest_vertex, dba.NameToEdgeType(edge_type_id));
|
||||
ASSERT_FALSE(impl_edge.HasError());
|
||||
(*impl_edge).SetGid(memgraph::storage::Gid::FromUint(edge_gid));
|
||||
memgraph::query::MultiPropsInitChecked(&*impl_edge, edge_properties);
|
||||
db.StoreEdge(*impl_edge);
|
||||
// Test out edges of the source vertex
|
||||
auto src_out_edges = db.OutEdges(src_vertex, dba);
|
||||
ASSERT_EQ(src_out_edges.size(), 1);
|
||||
auto src_out_edge = src_out_edges[0];
|
||||
// test from edge accessor
|
||||
auto from_out_edge_acc = src_out_edge.From();
|
||||
ASSERT_EQ(from_out_edge_acc.Gid(), src_vertex.Gid());
|
||||
ASSERT_EQ(from_out_edge_acc.Labels(memgraph::storage::View::OLD)->size(), 1);
|
||||
ASSERT_EQ(from_out_edge_acc.Labels(memgraph::storage::View::OLD)->at(0), label_ids[0]);
|
||||
ASSERT_EQ(*from_out_edge_acc.Properties(memgraph::storage::View::OLD), properties_1);
|
||||
// test to edge accessor
|
||||
auto to_out_edge_acc = src_out_edge.To();
|
||||
ASSERT_EQ(to_out_edge_acc.Gid(), dest_vertex.Gid());
|
||||
ASSERT_EQ(to_out_edge_acc.Labels(memgraph::storage::View::OLD)->size(), 1);
|
||||
ASSERT_EQ(to_out_edge_acc.Labels(memgraph::storage::View::OLD)->at(0), label_ids[1]);
|
||||
ASSERT_EQ(*to_out_edge_acc.Properties(memgraph::storage::View::OLD), properties_2);
|
||||
// test edge accessor
|
||||
ASSERT_EQ(src_out_edge.Gid().AsUint(), edge_gid);
|
||||
ASSERT_EQ(src_out_edge.EdgeType(), dba.NameToEdgeType(edge_type_id));
|
||||
ASSERT_EQ(*src_out_edge.Properties(memgraph::storage::View::OLD), edge_properties);
|
||||
// Test in edge of the destination vertex
|
||||
auto dest_in_edges = db.InEdges(dest_vertex, dba);
|
||||
ASSERT_EQ(dest_in_edges.size(), 1);
|
||||
auto dest_in_edge = dest_in_edges[0];
|
||||
// test from edge accessor
|
||||
auto from_in_edge_acc = dest_in_edge.From();
|
||||
ASSERT_EQ(from_in_edge_acc.Gid(), from_out_edge_acc.Gid());
|
||||
ASSERT_EQ(from_in_edge_acc.Labels(memgraph::storage::View::OLD)->size(), 1);
|
||||
ASSERT_EQ(from_in_edge_acc.Labels(memgraph::storage::View::OLD)->at(0),
|
||||
from_out_edge_acc.Labels(memgraph::storage::View::OLD)->at(0));
|
||||
ASSERT_EQ(*from_in_edge_acc.Properties(memgraph::storage::View::OLD),
|
||||
*from_out_edge_acc.Properties(memgraph::storage::View::OLD));
|
||||
// test in edge accessors
|
||||
auto to_in_edge_acc = dest_in_edge.To();
|
||||
ASSERT_EQ(to_in_edge_acc.Gid(), to_out_edge_acc.Gid());
|
||||
ASSERT_EQ(to_in_edge_acc.Labels(memgraph::storage::View::OLD)->size(), 1);
|
||||
ASSERT_EQ(to_in_edge_acc.Labels(memgraph::storage::View::OLD)->at(0),
|
||||
to_out_edge_acc.Labels(memgraph::storage::View::OLD)->at(0));
|
||||
ASSERT_EQ(*to_in_edge_acc.Properties(memgraph::storage::View::OLD),
|
||||
*to_out_edge_acc.Properties(memgraph::storage::View::OLD));
|
||||
// test edge accessors
|
||||
ASSERT_EQ(dest_in_edge.Gid(), src_out_edge.Gid());
|
||||
ASSERT_EQ(dest_in_edge.EdgeType(), src_out_edge.EdgeType());
|
||||
ASSERT_EQ(*dest_in_edge.Properties(memgraph::storage::View::OLD),
|
||||
*src_out_edge.Properties(memgraph::storage::View::OLD));
|
||||
}
|
||||
Reference in New Issue
Block a user