Compare commits

...

58 Commits

Author SHA1 Message Date
Andi Skrgat
153222a2a7 Merge remote-tracking branch 'origin/accessors-interface' into disk-storage-v1 2023-05-05 14:29:23 +02:00
Andi Skrgat
cbe23f7efa Properties deserialization 2023-05-05 14:28:59 +02:00
Andi Skrgat
95c119999f Indexes e2e 2023-05-03 15:28:55 +02:00
Andi Skrgat
bd0801dc1f Create index and rocksb objects reorganization 2023-05-03 13:14:04 +02:00
Andi Skrgat
04261f7cce Disk vertices iterable 2023-05-02 18:37:38 +02:00
Andi Skrgat
7bf38442aa Non-abstract indices 2023-05-02 15:22:50 +02:00
Andi Skrgat
43fd6906b6 Expand from vertex 2023-04-27 13:12:36 +02:00
Andi Skrgat
697bed4348 Merge remote-tracking branch 'origin/accessors-interface' into disk-storage-v1 2023-04-26 13:55:06 +02:00
Andi Skrgat
e45decfae8 Detach delete 2023-04-25 15:17:38 +02:00
Andi Skrgat
6a5ec391d9 Delete vertex and edges 2023-04-25 15:07:01 +02:00
Andi Skrgat
243e716812 Creating objects compatible with in-memory 2023-04-25 12:55:01 +02:00
Andi Skrgat
7ed38621ce Scan edges from the disk 2023-04-25 11:57:19 +02:00
Andi Skrgat
5f1b05337f KV store refactor 2023-04-25 09:04:19 +02:00
Andi Skrgat
7a4d910282 Merge remote-tracking branch 'origin/accessors-interface' into disk-storage-v1 2023-04-24 14:03:17 +02:00
Andi Skrgat
d6b9309618 Deserialize edge and edge init properties 2023-04-24 14:01:09 +02:00
Andi Skrgat
0505f29b52 Delete vertex support 2023-04-24 13:21:37 +02:00
Andi Skrgat
e4225c4ac1 Commit ts inside rocksdb 2023-04-24 11:38:36 +02:00
Andi Skrgat
c960d253dd RocksDB commit timestamp 2023-04-24 07:45:03 +02:00
Andi Skrgat
f3c4b4cea1 Delete vertex working from the cache 2023-04-21 11:45:26 +02:00
Andi Skrgat
30328832f4 Storage mode config 2023-04-21 08:55:57 +02:00
Andi Skrgat
6ffbade6d8 MATCH (n) RETURN n cached and from disk 2023-04-20 14:26:14 +02:00
Andi Skrgat
d37842e523 Create vertex works 2023-04-20 11:17:33 +02:00
Andi Skrgat
ead0f9db22 Garbage collection solved 2023-04-20 10:25:15 +02:00
Andi Skrgat
9e2dc55750 Approx vertex count, sessions correctly closed 2023-04-20 08:25:33 +02:00
Andi Skrgat
245f2a34e1 Disk-based Memgraph accepting connections 2023-04-19 15:28:50 +02:00
Andi Skrgat
e51f82db1d Memgraph with disk compiled 2023-04-19 14:18:21 +02:00
Andi Skrgat
225922206c Fixed disk storage build with comparators 2023-04-19 13:33:30 +02:00
Andi Skrgat
26f56352e4 In-memory compile 2023-04-19 13:08:33 +02:00
Andi Skrgat
39c14746ad Merge remote-tracking branch 'origin/accessors-interface' into disk-storage-v1 2023-04-19 12:56:38 +02:00
Andi Skrgat
955d2f1322 Create vertex, create edge, find vertex 2023-04-19 12:54:14 +02:00
Andi Skrgat
69778d6ca9 Flush cache at commit time 2023-04-19 10:42:15 +02:00
Andi Skrgat
c4b8d7d568 Cache modification_ts 2023-04-18 13:40:34 +02:00
Andi Skrgat
c2ebd511ab DiskVertex and DiskEdge 2023-04-18 13:34:59 +02:00
Andi Skrgat
9ce31bbab1 (de)serialization of commit timestamp 2023-04-18 12:35:57 +02:00
Andi Skrgat
797fb78910 Cache gid on accessor level 2023-04-18 08:49:18 +02:00
Andi Skrgat
a1bf90a687 Naming and memgraph build partial fix 2023-04-18 08:27:02 +02:00
Andi Skrgat
3a96db481e Mg-glue build fix and merge with accessors 2023-04-18 08:11:50 +02:00
Andi Skrgat
6d959d07d3 Merge remote-tracking branch 'origin/accessors-interface' into disk-storage-v1 2023-04-18 07:35:05 +02:00
Andi Skrgat
9b3bdb5c84 Merge remote-tracking branch 'origin/accessors-interface' into disk-storage-v1 2023-04-17 13:40:53 +02:00
Andi Skrgat
177d9dabf5 All vertices from the disk 2023-04-17 13:39:52 +02:00
Andi Skrgat
c819012a52 Transaction level cache 2023-04-17 12:21:11 +02:00
Andi Skrgat
53623da2b7 rocksdb functions on disk storage 2023-04-14 15:49:10 +02:00
Andi Skrgat
148f6e1498 Disk partial merge 2023-04-14 14:17:19 +02:00
Andi Skrgat
bc6f8e31fc Merge remote-tracking branch 'origin/accessors-interface' into HEAD 2023-04-14 14:15:34 +02:00
Andi Skrgat
b34044459e Rocks to v2 storage 2023-04-14 10:54:18 +02:00
Andi Skrgat
a22b011736 Compaction filter poc 2023-04-14 10:09:38 +02:00
Andi Skrgat
876359f4b6 Code edits 2023-04-12 13:43:47 +02:00
Andi Skrgat
c441b7de0b Solved memory leaks 2023-04-07 15:10:59 +02:00
Andi Skrgat
b6beffa9e2 Delete api 2023-04-07 09:56:00 +02:00
Andi Skrgat
1bed62da4a Code cleaning 2023-04-06 16:28:29 +02:00
Andi Skrgat
2019f5cc72 in and out edges 2023-04-06 11:31:50 +02:00
Andi Skrgat
f070329e77 Edge serializaiton 2023-04-05 15:29:44 +02:00
Andi Skrgat
b8d956294b Buffer serialization of properties 2023-04-05 12:04:05 +02:00
Andi Skrgat
72371cc8ee Dummy properties serialization 2023-04-05 09:33:47 +02:00
Andi Skrgat
e193d4036e Multiple labels serialization test cases 2023-04-04 11:12:58 +02:00
Andi Skrgat
ceb3e67f8e Merge branch 'add-on-disk-transactional-storage' into disk-storage-poc 2023-04-03 11:13:25 +02:00
Andi Skrgat
f7c1923ab2 ScanAll 2023-04-03 11:08:47 +02:00
Andi Skrgat
7503b304f2 POC started 2023-03-31 15:00:08 +02:00
62 changed files with 5467 additions and 179 deletions

View File

@@ -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(),

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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

View File

@@ -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);

View File

@@ -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"));

View File

@@ -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();

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
}
}

View 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;
}
};

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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, &degree](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, &degree](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

View 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

View 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

View File

@@ -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"

View File

@@ -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.

View File

@@ -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);

View File

@@ -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,

View File

@@ -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!");
}

View File

@@ -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 {

View File

@@ -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); }

View File

@@ -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

View File

@@ -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 {

View File

@@ -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),

View File

@@ -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`.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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 {

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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 *)];
};

View File

@@ -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;

View File

@@ -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:

View File

@@ -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;

View File

@@ -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!");
}

View File

@@ -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);

View File

@@ -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().

View File

@@ -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

View File

@@ -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
View 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

View File

@@ -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)

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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.

View 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));
}