From 2891041468aa59b722681a54ece2d448fa169120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= Date: Wed, 3 Aug 2022 18:10:58 +0200 Subject: [PATCH 01/10] Make storage use `KeyStore` (#455) --- src/storage/v3/CMakeLists.txt | 1 + src/storage/v3/constraints.cpp | 5 +- src/storage/v3/constraints.hpp | 12 +- src/storage/v3/durability/durability.cpp | 15 +- src/storage/v3/durability/durability.hpp | 15 +- src/storage/v3/durability/snapshot.cpp | 40 +- src/storage/v3/durability/snapshot.hpp | 4 +- src/storage/v3/durability/wal.cpp | 119 +- src/storage/v3/durability/wal.hpp | 3 +- src/storage/v3/indices.cpp | 10 +- src/storage/v3/indices.hpp | 5 +- src/storage/v3/key_store.cpp | 40 + src/storage/v3/key_store.hpp | 60 + .../v3/lexicographically_ordered_vertex.hpp | 42 + .../v3/replication/replication_server.cpp | 7 +- src/storage/v3/storage.cpp | 58 +- src/storage/v3/storage.hpp | 12 +- src/storage/v3/vertex.hpp | 13 +- src/storage/v3/vertex_accessor.hpp | 5 +- src/storage/v3/vertices_skip_list.hpp | 19 + src/utils/concepts.hpp | 16 + tests/unit/CMakeLists.txt | 12 +- tests/unit/storage_v3.cpp | 2548 ++++++++++++++++- tests/unit/storage_v3_key_store.cpp | 46 + tests/unit/storage_v3_property_store.cpp | 622 ++++ tests/unit/storage_v3_test_utils.cpp | 23 + tests/unit/storage_v3_test_utils.hpp | 21 + 27 files changed, 3629 insertions(+), 144 deletions(-) create mode 100644 src/storage/v3/key_store.cpp create mode 100644 src/storage/v3/key_store.hpp create mode 100644 src/storage/v3/lexicographically_ordered_vertex.hpp create mode 100644 src/storage/v3/vertices_skip_list.hpp create mode 100644 tests/unit/storage_v3_key_store.cpp create mode 100644 tests/unit/storage_v3_property_store.cpp create mode 100644 tests/unit/storage_v3_test_utils.cpp create mode 100644 tests/unit/storage_v3_test_utils.hpp diff --git a/src/storage/v3/CMakeLists.txt b/src/storage/v3/CMakeLists.txt index e9322b45a..723fd406f 100644 --- a/src/storage/v3/CMakeLists.txt +++ b/src/storage/v3/CMakeLists.txt @@ -8,6 +8,7 @@ set(storage_v3_src_files durability/wal.cpp edge_accessor.cpp indices.cpp + key_store.cpp property_store.cpp vertex_accessor.cpp storage.cpp) diff --git a/src/storage/v3/constraints.cpp b/src/storage/v3/constraints.cpp index e04fa4070..05bf7f0a6 100644 --- a/src/storage/v3/constraints.cpp +++ b/src/storage/v3/constraints.cpp @@ -279,7 +279,7 @@ void UniqueConstraints::UpdateBeforeCommit(const Vertex *vertex, const Transacti } utils::BasicResult UniqueConstraints::CreateConstraint( - LabelId label, const std::set &properties, utils::SkipList::Accessor vertices) { + LabelId label, const std::set &properties, VerticesSkipList::Accessor vertices) { if (properties.empty()) { return CreationStatus::EMPTY_PROPERTIES; } @@ -300,7 +300,8 @@ utils::BasicResult Uniqu { auto acc = constraint->second.access(); - for (const Vertex &vertex : vertices) { + for (const auto &lo_vertex : vertices) { + const auto &vertex = lo_vertex.vertex; if (vertex.deleted || !utils::Contains(vertex.labels, label)) { continue; } diff --git a/src/storage/v3/constraints.hpp b/src/storage/v3/constraints.hpp index 3c0715c4d..d127df4fe 100644 --- a/src/storage/v3/constraints.hpp +++ b/src/storage/v3/constraints.hpp @@ -18,6 +18,7 @@ #include "storage/v3/id_types.hpp" #include "storage/v3/transaction.hpp" #include "storage/v3/vertex.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/logging.hpp" #include "utils/result.hpp" #include "utils/skip_list.hpp" @@ -108,7 +109,7 @@ class UniqueConstraints { /// @throw std::bad_alloc utils::BasicResult CreateConstraint(LabelId label, const std::set &properties, - utils::SkipList::Accessor vertices); + VerticesSkipList::Accessor vertices); /// Deletes the specified constraint. Returns `DeletionStatus::NOT_FOUND` if /// there is not such constraint in the storage, @@ -152,12 +153,15 @@ struct Constraints { /// /// @throw std::bad_alloc /// @throw std::length_error -inline utils::BasicResult CreateExistenceConstraint( - Constraints *constraints, LabelId label, PropertyId property, utils::SkipList::Accessor vertices) { +inline utils::BasicResult CreateExistenceConstraint(Constraints *constraints, LabelId label, + PropertyId property, + VerticesSkipList::Accessor vertices) { if (utils::Contains(constraints->existence_constraints, std::make_pair(label, property))) { return false; } - for (const auto &vertex : vertices) { + for (const auto &lgo_vertex : vertices) { + const auto &vertex = lgo_vertex.vertex; + if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) { return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set{property}}; } diff --git a/src/storage/v3/durability/durability.cpp b/src/storage/v3/durability/durability.cpp index 8be9a8b17..a96594258 100644 --- a/src/storage/v3/durability/durability.cpp +++ b/src/storage/v3/durability/durability.cpp @@ -113,7 +113,7 @@ std::optional> GetWalFiles(const std::filesystem: // to ensure that the indices and constraints are consistent at the end of the // recovery process. void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices, - Constraints *constraints, utils::SkipList *vertices) { + Constraints *constraints, VerticesSkipList *vertices) { spdlog::info("Recreating indices from metadata."); // Recover label indices. spdlog::info("Recreating {} label indices from metadata.", indices_constraints.indices.label.size()); @@ -157,14 +157,11 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ spdlog::info("Constraints are recreated from metadata."); } -std::optional RecoverData(const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, std::string *uuid, - std::string *epoch_id, - std::deque> *epoch_history, - utils::SkipList *vertices, utils::SkipList *edges, - std::atomic *edge_count, NameIdMapper *name_id_mapper, - Indices *indices, Constraints *constraints, Config::Items items, - uint64_t *wal_seq_num) { +std::optional RecoverData( + const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, std::deque> *epoch_history, VerticesSkipList *vertices, + utils::SkipList *edges, std::atomic *edge_count, NameIdMapper *name_id_mapper, Indices *indices, + Constraints *constraints, Config::Items items, uint64_t *wal_seq_num) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory, wal_directory); diff --git a/src/storage/v3/durability/durability.hpp b/src/storage/v3/durability/durability.hpp index 3b0266062..b7fc39005 100644 --- a/src/storage/v3/durability/durability.hpp +++ b/src/storage/v3/durability/durability.hpp @@ -97,18 +97,15 @@ std::optional> GetWalFiles(const std::filesystem: // recovery process. /// @throw RecoveryFailure void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices, - Constraints *constraints, utils::SkipList *vertices); + Constraints *constraints, VerticesSkipList *vertices); /// Recovers data either from a snapshot and/or WAL files. /// @throw RecoveryFailure /// @throw std::bad_alloc -std::optional RecoverData(const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, std::string *uuid, - std::string *epoch_id, - std::deque> *epoch_history, - utils::SkipList *vertices, utils::SkipList *edges, - std::atomic *edge_count, NameIdMapper *name_id_mapper, - Indices *indices, Constraints *constraints, Config::Items items, - uint64_t *wal_seq_num); +std::optional RecoverData( + const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, std::deque> *epoch_history, VerticesSkipList *vertices, + utils::SkipList *edges, std::atomic *edge_count, NameIdMapper *name_id_mapper, Indices *indices, + Constraints *constraints, Config::Items items, uint64_t *wal_seq_num); } // namespace memgraph::storage::v3::durability diff --git a/src/storage/v3/durability/snapshot.cpp b/src/storage/v3/durability/snapshot.cpp index 48b4184ad..ee54f69d2 100644 --- a/src/storage/v3/durability/snapshot.cpp +++ b/src/storage/v3/durability/snapshot.cpp @@ -20,6 +20,7 @@ #include "storage/v3/edge_ref.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/file_locker.hpp" #include "utils/logging.hpp" #include "utils/message.hpp" @@ -157,7 +158,7 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { return info; } -RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList *vertices, +RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices, utils::SkipList *edges, std::deque> *epoch_history, NameIdMapper *name_id_mapper, std::atomic *edge_count, Config::Items items) { @@ -305,7 +306,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis } last_vertex_gid = *gid; spdlog::debug("Recovering vertex {}.", *gid); - auto [it, inserted] = vertex_acc.insert(Vertex{Gid::FromUint(*gid), nullptr}); + auto [it, inserted] = vertex_acc.insert({Vertex{Gid::FromUint(*gid), nullptr}}); if (!inserted) throw RecoveryFailure("The vertex must be inserted here!"); // Recover labels. @@ -313,7 +314,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis { auto labels_size = snapshot.ReadUint(); if (!labels_size) throw RecoveryFailure("Invalid snapshot data!"); - auto &labels = it->labels; + auto &labels = it->vertex.labels; labels.reserve(*labels_size); for (uint64_t j = 0; j < *labels_size; ++j) { auto label = snapshot.ReadUint(); @@ -329,7 +330,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis { auto props_size = snapshot.ReadUint(); if (!props_size) throw RecoveryFailure("Invalid snapshot data!"); - auto &props = it->properties; + auto &props = it->vertex.properties; for (uint64_t j = 0; j < *props_size; ++j) { auto key = snapshot.ReadUint(); if (!key) throw RecoveryFailure("Invalid snapshot data!"); @@ -372,17 +373,18 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis // Recover vertices (in/out edges). spdlog::info("Recovering connectivity."); if (!snapshot.SetPosition(info.offset_vertices)) throw RecoveryFailure("Couldn't read data from snapshot!"); - for (auto &vertex : vertex_acc) { + for (auto &lgo_vertex : vertex_acc) { + auto &vertex = lgo_vertex.vertex; { auto marker = snapshot.ReadMarker(); if (!marker || *marker != Marker::SECTION_VERTEX) throw RecoveryFailure("Invalid snapshot data!"); } - spdlog::trace("Recovering connectivity for vertex {}.", vertex.gid.AsUint()); + spdlog::trace("Recovering connectivity for vertex {}.", vertex.Gid().AsUint()); // Check vertex. auto gid = snapshot.ReadUint(); if (!gid) throw RecoveryFailure("Invalid snapshot data!"); - if (gid != vertex.gid.AsUint()) throw RecoveryFailure("Invalid snapshot data!"); + if (gid != vertex.Gid().AsUint()) throw RecoveryFailure("Invalid snapshot data!"); // Skip labels. { @@ -408,7 +410,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis // Recover in edges. { - spdlog::trace("Recovering inbound edges for vertex {}.", vertex.gid.AsUint()); + spdlog::trace("Recovering inbound edges for vertex {}.", vertex.Gid().AsUint()); auto in_size = snapshot.ReadUint(); if (!in_size) throw RecoveryFailure("Invalid snapshot data!"); vertex.in_edges.reserve(*in_size); @@ -422,7 +424,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis auto edge_type = snapshot.ReadUint(); if (!edge_type) throw RecoveryFailure("Invalid snapshot data!"); - auto from_vertex = vertex_acc.find(Gid::FromUint(*from_gid)); + auto from_vertex = vertex_acc.find(std::vector{PropertyValue{Gid::FromUint(*from_gid).AsInt()}}); if (from_vertex == vertex_acc.end()) throw RecoveryFailure("Invalid from vertex!"); EdgeRef edge_ref(Gid::FromUint(*edge_gid)); @@ -437,14 +439,14 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis } } SPDLOG_TRACE("Recovered inbound edge {} with label \"{}\" from vertex {}.", *edge_gid, - name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), from_vertex->gid.AsUint()); - vertex.in_edges.emplace_back(get_edge_type_from_id(*edge_type), &*from_vertex, edge_ref); + name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), from_vertex->vertex.Gid().AsUint()); + vertex.in_edges.emplace_back(get_edge_type_from_id(*edge_type), &from_vertex->vertex, edge_ref); } } // Recover out edges. { - spdlog::trace("Recovering outbound edges for vertex {}.", vertex.gid.AsUint()); + spdlog::trace("Recovering outbound edges for vertex {}.", vertex.Gid().AsUint()); auto out_size = snapshot.ReadUint(); if (!out_size) throw RecoveryFailure("Invalid snapshot data!"); vertex.out_edges.reserve(*out_size); @@ -458,7 +460,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis auto edge_type = snapshot.ReadUint(); if (!edge_type) throw RecoveryFailure("Invalid snapshot data!"); - auto to_vertex = vertex_acc.find(Gid::FromUint(*to_gid)); + auto to_vertex = vertex_acc.find(std::vector{PropertyValue{Gid::FromUint(*to_gid).AsInt()}}); if (to_vertex == vertex_acc.end()) throw RecoveryFailure("Invalid to vertex!"); EdgeRef edge_ref(Gid::FromUint(*edge_gid)); @@ -473,8 +475,8 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis } } SPDLOG_TRACE("Recovered outbound edge {} with label \"{}\" to vertex {}.", *edge_gid, - name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), to_vertex->gid.AsUint()); - vertex.out_edges.emplace_back(get_edge_type_from_id(*edge_type), &*to_vertex, edge_ref); + name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), to_vertex->vertex.Gid().AsUint()); + vertex.out_edges.emplace_back(get_edge_type_from_id(*edge_type), &to_vertex->vertex, edge_ref); } // Increment edge count. We only increment the count here because the // information is duplicated in in_edges. @@ -627,7 +629,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 *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, + VerticesSkipList *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid, const std::string_view epoch_id, const std::deque> &epoch_history, utils::FileRetainer *file_retainer) { @@ -740,9 +742,9 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps { offset_vertices = snapshot.GetPosition(); auto acc = vertices->access(); - for (auto &vertex : acc) { + for (auto &lgo_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); + auto va = VertexAccessor::Create(&lgo_vertex.vertex, transaction, indices, constraints, items, View::OLD); if (!va) continue; // Get vertex data. @@ -760,7 +762,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // Store the vertex. { snapshot.WriteMarker(Marker::SECTION_VERTEX); - snapshot.WriteUint(vertex.gid.AsUint()); + snapshot.WriteUint(lgo_vertex.vertex.Gid().AsUint()); const auto &labels = maybe_labels.GetValue(); snapshot.WriteUint(labels.size()); for (const auto &item : labels) { diff --git a/src/storage/v3/durability/snapshot.hpp b/src/storage/v3/durability/snapshot.hpp index 785ca7ed2..0ed81d1e2 100644 --- a/src/storage/v3/durability/snapshot.hpp +++ b/src/storage/v3/durability/snapshot.hpp @@ -59,7 +59,7 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path); /// Function used to load the snapshot data into the storage. /// @throw RecoveryFailure -RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipList *vertices, +RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices, utils::SkipList *edges, std::deque> *epoch_history, NameIdMapper *name_id_mapper, std::atomic *edge_count, Config::Items items); @@ -67,7 +67,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, utils::SkipLis /// Function used to create a snapshot using the given transaction. void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count, - utils::SkipList *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, + VerticesSkipList *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid, std::string_view epoch_id, const std::deque> &epoch_history, utils::FileRetainer *file_retainer); diff --git a/src/storage/v3/durability/wal.cpp b/src/storage/v3/durability/wal.cpp index 2cba7c07d..0699cca71 100644 --- a/src/storage/v3/durability/wal.cpp +++ b/src/storage/v3/durability/wal.cpp @@ -17,6 +17,7 @@ #include "storage/v3/durability/version.hpp" #include "storage/v3/edge.hpp" #include "storage/v3/vertex.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/file_locker.hpp" #include "utils/logging.hpp" @@ -492,12 +493,12 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite case Delta::Action::DELETE_OBJECT: case Delta::Action::RECREATE_OBJECT: { encoder->WriteMarker(VertexActionToMarker(delta.action)); - encoder->WriteUint(vertex.gid.AsUint()); + encoder->WriteUint(vertex.Gid().AsUint()); break; } case Delta::Action::SET_PROPERTY: { encoder->WriteMarker(Marker::DELTA_VERTEX_SET_PROPERTY); - encoder->WriteUint(vertex.gid.AsUint()); + encoder->WriteUint(vertex.Gid().AsUint()); encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint())); // The property value is the value that is currently stored in the // vertex. @@ -510,7 +511,7 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite case Delta::Action::ADD_LABEL: case Delta::Action::REMOVE_LABEL: { encoder->WriteMarker(VertexActionToMarker(delta.action)); - encoder->WriteUint(vertex.gid.AsUint()); + encoder->WriteUint(vertex.Gid().AsUint()); encoder->WriteString(name_id_mapper->IdToName(delta.label.AsUint())); break; } @@ -523,8 +524,8 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite encoder->WriteUint(delta.vertex_edge.edge.gid.AsUint()); } encoder->WriteString(name_id_mapper->IdToName(delta.vertex_edge.edge_type.AsUint())); - encoder->WriteUint(vertex.gid.AsUint()); - encoder->WriteUint(delta.vertex_edge.vertex->gid.AsUint()); + encoder->WriteUint(vertex.Gid().AsUint()); + encoder->WriteUint(delta.vertex_edge.vertex->Gid().AsUint()); break; } case Delta::Action::ADD_IN_EDGE: @@ -617,7 +618,7 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage } RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, - const std::optional last_loaded_timestamp, utils::SkipList *vertices, + const std::optional last_loaded_timestamp, VerticesSkipList *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, std::atomic *edge_count, Config::Items items) { spdlog::info("Trying to load WAL file {}.", path); @@ -653,7 +654,7 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst auto delta = ReadWalDeltaData(&wal); switch (delta.type) { case WalDeltaData::Type::VERTEX_CREATE: { - auto [vertex, inserted] = vertex_acc.insert(Vertex{delta.vertex_create_delete.gid, nullptr}); + auto [vertex, inserted] = vertex_acc.insert({Vertex{delta.vertex_create_delete.gid, nullptr}}); if (!inserted) throw RecoveryFailure("The vertex must be inserted here!"); ret.next_vertex_id = std::max(ret.next_vertex_id, delta.vertex_create_delete.gid.AsUint() + 1); @@ -661,51 +662,66 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst break; } case WalDeltaData::Type::VERTEX_DELETE: { - auto vertex = vertex_acc.find(delta.vertex_create_delete.gid); - if (vertex == vertex_acc.end()) throw RecoveryFailure("The vertex doesn't exist!"); - if (!vertex->in_edges.empty() || !vertex->out_edges.empty()) + auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_create_delete.gid.AsInt()}}); + if (lgo_vertex_it == vertex_acc.end()) { + throw RecoveryFailure("The vertex doesn't exist!"); + } + auto &vertex = lgo_vertex_it->vertex; + if (!vertex.in_edges.empty() || !vertex.out_edges.empty()) throw RecoveryFailure("The vertex can't be deleted because it still has edges!"); - if (!vertex_acc.remove(delta.vertex_create_delete.gid)) + if (!vertex_acc.remove(std::vector{PropertyValue{delta.vertex_create_delete.gid.AsInt()}})) throw RecoveryFailure("The vertex must be removed here!"); break; } case WalDeltaData::Type::VERTEX_ADD_LABEL: case WalDeltaData::Type::VERTEX_REMOVE_LABEL: { - auto vertex = vertex_acc.find(delta.vertex_add_remove_label.gid); - if (vertex == vertex_acc.end()) throw RecoveryFailure("The vertex doesn't exist!"); + auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_add_remove_label.gid.AsInt()}}); + if (lgo_vertex_it == vertex_acc.end()) { + throw RecoveryFailure("The vertex doesn't exist!"); + } + auto &vertex = lgo_vertex_it->vertex; auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.vertex_add_remove_label.label)); - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), label_id); + auto it = std::find(vertex.labels.begin(), vertex.labels.end(), label_id); if (delta.type == WalDeltaData::Type::VERTEX_ADD_LABEL) { - if (it != vertex->labels.end()) throw RecoveryFailure("The vertex already has the label!"); - vertex->labels.push_back(label_id); + if (it != vertex.labels.end()) throw RecoveryFailure("The vertex already has the label!"); + vertex.labels.push_back(label_id); } else { - if (it == vertex->labels.end()) throw RecoveryFailure("The vertex doesn't have the label!"); - std::swap(*it, vertex->labels.back()); - vertex->labels.pop_back(); + if (it == vertex.labels.end()) throw RecoveryFailure("The vertex doesn't have the label!"); + std::swap(*it, vertex.labels.back()); + vertex.labels.pop_back(); } break; } case WalDeltaData::Type::VERTEX_SET_PROPERTY: { - auto vertex = vertex_acc.find(delta.vertex_edge_set_property.gid); - if (vertex == vertex_acc.end()) throw RecoveryFailure("The vertex doesn't exist!"); + auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_edge_set_property.gid.AsInt()}}); + if (lgo_vertex_it == vertex_acc.end()) { + throw RecoveryFailure("The vertex doesn't exist!"); + } auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property)); auto &property_value = delta.vertex_edge_set_property.value; - vertex->properties.SetProperty(property_id, property_value); + lgo_vertex_it->vertex.properties.SetProperty(property_id, property_value); break; } case WalDeltaData::Type::EDGE_CREATE: { - auto from_vertex = vertex_acc.find(delta.edge_create_delete.from_vertex); - if (from_vertex == vertex_acc.end()) throw RecoveryFailure("The from vertex doesn't exist!"); - auto to_vertex = vertex_acc.find(delta.edge_create_delete.to_vertex); - if (to_vertex == vertex_acc.end()) throw RecoveryFailure("The to vertex doesn't exist!"); + auto from_lgo_vertex = + vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.from_vertex.AsInt()}}); + if (from_lgo_vertex == vertex_acc.end()) { + throw RecoveryFailure("The from vertex doesn't exist!"); + } + auto to_lgo_vertex = vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.to_vertex.AsInt()}}); + if (to_lgo_vertex == vertex_acc.end()) { + throw RecoveryFailure("The to vertex doesn't exist!"); + } + auto &from_vertex = from_lgo_vertex->vertex; + auto &to_vertex = to_lgo_vertex->vertex; auto edge_gid = delta.edge_create_delete.gid; auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type)); @@ -716,16 +732,16 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst edge_ref = EdgeRef(&*edge); } { - std::tuple link{edge_type_id, &*to_vertex, edge_ref}; - auto it = std::find(from_vertex->out_edges.begin(), from_vertex->out_edges.end(), link); - if (it != from_vertex->out_edges.end()) throw RecoveryFailure("The from vertex already has this edge!"); - from_vertex->out_edges.push_back(link); + std::tuple link{edge_type_id, &to_vertex, edge_ref}; + auto it = std::find(from_vertex.out_edges.begin(), from_vertex.out_edges.end(), link); + if (it != from_vertex.out_edges.end()) throw RecoveryFailure("The from vertex already has this edge!"); + from_vertex.out_edges.push_back(link); } { - std::tuple link{edge_type_id, &*from_vertex, edge_ref}; - auto it = std::find(to_vertex->in_edges.begin(), to_vertex->in_edges.end(), link); - if (it != to_vertex->in_edges.end()) throw RecoveryFailure("The to vertex already has this edge!"); - to_vertex->in_edges.push_back(link); + std::tuple link{edge_type_id, &from_vertex, edge_ref}; + auto it = std::find(to_vertex.in_edges.begin(), to_vertex.in_edges.end(), link); + if (it != to_vertex.in_edges.end()) throw RecoveryFailure("The to vertex already has this edge!"); + to_vertex.in_edges.push_back(link); } ret.next_edge_id = std::max(ret.next_edge_id, edge_gid.AsUint() + 1); @@ -736,10 +752,17 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst break; } case WalDeltaData::Type::EDGE_DELETE: { - auto from_vertex = vertex_acc.find(delta.edge_create_delete.from_vertex); - if (from_vertex == vertex_acc.end()) throw RecoveryFailure("The from vertex doesn't exist!"); - auto to_vertex = vertex_acc.find(delta.edge_create_delete.to_vertex); - if (to_vertex == vertex_acc.end()) throw RecoveryFailure("The to vertex doesn't exist!"); + auto from_lgo_vertex = + vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.from_vertex.AsInt()}}); + if (from_lgo_vertex == vertex_acc.end()) { + throw RecoveryFailure("The from vertex doesn't exist!"); + } + auto to_lgo_vertex = vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.to_vertex.AsInt()}}); + if (to_lgo_vertex == vertex_acc.end()) { + throw RecoveryFailure("The to vertex doesn't exist!"); + } + auto &from_vertex = from_lgo_vertex->vertex; + auto &to_vertex = to_lgo_vertex->vertex; auto edge_gid = delta.edge_create_delete.gid; auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type)); @@ -750,18 +773,18 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst edge_ref = EdgeRef(&*edge); } { - std::tuple link{edge_type_id, &*to_vertex, edge_ref}; - auto it = std::find(from_vertex->out_edges.begin(), from_vertex->out_edges.end(), link); - if (it == from_vertex->out_edges.end()) throw RecoveryFailure("The from vertex doesn't have this edge!"); - std::swap(*it, from_vertex->out_edges.back()); - from_vertex->out_edges.pop_back(); + std::tuple link{edge_type_id, &to_vertex, edge_ref}; + auto it = std::find(from_vertex.out_edges.begin(), from_vertex.out_edges.end(), link); + if (it == from_vertex.out_edges.end()) throw RecoveryFailure("The from vertex doesn't have this edge!"); + std::swap(*it, from_vertex.out_edges.back()); + from_vertex.out_edges.pop_back(); } { - std::tuple link{edge_type_id, &*from_vertex, edge_ref}; - auto it = std::find(to_vertex->in_edges.begin(), to_vertex->in_edges.end(), link); - if (it == to_vertex->in_edges.end()) throw RecoveryFailure("The to vertex doesn't have this edge!"); - std::swap(*it, to_vertex->in_edges.back()); - to_vertex->in_edges.pop_back(); + std::tuple link{edge_type_id, &from_vertex, edge_ref}; + auto it = std::find(to_vertex.in_edges.begin(), to_vertex.in_edges.end(), link); + if (it == to_vertex.in_edges.end()) throw RecoveryFailure("The to vertex doesn't have this edge!"); + std::swap(*it, to_vertex.in_edges.back()); + to_vertex.in_edges.pop_back(); } if (items.properties_on_edges) { if (!edge_acc.remove(edge_gid)) throw RecoveryFailure("The edge must be removed here!"); diff --git a/src/storage/v3/durability/wal.hpp b/src/storage/v3/durability/wal.hpp index 91b1f5b14..0e80134a5 100644 --- a/src/storage/v3/durability/wal.hpp +++ b/src/storage/v3/durability/wal.hpp @@ -25,6 +25,7 @@ #include "storage/v3/name_id_mapper.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/vertex.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/file_locker.hpp" #include "utils/skip_list.hpp" @@ -189,7 +190,7 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage /// Function used to load the WAL data into the storage. /// @throw RecoveryFailure RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, - std::optional last_loaded_timestamp, utils::SkipList *vertices, + std::optional last_loaded_timestamp, VerticesSkipList *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, std::atomic *edge_count, Config::Items items); diff --git a/src/storage/v3/indices.cpp b/src/storage/v3/indices.cpp index 340484228..8a788bd31 100644 --- a/src/storage/v3/indices.cpp +++ b/src/storage/v3/indices.cpp @@ -270,7 +270,7 @@ void LabelIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transacti acc.insert(Entry{vertex, tx.start_timestamp}); } -bool LabelIndex::CreateIndex(LabelId label, utils::SkipList::Accessor vertices) { +bool LabelIndex::CreateIndex(LabelId label, VerticesSkipList::Accessor vertices) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; auto [it, emplaced] = index_.emplace(std::piecewise_construct, std::forward_as_tuple(label), std::forward_as_tuple()); if (!emplaced) { @@ -279,7 +279,8 @@ bool LabelIndex::CreateIndex(LabelId label, utils::SkipList::Accessor ve } try { auto acc = it->second.access(); - for (Vertex &vertex : vertices) { + for (auto &lgo_vertex : vertices) { + auto &vertex = lgo_vertex.vertex; if (vertex.deleted || !utils::Contains(vertex.labels, label)) { continue; } @@ -416,7 +417,7 @@ void LabelPropertyIndex::UpdateOnSetProperty(PropertyId property, const Property } } -bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, utils::SkipList::Accessor vertices) { +bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, VerticesSkipList::Accessor vertices) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; auto [it, emplaced] = index_.emplace(std::piecewise_construct, std::forward_as_tuple(label, property), std::forward_as_tuple()); @@ -426,7 +427,8 @@ bool LabelPropertyIndex::CreateIndex(LabelId label, PropertyId property, utils:: } try { auto acc = it->second.access(); - for (Vertex &vertex : vertices) { + for (auto &lgo_vertex : vertices) { + auto &vertex = lgo_vertex.vertex; if (vertex.deleted || !utils::Contains(vertex.labels, label)) { continue; } diff --git a/src/storage/v3/indices.hpp b/src/storage/v3/indices.hpp index fe9e83b88..d943bea90 100644 --- a/src/storage/v3/indices.hpp +++ b/src/storage/v3/indices.hpp @@ -19,6 +19,7 @@ #include "storage/v3/property_value.hpp" #include "storage/v3/transaction.hpp" #include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/bound.hpp" #include "utils/logging.hpp" #include "utils/skip_list.hpp" @@ -58,7 +59,7 @@ class LabelIndex { void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx); /// @throw std::bad_alloc - bool CreateIndex(LabelId label, utils::SkipList::Accessor vertices); + bool CreateIndex(LabelId label, VerticesSkipList::Accessor vertices); /// Returns false if there was no index to drop bool DropIndex(LabelId label) { return index_.erase(label) > 0; } @@ -156,7 +157,7 @@ class LabelPropertyIndex { void UpdateOnSetProperty(PropertyId property, const PropertyValue &value, Vertex *vertex, const Transaction &tx); /// @throw std::bad_alloc - bool CreateIndex(LabelId label, PropertyId property, utils::SkipList::Accessor vertices); + bool CreateIndex(LabelId label, PropertyId property, VerticesSkipList::Accessor vertices); bool DropIndex(LabelId label, PropertyId property) { return index_.erase({label, property}) > 0; } diff --git a/src/storage/v3/key_store.cpp b/src/storage/v3/key_store.cpp new file mode 100644 index 000000000..c5e1f59a1 --- /dev/null +++ b/src/storage/v3/key_store.cpp @@ -0,0 +1,40 @@ +// Copyright 2022 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 +#include +#include + +#include "storage/v3/key_store.hpp" +#include "storage/v3/property_value.hpp" + +namespace memgraph::storage::v3 { + +KeyStore::KeyStore(const std::vector &key_values) { + for (auto i = 0; i < key_values.size(); ++i) { + MG_ASSERT(!key_values[i].IsNull()); + store_.SetProperty(PropertyId::FromInt(i), key_values[i]); + } +} + +PropertyValue KeyStore::GetKey(const size_t index) const { return store_.GetProperty(PropertyId::FromUint(index)); } + +std::vector KeyStore::Keys() const { + auto keys_map = store_.Properties(); + std::vector keys; + keys.reserve(keys_map.size()); + std::ranges::transform( + keys_map, std::back_inserter(keys), + [](std::pair &id_and_value) { return std::move(id_and_value.second); }); + return keys; +} + +} // namespace memgraph::storage::v3 diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp new file mode 100644 index 000000000..ec4905a4a --- /dev/null +++ b/src/storage/v3/key_store.hpp @@ -0,0 +1,60 @@ +// Copyright 2022 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 +#include +#include + +#include "storage/v3/property_store.hpp" +#include "storage/v3/property_value.hpp" + +namespace memgraph::storage::v3 { + +class KeyStore { + public: + explicit KeyStore(const std::vector &key_values); + + KeyStore(const KeyStore &) = delete; + KeyStore(KeyStore &&other) noexcept = default; + KeyStore &operator=(const KeyStore &) = delete; + KeyStore &operator=(KeyStore &&other) noexcept = default; + + ~KeyStore() = default; + + PropertyValue GetKey(size_t index) const; + + std::vector Keys() const; + + friend bool operator<(const KeyStore &lhs, const KeyStore &rhs) { + // TODO(antaljanosbenjamin): also compare the schema + return std::ranges::lexicographical_compare(lhs.Keys(), rhs.Keys(), std::less{}); + } + + friend bool operator==(const KeyStore &lhs, const KeyStore &rhs) { + return std::ranges::equal(lhs.Keys(), rhs.Keys()); + } + + friend bool operator<(const KeyStore &lhs, const std::vector &rhs) { + // TODO(antaljanosbenjamin): also compare the schema + return std::ranges::lexicographical_compare(lhs.Keys(), rhs, std::less{}); + } + + friend bool operator==(const KeyStore &lhs, const std::vector &rhs) { + return std::ranges::equal(lhs.Keys(), rhs); + } + + private: + PropertyStore store_; +}; + +} // namespace memgraph::storage::v3 diff --git a/src/storage/v3/lexicographically_ordered_vertex.hpp b/src/storage/v3/lexicographically_ordered_vertex.hpp new file mode 100644 index 000000000..e039a2687 --- /dev/null +++ b/src/storage/v3/lexicographically_ordered_vertex.hpp @@ -0,0 +1,42 @@ +// Copyright 2022 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 +#include + +#include "storage/v3/vertex.hpp" +#include "utils/concepts.hpp" + +namespace memgraph::storage::v3 { + +struct LexicographicallyOrderedVertex { + Vertex vertex; + + friend bool operator==(const LexicographicallyOrderedVertex &lhs, const LexicographicallyOrderedVertex &rhs) { + return lhs.vertex.keys == rhs.vertex.keys; + } + + friend bool operator<(const LexicographicallyOrderedVertex &lhs, const LexicographicallyOrderedVertex &rhs) { + return lhs.vertex.keys < rhs.vertex.keys; + } + + // TODO(antaljanosbenjamin): maybe it worth to overload this for std::array to avoid heap construction of the vector + friend bool operator==(const LexicographicallyOrderedVertex &lhs, const std::vector &rhs) { + return lhs.vertex.keys == rhs; + } + + friend bool operator<(const LexicographicallyOrderedVertex &lhs, const std::vector &rhs) { + return lhs.vertex.keys < rhs; + } +}; +} // namespace memgraph::storage::v3 diff --git a/src/storage/v3/replication/replication_server.cpp b/src/storage/v3/replication/replication_server.cpp index 0568598e1..55ef521fa 100644 --- a/src/storage/v3/replication/replication_server.cpp +++ b/src/storage/v3/replication/replication_server.cpp @@ -167,9 +167,10 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B LabelPropertyIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items); try { spdlog::debug("Loading snapshot"); - auto recovered_snapshot = durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, - &storage_->epoch_history_, &storage_->name_id_mapper_, - &storage_->edge_count_, storage_->config_.items); + auto recovered_snapshot = durability::RecoveredSnapshot{}; + + durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, &storage_->epoch_history_, + &storage_->name_id_mapper_, &storage_->edge_count_, storage_->config_.items); spdlog::debug("Snapshot loaded successfully"); // If this step is present it should always be the first step of // the recovery so we use the UUID we read from snasphost diff --git a/src/storage/v3/storage.cpp b/src/storage/v3/storage.cpp index 5ab334893..becad86a8 100644 --- a/src/storage/v3/storage.cpp +++ b/src/storage/v3/storage.cpp @@ -29,6 +29,7 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/indices.hpp" #include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" #include "storage/v3/replication/config.hpp" #include "storage/v3/replication/replication_client.hpp" #include "storage/v3/replication/replication_server.hpp" @@ -52,11 +53,11 @@ namespace { inline constexpr uint16_t kEpochHistoryRetention = 1000; } // namespace -auto AdvanceToVisibleVertex(utils::SkipList::Iterator it, utils::SkipList::Iterator end, +auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end, std::optional *vertex, Transaction *tx, View view, Indices *indices, Constraints *constraints, Config::Items config) { while (it != end) { - *vertex = VertexAccessor::Create(&*it, tx, indices, constraints, config, view); + *vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, view); if (!*vertex) { ++it; continue; @@ -66,7 +67,7 @@ auto AdvanceToVisibleVertex(utils::SkipList::Iterator it, utils::SkipLis return it; } -AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, utils::SkipList::Iterator it) +AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it) : self_(self), it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, self->indices_, self_->constraints_, self->config_)) {} @@ -342,9 +343,11 @@ Storage::Storage(Config config) config_.durability.storage_directory); } if (config_.durability.recover_on_startup) { - auto info = durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, - &vertices_, &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, - config_.items, &wal_seq_num_); + auto info = std::optional{}; + + durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, &vertices_, + &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, + &wal_seq_num_); if (info) { vertex_id_ = info->next_vertex_id; edge_id_ = info->next_edge_id; @@ -467,11 +470,12 @@ VertexAccessor Storage::Accessor::CreateVertex() { auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel); auto acc = storage_->vertices_.access(); auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Vertex{Gid::FromUint(gid), delta}); + // TODO(antaljanosbenjamin): handle keys and schema + auto [it, inserted] = acc.insert({Vertex{Gid::FromUint(gid), delta}}); MG_ASSERT(inserted, "The vertex must be inserted here!"); MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - delta->prev.Set(&*it); - return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; + delta->prev.Set(&it->vertex); + return {&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; } VertexAccessor Storage::Accessor::CreateVertex(Gid gid) { @@ -486,18 +490,20 @@ VertexAccessor Storage::Accessor::CreateVertex(Gid gid) { std::memory_order_release); auto acc = storage_->vertices_.access(); auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Vertex{gid, delta}); + auto [it, inserted] = acc.insert({Vertex{gid, delta}}); MG_ASSERT(inserted, "The vertex must be inserted here!"); MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - delta->prev.Set(&*it); - return {&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; + delta->prev.Set(&it->vertex); + return {&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_}; } std::optional Storage::Accessor::FindVertex(Gid gid, View view) { auto acc = storage_->vertices_.access(); - auto it = acc.find(gid); + auto it = acc.find(std::vector{PropertyValue{gid.AsInt()}}); if (it == acc.end()) return std::nullopt; - return VertexAccessor::Create(&*it, &transaction_, &storage_->indices_, &storage_->constraints_, config_, view); + return VertexAccessor::Create(&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, + view); + return {}; } Result> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) { @@ -609,10 +615,10 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA // Obtain the locks by `gid` order to avoid lock cycles. std::unique_lock guard_from(from_vertex->lock, std::defer_lock); std::unique_lock guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { + if (from_vertex < to_vertex) { guard_from.lock(); guard_to.lock(); - } else if (from_vertex->gid > to_vertex->gid) { + } else if (from_vertex > to_vertex) { guard_to.lock(); guard_from.lock(); } else { @@ -669,10 +675,10 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA // Obtain the locks by `gid` order to avoid lock cycles. std::unique_lock guard_from(from_vertex->lock, std::defer_lock); std::unique_lock guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { + if (&from_vertex < &to_vertex) { guard_from.lock(); guard_to.lock(); - } else if (from_vertex->gid > to_vertex->gid) { + } else if (&from_vertex > &to_vertex) { guard_to.lock(); guard_from.lock(); } else { @@ -744,10 +750,10 @@ Result> Storage::Accessor::DeleteEdge(EdgeAccessor * // Obtain the locks by `gid` order to avoid lock cycles. std::unique_lock guard_from(from_vertex->lock, std::defer_lock); std::unique_lock guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { + if (&from_vertex < &to_vertex) { guard_from.lock(); guard_to.lock(); - } else if (from_vertex->gid > to_vertex->gid) { + } else if (&from_vertex > &to_vertex) { guard_to.lock(); guard_from.lock(); } else { @@ -1021,7 +1027,7 @@ void Storage::Accessor::Abort() { } case Delta::Action::DELETE_OBJECT: { vertex->deleted = true; - my_deleted_vertices.push_back(vertex->gid); + my_deleted_vertices.push_back(vertex->Gid()); break; } case Delta::Action::RECREATE_OBJECT: { @@ -1412,7 +1418,7 @@ void Storage::CollectGarbage() { } vertex->delta = nullptr; if (vertex->deleted) { - current_deleted_vertices.push_back(vertex->gid); + current_deleted_vertices.push_back(vertex->Gid()); } break; } @@ -1530,13 +1536,17 @@ void Storage::CollectGarbage() { if constexpr (force) { // if force is set to true, then we have unique_lock and no transactions are active // so we can clean all of the deleted vertices + std::vector key(1); while (!garbage_vertices_.empty()) { - MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); + key.front() = PropertyValue{garbage_vertices_.front().second.AsInt()}; + MG_ASSERT(vertex_acc.remove(key), "Invalid database state!"); garbage_vertices_.pop_front(); } } else { + std::vector key(1); while (!garbage_vertices_.empty() && garbage_vertices_.front().first < oldest_active_start_timestamp) { - MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); + key.front() = PropertyValue{garbage_vertices_.front().second.AsInt()}; + MG_ASSERT(vertex_acc.remove(key), "Invalid database state!"); garbage_vertices_.pop_front(); } } diff --git a/src/storage/v3/storage.hpp b/src/storage/v3/storage.hpp index 40fe708b1..0c23667d2 100644 --- a/src/storage/v3/storage.hpp +++ b/src/storage/v3/storage.hpp @@ -27,12 +27,14 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/indices.hpp" #include "storage/v3/isolation_level.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/name_id_mapper.hpp" #include "storage/v3/result.hpp" #include "storage/v3/transaction.hpp" #include "storage/v3/vertex.hpp" #include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/file_locker.hpp" #include "utils/on_scope_exit.hpp" #include "utils/rw_lock.hpp" @@ -60,7 +62,7 @@ namespace memgraph::storage::v3 { /// An instance of this will be usually be wrapped inside VerticesIterable for /// generic, public use. class AllVerticesIterable final { - utils::SkipList::Accessor vertices_accessor_; + VerticesSkipList::Accessor vertices_accessor_; Transaction *transaction_; View view_; Indices *indices_; @@ -71,10 +73,10 @@ class AllVerticesIterable final { public: class Iterator final { AllVerticesIterable *self_; - utils::SkipList::Iterator it_; + VerticesSkipList::Iterator it_; public: - Iterator(AllVerticesIterable *self, utils::SkipList::Iterator it); + Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it); VertexAccessor operator*() const; @@ -85,7 +87,7 @@ class AllVerticesIterable final { bool operator!=(const Iterator &other) const { return !(*this == other); } }; - AllVerticesIterable(utils::SkipList::Accessor vertices_accessor, Transaction *transaction, View view, + AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view, Indices *indices, Constraints *constraints, Config::Items config) : vertices_accessor_(std::move(vertices_accessor)), transaction_(transaction), @@ -482,7 +484,7 @@ class Storage final { mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE}; // Main object storage - utils::SkipList vertices_; + VerticesSkipList vertices_; utils::SkipList edges_; std::atomic vertex_id_{0}; std::atomic edge_id_{0}; diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 3bf1bbb49..92377096c 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -13,24 +13,28 @@ #include #include +#include #include #include "storage/v3/delta.hpp" #include "storage/v3/edge_ref.hpp" #include "storage/v3/id_types.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/property_store.hpp" +#include "storage/v3/property_value.hpp" #include "utils/spin_lock.hpp" namespace memgraph::storage::v3 { struct Vertex { - Vertex(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) { + Vertex(Gid gid, Delta *delta) : keys{{PropertyValue{gid.AsInt()}}}, deleted(false), delta(delta) { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } - Gid gid; + Gid Gid() const { return Gid::FromInt(keys.GetKey(0).ValueInt()); } + KeyStore keys; std::vector labels; PropertyStore properties; @@ -47,9 +51,4 @@ struct Vertex { static_assert(alignof(Vertex) >= 8, "The Vertex should be aligned to at least 8!"); -inline bool operator==(const Vertex &first, const Vertex &second) { return first.gid == second.gid; } -inline bool operator<(const Vertex &first, const Vertex &second) { return first.gid < second.gid; } -inline bool operator==(const Vertex &first, const Gid &second) { return first.gid == second; } -inline bool operator<(const Vertex &first, const Gid &second) { return first.gid < second; } - } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/vertex_accessor.hpp b/src/storage/v3/vertex_accessor.hpp index 88a2828c8..3c8af3326 100644 --- a/src/storage/v3/vertex_accessor.hpp +++ b/src/storage/v3/vertex_accessor.hpp @@ -94,7 +94,10 @@ class VertexAccessor final { Result OutDegree(View view) const; - Gid Gid() const noexcept { return vertex_->gid; } + Gid Gid() const noexcept { + // TODO(antaljanosbenjamin): remove this whole function. + return vertex_->Gid(); + } bool operator==(const VertexAccessor &other) const noexcept { return vertex_ == other.vertex_ && transaction_ == other.transaction_; diff --git a/src/storage/v3/vertices_skip_list.hpp b/src/storage/v3/vertices_skip_list.hpp new file mode 100644 index 000000000..283dd5aeb --- /dev/null +++ b/src/storage/v3/vertices_skip_list.hpp @@ -0,0 +1,19 @@ +// Copyright 2022 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/v3/lexicographically_ordered_vertex.hpp" +#include "utils/skip_list.hpp" + +namespace memgraph::storage::v3 { +using VerticesSkipList = utils::SkipList; +} // namespace memgraph::storage::v3 diff --git a/src/utils/concepts.hpp b/src/utils/concepts.hpp index 3a5442d4a..7c1f3a9c8 100644 --- a/src/utils/concepts.hpp +++ b/src/utils/concepts.hpp @@ -11,6 +11,7 @@ #pragma once #include +#include namespace memgraph::utils { template @@ -18,4 +19,19 @@ concept SameAsAnyOf = (std::same_as || ...); template concept Enum = std::is_enum_v; + +// WithRef, CanReference and Dereferenceable is based on the similarly named concepts in GCC 11.2.0 +// bits/iterator_concepts.h +template +using WithRef = T &; + +template +concept CanReference = requires { + typename WithRef; +}; + +template +concept Dereferenceable = requires(T t) { + { *t } -> CanReference; +}; } // namespace memgraph::utils diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 319627715..2677f4ee6 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -363,5 +363,15 @@ add_unit_test(websocket.cpp) target_link_libraries(${test_prefix}websocket mg-communication Boost::headers) # Test storage-v3 +# Test utilities +add_library(storage_v3_test_utils storage_v3_test_utils.cpp) +target_link_libraries(storage_v3_test_utils mg-storage-v3) + add_unit_test(storage_v3.cpp) -target_link_libraries(${test_prefix}storage_v3 mg-storage-v3) +target_link_libraries(${test_prefix}storage_v3 mg-storage-v3 storage_v3_test_utils) + +add_unit_test(storage_v3_property_store.cpp) +target_link_libraries(${test_prefix}storage_v3_property_store mg-storage-v3 fmt) + +add_unit_test(storage_v3_key_store.cpp) +target_link_libraries(${test_prefix}storage_v3_key_store mg-storage-v3 rapidcheck rapidcheck_gtest) diff --git a/tests/unit/storage_v3.cpp b/tests/unit/storage_v3.cpp index e30114faf..f3b3438e1 100644 --- a/tests/unit/storage_v3.cpp +++ b/tests/unit/storage_v3.cpp @@ -9,13 +9,2555 @@ // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. +#include + #include #include +#include "storage/v3/property_value.hpp" #include "storage/v3/storage.hpp" +#include "storage/v3/vertex_accessor.hpp" +#include "storage_v3_test_utils.hpp" + +using testing::UnorderedElementsAre; + +namespace memgraph::storage::v3::tests { + +class StorageV3 : public ::testing::Test { + protected: + Storage store; +}; // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST(StorageV3, DummyTest) { - memgraph::storage::v3::Storage store; - EXPECT_EQ(store.GetInfo().vertex_count, 0); +TEST_F(StorageV3, Commit) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + ASSERT_TRUE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 1U); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + acc.Abort(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + + auto res = acc.DeleteVertex(&*vertex); + ASSERT_FALSE(res.HasError()); + EXPECT_EQ(CountVertices(acc, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + + acc.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_FALSE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.Abort(); + } } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, Abort) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + acc.Abort(); + } + { + auto acc = store.Access(); + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_FALSE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, AdvanceCommandCommit) { + Gid gid1 = Gid::FromUint(std::numeric_limits::max()); + Gid gid2 = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + + auto vertex1 = acc.CreateVertex(); + gid1 = vertex1.Gid(); + ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + + acc.AdvanceCommand(); + + auto vertex2 = acc.CreateVertex(); + gid2 = vertex2.Gid(); + ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 1U); + ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 2U); + + ASSERT_TRUE(acc.FindVertex(gid1, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + ASSERT_TRUE(acc.FindVertex(gid1, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(gid2, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 2U); + EXPECT_EQ(CountVertices(acc, View::NEW), 2U); + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, AdvanceCommandAbort) { + Gid gid1 = Gid::FromUint(std::numeric_limits::max()); + Gid gid2 = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + + auto vertex1 = acc.CreateVertex(); + gid1 = vertex1.Gid(); + ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + + acc.AdvanceCommand(); + + auto vertex2 = acc.CreateVertex(); + gid2 = vertex2.Gid(); + ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 1U); + ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 2U); + + ASSERT_TRUE(acc.FindVertex(gid1, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + + acc.Abort(); + } + { + auto acc = store.Access(); + ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(gid2, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, SnapshotIsolation) { + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + auto vertex = acc1.CreateVertex(); + auto gid = vertex.Gid(); + + ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); + ASSERT_FALSE(acc2.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc1, View::NEW), 1U); + EXPECT_EQ(CountVertices(acc2, View::NEW), 0U); + + ASSERT_FALSE(acc1.Commit().HasError()); + + ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); + ASSERT_FALSE(acc2.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc2, View::NEW), 0U); + + acc2.Abort(); + + auto acc3 = store.Access(); + ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); + ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); + acc3.Abort(); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, AccessorMove) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + + Storage::Accessor moved(std::move(acc)); + + ASSERT_FALSE(moved.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(moved, View::OLD), 0U); + ASSERT_TRUE(moved.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(moved, View::NEW), 1U); + + ASSERT_FALSE(moved.Commit().HasError()); + } + { + auto acc = store.Access(); + ASSERT_TRUE(acc.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 1U); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexDeleteCommit) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + auto acc1 = store.Access(); // read transaction + auto acc2 = store.Access(); // write transaction + + // Create the vertex in transaction 2 + { + auto vertex = acc2.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); + ASSERT_TRUE(acc2.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); + ASSERT_FALSE(acc2.Commit().HasError()); + } + + auto acc3 = store.Access(); // read transaction + auto acc4 = store.Access(); // write transaction + + // Check whether the vertex exists in transaction 1 + ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + + // Check whether the vertex exists in transaction 3 + ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); + ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); + + // Delete the vertex in transaction 4 + { + auto vertex = acc4.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + EXPECT_EQ(CountVertices(acc4, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc4, View::NEW), 1U); + + auto res = acc4.DeleteVertex(&*vertex); + ASSERT_TRUE(res.HasValue()); + EXPECT_EQ(CountVertices(acc4, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc4, View::NEW), 0U); + + acc4.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc4, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc4, View::NEW), 0U); + + ASSERT_FALSE(acc4.Commit().HasError()); + } + + auto acc5 = store.Access(); // read transaction + + // Check whether the vertex exists in transaction 1 + ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + + // Check whether the vertex exists in transaction 3 + ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); + ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); + + // Check whether the vertex exists in transaction 5 + ASSERT_FALSE(acc5.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc5, View::OLD), 0U); + ASSERT_FALSE(acc5.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc5, View::NEW), 0U); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexDeleteAbort) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + auto acc1 = store.Access(); // read transaction + auto acc2 = store.Access(); // write transaction + + // Create the vertex in transaction 2 + { + auto vertex = acc2.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); + ASSERT_TRUE(acc2.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); + ASSERT_FALSE(acc2.Commit().HasError()); + } + + auto acc3 = store.Access(); // read transaction + auto acc4 = store.Access(); // write transaction (aborted) + + // Check whether the vertex exists in transaction 1 + ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + + // Check whether the vertex exists in transaction 3 + ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); + ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); + + // Delete the vertex in transaction 4, but abort the transaction + { + auto vertex = acc4.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + EXPECT_EQ(CountVertices(acc4, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc4, View::NEW), 1U); + + auto res = acc4.DeleteVertex(&*vertex); + ASSERT_TRUE(res.HasValue()); + EXPECT_EQ(CountVertices(acc4, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc4, View::NEW), 0U); + + acc4.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc4, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc4, View::NEW), 0U); + + acc4.Abort(); + } + + auto acc5 = store.Access(); // read transaction + auto acc6 = store.Access(); // write transaction + + // Check whether the vertex exists in transaction 1 + ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + + // Check whether the vertex exists in transaction 3 + ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); + ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); + + // Check whether the vertex exists in transaction 5 + ASSERT_TRUE(acc5.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc5, View::OLD), 1U); + ASSERT_TRUE(acc5.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc5, View::NEW), 1U); + + // Delete the vertex in transaction 6 + { + auto vertex = acc6.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + EXPECT_EQ(CountVertices(acc6, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc6, View::NEW), 1U); + + auto res = acc6.DeleteVertex(&*vertex); + ASSERT_TRUE(res.HasValue()); + EXPECT_EQ(CountVertices(acc6, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc6, View::NEW), 0U); + + acc6.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc6, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc6, View::NEW), 0U); + + ASSERT_FALSE(acc6.Commit().HasError()); + } + + auto acc7 = store.Access(); // read transaction + + // Check whether the vertex exists in transaction 1 + ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + + // Check whether the vertex exists in transaction 3 + ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); + ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); + + // Check whether the vertex exists in transaction 5 + ASSERT_TRUE(acc5.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc5, View::OLD), 1U); + ASSERT_TRUE(acc5.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc5, View::NEW), 1U); + + // Check whether the vertex exists in transaction 7 + ASSERT_FALSE(acc7.FindVertex(gid, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc7, View::OLD), 0U); + ASSERT_FALSE(acc7.FindVertex(gid, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc7, View::NEW), 0U); + + // Commit all accessors + ASSERT_FALSE(acc1.Commit().HasError()); + ASSERT_FALSE(acc3.Commit().HasError()); + ASSERT_FALSE(acc5.Commit().HasError()); + ASSERT_FALSE(acc7.Commit().HasError()); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexDeleteSerializationError) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + // Create vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.Commit().HasError()); + } + + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + // Delete vertex in accessor 1 + { + auto vertex = acc1.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + EXPECT_EQ(CountVertices(acc1, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc1, View::NEW), 1U); + + { + auto res = acc1.DeleteVertex(&*vertex); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + } + + { + auto res = acc1.DeleteVertex(&*vertex); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + EXPECT_EQ(CountVertices(acc1, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + } + + acc1.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); + } + + // Delete vertex in accessor 2 + { + auto vertex = acc2.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + EXPECT_EQ(CountVertices(acc2, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); + auto res = acc2.DeleteVertex(&*vertex); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), Error::SERIALIZATION_ERROR); + EXPECT_EQ(CountVertices(acc2, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); + acc2.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc2, View::OLD), 1U); + EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); + } + + // Finalize both accessors + ASSERT_FALSE(acc1.Commit().HasError()); + acc2.Abort(); + + // Check whether the vertex exists + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_FALSE(vertex); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + ASSERT_FALSE(acc.Commit().HasError()); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexDeleteSpecialCases) { + Gid gid1 = Gid::FromUint(std::numeric_limits::max()); + Gid gid2 = Gid::FromUint(std::numeric_limits::max()); + + // Create vertex and delete it in the same transaction, but abort the + // transaction + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid1 = vertex.Gid(); + ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + auto res = acc.DeleteVertex(&vertex); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.Abort(); + } + + // Create vertex and delete it in the same transaction + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid2 = vertex.Gid(); + ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::NEW), 1U); + auto res = acc.DeleteVertex(&vertex); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.AdvanceCommand(); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the vertices exist + { + auto acc = store.Access(); + ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(gid2, View::NEW).has_value()); + EXPECT_EQ(CountVertices(acc, View::OLD), 0U); + EXPECT_EQ(CountVertices(acc, View::NEW), 0U); + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexDeleteLabel) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + // Create the vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Add label, delete the vertex and check the label API (same command) + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + // Check whether label 5 exists + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + // Add label 5 + ASSERT_TRUE(vertex->AddLabel(label).GetValue()); + + // Check whether label 5 exists + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + // Delete the vertex + ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + + // Check whether label 5 exists + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_EQ(vertex->HasLabel(label, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW).GetError(), Error::DELETED_OBJECT); + + // Try to add the label + { + auto ret = vertex->AddLabel(label); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + } + + // Try to remove the label + { + auto ret = vertex->RemoveLabel(label); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + } + + acc.Abort(); + } + + // Add label, delete the vertex and check the label API (different command) + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + // Check whether label 5 exists + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + // Add label 5 + ASSERT_TRUE(vertex->AddLabel(label).GetValue()); + + // Check whether label 5 exists + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 exists + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + // Delete the vertex + ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + + // Check whether label 5 exists + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_EQ(vertex->HasLabel(label, View::NEW).GetError(), Error::DELETED_OBJECT); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_EQ(vertex->Labels(View::NEW).GetError(), Error::DELETED_OBJECT); + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 exists + ASSERT_EQ(vertex->HasLabel(label, View::OLD).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->HasLabel(label, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->Labels(View::OLD).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->Labels(View::NEW).GetError(), Error::DELETED_OBJECT); + + // Try to add the label + { + auto ret = vertex->AddLabel(label); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + } + + // Try to remove the label + { + auto ret = vertex->RemoveLabel(label); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + } + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexDeleteProperty) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + // Create the vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Set property, delete the vertex and check the property API (same command) + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + // Check whether property 5 exists + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + // Set property 5 to "nandare" + ASSERT_TRUE(vertex->SetProperty(property, PropertyValue("nandare"))->IsNull()); + + // Check whether property 5 exists + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + // Delete the vertex + ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + + // Check whether label 5 exists + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW).GetError(), Error::DELETED_OBJECT); + + // Try to set the property + { + auto ret = vertex->SetProperty(property, PropertyValue("haihai")); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + } + + acc.Abort(); + } + + // Set property, delete the vertex and check the property API (different + // command) + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::NEW); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + // Check whether property 5 exists + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + // Set property 5 to "nandare" + ASSERT_TRUE(vertex->SetProperty(property, PropertyValue("nandare"))->IsNull()); + + // Check whether property 5 exists + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + // Advance command + acc.AdvanceCommand(); + + // Check whether property 5 exists + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + // Delete the vertex + ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); + + // Check whether property 5 exists + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + ASSERT_EQ(vertex->GetProperty(property, View::NEW).GetError(), Error::DELETED_OBJECT); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + ASSERT_EQ(vertex->Properties(View::NEW).GetError(), Error::DELETED_OBJECT); + + // Advance command + acc.AdvanceCommand(); + + // Check whether property 5 exists + ASSERT_EQ(vertex->GetProperty(property, View::OLD).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->GetProperty(property, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->Properties(View::OLD).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->Properties(View::NEW).GetError(), Error::DELETED_OBJECT); + + // Try to set the property + { + auto ret = vertex->SetProperty(property, PropertyValue("haihai")); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + } + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexLabelCommit) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + + auto label = acc.NameToLabel("label5"); + + ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + + { + auto res = vertex.AddLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + { + auto res = vertex.AddLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + auto other_label = acc.NameToLabel("other"); + + ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); + + acc.Abort(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + { + auto res = vertex->RemoveLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->RemoveLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + auto other_label = acc.NameToLabel("other"); + + ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexLabelAbort) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + // Create the vertex. + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Add label 5, but abort the transaction. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->AddLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + { + auto res = vertex->AddLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + + acc.Abort(); + } + + // Check that label 5 doesn't exist. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + auto other_label = acc.NameToLabel("other"); + + ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); + + acc.Abort(); + } + + // Add label 5. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->AddLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + { + auto res = vertex->AddLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check that label 5 exists. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + auto other_label = acc.NameToLabel("other"); + + ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); + + acc.Abort(); + } + + // Remove label 5, but abort the transaction. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + { + auto res = vertex->RemoveLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->RemoveLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + + acc.Abort(); + } + + // Check that label 5 exists. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + auto other_label = acc.NameToLabel("other"); + + ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); + + acc.Abort(); + } + + // Remove label 5. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + { + auto res = vertex->RemoveLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->RemoveLabel(label); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check that label 5 doesn't exist. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label = acc.NameToLabel("label5"); + + ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + auto other_label = acc.NameToLabel("other"); + + ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexLabelSerializationError) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.Commit().HasError()); + } + + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + // Add label 1 in accessor 1. + { + auto vertex = acc1.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label1 = acc1.NameToLabel("label1"); + auto label2 = acc1.NameToLabel("label2"); + + ASSERT_FALSE(vertex->HasLabel(label1, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label1, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->AddLabel(label1); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + } + + ASSERT_FALSE(vertex->HasLabel(label1, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label1, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label1); + } + + { + auto res = vertex->AddLabel(label1); + ASSERT_TRUE(res.HasValue()); + ASSERT_FALSE(res.GetValue()); + } + } + + // Add label 2 in accessor 2. + { + auto vertex = acc2.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label1 = acc2.NameToLabel("label1"); + auto label2 = acc2.NameToLabel("label2"); + + ASSERT_FALSE(vertex->HasLabel(label1, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label1, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::NEW).GetValue()); + ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); + + { + auto res = vertex->AddLabel(label1); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), Error::SERIALIZATION_ERROR); + } + } + + // Finalize both accessors. + ASSERT_FALSE(acc1.Commit().HasError()); + acc2.Abort(); + + // Check which labels exist. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto label1 = acc.NameToLabel("label1"); + auto label2 = acc.NameToLabel("label2"); + + ASSERT_TRUE(vertex->HasLabel(label1, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::OLD).GetValue()); + { + auto labels = vertex->Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label1); + } + + ASSERT_TRUE(vertex->HasLabel(label1, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label2, View::NEW).GetValue()); + { + auto labels = vertex->Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label1); + } + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexPropertyCommit) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + + auto property = acc.NameToProperty("property5"); + + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + + { + auto old_value = vertex.SetProperty(property, PropertyValue("temporary")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "temporary"); + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "temporary"); + } + + { + auto old_value = vertex.SetProperty(property, PropertyValue("nandare")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + auto other_property = acc.NameToProperty("other"); + + ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); + + acc.Abort(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + { + auto old_value = vertex->SetProperty(property, PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + { + auto old_value = vertex->SetProperty(property, PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + auto other_property = acc.NameToProperty("other"); + + ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexPropertyAbort) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + + // Create the vertex. + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Set property 5 to "nandare", but abort the transaction. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + { + auto old_value = vertex->SetProperty(property, PropertyValue("temporary")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "temporary"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "temporary"); + } + + { + auto old_value = vertex->SetProperty(property, PropertyValue("nandare")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + acc.Abort(); + } + + // Check that property 5 is null. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + auto other_property = acc.NameToProperty("other"); + + ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); + + acc.Abort(); + } + + // Set property 5 to "nandare". + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + { + auto old_value = vertex->SetProperty(property, PropertyValue("temporary")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "temporary"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "temporary"); + } + + { + auto old_value = vertex->SetProperty(property, PropertyValue("nandare")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check that property 5 is "nandare". + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + auto other_property = acc.NameToProperty("other"); + + ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); + + acc.Abort(); + } + + // Set property 5 to null, but abort the transaction. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + { + auto old_value = vertex->SetProperty(property, PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + acc.Abort(); + } + + // Check that property 5 is "nandare". + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + auto other_property = acc.NameToProperty("other"); + + ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); + + acc.Abort(); + } + + // Set property 5 to null. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + { + auto old_value = vertex->SetProperty(property, PropertyValue()); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_FALSE(old_value->IsNull()); + } + + ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check that property 5 is null. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property = acc.NameToProperty("property5"); + + ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + auto other_property = acc.NameToProperty("other"); + + ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexPropertySerializationError) { + Gid gid = Gid::FromUint(std::numeric_limits::max()); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(acc.Commit().HasError()); + } + + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + // Set property 1 to 123 in accessor 1. + { + auto vertex = acc1.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property1 = acc1.NameToProperty("property1"); + auto property2 = acc1.NameToProperty("property2"); + + ASSERT_TRUE(vertex->GetProperty(property1, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + { + auto old_value = vertex->SetProperty(property1, PropertyValue(123)); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + } + + ASSERT_TRUE(vertex->GetProperty(property1, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property1, View::NEW)->ValueInt(), 123); + ASSERT_TRUE(vertex->GetProperty(property2, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property1].ValueInt(), 123); + } + } + + // Set property 2 to "nandare" in accessor 2. + { + auto vertex = acc2.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property1 = acc2.NameToProperty("property1"); + auto property2 = acc2.NameToProperty("property2"); + + ASSERT_TRUE(vertex->GetProperty(property1, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); + + { + auto res = vertex->SetProperty(property2, PropertyValue("nandare")); + ASSERT_TRUE(res.HasError()); + ASSERT_EQ(res.GetError(), Error::SERIALIZATION_ERROR); + } + } + + // Finalize both accessors. + ASSERT_FALSE(acc1.Commit().HasError()); + acc2.Abort(); + + // Check which properties exist. + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto property1 = acc.NameToProperty("property1"); + auto property2 = acc.NameToProperty("property2"); + + ASSERT_EQ(vertex->GetProperty(property1, View::OLD)->ValueInt(), 123); + ASSERT_TRUE(vertex->GetProperty(property2, View::OLD)->IsNull()); + { + auto properties = vertex->Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property1].ValueInt(), 123); + } + + ASSERT_EQ(vertex->GetProperty(property1, View::NEW)->ValueInt(), 123); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + { + auto properties = vertex->Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property1].ValueInt(), 123); + } + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, VertexLabelPropertyMixed) { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + + auto label = acc.NameToLabel("label5"); + auto property = acc.NameToProperty("property5"); + + // Check whether label 5 and property 5 exist + ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + + // Add label 5 + ASSERT_TRUE(vertex.AddLabel(label).GetValue()); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_TRUE(vertex.GetProperty(property, View::OLD)->IsNull()); + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex.Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + + // Set property 5 to "nandare" + ASSERT_TRUE(vertex.SetProperty(property, PropertyValue("nandare"))->IsNull()); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_TRUE(vertex.GetProperty(property, View::OLD)->IsNull()); + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "nandare"); + ASSERT_EQ(vertex.Properties(View::OLD)->size(), 0); + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_EQ(vertex.GetProperty(property, View::OLD)->ValueString(), "nandare"); + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "nandare"); + { + auto properties = vertex.Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + + // Set property 5 to "haihai" + ASSERT_FALSE(vertex.SetProperty(property, PropertyValue("haihai"))->IsNull()); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_EQ(vertex.GetProperty(property, View::OLD)->ValueString(), "nandare"); + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "haihai"); + { + auto properties = vertex.Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "nandare"); + } + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + { + auto labels = vertex.Labels(View::NEW).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_EQ(vertex.GetProperty(property, View::OLD)->ValueString(), "haihai"); + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "haihai"); + { + auto properties = vertex.Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + + // Remove label 5 + ASSERT_TRUE(vertex.RemoveLabel(label).GetValue()); + + // Check whether label 5 and property 5 exist + ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); + { + auto labels = vertex.Labels(View::OLD).GetValue(); + ASSERT_EQ(labels.size(), 1); + ASSERT_EQ(labels[0], label); + } + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + ASSERT_EQ(vertex.GetProperty(property, View::OLD)->ValueString(), "haihai"); + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "haihai"); + { + auto properties = vertex.Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 and property 5 exist + ASSERT_FALSE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex.Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + ASSERT_EQ(vertex.GetProperty(property, View::OLD)->ValueString(), "haihai"); + ASSERT_EQ(vertex.GetProperty(property, View::NEW)->ValueString(), "haihai"); + { + auto properties = vertex.Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + { + auto properties = vertex.Properties(View::NEW).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + + // Set property 5 to null + ASSERT_FALSE(vertex.SetProperty(property, PropertyValue())->IsNull()); + + // Check whether label 5 and property 5 exist + ASSERT_FALSE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex.Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + ASSERT_EQ(vertex.GetProperty(property, View::OLD)->ValueString(), "haihai"); + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + { + auto properties = vertex.Properties(View::OLD).GetValue(); + ASSERT_EQ(properties.size(), 1); + ASSERT_EQ(properties[property].ValueString(), "haihai"); + } + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + + // Advance command + acc.AdvanceCommand(); + + // Check whether label 5 and property 5 exist + ASSERT_FALSE(vertex.HasLabel(label, View::OLD).GetValue()); + ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); + ASSERT_EQ(vertex.Labels(View::OLD)->size(), 0); + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); + ASSERT_EQ(vertex.Properties(View::OLD)->size(), 0); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); +} + +TEST_F(StorageV3, VertexPropertyClear) { + Gid gid; + auto property1 = store.NameToProperty("property1"); + auto property2 = store.NameToProperty("property2"); + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + + auto old_value = vertex.SetProperty(property1, PropertyValue("value")); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + ASSERT_EQ(vertex->GetProperty(property1, View::OLD)->ValueString(), "value"); + ASSERT_TRUE(vertex->GetProperty(property2, View::OLD)->IsNull()); + ASSERT_THAT(vertex->Properties(View::OLD).GetValue(), + UnorderedElementsAre(std::pair(property1, PropertyValue("value")))); + + { + auto old_values = vertex->ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_FALSE(old_values->empty()); + } + + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW).GetValue().size(), 0); + + { + auto old_values = vertex->ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_TRUE(old_values->empty()); + } + + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW).GetValue().size(), 0); + + acc.Abort(); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + auto old_value = vertex->SetProperty(property2, PropertyValue(42)); + ASSERT_TRUE(old_value.HasValue()); + ASSERT_TRUE(old_value->IsNull()); + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + ASSERT_EQ(vertex->GetProperty(property1, View::OLD)->ValueString(), "value"); + ASSERT_EQ(vertex->GetProperty(property2, View::OLD)->ValueInt(), 42); + ASSERT_THAT( + vertex->Properties(View::OLD).GetValue(), + UnorderedElementsAre(std::pair(property1, PropertyValue("value")), std::pair(property2, PropertyValue(42)))); + + { + auto old_values = vertex->ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_FALSE(old_values->empty()); + } + + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW).GetValue().size(), 0); + + { + auto old_values = vertex->ClearProperties(); + ASSERT_TRUE(old_values.HasValue()); + ASSERT_TRUE(old_values->empty()); + } + + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW).GetValue().size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = store.Access(); + auto vertex = acc.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property2, View::NEW)->IsNull()); + ASSERT_EQ(vertex->Properties(View::NEW).GetValue().size(), 0); + + acc.Abort(); + } +} + +TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { + auto label = store.NameToLabel("label"); + auto property = store.NameToProperty("property"); + + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + + // Check state before (OLD view). + ASSERT_EQ(vertex.Labels(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.HasLabel(label, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.Properties(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.GetProperty(property, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.InEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.OutEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.InDegree(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.OutDegree(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + + // Check state before (NEW view). + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); + ASSERT_EQ(*vertex.HasLabel(label, View::NEW), false); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); + ASSERT_EQ(*vertex.GetProperty(property, View::NEW), PropertyValue()); + ASSERT_EQ(vertex.InEdges(View::NEW)->size(), 0); + ASSERT_EQ(vertex.OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex.InDegree(View::NEW), 0); + ASSERT_EQ(*vertex.OutDegree(View::NEW), 0); + + // Modify vertex. + ASSERT_TRUE(vertex.AddLabel(label).HasValue()); + ASSERT_TRUE(vertex.SetProperty(property, PropertyValue("value")).HasValue()); + ASSERT_TRUE(acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge")).HasValue()); + + // Check state after (OLD view). + ASSERT_EQ(vertex.Labels(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.HasLabel(label, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.Properties(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.GetProperty(property, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.InEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.OutEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.InDegree(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.OutDegree(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + + // Check state after (NEW view). + ASSERT_EQ(vertex.Labels(View::NEW)->size(), 1); + ASSERT_EQ(*vertex.HasLabel(label, View::NEW), true); + ASSERT_EQ(vertex.Properties(View::NEW)->size(), 1); + ASSERT_EQ(*vertex.GetProperty(property, View::NEW), PropertyValue("value")); + ASSERT_EQ(vertex.InEdges(View::NEW)->size(), 1); + ASSERT_EQ(vertex.OutEdges(View::NEW)->size(), 1); + ASSERT_EQ(*vertex.InDegree(View::NEW), 1); + ASSERT_EQ(*vertex.OutDegree(View::NEW), 1); + + ASSERT_FALSE(acc.Commit().HasError()); +} + +TEST_F(StorageV3, VertexVisibilitySingleTransaction) { + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + auto vertex = acc1.CreateVertex(); + auto gid = vertex.Gid(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + + ASSERT_TRUE(vertex.AddLabel(acc1.NameToLabel("label")).HasValue()); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + + ASSERT_TRUE(vertex.SetProperty(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); + + auto acc3 = store.Access(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc3.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc3.FindVertex(gid, View::NEW)); + + ASSERT_TRUE(acc1.DeleteVertex(&vertex).HasValue()); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc3.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc3.FindVertex(gid, View::NEW)); + + acc1.AdvanceCommand(); + acc3.AdvanceCommand(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc3.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc3.FindVertex(gid, View::NEW)); + + acc1.Abort(); + acc2.Abort(); + acc3.Abort(); +} + +TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { + Gid gid; + + { + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + auto vertex = acc1.CreateVertex(); + gid = vertex.Gid(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + + acc2.AdvanceCommand(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + + acc1.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + + ASSERT_FALSE(acc1.Commit().HasError()); + ASSERT_FALSE(acc2.Commit().HasError()); + } + + { + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + auto vertex = acc1.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + + ASSERT_TRUE(vertex->AddLabel(acc1.NameToLabel("label")).HasValue()); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + + acc1.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + + acc2.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + + ASSERT_TRUE(vertex->SetProperty(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); + + auto acc3 = store.Access(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc1.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc2.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc3.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + ASSERT_FALSE(acc1.Commit().HasError()); + ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc3.Commit().HasError()); + } + + { + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + auto vertex = acc1.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + ASSERT_TRUE(acc1.DeleteVertex(&*vertex).HasValue()); + + auto acc3 = store.Access(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc2.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc1.AdvanceCommand(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc3.AdvanceCommand(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc1.Abort(); + acc2.Abort(); + acc3.Abort(); + } + + { + auto acc = store.Access(); + + EXPECT_TRUE(acc.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc.FindVertex(gid, View::NEW)); + + acc.AdvanceCommand(); + + EXPECT_TRUE(acc.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc.FindVertex(gid, View::NEW)); + + acc.Abort(); + } + + { + auto acc1 = store.Access(); + auto acc2 = store.Access(); + + auto vertex = acc1.FindVertex(gid, View::OLD); + ASSERT_TRUE(vertex); + + ASSERT_TRUE(acc1.DeleteVertex(&*vertex).HasValue()); + + auto acc3 = store.Access(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc2.AdvanceCommand(); + + EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc1.AdvanceCommand(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + acc3.AdvanceCommand(); + + EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + + ASSERT_FALSE(acc1.Commit().HasError()); + ASSERT_FALSE(acc2.Commit().HasError()); + ASSERT_FALSE(acc3.Commit().HasError()); + } + + { + auto acc = store.Access(); + + EXPECT_FALSE(acc.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc.FindVertex(gid, View::NEW)); + + acc.AdvanceCommand(); + + EXPECT_FALSE(acc.FindVertex(gid, View::OLD)); + EXPECT_FALSE(acc.FindVertex(gid, View::NEW)); + + acc.Abort(); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(StorageV3, DeletedVertexAccessor) { + const auto property = store.NameToProperty("property"); + const PropertyValue property_value{"property_value"}; + + std::optional gid; + // Create the vertex + { + auto acc = store.Access(); + auto vertex = acc.CreateVertex(); + gid = vertex.Gid(); + ASSERT_FALSE(vertex.SetProperty(property, property_value).HasError()); + ASSERT_FALSE(acc.Commit().HasError()); + } + + auto acc = store.Access(); + auto vertex = acc.FindVertex(*gid, View::OLD); + ASSERT_TRUE(vertex); + auto maybe_deleted_vertex = acc.DeleteVertex(&*vertex); + ASSERT_FALSE(maybe_deleted_vertex.HasError()); + + auto deleted_vertex = maybe_deleted_vertex.GetValue(); + ASSERT_TRUE(deleted_vertex); + // you cannot modify deleted vertex + ASSERT_TRUE(deleted_vertex->ClearProperties().HasError()); + + // you can call read only methods + const auto maybe_property = deleted_vertex->GetProperty(property, View::OLD); + ASSERT_FALSE(maybe_property.HasError()); + ASSERT_EQ(property_value, *maybe_property); + ASSERT_FALSE(acc.Commit().HasError()); + + { + // you can call read only methods and get valid results even after the + // transaction which deleted the vertex committed, but only if the transaction + // accessor is still alive + const auto maybe_property = deleted_vertex->GetProperty(property, View::OLD); + ASSERT_FALSE(maybe_property.HasError()); + ASSERT_EQ(property_value, *maybe_property); + } +} +} // namespace memgraph::storage::v3::tests diff --git a/tests/unit/storage_v3_key_store.cpp b/tests/unit/storage_v3_key_store.cpp new file mode 100644 index 000000000..4e9ae4da1 --- /dev/null +++ b/tests/unit/storage_v3_key_store.cpp @@ -0,0 +1,46 @@ +// Copyright 2022 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 +#include +#include + +/** + * gtest/gtest.h must be included before rapidcheck/gtest.h! + */ +#include +#include +#include + +#include "storage/v3/id_types.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/property_value.hpp" + +namespace memgraph::storage::v3::test { + +RC_GTEST_PROP(KeyStore, KeyStore, (std::vector values)) { + RC_PRE(!values.empty()); + + std::vector property_values; + property_values.reserve(values.size()); + std::transform(values.begin(), values.end(), std::back_inserter(property_values), + [](std::string &value) { return PropertyValue{std::move(value)}; }); + + KeyStore key_store{property_values}; + + const auto keys = key_store.Keys(); + RC_ASSERT(keys.size() == property_values.size()); + for (int i = 0; i < keys.size(); ++i) { + RC_ASSERT(keys[i] == property_values[i]); + RC_ASSERT(key_store.GetKey(i) == property_values[i]); + } +} +} // namespace memgraph::storage::v3::test diff --git a/tests/unit/storage_v3_property_store.cpp b/tests/unit/storage_v3_property_store.cpp new file mode 100644 index 000000000..2e4a1da19 --- /dev/null +++ b/tests/unit/storage_v3_property_store.cpp @@ -0,0 +1,622 @@ +// Copyright 2022 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 +#include + +#include +#include + +#include "storage/v3/property_store.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/temporal.hpp" + +namespace memgraph::storage::v3::tests { + +class StorageV3PropertyStore : public ::testing::Test { + protected: + PropertyStore props; + + const std::array kSampleValues = { + PropertyValue(), + PropertyValue(false), + PropertyValue(true), + PropertyValue(0), + PropertyValue(33), + PropertyValue(-33), + PropertyValue(-3137), + PropertyValue(3137), + PropertyValue(310000007), + PropertyValue(-310000007), + PropertyValue(3100000000007L), + PropertyValue(-3100000000007L), + PropertyValue(0.0), + PropertyValue(33.33), + PropertyValue(-33.33), + PropertyValue(3137.3137), + PropertyValue(-3137.3137), + PropertyValue("sample"), + PropertyValue(std::string(404, 'n')), + PropertyValue( + std::vector{PropertyValue(33), PropertyValue(std::string("sample")), PropertyValue(-33.33)}), + PropertyValue(std::vector{PropertyValue(), PropertyValue(false)}), + PropertyValue(std::map{{"sample", PropertyValue()}, {"key", PropertyValue(false)}}), + PropertyValue(std::map{ + {"test", PropertyValue(33)}, {"map", PropertyValue(std::string("sample"))}, {"item", PropertyValue(-33.33)}}), + PropertyValue(TemporalData(TemporalType::Date, 23)), + }; + + void AssertPropertyIsEqual(const PropertyStore &store, PropertyId property, const PropertyValue &value) { + ASSERT_TRUE(store.IsPropertyEqual(property, value)); + for (const auto &sample : kSampleValues) { + if (sample == value) { + ASSERT_TRUE(store.IsPropertyEqual(property, sample)); + } else { + ASSERT_FALSE(store.IsPropertyEqual(property, sample)); + } + } + } +}; + +using testing::UnorderedElementsAre; + +TEST_F(StorageV3PropertyStore, StoreTwoProperties) { + const auto make_prop = [](int64_t prop_id_and_value) { + auto prop = PropertyId::FromInt(prop_id_and_value); + auto value = PropertyValue(prop_id_and_value); + return std::make_pair(prop, value); + }; + + const auto first_prop_and_value = make_prop(42); + const auto second_prop_and_value = make_prop(43); + ASSERT_TRUE(props.SetProperty(first_prop_and_value.first, first_prop_and_value.second)); + ASSERT_TRUE(props.SetProperty(second_prop_and_value.first, second_prop_and_value.second)); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(first_prop_and_value, second_prop_and_value)); +} + +TEST_F(StorageV3PropertyStore, Simple) { + auto prop = PropertyId::FromInt(42); + auto value = PropertyValue(42); + ASSERT_TRUE(props.SetProperty(prop, value)); + ASSERT_EQ(props.GetProperty(prop), value); + ASSERT_TRUE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, value); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + + ASSERT_FALSE(props.SetProperty(prop, PropertyValue())); + ASSERT_TRUE(props.GetProperty(prop).IsNull()); + ASSERT_FALSE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, PropertyValue()); + ASSERT_EQ(props.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, SimpleLarge) { + auto prop = PropertyId::FromInt(42); + { + auto value = PropertyValue(std::string(10000, 'a')); + ASSERT_TRUE(props.SetProperty(prop, value)); + ASSERT_EQ(props.GetProperty(prop), value); + ASSERT_TRUE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, value); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + { + auto value = PropertyValue(TemporalData(TemporalType::Date, 23)); + ASSERT_FALSE(props.SetProperty(prop, value)); + ASSERT_EQ(props.GetProperty(prop), value); + ASSERT_TRUE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, value); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + + ASSERT_FALSE(props.SetProperty(prop, PropertyValue())); + ASSERT_TRUE(props.GetProperty(prop).IsNull()); + ASSERT_FALSE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, PropertyValue()); + ASSERT_EQ(props.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, EmptySetToNull) { + auto prop = PropertyId::FromInt(42); + ASSERT_TRUE(props.SetProperty(prop, PropertyValue())); + ASSERT_TRUE(props.GetProperty(prop).IsNull()); + ASSERT_FALSE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, PropertyValue()); + ASSERT_EQ(props.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, Clear) { + auto prop = PropertyId::FromInt(42); + auto value = PropertyValue(42); + ASSERT_TRUE(props.SetProperty(prop, value)); + ASSERT_EQ(props.GetProperty(prop), value); + ASSERT_TRUE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, value); + ASSERT_THAT(props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + ASSERT_TRUE(props.ClearProperties()); + ASSERT_TRUE(props.GetProperty(prop).IsNull()); + ASSERT_FALSE(props.HasProperty(prop)); + AssertPropertyIsEqual(props, prop, PropertyValue()); + ASSERT_EQ(props.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, EmptyClear) { + ASSERT_FALSE(props.ClearProperties()); + ASSERT_EQ(props.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, MoveConstruct) { + PropertyStore props1; + auto prop = PropertyId::FromInt(42); + auto value = PropertyValue(42); + ASSERT_TRUE(props1.SetProperty(prop, value)); + ASSERT_EQ(props1.GetProperty(prop), value); + ASSERT_TRUE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, value); + ASSERT_THAT(props1.Properties(), UnorderedElementsAre(std::pair(prop, value))); + { + PropertyStore props2(std::move(props1)); + ASSERT_EQ(props2.GetProperty(prop), value); + ASSERT_TRUE(props2.HasProperty(prop)); + AssertPropertyIsEqual(props2, prop, value); + ASSERT_THAT(props2.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move,hicpp-invalid-access-moved) + ASSERT_TRUE(props1.GetProperty(prop).IsNull()); + ASSERT_FALSE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, PropertyValue()); + ASSERT_EQ(props1.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, MoveConstructLarge) { + PropertyStore props1; + auto prop = PropertyId::FromInt(42); + auto value = PropertyValue(std::string(10000, 'a')); + ASSERT_TRUE(props1.SetProperty(prop, value)); + ASSERT_EQ(props1.GetProperty(prop), value); + ASSERT_TRUE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, value); + ASSERT_THAT(props1.Properties(), UnorderedElementsAre(std::pair(prop, value))); + { + PropertyStore props2(std::move(props1)); + ASSERT_EQ(props2.GetProperty(prop), value); + ASSERT_TRUE(props2.HasProperty(prop)); + AssertPropertyIsEqual(props2, prop, value); + ASSERT_THAT(props2.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move,hicpp-invalid-access-moved) + ASSERT_TRUE(props1.GetProperty(prop).IsNull()); + ASSERT_FALSE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, PropertyValue()); + ASSERT_EQ(props1.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, MoveAssign) { + PropertyStore props1; + auto prop = PropertyId::FromInt(42); + auto value = PropertyValue(42); + ASSERT_TRUE(props1.SetProperty(prop, value)); + ASSERT_EQ(props1.GetProperty(prop), value); + ASSERT_TRUE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, value); + ASSERT_THAT(props1.Properties(), UnorderedElementsAre(std::pair(prop, value))); + { + auto value2 = PropertyValue(68); + PropertyStore props2; + ASSERT_TRUE(props2.SetProperty(prop, value2)); + ASSERT_EQ(props2.GetProperty(prop), value2); + ASSERT_TRUE(props2.HasProperty(prop)); + AssertPropertyIsEqual(props2, prop, value2); + ASSERT_THAT(props2.Properties(), UnorderedElementsAre(std::pair(prop, value2))); + props2 = std::move(props1); + ASSERT_EQ(props2.GetProperty(prop), value); + ASSERT_TRUE(props2.HasProperty(prop)); + AssertPropertyIsEqual(props2, prop, value); + ASSERT_THAT(props2.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move,hicpp-invalid-access-moved) + ASSERT_TRUE(props1.GetProperty(prop).IsNull()); + ASSERT_FALSE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, PropertyValue()); + ASSERT_EQ(props1.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, MoveAssignLarge) { + PropertyStore props1; + auto prop = PropertyId::FromInt(42); + auto value = PropertyValue(std::string(10000, 'a')); + ASSERT_TRUE(props1.SetProperty(prop, value)); + ASSERT_EQ(props1.GetProperty(prop), value); + ASSERT_TRUE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, value); + ASSERT_THAT(props1.Properties(), UnorderedElementsAre(std::pair(prop, value))); + { + auto value2 = PropertyValue(std::string(10000, 'b')); + PropertyStore props2; + ASSERT_TRUE(props2.SetProperty(prop, value2)); + ASSERT_EQ(props2.GetProperty(prop), value2); + ASSERT_TRUE(props2.HasProperty(prop)); + AssertPropertyIsEqual(props2, prop, value2); + ASSERT_THAT(props2.Properties(), UnorderedElementsAre(std::pair(prop, value2))); + props2 = std::move(props1); + ASSERT_EQ(props2.GetProperty(prop), value); + ASSERT_TRUE(props2.HasProperty(prop)); + AssertPropertyIsEqual(props2, prop, value); + ASSERT_THAT(props2.Properties(), UnorderedElementsAre(std::pair(prop, value))); + } + // NOLINTNEXTLINE(bugprone-use-after-move,clang-analyzer-cplusplus.Move,hicpp-invalid-access-moved) + ASSERT_TRUE(props1.GetProperty(prop).IsNull()); + ASSERT_FALSE(props1.HasProperty(prop)); + AssertPropertyIsEqual(props1, prop, PropertyValue()); + ASSERT_EQ(props1.Properties().size(), 0); +} + +TEST_F(StorageV3PropertyStore, EmptySet) { + std::vector vec{PropertyValue(true), PropertyValue(123), PropertyValue()}; + std::map map{{"nandare", PropertyValue(false)}}; + const TemporalData temporal{TemporalType::LocalDateTime, 23}; + std::vector data{PropertyValue(true), PropertyValue(123), PropertyValue(123.5), + PropertyValue("nandare"), PropertyValue(vec), PropertyValue(map), + PropertyValue(temporal)}; + + auto prop = PropertyId::FromInt(42); + for (const auto &value : data) { + PropertyStore local_props; + + ASSERT_TRUE(local_props.SetProperty(prop, value)); + ASSERT_EQ(local_props.GetProperty(prop), value); + ASSERT_TRUE(local_props.HasProperty(prop)); + AssertPropertyIsEqual(local_props, prop, value); + ASSERT_THAT(local_props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + ASSERT_FALSE(local_props.SetProperty(prop, value)); + ASSERT_EQ(local_props.GetProperty(prop), value); + ASSERT_TRUE(local_props.HasProperty(prop)); + AssertPropertyIsEqual(local_props, prop, value); + ASSERT_THAT(local_props.Properties(), UnorderedElementsAre(std::pair(prop, value))); + ASSERT_FALSE(local_props.SetProperty(prop, PropertyValue())); + ASSERT_TRUE(local_props.GetProperty(prop).IsNull()); + ASSERT_FALSE(local_props.HasProperty(prop)); + AssertPropertyIsEqual(local_props, prop, PropertyValue()); + ASSERT_EQ(local_props.Properties().size(), 0); + ASSERT_TRUE(local_props.SetProperty(prop, PropertyValue())); + ASSERT_TRUE(local_props.GetProperty(prop).IsNull()); + ASSERT_FALSE(local_props.HasProperty(prop)); + AssertPropertyIsEqual(local_props, prop, PropertyValue()); + ASSERT_EQ(local_props.Properties().size(), 0); + } +} + +TEST_F(StorageV3PropertyStore, FullSet) { + std::vector vec{PropertyValue(true), PropertyValue(123), PropertyValue()}; + std::map map{{"nandare", PropertyValue(false)}}; + const TemporalData temporal{TemporalType::LocalDateTime, 23}; + std::map data{ + {PropertyId::FromInt(1), PropertyValue(true)}, {PropertyId::FromInt(2), PropertyValue(123)}, + {PropertyId::FromInt(3), PropertyValue(123.5)}, {PropertyId::FromInt(4), PropertyValue("nandare")}, + {PropertyId::FromInt(5), PropertyValue(vec)}, {PropertyId::FromInt(6), PropertyValue(map)}, + {PropertyId::FromInt(7), PropertyValue(temporal)}}; + + std::vector alt{PropertyValue(), + PropertyValue(std::string()), + PropertyValue(std::string(10, 'a')), + PropertyValue(std::string(100, 'a')), + PropertyValue(std::string(1000, 'a')), + PropertyValue(std::string(10000, 'a')), + PropertyValue(std::string(100000, 'a'))}; + + for (const auto &target : data) { + for (const auto &item : data) { + ASSERT_TRUE(props.SetProperty(item.first, item.second)); + } + + for (size_t i = 0; i < alt.size(); ++i) { + if (i == 1) { + ASSERT_TRUE(props.SetProperty(target.first, alt[i])); + } else { + ASSERT_FALSE(props.SetProperty(target.first, alt[i])); + } + for (const auto &item : data) { + if (item.first == target.first) { + ASSERT_EQ(props.GetProperty(item.first), alt[i]); + if (alt[i].IsNull()) { + ASSERT_FALSE(props.HasProperty(item.first)); + } else { + ASSERT_TRUE(props.HasProperty(item.first)); + } + AssertPropertyIsEqual(props, item.first, alt[i]); + } else { + ASSERT_EQ(props.GetProperty(item.first), item.second); + ASSERT_TRUE(props.HasProperty(item.first)); + AssertPropertyIsEqual(props, item.first, item.second); + } + } + auto current = data; + if (alt[i].IsNull()) { + current.erase(target.first); + } else { + current[target.first] = alt[i]; + } + ASSERT_EQ(props.Properties(), current); + } + + for (ssize_t i = alt.size() - 1; i >= 0; --i) { + ASSERT_FALSE(props.SetProperty(target.first, alt[i])); + for (const auto &item : data) { + if (item.first == target.first) { + ASSERT_EQ(props.GetProperty(item.first), alt[i]); + if (alt[i].IsNull()) { + ASSERT_FALSE(props.HasProperty(item.first)); + } else { + ASSERT_TRUE(props.HasProperty(item.first)); + } + AssertPropertyIsEqual(props, item.first, alt[i]); + } else { + ASSERT_EQ(props.GetProperty(item.first), item.second); + ASSERT_TRUE(props.HasProperty(item.first)); + AssertPropertyIsEqual(props, item.first, item.second); + } + } + auto current = data; + if (alt[i].IsNull()) { + current.erase(target.first); + } else { + current[target.first] = alt[i]; + } + ASSERT_EQ(props.Properties(), current); + } + + ASSERT_TRUE(props.SetProperty(target.first, target.second)); + ASSERT_EQ(props.GetProperty(target.first), target.second); + ASSERT_TRUE(props.HasProperty(target.first)); + AssertPropertyIsEqual(props, target.first, target.second); + + props.ClearProperties(); + ASSERT_EQ(props.Properties().size(), 0); + for (const auto &item : data) { + ASSERT_TRUE(props.GetProperty(item.first).IsNull()); + ASSERT_FALSE(props.HasProperty(item.first)); + AssertPropertyIsEqual(props, item.first, PropertyValue()); + } + } +} + +TEST_F(StorageV3PropertyStore, IntEncoding) { + std::map data{ + // {PropertyId::FromUint(0UL), + // PropertyValue(std::numeric_limits::min())}, + // {PropertyId::FromUint(10UL), PropertyValue(-137438953472L)}, + // {PropertyId::FromUint(std::numeric_limits::max()), + // PropertyValue(-4294967297L)}, + // {PropertyId::FromUint(256UL), + // PropertyValue(std::numeric_limits::min())}, + // {PropertyId::FromUint(1024UL), PropertyValue(-1048576L)}, + // {PropertyId::FromUint(1025UL), PropertyValue(-65537L)}, + // {PropertyId::FromUint(1026UL), + // PropertyValue(std::numeric_limits::min())}, + // {PropertyId::FromUint(1027UL), PropertyValue(-1024L)}, + // {PropertyId::FromUint(2000UL), PropertyValue(-257L)}, + // {PropertyId::FromUint(3000UL), + // PropertyValue(std::numeric_limits::min())}, + // {PropertyId::FromUint(4000UL), PropertyValue(-1L)}, + // {PropertyId::FromUint(10000UL), PropertyValue(0L)}, + // {PropertyId::FromUint(20000UL), PropertyValue(1L)}, + // {PropertyId::FromUint(30000UL), + // PropertyValue(std::numeric_limits::max())}, + // {PropertyId::FromUint(40000UL), PropertyValue(256L)}, + // {PropertyId::FromUint(50000UL), PropertyValue(1024L)}, + // {PropertyId::FromUint(std::numeric_limits::max()), + // PropertyValue(std::numeric_limits::max())}, + // {PropertyId::FromUint(65536UL), PropertyValue(65536L)}, + // {PropertyId::FromUint(1048576UL), PropertyValue(1048576L)}, + // {PropertyId::FromUint(std::numeric_limits::max()), + // PropertyValue(std::numeric_limits::max())}, + {PropertyId::FromUint(4294967296UL), PropertyValue(4294967296L)}, + {PropertyId::FromUint(137438953472UL), PropertyValue(137438953472L)}, + {PropertyId::FromUint(std::numeric_limits::max()), PropertyValue(std::numeric_limits::max())}}; + + for (const auto &item : data) { + ASSERT_TRUE(props.SetProperty(item.first, item.second)); + ASSERT_EQ(props.GetProperty(item.first), item.second); + ASSERT_TRUE(props.HasProperty(item.first)); + AssertPropertyIsEqual(props, item.first, item.second); + } + for (auto it = data.rbegin(); it != data.rend(); ++it) { + const auto &item = *it; + ASSERT_FALSE(props.SetProperty(item.first, item.second)) << item.first.AsInt(); + ASSERT_EQ(props.GetProperty(item.first), item.second); + ASSERT_TRUE(props.HasProperty(item.first)); + AssertPropertyIsEqual(props, item.first, item.second); + } + + ASSERT_EQ(props.Properties(), data); + + props.ClearProperties(); + ASSERT_EQ(props.Properties().size(), 0); + for (const auto &item : data) { + ASSERT_TRUE(props.GetProperty(item.first).IsNull()); + ASSERT_FALSE(props.HasProperty(item.first)); + AssertPropertyIsEqual(props, item.first, PropertyValue()); + } +} + +TEST_F(StorageV3PropertyStore, IsPropertyEqualIntAndDouble) { + auto prop = PropertyId::FromInt(42); + + ASSERT_TRUE(props.SetProperty(prop, PropertyValue(42))); + + std::vector> tests{ + {PropertyValue(0), PropertyValue(0.0)}, + {PropertyValue(123), PropertyValue(123.0)}, + {PropertyValue(12345), PropertyValue(12345.0)}, + {PropertyValue(12345678), PropertyValue(12345678.0)}, + {PropertyValue(1234567890123L), PropertyValue(1234567890123.0)}, + }; + + // Test equality with raw values. + for (auto test : tests) { + ASSERT_EQ(test.first, test.second); + + // Test first, second + ASSERT_FALSE(props.SetProperty(prop, test.first)); + ASSERT_EQ(props.GetProperty(prop), test.first); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + + // Test second, first + ASSERT_FALSE(props.SetProperty(prop, test.second)); + ASSERT_EQ(props.GetProperty(prop), test.second); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + + // Make both negative + test.first = PropertyValue(test.first.ValueInt() * -1); + test.second = PropertyValue(test.second.ValueDouble() * -1.0); + ASSERT_EQ(test.first, test.second); + + // Test -first, -second + ASSERT_FALSE(props.SetProperty(prop, test.first)); + ASSERT_EQ(props.GetProperty(prop), test.first); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + + // Test -second, -first + ASSERT_FALSE(props.SetProperty(prop, test.second)); + ASSERT_EQ(props.GetProperty(prop), test.second); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + } + + // Test equality with values wrapped in lists. + for (auto test : tests) { + test.first = PropertyValue(std::vector{PropertyValue(test.first.ValueInt())}); + test.second = PropertyValue(std::vector{PropertyValue(test.second.ValueDouble())}); + ASSERT_EQ(test.first, test.second); + + // Test first, second + ASSERT_FALSE(props.SetProperty(prop, test.first)); + ASSERT_EQ(props.GetProperty(prop), test.first); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + + // Test second, first + ASSERT_FALSE(props.SetProperty(prop, test.second)); + ASSERT_EQ(props.GetProperty(prop), test.second); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + + // Make both negative + test.first = PropertyValue(std::vector{PropertyValue(test.first.ValueList()[0].ValueInt() * -1)}); + test.second = + PropertyValue(std::vector{PropertyValue(test.second.ValueList()[0].ValueDouble() * -1.0)}); + ASSERT_EQ(test.first, test.second); + + // Test -first, -second + ASSERT_FALSE(props.SetProperty(prop, test.first)); + ASSERT_EQ(props.GetProperty(prop), test.first); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + + // Test -second, -first + ASSERT_FALSE(props.SetProperty(prop, test.second)); + ASSERT_EQ(props.GetProperty(prop), test.second); + ASSERT_TRUE(props.HasProperty(prop)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.second)); + ASSERT_TRUE(props.IsPropertyEqual(prop, test.first)); + } +} + +TEST_F(StorageV3PropertyStore, IsPropertyEqualString) { + auto prop = PropertyId::FromInt(42); + ASSERT_TRUE(props.SetProperty(prop, PropertyValue("test"))); + ASSERT_TRUE(props.IsPropertyEqual(prop, PropertyValue("test"))); + + // Different length. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue("helloworld"))); + + // Same length, different value. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue("asdf"))); + + // Shortened and extended. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue("tes"))); + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue("testt"))); +} + +TEST_F(StorageV3PropertyStore, IsPropertyEqualList) { + auto prop = PropertyId::FromInt(42); + ASSERT_TRUE( + props.SetProperty(prop, PropertyValue(std::vector{PropertyValue(42), PropertyValue("test")}))); + ASSERT_TRUE( + props.IsPropertyEqual(prop, PropertyValue(std::vector{PropertyValue(42), PropertyValue("test")}))); + + // Different length. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::vector{PropertyValue(24)}))); + + // Same length, different value. + ASSERT_FALSE( + props.IsPropertyEqual(prop, PropertyValue(std::vector{PropertyValue(42), PropertyValue("asdf")}))); + + // Shortened and extended. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::vector{PropertyValue(42)}))); + ASSERT_FALSE(props.IsPropertyEqual( + prop, PropertyValue(std::vector{PropertyValue(42), PropertyValue("test"), PropertyValue(true)}))); +} + +TEST_F(StorageV3PropertyStore, IsPropertyEqualMap) { + auto prop = PropertyId::FromInt(42); + ASSERT_TRUE(props.SetProperty(prop, PropertyValue(std::map{ + {"abc", PropertyValue(42)}, {"zyx", PropertyValue("test")}}))); + ASSERT_TRUE(props.IsPropertyEqual(prop, PropertyValue(std::map{ + {"abc", PropertyValue(42)}, {"zyx", PropertyValue("test")}}))); + + // Different length. + ASSERT_FALSE( + props.IsPropertyEqual(prop, PropertyValue(std::map{{"fgh", PropertyValue(24)}}))); + + // Same length, different value. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::map{ + {"abc", PropertyValue(42)}, {"zyx", PropertyValue("testt")}}))); + + // Same length, different key (different length). + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::map{ + {"abc", PropertyValue(42)}, {"zyxw", PropertyValue("test")}}))); + + // Same length, different key (same length). + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::map{ + {"abc", PropertyValue(42)}, {"zyw", PropertyValue("test")}}))); + + // Shortened and extended. + ASSERT_FALSE( + props.IsPropertyEqual(prop, PropertyValue(std::map{{"abc", PropertyValue(42)}}))); + ASSERT_FALSE(props.IsPropertyEqual( + prop, PropertyValue(std::map{ + {"abc", PropertyValue(42)}, {"sdf", PropertyValue(true)}, {"zyx", PropertyValue("test")}}))); +} + +TEST_F(StorageV3PropertyStore, IsPropertyEqualTemporalData) { + auto prop = PropertyId::FromInt(42); + const TemporalData temporal{TemporalType::Date, 23}; + ASSERT_TRUE(props.SetProperty(prop, PropertyValue(temporal))); + ASSERT_TRUE(props.IsPropertyEqual(prop, PropertyValue(temporal))); + + // Different type. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(TemporalData{TemporalType::Duration, 23}))); + + // Same type, different value. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(TemporalData{TemporalType::Date, 30}))); +} +} // namespace memgraph::storage::v3::tests diff --git a/tests/unit/storage_v3_test_utils.cpp b/tests/unit/storage_v3_test_utils.cpp new file mode 100644 index 000000000..b1a59b7aa --- /dev/null +++ b/tests/unit/storage_v3_test_utils.cpp @@ -0,0 +1,23 @@ +// Copyright 2022 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_v3_test_utils.hpp" + +namespace memgraph::storage::v3::tests { + +size_t CountVertices(Storage::Accessor &storage_accessor, View view) { + auto vertices = storage_accessor.Vertices(view); + size_t count = 0U; + for (auto it = vertices.begin(); it != vertices.end(); ++it, ++count) + ; + return count; +} +} // namespace memgraph::storage::v3::tests diff --git a/tests/unit/storage_v3_test_utils.hpp b/tests/unit/storage_v3_test_utils.hpp new file mode 100644 index 000000000..1cc772036 --- /dev/null +++ b/tests/unit/storage_v3_test_utils.hpp @@ -0,0 +1,21 @@ +// Copyright 2022 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/v3/storage.hpp" +#include "storage/v3/view.hpp" + +namespace memgraph::storage::v3::tests { + +size_t CountVertices(Storage::Accessor &storage_accessor, View view); + +} // namespace memgraph::storage::v3::tests From 95dbc022c00eb1bf966d15bfbb6963b8d81064f4 Mon Sep 17 00:00:00 2001 From: Jure Bajic Date: Mon, 29 Aug 2022 14:38:25 +0200 Subject: [PATCH 02/10] Integrate schema and keystore (#497) - Integrate schema and keystore on vertex creation - Add GC test for storage v3 - Add tests for accessors - Fix all tests related to this except for query v2 - Fix labels not returning primary label --- src/glue/v2/communication.cpp | 10 +- src/query/v2/common.hpp | 25 +- src/query/v2/db_accessor.hpp | 32 +- src/query/v2/plan/operator.cpp | 56 +- src/query/v2/procedure/mg_procedure_impl.cpp | 128 +- src/query/v2/procedure/py_module.cpp | 7 +- src/query/v2/trigger_context.cpp | 12 +- src/query/v2/trigger_context.hpp | 43 +- src/query/v2/typed_value.cpp | 3 +- src/storage/v3/durability/snapshot.cpp | 105 +- src/storage/v3/durability/wal.cpp | 592 ++-- src/storage/v3/id_types.hpp | 1 + src/storage/v3/indices.cpp | 1 + src/storage/v3/indices.hpp | 6 +- src/storage/v3/key_store.cpp | 6 +- src/storage/v3/key_store.hpp | 13 +- .../v3/replication/replication_server.cpp | 490 +-- src/storage/v3/schema_validator.cpp | 2 +- src/storage/v3/schema_validator.hpp | 2 +- src/storage/v3/schemas.cpp | 11 + src/storage/v3/schemas.hpp | 8 +- src/storage/v3/storage.cpp | 149 +- src/storage/v3/storage.hpp | 22 +- src/storage/v3/vertex.hpp | 42 +- src/storage/v3/vertex_accessor.cpp | 39 + src/storage/v3/vertex_accessor.hpp | 43 +- tests/unit/CMakeLists.txt | 36 +- tests/unit/query_v2_interpreter.cpp | 3101 +++++++++-------- ...query_plan_v2_create_set_remove_delete.cpp | 8 +- tests/unit/storage_v3.cpp | 1010 +++--- tests/unit/storage_v3_schema.cpp | 4 +- tests/unit/storage_v3_vertex_accessors.cpp | 209 ++ 32 files changed, 3305 insertions(+), 2911 deletions(-) create mode 100644 tests/unit/storage_v3_vertex_accessors.cpp diff --git a/src/glue/v2/communication.cpp b/src/glue/v2/communication.cpp index 6d99a9a92..55dfd8838 100644 --- a/src/glue/v2/communication.cpp +++ b/src/glue/v2/communication.cpp @@ -133,7 +133,8 @@ storage::v3::Result ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Result ToBoltVertex(const storage::v3::VertexAccessor &vertex, const storage::v3::Storage &db, storage::v3::View view) { - auto id = communication::bolt::Id::FromUint(vertex.Gid().AsUint()); + // TODO(jbajic) Fix bolt communication + auto id = communication::bolt::Id::FromUint(0); auto maybe_labels = vertex.Labels(view); if (maybe_labels.HasError()) return maybe_labels.GetError(); std::vector labels; @@ -152,9 +153,10 @@ storage::v3::Result ToBoltVertex(const storage::v3: storage::v3::Result ToBoltEdge(const storage::v3::EdgeAccessor &edge, const storage::v3::Storage &db, storage::v3::View view) { - auto id = communication::bolt::Id::FromUint(edge.Gid().AsUint()); - auto from = communication::bolt::Id::FromUint(edge.FromVertex().Gid().AsUint()); - auto to = communication::bolt::Id::FromUint(edge.ToVertex().Gid().AsUint()); + // TODO(jbajic) Fix bolt communication + auto id = communication::bolt::Id::FromUint(0); + auto from = communication::bolt::Id::FromUint(0); + auto to = communication::bolt::Id::FromUint(0); const auto &type = db.EdgeTypeToName(edge.EdgeType()); auto maybe_properties = edge.Properties(view); if (maybe_properties.HasError()) return maybe_properties.GetError(); diff --git a/src/query/v2/common.hpp b/src/query/v2/common.hpp index ee8f72ddf..447e588ff 100644 --- a/src/query/v2/common.hpp +++ b/src/query/v2/common.hpp @@ -86,6 +86,18 @@ concept AccessorWithSetProperty = requires(T accessor, const storage::v3::Proper { accessor.SetProperty(key, new_value) } -> std::same_as>; }; +template +concept AccessorWithSetPropertyAndValidate = requires(T accessor, const storage::v3::PropertyId key, + const storage::v3::PropertyValue new_value) { + { + accessor.SetPropertyAndValidate(key, new_value) + } -> std::same_as>; +}; + +template +concept RecordAccessor = + AccessorWithSetProperty || AccessorWithSetPropertyAndValidate; + inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_violation, const DbAccessor &dba) { switch (schema_violation.status) { case storage::v3::SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY: { @@ -109,13 +121,14 @@ inline void HandleSchemaViolation(const storage::v3::SchemaViolation &schema_vio *schema_violation.violated_property_value, dba.LabelToName(schema_violation.label))); } - case storage::v3::SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL: { - throw SchemaViolationException(fmt::format("Cannot add or remove label :{} since it is a primary label", - dba.LabelToName(schema_violation.label))); + case storage::v3::SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL: { + throw SchemaViolationException(fmt::format( + "Adding primary label as secondary or removing primary label:", *schema_violation.violated_property_value, + dba.LabelToName(schema_violation.label))); } case storage::v3::SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY: { - throw SchemaViolationException( - fmt::format("Cannot create vertex with secondary label :{}", dba.LabelToName(schema_violation.label))); + throw SchemaViolationException(fmt::format("Cannot create vertex where primary label is secondary:{}", + dba.LabelToName(schema_violation.label))); } } } @@ -137,7 +150,7 @@ inline void HandleErrorOnPropertyUpdate(const storage::v3::Error error) { /// Set a property `value` mapped with given `key` on a `record`. /// /// @throw QueryRuntimeException if value cannot be set as a property value -template +template storage::v3::PropertyValue PropsSetChecked(T *record, const DbAccessor &dba, const storage::v3::PropertyId &key, const TypedValue &value) { try { diff --git a/src/query/v2/db_accessor.hpp b/src/query/v2/db_accessor.hpp index a09bcffcb..beacf2138 100644 --- a/src/query/v2/db_accessor.hpp +++ b/src/query/v2/db_accessor.hpp @@ -11,6 +11,7 @@ #pragma once +#include #include #include @@ -19,6 +20,7 @@ #include "query/v2/exceptions.hpp" #include "storage/v3/id_types.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/result.hpp" @@ -109,13 +111,17 @@ class VertexAccessor final { auto PrimaryLabel(storage::v3::View view) const { return impl_.PrimaryLabel(view); } - storage::v3::Result AddLabel(storage::v3::LabelId label) { return impl_.AddLabel(label); } + auto PrimaryKey(storage::v3::View view) const { return impl_.PrimaryKey(view); } + + storage::v3::ResultSchema AddLabel(storage::v3::LabelId label) { return impl_.AddLabelAndValidate(label); } storage::v3::ResultSchema AddLabelAndValidate(storage::v3::LabelId label) { return impl_.AddLabelAndValidate(label); } - storage::v3::Result RemoveLabel(storage::v3::LabelId label) { return impl_.RemoveLabel(label); } + storage::v3::ResultSchema RemoveLabel(storage::v3::LabelId label) { + return impl_.RemoveLabelAndValidate(label); + } storage::v3::ResultSchema RemoveLabelAndValidate(storage::v3::LabelId label) { return impl_.RemoveLabelAndValidate(label); @@ -132,9 +138,9 @@ class VertexAccessor final { return impl_.GetProperty(key, view); } - storage::v3::Result SetProperty(storage::v3::PropertyId key, - const storage::v3::PropertyValue &value) { - return impl_.SetProperty(key, value); + storage::v3::ResultSchema SetProperty(storage::v3::PropertyId key, + const storage::v3::PropertyValue &value) { + return impl_.SetPropertyAndValidate(key, value); } storage::v3::ResultSchema SetPropertyAndValidate( @@ -188,9 +194,8 @@ class VertexAccessor final { storage::v3::Result OutDegree(storage::v3::View view) const { return impl_.OutDegree(view); } - int64_t CypherId() const { return impl_.Gid().AsInt(); } - - storage::v3::Gid Gid() const noexcept { return impl_.Gid(); } + // TODO(jbajic) Fix Remove Gid + static int64_t CypherId() { return 1; } bool operator==(const VertexAccessor &v) const noexcept { static_assert(noexcept(impl_ == v.impl_)); @@ -241,8 +246,12 @@ class DbAccessor final { public: explicit DbAccessor(storage::v3::Storage::Accessor *accessor) : accessor_(accessor) {} - std::optional FindVertex(storage::v3::Gid gid, storage::v3::View view) { - auto maybe_vertex = accessor_->FindVertex(gid, view); + // TODO(jbajic) Fix Remove Gid + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + std::optional FindVertex(uint64_t /*unused*/) { return std::nullopt; } + + std::optional FindVertex(storage::v3::PrimaryKey &primary_key, storage::v3::View view) { + auto maybe_vertex = accessor_->FindVertex(primary_key, view); if (maybe_vertex) return VertexAccessor(*maybe_vertex); return std::nullopt; } @@ -270,9 +279,6 @@ class DbAccessor final { return VerticesIterable(accessor_->Vertices(label, property, lower, upper, view)); } - // TODO Remove when query modules have been fixed - [[deprecated]] VertexAccessor InsertVertex() { return VertexAccessor(accessor_->CreateVertex()); } - storage::v3::ResultSchema InsertVertexAndValidate( const storage::v3::LabelId primary_label, const std::vector &labels, const std::vector> &properties) { diff --git a/src/query/v2/plan/operator.cpp b/src/query/v2/plan/operator.cpp index 4e3005d75..a76d4f9d2 100644 --- a/src/query/v2/plan/operator.cpp +++ b/src/query/v2/plan/operator.cpp @@ -607,17 +607,16 @@ ACCEPT_WITH_INPUT(ScanAllById) UniqueCursorPtr ScanAllById::MakeCursor(utils::MemoryResource *mem) const { EventCounter::IncrementCounter(EventCounter::ScanAllByIdOperator); - + // TODO Reimplement when we have reliable conversion between hash value and pk auto vertices = [this](Frame &frame, ExecutionContext &context) -> std::optional> { - auto *db = context.db_accessor; - ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, view_); - auto value = expression_->Accept(evaluator); - if (!value.IsNumeric()) return std::nullopt; - int64_t id = value.IsInt() ? value.ValueInt() : value.ValueDouble(); - if (value.IsDouble() && id != value.ValueDouble()) return std::nullopt; - auto maybe_vertex = db->FindVertex(storage::v3::Gid::FromInt(id), view_); - if (!maybe_vertex) return std::nullopt; - return std::vector{*maybe_vertex}; + // auto *db = context.db_accessor; + // ExpressionEvaluator evaluator(&frame, context.symbol_table, context.evaluation_context, context.db_accessor, + // view_); auto value = expression_->Accept(evaluator); if (!value.IsNumeric()) return std::nullopt; int64_t id = + // value.IsInt() ? value.ValueInt() : value.ValueDouble(); if (value.IsDouble() && id != value.ValueDouble()) return + // std::nullopt; auto maybe_vertex = db->FindVertex(storage::v3::Gid::FromInt(id), view_); auto maybe_vertex = + // nullptr; if (!maybe_vertex) return std::nullopt; + return std::nullopt; + // return std::vector{*maybe_vertex}; }; return MakeUniqueCursorPtr>(mem, output_symbol_, input_->MakeCursor(mem), std::move(vertices), "ScanAllById"); @@ -2134,7 +2133,7 @@ concept AccessorWithProperties = requires(T value, storage::v3::PropertyId prope /// /// @tparam TRecordAccessor Either RecordAccessor or /// RecordAccessor -template +template void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetProperties::Op op, ExecutionContext *context) { std::optional> old_values; @@ -2198,23 +2197,26 @@ void SetPropertiesOnRecord(TRecordAccessor *record, const TypedValue &rhs, SetPr auto set_props = [&, record](auto properties) { for (auto &kv : properties) { - auto maybe_error = record->SetProperty(kv.first, kv.second); - if (maybe_error.HasError()) { - switch (maybe_error.GetError()) { - case storage::v3::Error::DELETED_OBJECT: - throw QueryRuntimeException("Trying to set properties on a deleted graph element."); - case storage::v3::Error::SERIALIZATION_ERROR: - throw TransactionSerializationException(); - case storage::v3::Error::PROPERTIES_DISABLED: - throw QueryRuntimeException("Can't set property because properties on edges are disabled."); - case storage::v3::Error::VERTEX_HAS_EDGES: - case storage::v3::Error::NONEXISTENT_OBJECT: - throw QueryRuntimeException("Unexpected error when setting properties."); + if constexpr (AccessorWithSetPropertyAndValidate) { + const auto maybe_error = record->SetPropertyAndValidate(kv.first, storage::v3::PropertyValue(kv.second)); + if (maybe_error.HasError()) { + std::visit(utils::Overloaded{[](const storage::v3::Error error) { HandleErrorOnPropertyUpdate(error); }, + [&context](const storage::v3::SchemaViolation &schema_violation) { + HandleSchemaViolation(schema_violation, *context->db_accessor); + }}, + maybe_error.GetError()); + } + if (should_register_change) { + register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second)); + } + } else { + auto maybe_error = record->SetProperty(kv.first, kv.second); + if (maybe_error.HasError()) { + HandleErrorOnPropertyUpdate(maybe_error.GetError()); + } + if (should_register_change) { + register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second)); } - } - - if (should_register_change) { - register_set_property(std::move(*maybe_error), kv.first, std::move(kv.second)); } } }; diff --git a/src/query/v2/procedure/mg_procedure_impl.cpp b/src/query/v2/procedure/mg_procedure_impl.cpp index b2a467ef0..11b65f0dd 100644 --- a/src/query/v2/procedure/mg_procedure_impl.cpp +++ b/src/query/v2/procedure/mg_procedure_impl.cpp @@ -27,6 +27,7 @@ #include "query/v2/procedure/cypher_types.hpp" #include "query/v2/procedure/mg_procedure_helpers.hpp" #include "query/v2/stream/common.hpp" +#include "storage/v3/id_types.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/view.hpp" #include "utils/algorithm.hpp" @@ -1508,8 +1509,9 @@ mgp_error mgp_properties_iterator_next(mgp_properties_iterator *it, mgp_property result); } +// TODO(jbajic) Fix Remove Gid mgp_error mgp_vertex_get_id(mgp_vertex *v, mgp_vertex_id *result) { - return WrapExceptions([v] { return mgp_vertex_id{.as_int = v->impl.Gid().AsInt()}; }, result); + return WrapExceptions([] { return mgp_vertex_id{.as_int = 0}; }, result); } mgp_error mgp_vertex_underlying_graph_is_mutable(mgp_vertex *v, int *result) { @@ -1584,17 +1586,19 @@ mgp_error mgp_vertex_set_property(struct mgp_vertex *v, const char *property_nam const auto prop_key = v->graph->impl->NameToProperty(property_name); const auto result = v->impl.SetProperty(prop_key, ToPropertyValue(*property_value)); if (result.HasError()) { - switch (result.GetError()) { - case memgraph::storage::v3::Error::DELETED_OBJECT: - throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"}; - case memgraph::storage::v3::Error::NONEXISTENT_OBJECT: - LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of a vertex!"); - case memgraph::storage::v3::Error::PROPERTIES_DISABLED: - case memgraph::storage::v3::Error::VERTEX_HAS_EDGES: - LOG_FATAL("Unexpected error when setting a property of a vertex."); - case memgraph::storage::v3::Error::SERIALIZATION_ERROR: - throw SerializationException{"Cannot serialize setting a property of a vertex."}; - } + // TODO(jbajic) Fix query modules + // switch (result.GetError()) { + // case memgraph::storage::v3::Error::DELETED_OBJECT: + // throw DeletedObjectException{"Cannot set the properties of a deleted vertex!"}; + // case memgraph::storage::v3::Error::NONEXISTENT_OBJECT: + // LOG_FATAL("Query modules shouldn't have access to nonexistent objects when setting a property of a + // vertex!"); + // case memgraph::storage::v3::Error::PROPERTIES_DISABLED: + // case memgraph::storage::v3::Error::VERTEX_HAS_EDGES: + // LOG_FATAL("Unexpected error when setting a property of a vertex."); + // case memgraph::storage::v3::Error::SERIALIZATION_ERROR: + // throw SerializationException{"Cannot serialize setting a property of a vertex."}; + // } } auto &ctx = v->graph->ctx; @@ -1625,17 +1629,18 @@ mgp_error mgp_vertex_add_label(struct mgp_vertex *v, mgp_label label) { const auto result = v->impl.AddLabel(label_id); if (result.HasError()) { - switch (result.GetError()) { - case memgraph::storage::v3::Error::DELETED_OBJECT: - throw DeletedObjectException{"Cannot add a label to a deleted vertex!"}; - case memgraph::storage::v3::Error::NONEXISTENT_OBJECT: - LOG_FATAL("Query modules shouldn't have access to nonexistent objects when adding a label to a vertex!"); - case memgraph::storage::v3::Error::PROPERTIES_DISABLED: - case memgraph::storage::v3::Error::VERTEX_HAS_EDGES: - LOG_FATAL("Unexpected error when adding a label to a vertex."); - case memgraph::storage::v3::Error::SERIALIZATION_ERROR: - throw SerializationException{"Cannot serialize adding a label to a vertex."}; - } + // TODO(jbajic) Fix query modules + // switch (result.GetError()) { + // case memgraph::storage::v3::Error::DELETED_OBJECT: + // throw DeletedObjectException{"Cannot add a label to a deleted vertex!"}; + // case memgraph::storage::v3::Error::NONEXISTENT_OBJECT: + // LOG_FATAL("Query modules shouldn't have access to nonexistent objects when adding a label to a vertex!"); + // case memgraph::storage::v3::Error::PROPERTIES_DISABLED: + // case memgraph::storage::v3::Error::VERTEX_HAS_EDGES: + // LOG_FATAL("Unexpected error when adding a label to a vertex."); + // case memgraph::storage::v3::Error::SERIALIZATION_ERROR: + // throw SerializationException{"Cannot serialize adding a label to a vertex."}; + // } } auto &ctx = v->graph->ctx; @@ -1657,17 +1662,19 @@ mgp_error mgp_vertex_remove_label(struct mgp_vertex *v, mgp_label label) { const auto result = v->impl.RemoveLabel(label_id); if (result.HasError()) { - switch (result.GetError()) { - case memgraph::storage::v3::Error::DELETED_OBJECT: - throw DeletedObjectException{"Cannot remove a label from a deleted vertex!"}; - case memgraph::storage::v3::Error::NONEXISTENT_OBJECT: - LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a label from a vertex!"); - case memgraph::storage::v3::Error::PROPERTIES_DISABLED: - case memgraph::storage::v3::Error::VERTEX_HAS_EDGES: - LOG_FATAL("Unexpected error when removing a label from a vertex."); - case memgraph::storage::v3::Error::SERIALIZATION_ERROR: - throw SerializationException{"Cannot serialize removing a label from a vertex."}; - } + // TODO(jbajic) Fix query modules + // switch (result.GetError()) { + // case memgraph::storage::v3::Error::DELETED_OBJECT: + // throw DeletedObjectException{"Cannot remove a label from a deleted vertex!"}; + // case memgraph::storage::v3::Error::NONEXISTENT_OBJECT: + // LOG_FATAL("Query modules shouldn't have access to nonexistent objects when removing a label from a + // vertex!"); + // case memgraph::storage::v3::Error::PROPERTIES_DISABLED: + // case memgraph::storage::v3::Error::VERTEX_HAS_EDGES: + // LOG_FATAL("Unexpected error when removing a label from a vertex."); + // case memgraph::storage::v3::Error::SERIALIZATION_ERROR: + // throw SerializationException{"Cannot serialize removing a label from a vertex."}; + // } } auto &ctx = v->graph->ctx; @@ -2070,11 +2077,12 @@ mgp_error mgp_edge_iter_properties(mgp_edge *e, mgp_memory *memory, mgp_properti mgp_error mgp_graph_get_vertex_by_id(mgp_graph *graph, mgp_vertex_id id, mgp_memory *memory, mgp_vertex **result) { return WrapExceptions( - [graph, id, memory]() -> mgp_vertex * { - auto maybe_vertex = graph->impl->FindVertex(memgraph::storage::v3::Gid::FromInt(id.as_int), graph->view); - if (maybe_vertex) { - return NewRawMgpObject(memory, *maybe_vertex, graph); - } + []() -> mgp_vertex * { + // TODO(jbajic) Fix Remove Gid + // auto maybe_vertex = graph->impl->FindVertex(0); + // if (maybe_vertex) { + // return NewRawMgpObject(memory, *maybe_vertex, graph); + // } return nullptr; }, result); @@ -2085,23 +2093,25 @@ mgp_error mgp_graph_is_mutable(mgp_graph *graph, int *result) { return mgp_error::MGP_ERROR_NO_ERROR; }; -mgp_error mgp_graph_create_vertex(struct mgp_graph *graph, mgp_memory *memory, mgp_vertex **result) { - return WrapExceptions( - [=] { - if (!MgpGraphIsMutable(*graph)) { - throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"}; - } - auto vertex = graph->impl->InsertVertex(); +// TODO(jbajic) Fix Remove Gid +mgp_error mgp_graph_create_vertex(struct mgp_graph * /*graph*/, mgp_memory * /*memory*/, mgp_vertex ** /*result*/) { + // return WrapExceptions( + // [=] { + // if (!MgpGraphIsMutable(*graph)) { + // throw ImmutableObjectException{"Cannot create a vertex in an immutable graph!"}; + // } + // auto vertex = graph->impl->InsertVertex(); - auto &ctx = graph->ctx; - ctx->execution_stats[memgraph::query::v2::ExecutionStats::Key::CREATED_NODES] += 1; + // auto &ctx = graph->ctx; + // ctx->execution_stats[memgraph::query::v2::ExecutionStats::Key::CREATED_NODES] += 1; - if (ctx->trigger_context_collector) { - ctx->trigger_context_collector->RegisterCreatedObject(vertex); - } - return NewRawMgpObject(memory, vertex, graph); - }, - result); + // if (ctx->trigger_context_collector) { + // ctx->trigger_context_collector->RegisterCreatedObject(vertex); + // } + // return NewRawMgpObject(memory, nullptr, graph); + // }, + // result); + return mgp_error::MGP_ERROR_NO_ERROR; } mgp_error mgp_graph_delete_vertex(struct mgp_graph *graph, mgp_vertex *vertex) { @@ -2173,11 +2183,11 @@ mgp_error mgp_graph_detach_delete_vertex(struct mgp_graph *graph, mgp_vertex *ve if (!trigger_ctx_collector) { return; } - - trigger_ctx_collector->RegisterDeletedObject((*result)->first); - if (!trigger_ctx_collector->ShouldRegisterDeletedObject()) { - return; - } + // TODO(jbajic) Fix Remove Gid + // trigger_ctx_collector->RegisterDeletedObject((*result)->first); + // if (!trigger_ctx_collector->ShouldRegisterDeletedObject()) { + // return; + // } for (const auto &edge : (*result)->second) { trigger_ctx_collector->RegisterDeletedObject(edge); } diff --git a/src/query/v2/procedure/py_module.cpp b/src/query/v2/procedure/py_module.cpp index 9a26532bb..3295f6cff 100644 --- a/src/query/v2/procedure/py_module.cpp +++ b/src/query/v2/procedure/py_module.cpp @@ -336,9 +336,10 @@ PyObject *PyGraphCreateVertex(PyGraph *self, PyObject *Py_UNUSED(ignored)) { MG_ASSERT(PyGraphIsValidImpl(*self)); MG_ASSERT(self->memory); MgpUniquePtr new_vertex{nullptr, mgp_vertex_destroy}; - if (RaiseExceptionFromErrorCode(CreateMgpObject(new_vertex, mgp_graph_create_vertex, self->graph, self->memory))) { - return nullptr; - } + // TODO(jbajic) Fix query module + // if (RaiseExceptionFromErrorCode(CreateMgpObject(new_vertex, mgp_graph_create_vertex, self->graph, self->memory))) { + // return nullptr; + // } auto *py_vertex = MakePyVertexWithoutCopy(*new_vertex, self); if (py_vertex != nullptr) { static_cast(new_vertex.release()); diff --git a/src/query/v2/trigger_context.cpp b/src/query/v2/trigger_context.cpp index f69a48ed4..ca2ba02fb 100644 --- a/src/query/v2/trigger_context.cpp +++ b/src/query/v2/trigger_context.cpp @@ -264,8 +264,8 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) { { // adapt created_vertices_ auto it = created_vertices_.begin(); - for (auto &created_vertex : created_vertices_) { - if (auto maybe_vertex = accessor->FindVertex(created_vertex.object.Gid(), storage::v3::View::OLD); maybe_vertex) { + for ([[maybe_unused]] auto &created_vertex : created_vertices_) { + if (auto maybe_vertex = accessor->FindVertex(kFakeVertexGid); maybe_vertex) { *it = detail::CreatedObject{*maybe_vertex}; ++it; } @@ -280,7 +280,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) { const auto adapt_context_with_vertex = [accessor](auto *values) { auto it = values->begin(); for (auto &value : *values) { - if (auto maybe_vertex = accessor->FindVertex(value.object.Gid(), storage::v3::View::OLD); maybe_vertex) { + if (auto maybe_vertex = accessor->FindVertex(kFakeVertexGid); maybe_vertex) { *it = std::move(value); it->object = *maybe_vertex; ++it; @@ -298,7 +298,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) { // adapt created_edges auto it = created_edges_.begin(); for (auto &created_edge : created_edges_) { - const auto maybe_from_vertex = accessor->FindVertex(created_edge.object.From().Gid(), storage::v3::View::OLD); + const auto maybe_from_vertex = accessor->FindVertex(kFakeVertexGid); if (!maybe_from_vertex) { continue; } @@ -322,7 +322,7 @@ void TriggerContext::AdaptForAccessor(DbAccessor *accessor) { const auto adapt_context_with_edge = [accessor](auto *values) { auto it = values->begin(); for (const auto &value : *values) { - if (auto maybe_vertex = accessor->FindVertex(value.object.From().Gid(), storage::v3::View::OLD); maybe_vertex) { + if (auto maybe_vertex = accessor->FindVertex(kFakeVertexGid); maybe_vertex) { auto maybe_out_edges = maybe_vertex->OutEdges(storage::v3::View::OLD); MG_ASSERT(maybe_out_edges.HasValue()); for (const auto &edge : *maybe_out_edges) { @@ -435,7 +435,7 @@ bool TriggerContext::ShouldEventTrigger(const TriggerEventType event_type) const void TriggerContextCollector::UpdateLabelMap(const VertexAccessor vertex, const storage::v3::LabelId label_id, const LabelChange change) { auto ®istry = GetRegistry(); - if (!registry.should_register_updated_objects || registry.created_objects.count(vertex.Gid())) { + if (!registry.should_register_updated_objects || registry.created_objects.count(kFakeVertexGid)) { return; } diff --git a/src/query/v2/trigger_context.hpp b/src/query/v2/trigger_context.hpp index 5f4e62b40..bacb55555 100644 --- a/src/query/v2/trigger_context.hpp +++ b/src/query/v2/trigger_context.hpp @@ -22,12 +22,16 @@ #include "query/v2/db_accessor.hpp" #include "query/v2/typed_value.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/view.hpp" #include "utils/concepts.hpp" #include "utils/fnv.hpp" namespace memgraph::query::v2 { + +// TODO(jbajic) Fix triggers +inline constexpr uint64_t kFakeVertexGid{0}; namespace detail { template concept ObjectAccessor = utils::SameAsAnyOf; @@ -223,8 +227,13 @@ class TriggerContextCollector { struct HashPairWithAccessor { template size_t operator()(const std::pair &pair) const { - using GidType = decltype(std::declval().Gid()); - return utils::HashCombine{}(pair.first.Gid(), pair.second); + // TODO(jbajic) Fix Remove Gid + if constexpr (std::is_same_v) { + return utils::HashCombine{}(kFakeVertexGid, pair.second); + } else { + using UniqueIdentifierType = decltype(std::declval().Gid()); + return utils::HashCombine{}(pair.first.Gid(), pair.second); + } } }; @@ -239,10 +248,12 @@ class TriggerContextCollector { template struct Registry { + using UniqueIdentifier = + typename std::conditional_t<(std::is_same_v), uint64_t, storage::v3::Gid>; bool should_register_created_objects{false}; bool should_register_deleted_objects{false}; bool should_register_updated_objects{false}; // Set/removed properties (and labels for vertices) - std::unordered_map> created_objects; + std::unordered_map> created_objects; std::vector> deleted_objects; // During the transaction, a single property on a single object could be changed multiple times. // We want to register only the global change, at the end of the transaction. The change consists of @@ -268,7 +279,11 @@ class TriggerContextCollector { if (!registry.should_register_created_objects) { return; } - registry.created_objects.emplace(created_object.Gid(), detail::CreatedObject{created_object}); + if constexpr (std::is_same_v) { + registry.created_objects.emplace(kFakeVertexGid, detail::CreatedObject{created_object}); + } else { + registry.created_objects.emplace(created_object.Gid(), detail::CreatedObject{created_object}); + } } template @@ -276,9 +291,8 @@ class TriggerContextCollector { return GetRegistry().should_register_deleted_objects; } - template - void RegisterDeletedObject(const TAccessor &deleted_object) { - auto ®istry = GetRegistry(); + void RegisterDeletedObject(const EdgeAccessor &deleted_object) { + auto ®istry = GetRegistry(); if (!registry.should_register_deleted_objects || registry.created_objects.count(deleted_object.Gid())) { return; } @@ -286,6 +300,10 @@ class TriggerContextCollector { registry.deleted_objects.emplace_back(deleted_object); } + void RegisterDeletedObject(const VertexAccessor &deleted_object) { + // TODO(jbajic) Fix Remove Gid + } + template bool ShouldRegisterObjectPropertyChange() const { return GetRegistry().should_register_updated_objects; @@ -298,9 +316,14 @@ class TriggerContextCollector { if (!registry.should_register_updated_objects) { return; } - - if (registry.created_objects.count(object.Gid())) { - return; + if constexpr (std::is_same_v) { + if (registry.created_objects.count(kFakeVertexGid)) { + return; + } + } else { + if (registry.created_objects.count(object.Gid())) { + return; + } } if (auto it = registry.property_changes.find({object, key}); it != registry.property_changes.end()) { diff --git a/src/query/v2/typed_value.cpp b/src/query/v2/typed_value.cpp index b6ded09d8..dcced2819 100644 --- a/src/query/v2/typed_value.cpp +++ b/src/query/v2/typed_value.cpp @@ -1083,7 +1083,8 @@ size_t TypedValue::Hash::operator()(const TypedValue &value) const { return hash; } case TypedValue::Type::Vertex: - return value.ValueVertex().Gid().AsUint(); + // TODO(jbajic) Fix vertex hashing + return 0; case TypedValue::Type::Edge: return value.ValueEdge().Gid().AsUint(); case TypedValue::Type::Path: { diff --git a/src/storage/v3/durability/snapshot.cpp b/src/storage/v3/durability/snapshot.cpp index 400d13010..3c3ec9fe6 100644 --- a/src/storage/v3/durability/snapshot.cpp +++ b/src/storage/v3/durability/snapshot.cpp @@ -19,6 +19,7 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/edge_ref.hpp" #include "storage/v3/mvcc.hpp" +#include "storage/v3/schemas.hpp" #include "storage/v3/vertex_accessor.hpp" #include "storage/v3/vertices_skip_list.hpp" #include "utils/file_locker.hpp" @@ -299,48 +300,48 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi } // Insert vertex. - auto gid = snapshot.ReadUint(); - if (!gid) throw RecoveryFailure("Invalid snapshot data!"); - if (i > 0 && *gid <= last_vertex_gid) { - throw RecoveryFailure("Invalid snapshot data!"); - } - last_vertex_gid = *gid; - spdlog::debug("Recovering vertex {}.", *gid); - auto [it, inserted] = vertex_acc.insert({Vertex{Gid::FromUint(*gid), nullptr}}); - if (!inserted) throw RecoveryFailure("The vertex must be inserted here!"); + // auto gid = snapshot.ReadUint(); + // if (!gid) throw RecoveryFailure("Invalid snapshot data!"); + // if (i > 0 && *gid <= last_vertex_gid) { + // throw RecoveryFailure("Invalid snapshot data!"); + // } + // last_vertex_gid = *gid; + // spdlog::debug("Recovering vertex {}.", *gid); + // auto [it, inserted] = vertex_acc.insert({Vertex{Gid::FromUint(*gid), nullptr}}); + // if (!inserted) throw RecoveryFailure("The vertex must be inserted here!"); // Recover labels. - spdlog::trace("Recovering labels for vertex {}.", *gid); - { - auto labels_size = snapshot.ReadUint(); - if (!labels_size) throw RecoveryFailure("Invalid snapshot data!"); - auto &labels = it->vertex.labels; - labels.reserve(*labels_size); - for (uint64_t j = 0; j < *labels_size; ++j) { - auto label = snapshot.ReadUint(); - if (!label) throw RecoveryFailure("Invalid snapshot data!"); - SPDLOG_TRACE("Recovered label \"{}\" for vertex {}.", name_id_mapper->IdToName(snapshot_id_map.at(*label)), - *gid); - labels.emplace_back(get_label_from_id(*label)); - } - } + // spdlog::trace("Recovering labels for vertex {}.", *gid); + // { + // auto labels_size = snapshot.ReadUint(); + // if (!labels_size) throw RecoveryFailure("Invalid snapshot data!"); + // auto &labels = it->vertex.labels; + // labels.reserve(*labels_size); + // for (uint64_t j = 0; j < *labels_size; ++j) { + // auto label = snapshot.ReadUint(); + // if (!label) throw RecoveryFailure("Invalid snapshot data!"); + // SPDLOG_TRACE("Recovered label \"{}\" for vertex {}.", name_id_mapper->IdToName(snapshot_id_map.at(*label)), + // *gid); + // labels.emplace_back(get_label_from_id(*label)); + // } + // } // Recover properties. - spdlog::trace("Recovering properties for vertex {}.", *gid); - { - auto props_size = snapshot.ReadUint(); - if (!props_size) throw RecoveryFailure("Invalid snapshot data!"); - auto &props = it->vertex.properties; - for (uint64_t j = 0; j < *props_size; ++j) { - auto key = snapshot.ReadUint(); - if (!key) throw RecoveryFailure("Invalid snapshot data!"); - auto value = snapshot.ReadPropertyValue(); - if (!value) throw RecoveryFailure("Invalid snapshot data!"); - SPDLOG_TRACE("Recovered property \"{}\" with value \"{}\" for vertex {}.", - name_id_mapper->IdToName(snapshot_id_map.at(*key)), *value, *gid); - props.SetProperty(get_property_from_id(*key), *value); - } - } + // spdlog::trace("Recovering properties for vertex {}.", *gid); + // { + // auto props_size = snapshot.ReadUint(); + // if (!props_size) throw RecoveryFailure("Invalid snapshot data!"); + // auto &props = it->vertex.properties; + // for (uint64_t j = 0; j < *props_size; ++j) { + // auto key = snapshot.ReadUint(); + // if (!key) throw RecoveryFailure("Invalid snapshot data!"); + // auto value = snapshot.ReadPropertyValue(); + // if (!value) throw RecoveryFailure("Invalid snapshot data!"); + // SPDLOG_TRACE("Recovered property \"{}\" with value \"{}\" for vertex {}.", + // name_id_mapper->IdToName(snapshot_id_map.at(*key)), *value, *gid); + // props.SetProperty(get_property_from_id(*key), *value); + // } + // } // Skip in edges. { @@ -380,11 +381,11 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi if (!marker || *marker != Marker::SECTION_VERTEX) throw RecoveryFailure("Invalid snapshot data!"); } - spdlog::trace("Recovering connectivity for vertex {}.", vertex.Gid().AsUint()); + // spdlog::trace("Recovering connectivity for vertex {}.", vertex.Gid().AsUint()); // Check vertex. - auto gid = snapshot.ReadUint(); - if (!gid) throw RecoveryFailure("Invalid snapshot data!"); - if (gid != vertex.Gid().AsUint()) throw RecoveryFailure("Invalid snapshot data!"); + // auto gid = snapshot.ReadUint(); + // if (!gid) throw RecoveryFailure("Invalid snapshot data!"); + // if (gid != vertex.Gid().AsUint()) throw RecoveryFailure("Invalid snapshot data!"); // Skip labels. { @@ -410,7 +411,8 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi // Recover in edges. { - spdlog::trace("Recovering inbound edges for vertex {}.", vertex.Gid().AsUint()); + // TODO Fix Gid + spdlog::trace("Recovering inbound edges for vertex {}.", 1); auto in_size = snapshot.ReadUint(); if (!in_size) throw RecoveryFailure("Invalid snapshot data!"); vertex.in_edges.reserve(*in_size); @@ -438,15 +440,17 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi edge_ref = EdgeRef(&*edge); } } + // TODO Fix Gid SPDLOG_TRACE("Recovered inbound edge {} with label \"{}\" from vertex {}.", *edge_gid, - name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), from_vertex->vertex.Gid().AsUint()); + name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), 1); vertex.in_edges.emplace_back(get_edge_type_from_id(*edge_type), &from_vertex->vertex, edge_ref); } } // Recover out edges. { - spdlog::trace("Recovering outbound edges for vertex {}.", vertex.Gid().AsUint()); + // TODO Fix Gid + spdlog::trace("Recovering outbound edges for vertex {}.", 1); auto out_size = snapshot.ReadUint(); if (!out_size) throw RecoveryFailure("Invalid snapshot data!"); vertex.out_edges.reserve(*out_size); @@ -474,8 +478,9 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi edge_ref = EdgeRef(&*edge); } } + // TODO Fix Gid SPDLOG_TRACE("Recovered outbound edge {} with label \"{}\" to vertex {}.", *edge_gid, - name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), to_vertex->vertex.Gid().AsUint()); + name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), 1); vertex.out_edges.emplace_back(get_edge_type_from_id(*edge_type), &to_vertex->vertex, edge_ref); } // Increment edge count. We only increment the count here because the @@ -765,7 +770,8 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // Store the vertex. { snapshot.WriteMarker(Marker::SECTION_VERTEX); - snapshot.WriteUint(lgo_vertex.vertex.Gid().AsUint()); + // TODO Fix Gid + snapshot.WriteUint(1); const auto &labels = maybe_labels.GetValue(); snapshot.WriteUint(labels.size()); for (const auto &item : labels) { @@ -779,16 +785,17 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps } const auto &in_edges = maybe_in_edges.GetValue(); snapshot.WriteUint(in_edges.size()); + // TODO Disabled serialization for vertices for (const auto &item : in_edges) { snapshot.WriteUint(item.Gid().AsUint()); - snapshot.WriteUint(item.FromVertex().Gid().AsUint()); + snapshot.WriteUint(1); write_mapping(item.EdgeType()); } const auto &out_edges = maybe_out_edges.GetValue(); snapshot.WriteUint(out_edges.size()); for (const auto &item : out_edges) { snapshot.WriteUint(item.Gid().AsUint()); - snapshot.WriteUint(item.ToVertex().Gid().AsUint()); + snapshot.WriteUint(1); write_mapping(item.EdgeType()); } } diff --git a/src/storage/v3/durability/wal.cpp b/src/storage/v3/durability/wal.cpp index 0699cca71..7e1a0108c 100644 --- a/src/storage/v3/durability/wal.cpp +++ b/src/storage/v3/durability/wal.cpp @@ -486,93 +486,93 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite // When converting a Delta to a WAL delta the logic is inverted. That is // because the Delta's represent undo actions and we want to store redo // actions. - encoder->WriteMarker(Marker::SECTION_DELTA); - encoder->WriteUint(timestamp); - std::lock_guard guard(vertex.lock); - switch (delta.action) { - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: { - encoder->WriteMarker(VertexActionToMarker(delta.action)); - encoder->WriteUint(vertex.Gid().AsUint()); - break; - } - case Delta::Action::SET_PROPERTY: { - encoder->WriteMarker(Marker::DELTA_VERTEX_SET_PROPERTY); - encoder->WriteUint(vertex.Gid().AsUint()); - encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint())); - // The property value is the value that is currently stored in the - // vertex. - // TODO (mferencevic): Mitigate the memory allocation introduced here - // (with the `GetProperty` call). It is the only memory allocation in the - // entire WAL file writing logic. - encoder->WritePropertyValue(vertex.properties.GetProperty(delta.property.key)); - break; - } - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: { - encoder->WriteMarker(VertexActionToMarker(delta.action)); - encoder->WriteUint(vertex.Gid().AsUint()); - encoder->WriteString(name_id_mapper->IdToName(delta.label.AsUint())); - break; - } - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: { - encoder->WriteMarker(VertexActionToMarker(delta.action)); - if (items.properties_on_edges) { - encoder->WriteUint(delta.vertex_edge.edge.ptr->gid.AsUint()); - } else { - encoder->WriteUint(delta.vertex_edge.edge.gid.AsUint()); - } - encoder->WriteString(name_id_mapper->IdToName(delta.vertex_edge.edge_type.AsUint())); - encoder->WriteUint(vertex.Gid().AsUint()); - encoder->WriteUint(delta.vertex_edge.vertex->Gid().AsUint()); - break; - } - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - // These actions are already encoded in the *_OUT_EDGE actions. This - // function should never be called for this type of deltas. - LOG_FATAL("Invalid delta action!"); - } + // encoder->WriteMarker(Marker::SECTION_DELTA); + // encoder->WriteUint(timestamp); + // std::lock_guard guard(vertex.lock); + // switch (delta.action) { + // case Delta::Action::DELETE_OBJECT: + // case Delta::Action::RECREATE_OBJECT: { + // encoder->WriteMarker(VertexActionToMarker(delta.action)); + // encoder->WriteUint(vertex.Gid().AsUint()); + // break; + // } + // case Delta::Action::SET_PROPERTY: { + // encoder->WriteMarker(Marker::DELTA_VERTEX_SET_PROPERTY); + // encoder->WriteUint(vertex.Gid().AsUint()); + // encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint())); + // // The property value is the value that is currently stored in the + // // vertex. + // // TODO (mferencevic): Mitigate the memory allocation introduced here + // // (with the `GetProperty` call). It is the only memory allocation in the + // // entire WAL file writing logic. + // encoder->WritePropertyValue(vertex.properties.GetProperty(delta.property.key)); + // break; + // } + // case Delta::Action::ADD_LABEL: + // case Delta::Action::REMOVE_LABEL: { + // encoder->WriteMarker(VertexActionToMarker(delta.action)); + // encoder->WriteUint(vertex.Gid().AsUint()); + // encoder->WriteString(name_id_mapper->IdToName(delta.label.AsUint())); + // break; + // } + // case Delta::Action::ADD_OUT_EDGE: + // case Delta::Action::REMOVE_OUT_EDGE: { + // encoder->WriteMarker(VertexActionToMarker(delta.action)); + // if (items.properties_on_edges) { + // encoder->WriteUint(delta.vertex_edge.edge.ptr->gid.AsUint()); + // } else { + // encoder->WriteUint(delta.vertex_edge.edge.gid.AsUint()); + // } + // encoder->WriteString(name_id_mapper->IdToName(delta.vertex_edge.edge_type.AsUint())); + // encoder->WriteUint(vertex.Gid().AsUint()); + // encoder->WriteUint(delta.vertex_edge.vertex->Gid().AsUint()); + // break; + // } + // case Delta::Action::ADD_IN_EDGE: + // case Delta::Action::REMOVE_IN_EDGE: + // // These actions are already encoded in the *_OUT_EDGE actions. This + // // function should never be called for this type of deltas. + // LOG_FATAL("Invalid delta action!"); + // } } void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta &delta, const Edge &edge, uint64_t timestamp) { // When converting a Delta to a WAL delta the logic is inverted. That is // because the Delta's represent undo actions and we want to store redo - // actions. - encoder->WriteMarker(Marker::SECTION_DELTA); - encoder->WriteUint(timestamp); - std::lock_guard guard(edge.lock); - switch (delta.action) { - case Delta::Action::SET_PROPERTY: { - encoder->WriteMarker(Marker::DELTA_EDGE_SET_PROPERTY); - encoder->WriteUint(edge.gid.AsUint()); - encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint())); - // The property value is the value that is currently stored in the - // edge. - // TODO (mferencevic): Mitigate the memory allocation introduced here - // (with the `GetProperty` call). It is the only memory allocation in the - // entire WAL file writing logic. - encoder->WritePropertyValue(edge.properties.GetProperty(delta.property.key)); - break; - } - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - // These actions are already encoded in vertex *_OUT_EDGE actions. Also, - // these deltas don't contain any information about the from vertex, to - // vertex or edge type so they are useless. This function should never - // be called for this type of deltas. - LOG_FATAL("Invalid delta action!"); - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - // These deltas shouldn't appear for edges. - LOG_FATAL("Invalid database state!"); - } + // // actions. + // encoder->WriteMarker(Marker::SECTION_DELTA); + // encoder->WriteUint(timestamp); + // std::lock_guard guard(edge.lock); + // switch (delta.action) { + // case Delta::Action::SET_PROPERTY: { + // encoder->WriteMarker(Marker::DELTA_EDGE_SET_PROPERTY); + // encoder->WriteUint(edge.gid.AsUint()); + // encoder->WriteString(name_id_mapper->IdToName(delta.property.key.AsUint())); + // // The property value is the value that is currently stored in the + // // edge. + // // TODO (mferencevic): Mitigate the memory allocation introduced here + // // (with the `GetProperty` call). It is the only memory allocation in the + // // entire WAL file writing logic. + // encoder->WritePropertyValue(edge.properties.GetProperty(delta.property.key)); + // break; + // } + // case Delta::Action::DELETE_OBJECT: + // case Delta::Action::RECREATE_OBJECT: + // // These actions are already encoded in vertex *_OUT_EDGE actions. Also, + // // these deltas don't contain any information about the from vertex, to + // // vertex or edge type so they are useless. This function should never + // // be called for this type of deltas. + // LOG_FATAL("Invalid delta action!"); + // case Delta::Action::ADD_LABEL: + // case Delta::Action::REMOVE_LABEL: + // case Delta::Action::ADD_OUT_EDGE: + // case Delta::Action::REMOVE_OUT_EDGE: + // case Delta::Action::ADD_IN_EDGE: + // case Delta::Action::REMOVE_IN_EDGE: + // // These deltas shouldn't appear for edges. + // LOG_FATAL("Invalid database state!"); + // } } void EncodeTransactionEnd(BaseEncoder *encoder, uint64_t timestamp) { @@ -645,237 +645,241 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst auto edge_acc = edges->access(); auto vertex_acc = vertices->access(); spdlog::info("WAL file contains {} deltas.", info.num_deltas); - for (uint64_t i = 0; i < info.num_deltas; ++i) { - // Read WAL delta header to find out the delta timestamp. - auto timestamp = ReadWalDeltaHeader(&wal); + // for (uint64_t i = 0; i < info.num_deltas; ++i) { + // // Read WAL delta header to find out the delta timestamp. + // auto timestamp = ReadWalDeltaHeader(&wal); - if (!last_loaded_timestamp || timestamp > *last_loaded_timestamp) { - // This delta should be loaded. - auto delta = ReadWalDeltaData(&wal); - switch (delta.type) { - case WalDeltaData::Type::VERTEX_CREATE: { - auto [vertex, inserted] = vertex_acc.insert({Vertex{delta.vertex_create_delete.gid, nullptr}}); - if (!inserted) throw RecoveryFailure("The vertex must be inserted here!"); + // if (!last_loaded_timestamp || timestamp > *last_loaded_timestamp) { + // // This delta should be loaded. + // auto delta = ReadWalDeltaData(&wal); + // switch (delta.type) { + // case WalDeltaData::Type::VERTEX_CREATE: { + // auto [vertex, inserted] = vertex_acc.insert({Vertex{delta.vertex_create_delete.gid, nullptr}}); + // if (!inserted) throw RecoveryFailure("The vertex must be inserted here!"); - ret.next_vertex_id = std::max(ret.next_vertex_id, delta.vertex_create_delete.gid.AsUint() + 1); + // ret.next_vertex_id = std::max(ret.next_vertex_id, delta.vertex_create_delete.gid.AsUint() + 1); - break; - } - case WalDeltaData::Type::VERTEX_DELETE: { - auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_create_delete.gid.AsInt()}}); - if (lgo_vertex_it == vertex_acc.end()) { - throw RecoveryFailure("The vertex doesn't exist!"); - } - auto &vertex = lgo_vertex_it->vertex; - if (!vertex.in_edges.empty() || !vertex.out_edges.empty()) - throw RecoveryFailure("The vertex can't be deleted because it still has edges!"); + // break; + // } + // case WalDeltaData::Type::VERTEX_DELETE: { + // auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_create_delete.gid.AsInt()}}); + // if (lgo_vertex_it == vertex_acc.end()) { + // throw RecoveryFailure("The vertex doesn't exist!"); + // } + // auto &vertex = lgo_vertex_it->vertex; + // if (!vertex.in_edges.empty() || !vertex.out_edges.empty()) + // throw RecoveryFailure("The vertex can't be deleted because it still has edges!"); - if (!vertex_acc.remove(std::vector{PropertyValue{delta.vertex_create_delete.gid.AsInt()}})) - throw RecoveryFailure("The vertex must be removed here!"); + // if (!vertex_acc.remove(std::vector{PropertyValue{delta.vertex_create_delete.gid.AsInt()}})) + // throw RecoveryFailure("The vertex must be removed here!"); - break; - } - case WalDeltaData::Type::VERTEX_ADD_LABEL: - case WalDeltaData::Type::VERTEX_REMOVE_LABEL: { - auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_add_remove_label.gid.AsInt()}}); - if (lgo_vertex_it == vertex_acc.end()) { - throw RecoveryFailure("The vertex doesn't exist!"); - } - auto &vertex = lgo_vertex_it->vertex; + // break; + // } + // case WalDeltaData::Type::VERTEX_ADD_LABEL: + // case WalDeltaData::Type::VERTEX_REMOVE_LABEL: { + // auto lgo_vertex_it = + // vertex_acc.find(std::vector{PropertyValue{delta.vertex_add_remove_label.gid.AsInt()}}); if (lgo_vertex_it + // == vertex_acc.end()) { + // throw RecoveryFailure("The vertex doesn't exist!"); + // } + // auto &vertex = lgo_vertex_it->vertex; - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.vertex_add_remove_label.label)); - auto it = std::find(vertex.labels.begin(), vertex.labels.end(), label_id); + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.vertex_add_remove_label.label)); + // auto it = std::find(vertex.labels.begin(), vertex.labels.end(), label_id); - if (delta.type == WalDeltaData::Type::VERTEX_ADD_LABEL) { - if (it != vertex.labels.end()) throw RecoveryFailure("The vertex already has the label!"); - vertex.labels.push_back(label_id); - } else { - if (it == vertex.labels.end()) throw RecoveryFailure("The vertex doesn't have the label!"); - std::swap(*it, vertex.labels.back()); - vertex.labels.pop_back(); - } + // if (delta.type == WalDeltaData::Type::VERTEX_ADD_LABEL) { + // if (it != vertex.labels.end()) throw RecoveryFailure("The vertex already has the label!"); + // vertex.labels.push_back(label_id); + // } else { + // if (it == vertex.labels.end()) throw RecoveryFailure("The vertex doesn't have the label!"); + // std::swap(*it, vertex.labels.back()); + // vertex.labels.pop_back(); + // } - break; - } - case WalDeltaData::Type::VERTEX_SET_PROPERTY: { - auto lgo_vertex_it = vertex_acc.find(std::vector{PropertyValue{delta.vertex_edge_set_property.gid.AsInt()}}); - if (lgo_vertex_it == vertex_acc.end()) { - throw RecoveryFailure("The vertex doesn't exist!"); - } + // break; + // } + // case WalDeltaData::Type::VERTEX_SET_PROPERTY: { + // auto lgo_vertex_it = + // vertex_acc.find(std::vector{PropertyValue{delta.vertex_edge_set_property.gid.AsInt()}}); if (lgo_vertex_it + // == vertex_acc.end()) { + // throw RecoveryFailure("The vertex doesn't exist!"); + // } - auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property)); - auto &property_value = delta.vertex_edge_set_property.value; + // auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property)); + // auto &property_value = delta.vertex_edge_set_property.value; - lgo_vertex_it->vertex.properties.SetProperty(property_id, property_value); + // lgo_vertex_it->vertex.properties.SetProperty(property_id, property_value); - break; - } - case WalDeltaData::Type::EDGE_CREATE: { - auto from_lgo_vertex = - vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.from_vertex.AsInt()}}); - if (from_lgo_vertex == vertex_acc.end()) { - throw RecoveryFailure("The from vertex doesn't exist!"); - } - auto to_lgo_vertex = vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.to_vertex.AsInt()}}); - if (to_lgo_vertex == vertex_acc.end()) { - throw RecoveryFailure("The to vertex doesn't exist!"); - } - auto &from_vertex = from_lgo_vertex->vertex; - auto &to_vertex = to_lgo_vertex->vertex; + // break; + // } + // case WalDeltaData::Type::EDGE_CREATE: { + // auto from_lgo_vertex = + // vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.from_vertex.AsInt()}}); + // if (from_lgo_vertex == vertex_acc.end()) { + // throw RecoveryFailure("The from vertex doesn't exist!"); + // } + // auto to_lgo_vertex = + // vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.to_vertex.AsInt()}}); if (to_lgo_vertex + // == vertex_acc.end()) { + // throw RecoveryFailure("The to vertex doesn't exist!"); + // } + // auto &from_vertex = from_lgo_vertex->vertex; + // auto &to_vertex = to_lgo_vertex->vertex; - auto edge_gid = delta.edge_create_delete.gid; - auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type)); - EdgeRef edge_ref(edge_gid); - if (items.properties_on_edges) { - auto [edge, inserted] = edge_acc.insert(Edge{edge_gid, nullptr}); - if (!inserted) throw RecoveryFailure("The edge must be inserted here!"); - edge_ref = EdgeRef(&*edge); - } - { - std::tuple link{edge_type_id, &to_vertex, edge_ref}; - auto it = std::find(from_vertex.out_edges.begin(), from_vertex.out_edges.end(), link); - if (it != from_vertex.out_edges.end()) throw RecoveryFailure("The from vertex already has this edge!"); - from_vertex.out_edges.push_back(link); - } - { - std::tuple link{edge_type_id, &from_vertex, edge_ref}; - auto it = std::find(to_vertex.in_edges.begin(), to_vertex.in_edges.end(), link); - if (it != to_vertex.in_edges.end()) throw RecoveryFailure("The to vertex already has this edge!"); - to_vertex.in_edges.push_back(link); - } + // auto edge_gid = delta.edge_create_delete.gid; + // auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type)); + // EdgeRef edge_ref(edge_gid); + // if (items.properties_on_edges) { + // auto [edge, inserted] = edge_acc.insert(Edge{edge_gid, nullptr}); + // if (!inserted) throw RecoveryFailure("The edge must be inserted here!"); + // edge_ref = EdgeRef(&*edge); + // } + // { + // std::tuple link{edge_type_id, &to_vertex, edge_ref}; + // auto it = std::find(from_vertex.out_edges.begin(), from_vertex.out_edges.end(), link); + // if (it != from_vertex.out_edges.end()) throw RecoveryFailure("The from vertex already has this edge!"); + // from_vertex.out_edges.push_back(link); + // } + // { + // std::tuple link{edge_type_id, &from_vertex, edge_ref}; + // auto it = std::find(to_vertex.in_edges.begin(), to_vertex.in_edges.end(), link); + // if (it != to_vertex.in_edges.end()) throw RecoveryFailure("The to vertex already has this edge!"); + // to_vertex.in_edges.push_back(link); + // } - ret.next_edge_id = std::max(ret.next_edge_id, edge_gid.AsUint() + 1); + // ret.next_edge_id = std::max(ret.next_edge_id, edge_gid.AsUint() + 1); - // Increment edge count. - edge_count->fetch_add(1, std::memory_order_acq_rel); + // // Increment edge count. + // edge_count->fetch_add(1, std::memory_order_acq_rel); - break; - } - case WalDeltaData::Type::EDGE_DELETE: { - auto from_lgo_vertex = - vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.from_vertex.AsInt()}}); - if (from_lgo_vertex == vertex_acc.end()) { - throw RecoveryFailure("The from vertex doesn't exist!"); - } - auto to_lgo_vertex = vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.to_vertex.AsInt()}}); - if (to_lgo_vertex == vertex_acc.end()) { - throw RecoveryFailure("The to vertex doesn't exist!"); - } - auto &from_vertex = from_lgo_vertex->vertex; - auto &to_vertex = to_lgo_vertex->vertex; + // break; + // } + // case WalDeltaData::Type::EDGE_DELETE: { + // auto from_lgo_vertex = + // vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.from_vertex.AsInt()}}); + // if (from_lgo_vertex == vertex_acc.end()) { + // throw RecoveryFailure("The from vertex doesn't exist!"); + // } + // auto to_lgo_vertex = + // vertex_acc.find(std::vector{PropertyValue{delta.edge_create_delete.to_vertex.AsInt()}}); if (to_lgo_vertex + // == vertex_acc.end()) { + // throw RecoveryFailure("The to vertex doesn't exist!"); + // } + // auto &from_vertex = from_lgo_vertex->vertex; + // auto &to_vertex = to_lgo_vertex->vertex; - auto edge_gid = delta.edge_create_delete.gid; - auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type)); - EdgeRef edge_ref(edge_gid); - if (items.properties_on_edges) { - auto edge = edge_acc.find(edge_gid); - if (edge == edge_acc.end()) throw RecoveryFailure("The edge doesn't exist!"); - edge_ref = EdgeRef(&*edge); - } - { - std::tuple link{edge_type_id, &to_vertex, edge_ref}; - auto it = std::find(from_vertex.out_edges.begin(), from_vertex.out_edges.end(), link); - if (it == from_vertex.out_edges.end()) throw RecoveryFailure("The from vertex doesn't have this edge!"); - std::swap(*it, from_vertex.out_edges.back()); - from_vertex.out_edges.pop_back(); - } - { - std::tuple link{edge_type_id, &from_vertex, edge_ref}; - auto it = std::find(to_vertex.in_edges.begin(), to_vertex.in_edges.end(), link); - if (it == to_vertex.in_edges.end()) throw RecoveryFailure("The to vertex doesn't have this edge!"); - std::swap(*it, to_vertex.in_edges.back()); - to_vertex.in_edges.pop_back(); - } - if (items.properties_on_edges) { - if (!edge_acc.remove(edge_gid)) throw RecoveryFailure("The edge must be removed here!"); - } + // auto edge_gid = delta.edge_create_delete.gid; + // auto edge_type_id = EdgeTypeId::FromUint(name_id_mapper->NameToId(delta.edge_create_delete.edge_type)); + // EdgeRef edge_ref(edge_gid); + // if (items.properties_on_edges) { + // auto edge = edge_acc.find(edge_gid); + // if (edge == edge_acc.end()) throw RecoveryFailure("The edge doesn't exist!"); + // edge_ref = EdgeRef(&*edge); + // } + // { + // std::tuple link{edge_type_id, &to_vertex, edge_ref}; + // auto it = std::find(from_vertex.out_edges.begin(), from_vertex.out_edges.end(), link); + // if (it == from_vertex.out_edges.end()) throw RecoveryFailure("The from vertex doesn't have this edge!"); + // std::swap(*it, from_vertex.out_edges.back()); + // from_vertex.out_edges.pop_back(); + // } + // { + // std::tuple link{edge_type_id, &from_vertex, edge_ref}; + // auto it = std::find(to_vertex.in_edges.begin(), to_vertex.in_edges.end(), link); + // if (it == to_vertex.in_edges.end()) throw RecoveryFailure("The to vertex doesn't have this edge!"); + // std::swap(*it, to_vertex.in_edges.back()); + // to_vertex.in_edges.pop_back(); + // } + // if (items.properties_on_edges) { + // if (!edge_acc.remove(edge_gid)) throw RecoveryFailure("The edge must be removed here!"); + // } - // Decrement edge count. - edge_count->fetch_add(-1, std::memory_order_acq_rel); + // // Decrement edge count. + // edge_count->fetch_add(-1, std::memory_order_acq_rel); - break; - } - case WalDeltaData::Type::EDGE_SET_PROPERTY: { - if (!items.properties_on_edges) - throw RecoveryFailure( - "The WAL has properties on edges, but the storage is " - "configured without properties on edges!"); - auto edge = edge_acc.find(delta.vertex_edge_set_property.gid); - if (edge == edge_acc.end()) throw RecoveryFailure("The edge doesn't exist!"); - auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property)); - auto &property_value = delta.vertex_edge_set_property.value; - edge->properties.SetProperty(property_id, property_value); - break; - } - case WalDeltaData::Type::TRANSACTION_END: - break; - case WalDeltaData::Type::LABEL_INDEX_CREATE: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label)); - AddRecoveredIndexConstraint(&indices_constraints->indices.label, label_id, "The label index already exists!"); - break; - } - case WalDeltaData::Type::LABEL_INDEX_DROP: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label)); - RemoveRecoveredIndexConstraint(&indices_constraints->indices.label, label_id, - "The label index doesn't exist!"); - break; - } - case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); - auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); - AddRecoveredIndexConstraint(&indices_constraints->indices.label_property, {label_id, property_id}, - "The label property index already exists!"); - break; - } - case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); - auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); - RemoveRecoveredIndexConstraint(&indices_constraints->indices.label_property, {label_id, property_id}, - "The label property index doesn't exist!"); - break; - } - case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); - auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); - AddRecoveredIndexConstraint(&indices_constraints->constraints.existence, {label_id, property_id}, - "The existence constraint already exists!"); - break; - } - case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); - auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); - RemoveRecoveredIndexConstraint(&indices_constraints->constraints.existence, {label_id, property_id}, - "The existence constraint doesn't exist!"); - break; - } - case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_properties.label)); - std::set property_ids; - for (const auto &prop : delta.operation_label_properties.properties) { - property_ids.insert(PropertyId::FromUint(name_id_mapper->NameToId(prop))); - } - AddRecoveredIndexConstraint(&indices_constraints->constraints.unique, {label_id, property_ids}, - "The unique constraint already exists!"); - break; - } - case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { - auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_properties.label)); - std::set property_ids; - for (const auto &prop : delta.operation_label_properties.properties) { - property_ids.insert(PropertyId::FromUint(name_id_mapper->NameToId(prop))); - } - RemoveRecoveredIndexConstraint(&indices_constraints->constraints.unique, {label_id, property_ids}, - "The unique constraint doesn't exist!"); - break; - } - } - ret.next_timestamp = std::max(ret.next_timestamp, timestamp + 1); - ++deltas_applied; - } else { - // This delta should be skipped. - SkipWalDeltaData(&wal); - } - } + // break; + // } + // case WalDeltaData::Type::EDGE_SET_PROPERTY: { + // if (!items.properties_on_edges) + // throw RecoveryFailure( + // "The WAL has properties on edges, but the storage is " + // "configured without properties on edges!"); + // auto edge = edge_acc.find(delta.vertex_edge_set_property.gid); + // if (edge == edge_acc.end()) throw RecoveryFailure("The edge doesn't exist!"); + // auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.vertex_edge_set_property.property)); + // auto &property_value = delta.vertex_edge_set_property.value; + // edge->properties.SetProperty(property_id, property_value); + // break; + // } + // case WalDeltaData::Type::TRANSACTION_END: + // break; + // case WalDeltaData::Type::LABEL_INDEX_CREATE: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label)); + // AddRecoveredIndexConstraint(&indices_constraints->indices.label, label_id, "The label index already + // exists!"); break; + // } + // case WalDeltaData::Type::LABEL_INDEX_DROP: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label.label)); + // RemoveRecoveredIndexConstraint(&indices_constraints->indices.label, label_id, + // "The label index doesn't exist!"); + // break; + // } + // case WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); + // auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); + // AddRecoveredIndexConstraint(&indices_constraints->indices.label_property, {label_id, property_id}, + // "The label property index already exists!"); + // break; + // } + // case WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); + // auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); + // RemoveRecoveredIndexConstraint(&indices_constraints->indices.label_property, {label_id, property_id}, + // "The label property index doesn't exist!"); + // break; + // } + // case WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); + // auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); + // AddRecoveredIndexConstraint(&indices_constraints->constraints.existence, {label_id, property_id}, + // "The existence constraint already exists!"); + // break; + // } + // case WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.label)); + // auto property_id = PropertyId::FromUint(name_id_mapper->NameToId(delta.operation_label_property.property)); + // RemoveRecoveredIndexConstraint(&indices_constraints->constraints.existence, {label_id, property_id}, + // "The existence constraint doesn't exist!"); + // break; + // } + // case WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_properties.label)); + // std::set property_ids; + // for (const auto &prop : delta.operation_label_properties.properties) { + // property_ids.insert(PropertyId::FromUint(name_id_mapper->NameToId(prop))); + // } + // AddRecoveredIndexConstraint(&indices_constraints->constraints.unique, {label_id, property_ids}, + // "The unique constraint already exists!"); + // break; + // } + // case WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { + // auto label_id = LabelId::FromUint(name_id_mapper->NameToId(delta.operation_label_properties.label)); + // std::set property_ids; + // for (const auto &prop : delta.operation_label_properties.properties) { + // property_ids.insert(PropertyId::FromUint(name_id_mapper->NameToId(prop))); + // } + // RemoveRecoveredIndexConstraint(&indices_constraints->constraints.unique, {label_id, property_ids}, + // "The unique constraint doesn't exist!"); + // break; + // } + // } + // ret.next_timestamp = std::max(ret.next_timestamp, timestamp + 1); + // ++deltas_applied; + // } else { + // // This delta should be skipped. + // SkipWalDeltaData(&wal); + // } + // } spdlog::info("Applied {} deltas from WAL. Skipped {} deltas, because they were too old.", deltas_applied, info.num_deltas - deltas_applied); diff --git a/src/storage/v3/id_types.hpp b/src/storage/v3/id_types.hpp index c9a99c322..5fe83b3aa 100644 --- a/src/storage/v3/id_types.hpp +++ b/src/storage/v3/id_types.hpp @@ -11,6 +11,7 @@ #pragma once +#include #include #include diff --git a/src/storage/v3/indices.cpp b/src/storage/v3/indices.cpp index 590c93866..b9874d023 100644 --- a/src/storage/v3/indices.cpp +++ b/src/storage/v3/indices.cpp @@ -16,6 +16,7 @@ #include "storage/v3/mvcc.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/schema_validator.hpp" +#include "storage/v3/schemas.hpp" #include "utils/bound.hpp" #include "utils/logging.hpp" #include "utils/memory_tracker.hpp" diff --git a/src/storage/v3/indices.hpp b/src/storage/v3/indices.hpp index 9dc4a94f7..e7ae2d593 100644 --- a/src/storage/v3/indices.hpp +++ b/src/storage/v3/indices.hpp @@ -219,12 +219,12 @@ class LabelPropertyIndex { Iterable Vertices(LabelId label, PropertyId property, const std::optional> &lower_bound, const std::optional> &upper_bound, View view, Transaction *transaction, - const SchemaValidator &schema_validator_) { + const SchemaValidator &schema_validator) { auto it = index_.find({label, property}); MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); - return {it->second.access(), label, property, lower_bound, upper_bound, view, - transaction, indices_, constraints_, config_, schema_validator_}; + return {it->second.access(), label, property, lower_bound, upper_bound, view, + transaction, indices_, constraints_, config_, schema_validator}; } int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { diff --git a/src/storage/v3/key_store.cpp b/src/storage/v3/key_store.cpp index c5e1f59a1..7f8e69ec9 100644 --- a/src/storage/v3/key_store.cpp +++ b/src/storage/v3/key_store.cpp @@ -18,7 +18,7 @@ namespace memgraph::storage::v3 { -KeyStore::KeyStore(const std::vector &key_values) { +KeyStore::KeyStore(const PrimaryKey &key_values) { for (auto i = 0; i < key_values.size(); ++i) { MG_ASSERT(!key_values[i].IsNull()); store_.SetProperty(PropertyId::FromInt(i), key_values[i]); @@ -27,9 +27,9 @@ KeyStore::KeyStore(const std::vector &key_values) { PropertyValue KeyStore::GetKey(const size_t index) const { return store_.GetProperty(PropertyId::FromUint(index)); } -std::vector KeyStore::Keys() const { +PrimaryKey KeyStore::Keys() const { auto keys_map = store_.Properties(); - std::vector keys; + PrimaryKey keys; keys.reserve(keys_map.size()); std::ranges::transform( keys_map, std::back_inserter(keys), diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp index ec4905a4a..034e6c280 100644 --- a/src/storage/v3/key_store.hpp +++ b/src/storage/v3/key_store.hpp @@ -20,9 +20,12 @@ namespace memgraph::storage::v3 { +// Primary key is a collection of primary properties. +using PrimaryKey = std::vector; + class KeyStore { public: - explicit KeyStore(const std::vector &key_values); + explicit KeyStore(const PrimaryKey &key_values); KeyStore(const KeyStore &) = delete; KeyStore(KeyStore &&other) noexcept = default; @@ -33,7 +36,7 @@ class KeyStore { PropertyValue GetKey(size_t index) const; - std::vector Keys() const; + PrimaryKey Keys() const; friend bool operator<(const KeyStore &lhs, const KeyStore &rhs) { // TODO(antaljanosbenjamin): also compare the schema @@ -44,14 +47,12 @@ class KeyStore { return std::ranges::equal(lhs.Keys(), rhs.Keys()); } - friend bool operator<(const KeyStore &lhs, const std::vector &rhs) { + friend bool operator<(const KeyStore &lhs, const PrimaryKey &rhs) { // TODO(antaljanosbenjamin): also compare the schema return std::ranges::lexicographical_compare(lhs.Keys(), rhs, std::less{}); } - friend bool operator==(const KeyStore &lhs, const std::vector &rhs) { - return std::ranges::equal(lhs.Keys(), rhs); - } + friend bool operator==(const KeyStore &lhs, const PrimaryKey &rhs) { return std::ranges::equal(lhs.Keys(), rhs); } private: PropertyStore store_; diff --git a/src/storage/v3/replication/replication_server.cpp b/src/storage/v3/replication/replication_server.cpp index 14260136f..8f8fe10f8 100644 --- a/src/storage/v3/replication/replication_server.cpp +++ b/src/storage/v3/replication/replication_server.cpp @@ -171,20 +171,20 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B spdlog::debug("Loading snapshot"); auto recovered_snapshot = durability::RecoveredSnapshot{}; - durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, &storage_->epoch_history_, - &storage_->name_id_mapper_, &storage_->edge_count_, storage_->config_.items); + // durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, + // &storage_->epoch_history_, + // &storage_->name_id_mapper_, &storage_->edge_count_, storage_->config_.items); spdlog::debug("Snapshot loaded successfully"); // If this step is present it should always be the first step of // the recovery so we use the UUID we read from snasphost storage_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid); storage_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id); const auto &recovery_info = recovered_snapshot.recovery_info; - storage_->vertex_id_ = recovery_info.next_vertex_id; storage_->edge_id_ = recovery_info.next_edge_id; storage_->timestamp_ = std::max(storage_->timestamp_, recovery_info.next_timestamp); - durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage_->indices_, - &storage_->constraints_, &storage_->vertices_); + // durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage_->indices_, + // &storage_->constraints_, &storage_->vertices_); } catch (const durability::RecoveryFailure &e) { LOG_FATAL("Couldn't load the snapshot because of: {}", e.what()); } @@ -295,7 +295,7 @@ Storage::ReplicationServer::~ReplicationServer() { } uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) { auto edge_acc = storage_->edges_.access(); - auto vertex_acc = storage_->vertices_.access(); + // auto vertex_acc = storage_->vertices_.access(); std::optional> commit_timestamp_and_accessor; auto get_transaction = [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) { @@ -322,250 +322,252 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * continue; } - SPDLOG_INFO(" Delta {}", applied_deltas); - switch (delta.type) { - case durability::WalDeltaData::Type::VERTEX_CREATE: { - spdlog::trace(" Create vertex {}", delta.vertex_create_delete.gid.AsUint()); - auto *transaction = get_transaction(timestamp); - transaction->CreateVertex(delta.vertex_create_delete.gid); - break; - } - case durability::WalDeltaData::Type::VERTEX_DELETE: { - spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint()); - auto *transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = transaction->DeleteVertex(&*vertex); - if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: { - spdlog::trace(" Vertex {} add label {}", delta.vertex_add_remove_label.gid.AsUint(), - delta.vertex_add_remove_label.label); - auto *transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label)); - if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: { - spdlog::trace(" Vertex {} remove label {}", delta.vertex_add_remove_label.gid.AsUint(), - delta.vertex_add_remove_label.label); - auto *transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label)); - if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: { - spdlog::trace(" Vertex {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(), - delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value); - auto *transaction = get_transaction(timestamp); - auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW); - if (!vertex) throw utils::BasicException("Invalid transaction!"); - auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property), - delta.vertex_edge_set_property.value); - if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EDGE_CREATE: { - spdlog::trace(" Create edge {} of type {} from vertex {} to vertex {}", - delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type, - delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint()); - auto *transaction = get_transaction(timestamp); - auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW); - if (!from_vertex) throw utils::BasicException("Invalid transaction!"); - auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW); - if (!to_vertex) throw utils::BasicException("Invalid transaction!"); - auto edge = transaction->CreateEdge(&*from_vertex, &*to_vertex, - transaction->NameToEdgeType(delta.edge_create_delete.edge_type), - delta.edge_create_delete.gid); - if (edge.HasError()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EDGE_DELETE: { - spdlog::trace(" Delete edge {} of type {} from vertex {} to vertex {}", - delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type, - delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint()); - auto *transaction = get_transaction(timestamp); - auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW); - if (!from_vertex) throw utils::BasicException("Invalid transaction!"); - auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW); - if (!to_vertex) throw utils::BasicException("Invalid transaction!"); - auto edges = from_vertex->OutEdges(View::NEW, {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)}, - &*to_vertex); - if (edges.HasError()) throw utils::BasicException("Invalid transaction!"); - if (edges->size() != 1) throw utils::BasicException("Invalid transaction!"); - auto &edge = (*edges)[0]; - auto ret = transaction->DeleteEdge(&edge); - if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: { - spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(), - delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value); + // SPDLOG_INFO(" Delta {}", applied_deltas); + // switch (delta.type) { + // case durability::WalDeltaData::Type::VERTEX_CREATE: { + // spdlog::trace(" Create vertex {}", delta.vertex_create_delete.gid.AsUint()); + // auto *transaction = get_transaction(timestamp); + // transaction->CreateVertex(delta.vertex_create_delete.gid); + // break; + // } + // case durability::WalDeltaData::Type::VERTEX_DELETE: { + // spdlog::trace(" Delete vertex {}", delta.vertex_create_delete.gid.AsUint()); + // auto *transaction = get_transaction(timestamp); + // auto vertex = transaction->FindVertex(delta.vertex_create_delete.gid, View::NEW); + // if (!vertex) throw utils::BasicException("Invalid transaction!"); + // auto ret = transaction->DeleteVertex(&*vertex); + // if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::VERTEX_ADD_LABEL: { + // spdlog::trace(" Vertex {} add label {}", delta.vertex_add_remove_label.gid.AsUint(), + // delta.vertex_add_remove_label.label); + // auto *transaction = get_transaction(timestamp); + // auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW); + // if (!vertex) throw utils::BasicException("Invalid transaction!"); + // auto ret = vertex->AddLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label)); + // if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::VERTEX_REMOVE_LABEL: { + // spdlog::trace(" Vertex {} remove label {}", delta.vertex_add_remove_label.gid.AsUint(), + // delta.vertex_add_remove_label.label); + // auto *transaction = get_transaction(timestamp); + // auto vertex = transaction->FindVertex(delta.vertex_add_remove_label.gid, View::NEW); + // if (!vertex) throw utils::BasicException("Invalid transaction!"); + // auto ret = vertex->RemoveLabel(transaction->NameToLabel(delta.vertex_add_remove_label.label)); + // if (ret.HasError() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::VERTEX_SET_PROPERTY: { + // spdlog::trace(" Vertex {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(), + // delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value); + // auto *transaction = get_transaction(timestamp); + // auto vertex = transaction->FindVertex(delta.vertex_edge_set_property.gid, View::NEW); + // if (!vertex) throw utils::BasicException("Invalid transaction!"); + // auto ret = vertex->SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property), + // delta.vertex_edge_set_property.value); + // if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::EDGE_CREATE: { + // spdlog::trace(" Create edge {} of type {} from vertex {} to vertex {}", + // delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type, + // delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint()); + // auto *transaction = get_transaction(timestamp); + // auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW); + // if (!from_vertex) throw utils::BasicException("Invalid transaction!"); + // auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW); + // if (!to_vertex) throw utils::BasicException("Invalid transaction!"); + // auto edge = transaction->CreateEdge(&*from_vertex, &*to_vertex, + // transaction->NameToEdgeType(delta.edge_create_delete.edge_type), + // delta.edge_create_delete.gid); + // if (edge.HasError()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::EDGE_DELETE: { + // spdlog::trace(" Delete edge {} of type {} from vertex {} to vertex {}", + // delta.edge_create_delete.gid.AsUint(), delta.edge_create_delete.edge_type, + // delta.edge_create_delete.from_vertex.AsUint(), delta.edge_create_delete.to_vertex.AsUint()); + // auto *transaction = get_transaction(timestamp); + // auto from_vertex = transaction->FindVertex(delta.edge_create_delete.from_vertex, View::NEW); + // if (!from_vertex) throw utils::BasicException("Invalid transaction!"); + // auto to_vertex = transaction->FindVertex(delta.edge_create_delete.to_vertex, View::NEW); + // if (!to_vertex) throw utils::BasicException("Invalid transaction!"); + // auto edges = from_vertex->OutEdges(View::NEW, + // {transaction->NameToEdgeType(delta.edge_create_delete.edge_type)}, + // &*to_vertex); + // if (edges.HasError()) throw utils::BasicException("Invalid transaction!"); + // if (edges->size() != 1) throw utils::BasicException("Invalid transaction!"); + // auto &edge = (*edges)[0]; + // auto ret = transaction->DeleteEdge(&edge); + // if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::EDGE_SET_PROPERTY: { + // spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(), + // delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value); - if (!storage_->config_.items.properties_on_edges) - throw utils::BasicException( - "Can't set properties on edges because properties on edges " - "are disabled!"); + // if (!storage_->config_.items.properties_on_edges) + // throw utils::BasicException( + // "Can't set properties on edges because properties on edges " + // "are disabled!"); - auto *transaction = get_transaction(timestamp); + // auto *transaction = get_transaction(timestamp); - // The following block of code effectively implements `FindEdge` and - // yields an accessor that is only valid for managing the edge's - // properties. - auto edge = edge_acc.find(delta.vertex_edge_set_property.gid); - if (edge == edge_acc.end()) throw utils::BasicException("Invalid transaction!"); - // The edge visibility check must be done here manually because we - // don't allow direct access to the edges through the public API. - { - bool is_visible = true; - Delta *delta = nullptr; - { - std::lock_guard guard(edge->lock); - is_visible = !edge->deleted; - delta = edge->delta; - } - ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, [&is_visible](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: { - is_visible = true; - break; - } - case Delta::Action::DELETE_OBJECT: { - is_visible = false; - break; - } - } - }); - if (!is_visible) throw utils::BasicException("Invalid transaction!"); - } - EdgeRef edge_ref(&*edge); - // Here we create an edge accessor that we will use to get the - // properties of the edge. The accessor is created with an invalid - // type and invalid from/to pointers because we don't know them - // here, but that isn't an issue because we won't use that part of - // the API here. - auto ea = EdgeAccessor{edge_ref, - EdgeTypeId::FromUint(0UL), - nullptr, - nullptr, - &transaction->transaction_, - &storage_->indices_, - &storage_->constraints_, - storage_->config_.items, - storage_->schema_validator_}; + // // The following block of code effectively implements `FindEdge` and + // // yields an accessor that is only valid for managing the edge's + // // properties. + // auto edge = edge_acc.find(delta.vertex_edge_set_property.gid); + // if (edge == edge_acc.end()) throw utils::BasicException("Invalid transaction!"); + // // The edge visibility check must be done here manually because we + // // don't allow direct access to the edges through the public API. + // { + // bool is_visible = true; + // Delta *delta = nullptr; + // { + // std::lock_guard guard(edge->lock); + // is_visible = !edge->deleted; + // delta = edge->delta; + // } + // ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, [&is_visible](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: { + // is_visible = true; + // break; + // } + // case Delta::Action::DELETE_OBJECT: { + // is_visible = false; + // break; + // } + // } + // }); + // if (!is_visible) throw utils::BasicException("Invalid transaction!"); + // } + // EdgeRef edge_ref(&*edge); + // // Here we create an edge accessor that we will use to get the + // // properties of the edge. The accessor is created with an invalid + // // type and invalid from/to pointers because we don't know them + // // here, but that isn't an issue because we won't use that part of + // // the API here. + // auto ea = EdgeAccessor{edge_ref, + // EdgeTypeId::FromUint(0UL), + // nullptr, + // nullptr, + // &transaction->transaction_, + // &storage_->indices_, + // &storage_->constraints_, + // storage_->config_.items, + // storage_->schema_validator_, + // storage_->schemas_}; - auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property), - delta.vertex_edge_set_property.value); - if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); - break; - } + // auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property), + // delta.vertex_edge_set_property.value); + // if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + // break; + // } - case durability::WalDeltaData::Type::TRANSACTION_END: { - spdlog::trace(" Transaction end"); - if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp) - throw utils::BasicException("Invalid data!"); - auto ret = commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first); - if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); - commit_timestamp_and_accessor = std::nullopt; - break; - } + // case durability::WalDeltaData::Type::TRANSACTION_END: { + // spdlog::trace(" Transaction end"); + // if (!commit_timestamp_and_accessor || commit_timestamp_and_accessor->first != timestamp) + // throw utils::BasicException("Invalid data!"); + // auto ret = commit_timestamp_and_accessor->second.Commit(commit_timestamp_and_accessor->first); + // if (ret.HasError()) throw utils::BasicException("Invalid transaction!"); + // commit_timestamp_and_accessor = std::nullopt; + // break; + // } - case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: { - spdlog::trace(" Create label index on :{}", delta.operation_label.label); - // Need to send the timestamp - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label.label), timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::LABEL_INDEX_DROP: { - spdlog::trace(" Drop label index on :{}", delta.operation_label.label); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label.label), timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: { - spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label, - delta.operation_label_property.property); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label_property.label), - storage_->NameToProperty(delta.operation_label_property.property), timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: { - spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label, - delta.operation_label_property.property); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label_property.label), - storage_->NameToProperty(delta.operation_label_property.property), timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { - spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label, - delta.operation_label_property.property); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - auto ret = storage_->CreateExistenceConstraint( - storage_->NameToLabel(delta.operation_label_property.label), - storage_->NameToProperty(delta.operation_label_property.property), timestamp); - if (!ret.HasValue() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: { - spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label, - delta.operation_label_property.property); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - if (!storage_->DropExistenceConstraint(storage_->NameToLabel(delta.operation_label_property.label), - storage_->NameToProperty(delta.operation_label_property.property), - timestamp)) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: { - std::stringstream ss; - utils::PrintIterable(ss, delta.operation_label_properties.properties); - spdlog::trace(" Create unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str()); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - std::set properties; - for (const auto &prop : delta.operation_label_properties.properties) { - properties.emplace(storage_->NameToProperty(prop)); - } - auto ret = storage_->CreateUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label), - properties, timestamp); - if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) - throw utils::BasicException("Invalid transaction!"); - break; - } - case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { - std::stringstream ss; - utils::PrintIterable(ss, delta.operation_label_properties.properties); - spdlog::trace(" Drop unique constraint on :{} ({})", delta.operation_label_properties.label, ss.str()); - if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - std::set properties; - for (const auto &prop : delta.operation_label_properties.properties) { - properties.emplace(storage_->NameToProperty(prop)); - } - auto ret = storage_->DropUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label), - properties, timestamp); - if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!"); - break; - } - } + // case durability::WalDeltaData::Type::LABEL_INDEX_CREATE: { + // spdlog::trace(" Create label index on :{}", delta.operation_label.label); + // // Need to send the timestamp + // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label.label), timestamp)) + // throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::LABEL_INDEX_DROP: { + // spdlog::trace(" Drop label index on :{}", delta.operation_label.label); + // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label.label), timestamp)) + // throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_CREATE: { + // spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label, + // delta.operation_label_property.property); + // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label_property.label), + // storage_->NameToProperty(delta.operation_label_property.property), timestamp)) + // throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::LABEL_PROPERTY_INDEX_DROP: { + // spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label, + // delta.operation_label_property.property); + // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label_property.label), + // storage_->NameToProperty(delta.operation_label_property.property), timestamp)) + // throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_CREATE: { + // spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label, + // delta.operation_label_property.property); + // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // auto ret = storage_->CreateExistenceConstraint( + // storage_->NameToLabel(delta.operation_label_property.label), + // storage_->NameToProperty(delta.operation_label_property.property), timestamp); + // if (!ret.HasValue() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::EXISTENCE_CONSTRAINT_DROP: { + // spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label, + // delta.operation_label_property.property); + // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // if (!storage_->DropExistenceConstraint(storage_->NameToLabel(delta.operation_label_property.label), + // storage_->NameToProperty(delta.operation_label_property.property), + // timestamp)) + // throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_CREATE: { + // std::stringstream ss; + // utils::PrintIterable(ss, delta.operation_label_properties.properties); + // spdlog::trace(" Create unique constraint on :{} ({})", delta.operation_label_properties.label, + // ss.str()); if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // std::set properties; + // for (const auto &prop : delta.operation_label_properties.properties) { + // properties.emplace(storage_->NameToProperty(prop)); + // } + // auto ret = storage_->CreateUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label), + // properties, timestamp); + // if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) + // throw utils::BasicException("Invalid transaction!"); + // break; + // } + // case durability::WalDeltaData::Type::UNIQUE_CONSTRAINT_DROP: { + // std::stringstream ss; + // utils::PrintIterable(ss, delta.operation_label_properties.properties); + // spdlog::trace(" Drop unique constraint on :{} ({})", delta.operation_label_properties.label, + // ss.str()); if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); + // std::set properties; + // for (const auto &prop : delta.operation_label_properties.properties) { + // properties.emplace(storage_->NameToProperty(prop)); + // } + // auto ret = storage_->DropUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label), + // properties, timestamp); + // if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!"); + // break; + // } + // } } if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid data!"); diff --git a/src/storage/v3/schema_validator.cpp b/src/storage/v3/schema_validator.cpp index 4aa466f7f..ea46e550b 100644 --- a/src/storage/v3/schema_validator.cpp +++ b/src/storage/v3/schema_validator.cpp @@ -98,7 +98,7 @@ SchemaValidator::SchemaValidator(Schemas &schemas) : schemas_{schemas} {} [[nodiscard]] std::optional SchemaValidator::ValidateLabelUpdate(const LabelId label) const { const auto *schema = schemas_.GetSchema(label); if (schema) { - return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label); + return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, label); } return std::nullopt; } diff --git a/src/storage/v3/schema_validator.hpp b/src/storage/v3/schema_validator.hpp index a2da4609c..e64a2ead5 100644 --- a/src/storage/v3/schema_validator.hpp +++ b/src/storage/v3/schema_validator.hpp @@ -27,7 +27,7 @@ struct SchemaViolation { NO_SCHEMA_DEFINED_FOR_LABEL, VERTEX_PROPERTY_WRONG_TYPE, VERTEX_UPDATE_PRIMARY_KEY, - VERTEX_MODIFY_PRIMARY_LABEL, + VERTEX_UPDATE_PRIMARY_LABEL, VERTEX_SECONDARY_LABEL_IS_PRIMARY, }; diff --git a/src/storage/v3/schemas.cpp b/src/storage/v3/schemas.cpp index 2f89c80c0..2dee6873a 100644 --- a/src/storage/v3/schemas.cpp +++ b/src/storage/v3/schemas.cpp @@ -11,10 +11,12 @@ #include "storage/v3/schemas.hpp" +#include #include #include #include "storage/v3/property_value.hpp" +#include "utils/exceptions.hpp" namespace memgraph::storage::v3 { @@ -47,6 +49,15 @@ bool Schemas::CreateSchema(const LabelId primary_label, const std::vectorsecond, [property_id](const auto &elem) { + return elem.property_id == property_id; + }) != schema->second.end(); + } + throw utils::BasicException("Schema not found!"); +} + std::optional PropertyTypeToSchemaType(const PropertyValue &property_value) { switch (property_value.type()) { case PropertyValue::Type::Bool: { diff --git a/src/storage/v3/schemas.hpp b/src/storage/v3/schemas.hpp index 157ee7a35..8c3232dfc 100644 --- a/src/storage/v3/schemas.hpp +++ b/src/storage/v3/schemas.hpp @@ -53,11 +53,15 @@ class Schemas { // Returns true if it was successfully created or false if the schema // already exists - [[nodiscard]] bool CreateSchema(LabelId label, const std::vector &schemas_types); + [[nodiscard]] bool CreateSchema(LabelId primary_label, const std::vector &schemas_types); // Returns true if it was successfully dropped or false if the schema // does not exist - [[nodiscard]] bool DropSchema(LabelId label); + [[nodiscard]] bool DropSchema(LabelId primary_label); + + // Returns true if property is part of schema defined + // by primary label + [[nodiscard]] bool IsPropertyKey(LabelId primary_label, PropertyId property_id) const; private: SchemasMap schemas_; diff --git a/src/storage/v3/storage.cpp b/src/storage/v3/storage.cpp index a84138bcd..bbe1aa0f4 100644 --- a/src/storage/v3/storage.cpp +++ b/src/storage/v3/storage.cpp @@ -13,11 +13,14 @@ #include #include +#include +#include #include #include #include #include +#include #include #include @@ -31,14 +34,18 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/id_types.hpp" #include "storage/v3/indices.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/replication/config.hpp" #include "storage/v3/replication/replication_client.hpp" #include "storage/v3/replication/replication_server.hpp" #include "storage/v3/replication/rpc.hpp" +#include "storage/v3/schema_validator.hpp" #include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" #include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertices_skip_list.hpp" #include "utils/exceptions.hpp" #include "utils/file.hpp" #include "utils/logging.hpp" @@ -46,6 +53,7 @@ #include "utils/message.hpp" #include "utils/result.hpp" #include "utils/rw_lock.hpp" +#include "utils/skip_list.hpp" #include "utils/spin_lock.hpp" #include "utils/stat.hpp" #include "utils/uuid.hpp" @@ -56,6 +64,8 @@ using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; namespace { inline constexpr uint16_t kEpochHistoryRetention = 1000; + +void InsertVertexPKIntoList(auto &container, const PrimaryKey &primary_key) { container.push_back(primary_key); } } // namespace auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end, @@ -75,7 +85,7 @@ auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Ite AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it) : self_(self), it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, - self->indices_, self_->constraints_, self->config_, *self->schema_validator_)) {} + self->indices_, self_->constraints_, self->config_, *self_->schema_validator_)) {} VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; } @@ -351,11 +361,10 @@ Storage::Storage(Config config) if (config_.durability.recover_on_startup) { auto info = std::optional{}; - durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, &vertices_, - &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, - &wal_seq_num_); + // durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, &vertices_, + // &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, + // &wal_seq_num_); if (info) { - vertex_id_ = info->next_vertex_id; edge_id_ = info->next_edge_id; timestamp_ = std::max(timestamp_, info->next_timestamp); if (info->last_commit_timestamp) { @@ -471,42 +480,6 @@ Storage::Accessor::~Accessor() { FinalizeTransaction(); } -// TODO Remove when import csv is fixed -[[deprecated]] VertexAccessor Storage::Accessor::CreateVertex() { - OOMExceptionEnabler oom_exception; - auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel); - auto acc = storage_->vertices_.access(); - auto *delta = CreateDeleteObjectDelta(&transaction_); - // TODO(antaljanosbenjamin): handle keys and schema - auto [it, inserted] = acc.insert({Vertex{Gid::FromUint(gid), delta}}); - MG_ASSERT(inserted, "The vertex must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - delta->prev.Set(&it->vertex); - return { - &it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_}; -} - -// TODO Remove when replication is fixed -VertexAccessor Storage::Accessor::CreateVertex(Gid gid) { - OOMExceptionEnabler oom_exception; - // NOTE: When we update the next `vertex_id_` here we perform a RMW - // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue - // because this function is only called from the replication delta applier - // that runs single-threadedly and while this instance is set-up to apply - // threads (it is the replica), it is guaranteed that no other writes are - // possible. - storage_->vertex_id_.store(std::max(storage_->vertex_id_.load(std::memory_order_acquire), gid.AsUint() + 1), - std::memory_order_release); - auto acc = storage_->vertices_.access(); - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert({Vertex{gid, delta}}); - MG_ASSERT(inserted, "The vertex must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - delta->prev.Set(&it->vertex); - return { - &it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_}; -} - ResultSchema Storage::Accessor::CreateVertexAndValidate( LabelId primary_label, const std::vector &labels, const std::vector> &properties) { @@ -515,36 +488,41 @@ ResultSchema Storage::Accessor::CreateVertexAndValidate( return {std::move(*maybe_schema_violation)}; } OOMExceptionEnabler oom_exception; - auto gid = storage_->vertex_id_.fetch_add(1, std::memory_order_acq_rel); + // Extract key properties + std::vector primary_properties; + for ([[maybe_unused]] const auto &[property_id, property_type] : storage_->GetSchema(primary_label)->second) { + // We know there definitely is key in properties since we have validated + primary_properties.push_back( + std::ranges::find_if(properties, [property_id = property_id](const auto &property_pair) { + return property_pair.first == property_id; + })->second); + } + + // Get secondary properties + std::vector> secondary_properties; + for (const auto &[property_id, property_value] : properties) { + if (!storage_->schemas_.IsPropertyKey(primary_label, property_id)) { + secondary_properties.emplace_back(property_id, property_value); + } + } + auto acc = storage_->vertices_.access(); auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert({Vertex{Gid::FromUint(gid), delta, primary_label}}); + auto [it, inserted] = acc.insert({Vertex{delta, primary_label, primary_properties, labels, secondary_properties}}); MG_ASSERT(inserted, "The vertex must be inserted here!"); MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); delta->prev.Set(&it->vertex); - - auto va = VertexAccessor{ + return VertexAccessor{ &it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_}; - for (const auto label : labels) { - const auto maybe_error = va.AddLabel(label); - if (maybe_error.HasError()) { - return {maybe_error.GetError()}; - } - } - // Set properties - for (auto [property_id, property_value] : properties) { - const auto maybe_error = va.SetProperty(property_id, property_value); - if (maybe_error.HasError()) { - return {maybe_error.GetError()}; - } - } - return va; } -std::optional Storage::Accessor::FindVertex(Gid gid, View view) { +std::optional Storage::Accessor::FindVertex(std::vector primary_key, View view) { auto acc = storage_->vertices_.access(); - auto it = acc.find(std::vector{PropertyValue{gid.AsInt()}}); - if (it == acc.end()) return std::nullopt; + // Later on use label space + auto it = acc.find(primary_key); + if (it == acc.end()) { + return std::nullopt; + } return VertexAccessor::Create(&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_, view); } @@ -996,7 +974,7 @@ void Storage::Accessor::Abort() { // We collect vertices and edges we've created here and then splice them into // `deleted_vertices_` and `deleted_edges_` lists, instead of adding them one // by one and acquiring lock every time. - std::list my_deleted_vertices; + std::list my_deleted_vertices; std::list my_deleted_edges; for (const auto &delta : transaction_.deltas) { @@ -1072,7 +1050,7 @@ void Storage::Accessor::Abort() { } case Delta::Action::DELETE_OBJECT: { vertex->deleted = true; - my_deleted_vertices.push_back(vertex->Gid()); + InsertVertexPKIntoList(my_deleted_vertices, vertex->keys.Keys()); break; } case Delta::Action::RECREATE_OBJECT: { @@ -1187,7 +1165,8 @@ EdgeTypeId Storage::NameToEdgeType(const std::string_view name) { bool Storage::CreateIndex(LabelId label, const std::optional desired_commit_timestamp) { std::unique_lock storage_guard(main_lock_); - if (!indices_.label_index.CreateIndex(label, vertices_.access())) return false; + // TODO Fix Index + return false; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp); commit_log_->MarkFinished(commit_timestamp); @@ -1197,7 +1176,9 @@ bool Storage::CreateIndex(LabelId label, const std::optional desired_c bool Storage::CreateIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { std::unique_lock storage_guard(main_lock_); - if (!indices_.label_property_index.CreateIndex(label, property, vertices_.access())) return false; + // TODO Fix Index + // if (!indices_.label_property_index.CreateIndex(label, property, labelspace.access())) return false; + return false; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, {property}, commit_timestamp); commit_log_->MarkFinished(commit_timestamp); @@ -1235,8 +1216,10 @@ IndicesInfo Storage::ListAllIndices() const { utils::BasicResult Storage::CreateExistenceConstraint( LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { std::unique_lock storage_guard(main_lock_); - auto ret = ::memgraph::storage::v3::CreateExistenceConstraint(&constraints_, label, property, vertices_.access()); - if (ret.HasError() || !ret.GetValue()) return ret; + // TODO Fix constraints + // auto ret = ::memgraph::storage::v3::CreateExistenceConstraint(&constraints_, label, property, vertices_.access()); + // if (ret.HasError() || !ret.GetValue()) return ret; + return false; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, {property}, commit_timestamp); commit_log_->MarkFinished(commit_timestamp); @@ -1258,10 +1241,12 @@ bool Storage::DropExistenceConstraint(LabelId label, PropertyId property, utils::BasicResult Storage::CreateUniqueConstraint( LabelId label, const std::set &properties, const std::optional desired_commit_timestamp) { std::unique_lock storage_guard(main_lock_); - auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access()); - if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { - return ret; - } + // TODO Fix constraints + // auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access()); + // if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { + // return ret; + // } + return UniqueConstraints::CreationStatus::ALREADY_EXISTS; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, properties, commit_timestamp); commit_log_->MarkFinished(commit_timestamp); @@ -1410,7 +1395,7 @@ void Storage::CollectGarbage() { // will do it after cleaning-up the indices. That way we are sure that all // vertices that appear in an index also exist in main storage. std::list current_deleted_edges; - std::list current_deleted_vertices; + std::list current_deleted_vertices; deleted_vertices_->swap(current_deleted_vertices); deleted_edges_->swap(current_deleted_edges); @@ -1482,7 +1467,7 @@ void Storage::CollectGarbage() { } vertex->delta = nullptr; if (vertex->deleted) { - current_deleted_vertices.push_back(vertex->Gid()); + InsertVertexPKIntoList(current_deleted_vertices, vertex->keys.Keys()); } break; } @@ -1600,17 +1585,13 @@ void Storage::CollectGarbage() { if constexpr (force) { // if force is set to true, then we have unique_lock and no transactions are active // so we can clean all of the deleted vertices - std::vector key(1); while (!garbage_vertices_.empty()) { - key.front() = PropertyValue{garbage_vertices_.front().second.AsInt()}; - MG_ASSERT(vertex_acc.remove(key), "Invalid database state!"); + MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); garbage_vertices_.pop_front(); } } else { - std::vector key(1); while (!garbage_vertices_.empty() && garbage_vertices_.front().first < oldest_active_start_timestamp) { - key.front() = PropertyValue{garbage_vertices_.front().second.AsInt()}; - MG_ASSERT(vertex_acc.remove(key), "Invalid database state!"); + MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); garbage_vertices_.pop_front(); } } @@ -1867,10 +1848,10 @@ utils::BasicResult Storage::CreateSnapshot() { auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION); // Create snapshot. - durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, - config_.durability.snapshot_retention_count, &vertices_, &edges_, &name_id_mapper_, - &indices_, &constraints_, config_.items, schema_validator_, uuid_, epoch_id_, - epoch_history_, &file_retainer_); + // durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, + // config_.durability.snapshot_retention_count, &vertices_, &edges_, + // &name_id_mapper_, &indices_, &constraints_, config_.items, schema_validator_, + // uuid_, epoch_id_, epoch_history_, &file_retainer_); // Finalize snapshot transaction. commit_log_->MarkFinished(transaction.start_timestamp); diff --git a/src/storage/v3/storage.hpp b/src/storage/v3/storage.hpp index 5602d9425..1862503d7 100644 --- a/src/storage/v3/storage.hpp +++ b/src/storage/v3/storage.hpp @@ -12,7 +12,10 @@ #pragma once #include +#include #include +#include +#include #include #include #include @@ -30,6 +33,7 @@ #include "storage/v3/id_types.hpp" #include "storage/v3/indices.hpp" #include "storage/v3/isolation_level.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/lexicographically_ordered_vertex.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/name_id_mapper.hpp" @@ -76,6 +80,7 @@ class AllVerticesIterable final { Constraints *constraints_; Config::Items config_; const SchemaValidator *schema_validator_; + const Schemas *schemas_; std::optional vertex_; public: @@ -97,14 +102,14 @@ class AllVerticesIterable final { AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view, Indices *indices, Constraints *constraints, Config::Items config, - SchemaValidator *schema_validator) + const SchemaValidator &schema_validator) : vertices_accessor_(std::move(vertices_accessor)), transaction_(transaction), view_(view), indices_(indices), constraints_(constraints), config_(config), - schema_validator_(schema_validator) {} + schema_validator_{&schema_validator} {} Iterator begin() { return {this, vertices_accessor_.begin()}; } Iterator end() { return {this, vertices_accessor_.end()}; } @@ -226,21 +231,17 @@ class Storage final { ~Accessor(); - VertexAccessor CreateVertex(); - - VertexAccessor CreateVertex(Gid gid); - /// @throw std::bad_alloc ResultSchema CreateVertexAndValidate( LabelId primary_label, const std::vector &labels, const std::vector> &properties); - std::optional FindVertex(Gid gid, View view); + std::optional FindVertex(std::vector primary_key, View view); VerticesIterable Vertices(View view) { return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view, &storage_->indices_, &storage_->constraints_, storage_->config_.items, - &storage_->schema_validator_)); + storage_->schema_validator_)); } VerticesIterable Vertices(LabelId label, View view); @@ -520,7 +521,6 @@ class Storage final { // Main object storage VerticesSkipList vertices_; utils::SkipList edges_; - std::atomic vertex_id_{0}; std::atomic 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 @@ -556,11 +556,11 @@ class Storage final { // Vertices that are logically deleted but still have to be removed from // indices before removing them from the main storage. - utils::Synchronized, utils::SpinLock> deleted_vertices_; + utils::Synchronized, 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> garbage_vertices_; + std::list> garbage_vertices_; // Edges that are logically deleted and wait to be removed from the main // storage. diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 415538118..44698381c 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -28,43 +28,49 @@ namespace memgraph::storage::v3 { struct Vertex { - Vertex(Gid gid, Delta *delta, LabelId primary_label) - : primary_label{primary_label}, keys{{PropertyValue{gid.AsInt()}}}, deleted(false), delta(delta) { + Vertex(Delta *delta, LabelId primary_label, const std::vector &primary_properties) + : primary_label{primary_label}, keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } - // TODO remove this when import replication is solved - Vertex(Gid gid, LabelId primary_label) - : primary_label{primary_label}, keys{{PropertyValue{gid.AsInt()}}}, deleted(false) { + Vertex(Delta *delta, LabelId primary_label, const std::vector &primary_properties, + const std::vector &secondary_labels, + const std::vector> &secondary_properties) + : primary_label{primary_label}, keys{primary_properties}, labels{secondary_labels}, delta{delta} { + MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, + "Vertex must be created with an initial DELETE_OBJECT delta!"); + for (const auto &[property_id, property_value] : secondary_properties) { + properties.SetProperty(property_id, property_value); + } + } + + Vertex(LabelId primary_label, const std::vector &primary_properties) + : primary_label{primary_label}, keys(primary_properties) { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } - - // TODO remove this when import csv is solved - Vertex(Gid gid, Delta *delta) : keys{{PropertyValue{gid.AsInt()}}}, deleted(false), delta(delta) { + Vertex(LabelId primary_label, const std::vector &primary_properties, + const std::vector &secondary_labels, + const std::vector> &secondary_properties) + : primary_label{primary_label}, keys{primary_properties}, labels{secondary_labels} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); + for (const auto &[property_id, property_value] : secondary_properties) { + properties.SetProperty(property_id, property_value); + } } - // TODO remove this when import replication is solved - explicit Vertex(Gid gid) : keys{{PropertyValue{gid.AsInt()}}}, deleted(false) { - MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, - "Vertex must be created with an initial DELETE_OBJECT delta!"); - } - - Gid Gid() const { return Gid::FromInt(keys.GetKey(0).ValueInt()); } - LabelId primary_label; KeyStore keys; + std::vector labels; PropertyStore properties; - std::vector> in_edges; std::vector> out_edges; mutable utils::SpinLock lock; - bool deleted; + bool deleted{false}; // uint8_t PAD; // uint16_t PAD; diff --git a/src/storage/v3/vertex_accessor.cpp b/src/storage/v3/vertex_accessor.cpp index 867537d47..6b5dae827 100644 --- a/src/storage/v3/vertex_accessor.cpp +++ b/src/storage/v3/vertex_accessor.cpp @@ -16,6 +16,7 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/id_types.hpp" #include "storage/v3/indices.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/schema_validator.hpp" @@ -237,6 +238,44 @@ Result VertexAccessor::PrimaryLabel(const View view) const { return vertex_->primary_label; } +Result VertexAccessor::PrimaryKey(const View view) const { + bool exists = true; + bool deleted = false; + Delta *delta = nullptr; + { + std::lock_guard guard(vertex_->lock); + deleted = vertex_->deleted; + delta = vertex_->delta; + } + ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) { + switch (delta.action) { + 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::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 vertex_->keys.Keys(); +} + Result> VertexAccessor::Labels(View view) const { bool exists = true; bool deleted = false; diff --git a/src/storage/v3/vertex_accessor.hpp b/src/storage/v3/vertex_accessor.hpp index 66ad98d74..061cf399d 100644 --- a/src/storage/v3/vertex_accessor.hpp +++ b/src/storage/v3/vertex_accessor.hpp @@ -13,13 +13,13 @@ #include -#include "storage/v3/id_types.hpp" -#include "storage/v3/schema_validator.hpp" -#include "storage/v3/vertex.hpp" - #include "storage/v3/config.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/result.hpp" +#include "storage/v3/schema_validator.hpp" #include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" #include "storage/v3/view.hpp" namespace memgraph::storage::v3 { @@ -68,22 +68,12 @@ class VertexAccessor final { /// @return true if the object is visible from the current transaction bool IsVisible(View view) const; - /// Add a label and return `true` if insertion took place. - /// `false` is returned if the label already existed. - /// @throw std::bad_alloc - Result AddLabel(LabelId label); - /// Add a label and return `true` if insertion took place. /// `false` is returned if the label already existed, or SchemaViolation /// if adding the label has violated one of the schema constraints. /// @throw std::bad_alloc ResultSchema AddLabelAndValidate(LabelId label); - /// 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 RemoveLabel(LabelId label); - /// Remove a label and return `true` if deletion took place. /// `false` is returned if the vertex did not have a label already. or SchemaViolation /// if adding the label has violated one of the schema constraints. @@ -99,9 +89,7 @@ class VertexAccessor final { Result PrimaryLabel(View view) const; - /// Set a property value and return the old value. - /// @throw std::bad_alloc - Result SetProperty(PropertyId property, const PropertyValue &value); + Result PrimaryKey(View view) const; /// Set a property value and return the old value or error. /// @throw std::bad_alloc @@ -133,11 +121,6 @@ class VertexAccessor final { Result OutDegree(View view) const; - Gid Gid() const noexcept { - // TODO(antaljanosbenjamin): remove this whole function. - return vertex_->Gid(); - } - const SchemaValidator *GetSchemaValidator() const; bool operator==(const VertexAccessor &other) const noexcept { @@ -146,6 +129,20 @@ class VertexAccessor final { bool operator!=(const VertexAccessor &other) const noexcept { return !(*this == other); } private: + /// Add a label and return `true` if insertion took place. + /// `false` is returned if the label already existed. + /// @throw std::bad_alloc + Result AddLabel(LabelId label); + + /// 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 RemoveLabel(LabelId label); + + /// Set a property value and return the old value. + /// @throw std::bad_alloc + Result SetProperty(PropertyId property, const PropertyValue &value); + Vertex *vertex_; Transaction *transaction_; Indices *indices_; @@ -168,6 +165,6 @@ class VertexAccessor final { namespace std { template <> struct hash { - size_t operator()(const memgraph::storage::v3::VertexAccessor &v) const noexcept { return v.Gid().AsUint(); } + size_t operator()(const memgraph::storage::v3::VertexAccessor & /*v*/) const noexcept { return 0; } }; } // namespace std diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 19f97541b..afd1f8dd9 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -334,34 +334,36 @@ target_link_libraries(${test_prefix}storage_v3_property_store mg-storage-v3 fmt) add_unit_test(storage_v3_key_store.cpp) target_link_libraries(${test_prefix}storage_v3_key_store mg-storage-v3 rapidcheck rapidcheck_gtest) +add_unit_test(storage_v3_vertex_accessors.cpp) +target_link_libraries(${test_prefix}storage_v3_vertex_accessors mg-storage-v3) + # Test mg-query-v3 add_unit_test(query_v2_interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) target_link_libraries(${test_prefix}query_v2_interpreter mg-storage-v3 mg-query-v2 mg-communication) -add_unit_test(query_v2_query_plan_accumulate_aggregate.cpp) -target_link_libraries(${test_prefix}query_v2_query_plan_accumulate_aggregate mg-query-v2) +# add_unit_test(query_v2_query_plan_accumulate_aggregate.cpp) +# target_link_libraries(${test_prefix}query_v2_query_plan_accumulate_aggregate mg-query-v2) -add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp) -target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2) +# add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp) +# target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2) -add_unit_test(query_v2_query_plan_bag_semantics.cpp) -target_link_libraries(${test_prefix}query_v2_query_plan_bag_semantics mg-query-v2) +# add_unit_test(query_v2_query_plan_bag_semantics.cpp) +# target_link_libraries(${test_prefix}query_v2_query_plan_bag_semantics mg-query-v2) -add_unit_test(query_v2_query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) -target_link_libraries(${test_prefix}query_v2_query_plan_edge_cases mg-communication mg-query-v2) +# add_unit_test(query_v2_query_plan_edge_cases.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) +# target_link_libraries(${test_prefix}query_v2_query_plan_edge_cases mg-communication mg-query-v2) -add_unit_test(query_v2_query_plan_v2_create_set_remove_delete.cpp) -target_link_libraries(${test_prefix}query_v2_query_plan_v2_create_set_remove_delete mg-query-v2) +# add_unit_test(query_v2_query_plan_v2_create_set_remove_delete.cpp) +# target_link_libraries(${test_prefix}query_v2_query_plan_v2_create_set_remove_delete mg-query-v2) -add_unit_test(query_v2_query_plan_match_filter_return.cpp) -target_link_libraries(${test_prefix}query_v2_query_plan_match_filter_return mg-query-v2) +# add_unit_test(query_v2_query_plan_match_filter_return.cpp) +# target_link_libraries(${test_prefix}query_v2_query_plan_match_filter_return mg-query-v2) -add_unit_test(query_v2_cypher_main_visitor.cpp) -target_link_libraries(${test_prefix}query_v2_cypher_main_visitor mg-query-v2) - -add_unit_test(query_v2_query_required_privileges.cpp) -target_link_libraries(${test_prefix}query_v2_query_required_privileges mg-query-v2) +# add_unit_test(query_v2_cypher_main_visitor.cpp) +# target_link_libraries(${test_prefix}query_v2_cypher_main_visitor mg-query-v2) +# add_unit_test(query_v2_query_required_privileges.cpp) +# target_link_libraries(${test_prefix}query_v2_query_required_privileges mg-query-v2) add_unit_test(replication_persistence_helper.cpp) target_link_libraries(${test_prefix}replication_persistence_helper mg-storage-v2) diff --git a/tests/unit/query_v2_interpreter.cpp b/tests/unit/query_v2_interpreter.cpp index b73cbeb5a..fdc15b032 100644 --- a/tests/unit/query_v2_interpreter.cpp +++ b/tests/unit/query_v2_interpreter.cpp @@ -33,20 +33,20 @@ #include "utils/csv_parsing.hpp" #include "utils/logging.hpp" -namespace { +namespace memgraph::query::v2::tests { -auto ToEdgeList(const memgraph::communication::bolt::Value &v) { - std::vector list; - for (auto x : v.ValueList()) { - list.push_back(x.ValueEdge()); - } - return list; -} +// auto ToEdgeList(const memgraph::communication::bolt::Value &v) { +// std::vector list; +// for (auto x : v.ValueList()) { +// list.push_back(x.ValueEdge()); +// } +// return list; +// } -auto StringToUnorderedSet(const std::string &element) { - const auto element_split = memgraph::utils::Split(element, ", "); - return std::unordered_set(element_split.begin(), element_split.end()); -}; +// auto StringToUnorderedSet(const std::string &element) { +// const auto element_split = memgraph::utils::Split(element, ", "); +// return std::unordered_set(element_split.begin(), element_split.end()); +// }; struct InterpreterFaker { InterpreterFaker(memgraph::storage::v3::Storage *db, const memgraph::query::v2::InterpreterConfig config, @@ -90,17 +90,14 @@ struct InterpreterFaker { memgraph::query::v2::Interpreter interpreter; }; -} // namespace - // TODO: This is not a unit test, but tests/integration dir is chaotic at the // moment. After tests refactoring is done, move/rename this. class InterpreterTest : public ::testing::Test { protected: - memgraph::storage::v3::Storage db_; - std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_v2_interpreter"}; - - InterpreterFaker default_interpreter{&db_, {}, data_directory}; + void SetUp() override { + ASSERT_TRUE(db_.CreateSchema(label, {storage::v3::SchemaProperty{property, common::SchemaType::INT}})); + } auto Prepare(const std::string &query, const std::map ¶ms = {}) { @@ -115,1531 +112,1551 @@ class InterpreterTest : public ::testing::Test { const std::map ¶ms = {}) { return default_interpreter.Interpret(query, params); } + + memgraph::storage::v3::Storage db_; + std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_v2_interpreter"}; + const storage::v3::LabelId label{db_.NameToLabel("label")}; + const storage::v3::PropertyId property{db_.NameToProperty("property")}; + InterpreterFaker default_interpreter{&db_, {}, data_directory}; }; -TEST_F(InterpreterTest, MultiplePulls) { - { - auto [stream, qid] = Prepare("UNWIND [1,2,3,4,5] as n RETURN n"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "n"); - Pull(&stream, 1); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 1); - Pull(&stream, 2); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults().size(), 3U); - ASSERT_EQ(stream.GetResults()[1][0].ValueInt(), 2); - ASSERT_EQ(stream.GetResults()[2][0].ValueInt(), 3); - Pull(&stream); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults().size(), 5U); - ASSERT_EQ(stream.GetResults()[3][0].ValueInt(), 4); - ASSERT_EQ(stream.GetResults()[4][0].ValueInt(), 5); - } +TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) { + ASSERT_TRUE(true) << "This test is only here to make sure mg-query-v2 gets compiled when building unit tests so " + "clang-tidy can analyze it properly."; } -// Run query with different ast twice to see if query executes correctly when -// ast is read from cache. -TEST_F(InterpreterTest, AstCache) { - { - auto stream = Interpret("RETURN 2 + 3"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "2 + 3"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 5); - } - { - // Cached ast, different literals. - auto stream = Interpret("RETURN 5 + 4"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 9); - } - { - // Different ast (because of different types). - auto stream = Interpret("RETURN 5.5 + 4"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 9.5); - } - { - // Cached ast, same literals. - auto stream = Interpret("RETURN 2 + 3"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 5); - } - { - // Cached ast, different literals. - auto stream = Interpret("RETURN 10.5 + 1"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); - } - { - // Cached ast, same literals, different whitespaces. - auto stream = Interpret("RETURN 10.5 + 1"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); - } - { - // Cached ast, same literals, different named header. - auto stream = Interpret("RETURN 10.5+1"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "10.5+1"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); - } -} - -// Run query with same ast multiple times with different parameters. -TEST_F(InterpreterTest, Parameters) { - { - auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue(10)}, - {"a b", memgraph::storage::v3::PropertyValue(15)}}); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 25); - } - { - // Not needed parameter. - auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue(10)}, - {"a b", memgraph::storage::v3::PropertyValue(15)}, - {"c", memgraph::storage::v3::PropertyValue(10)}}); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 25); - } - { - // Cached ast, different parameters. - auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue("da")}, - {"a b", memgraph::storage::v3::PropertyValue("ne")}}); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "dane"); - } - { - // Non-primitive literal. - auto stream = Interpret( - "RETURN $2", {{"2", memgraph::storage::v3::PropertyValue(std::vector{ - memgraph::storage::v3::PropertyValue(5), memgraph::storage::v3::PropertyValue(2), - memgraph::storage::v3::PropertyValue(3)})}}); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - auto result = - memgraph::query::v2::test_common::ToIntList(memgraph::glue::v2::ToTypedValue(stream.GetResults()[0][0])); - ASSERT_THAT(result, testing::ElementsAre(5, 2, 3)); - } - { - // Cached ast, unprovided parameter. - ASSERT_THROW(Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue("da")}, - {"ab", memgraph::storage::v3::PropertyValue("ne")}}), - memgraph::query::v2::UnprovidedParameterError); - } -} - -// Run CREATE/MATCH/MERGE queries with property map -TEST_F(InterpreterTest, ParametersAsPropertyMap) { - { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)")); - std::map property_map{}; - property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); - property_map["age"] = memgraph::storage::v3::PropertyValue(25); - auto stream = - Interpret("CREATE (n:label $prop) RETURN n", { - {"prop", memgraph::storage::v3::PropertyValue(property_map)}, - }); - ASSERT_EQ(stream.GetHeader().size(), 1U); - ASSERT_EQ(stream.GetHeader()[0], "n"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - auto result = stream.GetResults()[0][0].ValueVertex(); - EXPECT_EQ(result.properties["name"].ValueString(), "name1"); - EXPECT_EQ(result.properties["age"].ValueInt(), 25); - } - { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :Person(name STRING, age INTEGER)")); - std::map property_map{}; - property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); - property_map["age"] = memgraph::storage::v3::PropertyValue(25); - EXPECT_NO_THROW(Interpret("CREATE (:Person {name: 'test', age: 30})")); - auto stream = Interpret("MATCH (m:Person) CREATE (n:Person $prop) RETURN n", - { - {"prop", memgraph::storage::v3::PropertyValue(property_map)}, - }); - ASSERT_EQ(stream.GetHeader().size(), 1U); - ASSERT_EQ(stream.GetHeader()[0], "n"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - auto result = stream.GetResults()[0][0].ValueVertex(); - EXPECT_EQ(result.properties["name"].ValueString(), "name1"); - EXPECT_EQ(result.properties["age"].ValueInt(), 25); - } - { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)")); - std::map property_map{}; - property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); - property_map["weight"] = memgraph::storage::v3::PropertyValue(121); - auto stream = Interpret("CREATE (:L1 {name: 'name1'})-[r:TO $prop]->(:L1 {name: 'name2'}) RETURN r", - { - {"prop", memgraph::storage::v3::PropertyValue(property_map)}, - }); - ASSERT_EQ(stream.GetHeader().size(), 1U); - ASSERT_EQ(stream.GetHeader()[0], "r"); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - auto result = stream.GetResults()[0][0].ValueEdge(); - EXPECT_EQ(result.properties["name"].ValueString(), "name1"); - EXPECT_EQ(result.properties["weight"].ValueInt(), 121); - } - { - std::map property_map{}; - property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); - property_map["age"] = memgraph::storage::v3::PropertyValue(15); - ASSERT_THROW(Interpret("MATCH (n $prop) RETURN n", - { - {"prop", memgraph::storage::v3::PropertyValue(property_map)}, - }), - memgraph::query::v2::SemanticException); - } - { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L2(name STRING, age INTEGER)")); - std::map property_map{}; - property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); - property_map["age"] = memgraph::storage::v3::PropertyValue(15); - ASSERT_THROW(Interpret("MERGE (n:L2 $prop) RETURN n", - { - {"prop", memgraph::storage::v3::PropertyValue(property_map)}, - }), - memgraph::query::v2::SemanticException); - } -} - -// Test bfs end to end. -TEST_F(InterpreterTest, Bfs) { - srand(0); - const auto kNumLevels = 10; - const auto kNumNodesPerLevel = 100; - const auto kNumEdgesPerNode = 100; - const auto kNumUnreachableNodes = 1000; - const auto kNumUnreachableEdges = 100000; - const auto kReachable = "reachable"; - const auto kId = "id"; - - std::vector> levels(kNumLevels); - int id = 0; - - // Set up. - { - auto storage_dba = db_.Access(); - memgraph::query::v2::DbAccessor dba(&storage_dba); - auto add_node = [&](int level, bool reachable) { - auto node = dba.InsertVertex(); - MG_ASSERT(node.SetProperty(dba.NameToProperty(kId), memgraph::storage::v3::PropertyValue(id++)).HasValue()); - MG_ASSERT( - node.SetProperty(dba.NameToProperty(kReachable), memgraph::storage::v3::PropertyValue(reachable)).HasValue()); - levels[level].push_back(node); - return node; - }; - - auto add_edge = [&](auto &v1, auto &v2, bool reachable) { - auto edge = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge")); - MG_ASSERT(edge->SetProperty(dba.NameToProperty(kReachable), memgraph::storage::v3::PropertyValue(reachable)) - .HasValue()); - }; - - // Add source node. - add_node(0, true); - - // Add reachable nodes. - for (int i = 1; i < kNumLevels; ++i) { - for (int j = 0; j < kNumNodesPerLevel; ++j) { - auto node = add_node(i, true); - for (int k = 0; k < kNumEdgesPerNode; ++k) { - auto &node2 = levels[i - 1][rand() % levels[i - 1].size()]; - add_edge(node2, node, true); - } - } - } - - // Add unreachable nodes. - for (int i = 0; i < kNumUnreachableNodes; ++i) { - auto node = add_node(rand() % kNumLevels, // Not really important. - false); - for (int j = 0; j < kNumEdgesPerNode; ++j) { - auto &level = levels[rand() % kNumLevels]; - auto &node2 = level[rand() % level.size()]; - add_edge(node2, node, true); - add_edge(node, node2, true); - } - } - - // Add unreachable edges. - for (int i = 0; i < kNumUnreachableEdges; ++i) { - auto &level1 = levels[rand() % kNumLevels]; - auto &node1 = level1[rand() % level1.size()]; - auto &level2 = levels[rand() % kNumLevels]; - auto &node2 = level2[rand() % level2.size()]; - add_edge(node1, node2, false); - } - - ASSERT_FALSE(dba.Commit().HasError()); - } - - auto stream = Interpret( - "MATCH (n {id: 0})-[r *bfs..5 (e, n | n.reachable and " - "e.reachable)]->(m) RETURN n, r, m"); - - ASSERT_EQ(stream.GetHeader().size(), 3U); - EXPECT_EQ(stream.GetHeader()[0], "n"); - EXPECT_EQ(stream.GetHeader()[1], "r"); - EXPECT_EQ(stream.GetHeader()[2], "m"); - ASSERT_EQ(stream.GetResults().size(), 5 * kNumNodesPerLevel); - - auto dba = db_.Access(); - int expected_level = 1; - int remaining_nodes_in_level = kNumNodesPerLevel; - std::unordered_set matched_ids; - - for (const auto &result : stream.GetResults()) { - const auto &begin = result[0].ValueVertex(); - const auto &edges = ToEdgeList(result[1]); - const auto &end = result[2].ValueVertex(); - - // Check that path is of expected length. Returned paths should be from - // shorter to longer ones. - EXPECT_EQ(edges.size(), expected_level); - // Check that starting node is correct. - EXPECT_EQ(edges.front().from, begin.id); - EXPECT_EQ(begin.properties.at(kId).ValueInt(), 0); - for (int i = 1; i < static_cast(edges.size()); ++i) { - // Check that edges form a connected path. - EXPECT_EQ(edges[i - 1].to.AsInt(), edges[i].from.AsInt()); - } - auto matched_id = end.properties.at(kId).ValueInt(); - EXPECT_EQ(edges.back().to, end.id); - // Check that we didn't match that node already. - EXPECT_TRUE(matched_ids.insert(matched_id).second); - // Check that shortest path was found. - EXPECT_TRUE(matched_id > kNumNodesPerLevel * (expected_level - 1) && - matched_id <= kNumNodesPerLevel * expected_level); - if (!--remaining_nodes_in_level) { - remaining_nodes_in_level = kNumNodesPerLevel; - ++expected_level; - } - } -} - -// Test shortest path end to end. -TEST_F(InterpreterTest, ShortestPath) { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :A(x INTEGER)")); - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :B(x INTEGER)")); - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :C(x INTEGER)")); - const auto test_shortest_path = [this](const bool use_duration) { - const auto get_weight = [use_duration](const auto value) { - return fmt::format(fmt::runtime(use_duration ? "DURATION('PT{}S')" : "{}"), value); - }; - - Interpret( - fmt::format("CREATE (n:A {{x: 1}}), (m:B {{x: 2}}), (l:C {{x: 1}}), (n)-[:r1 {{w: {} " - "}}]->(m)-[:r2 {{w: {}}}]->(l), (n)-[:r3 {{w: {}}}]->(l)", - get_weight(1), get_weight(2), get_weight(4))); - - auto stream = Interpret("MATCH (n)-[e *wshortest 5 (e, n | e.w) ]->(m) return e"); - - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "e"); - ASSERT_EQ(stream.GetResults().size(), 3U); - - auto dba = db_.Access(); - std::vector> expected_results{{"r1"}, {"r2"}, {"r1", "r2"}}; - - for (const auto &result : stream.GetResults()) { - const auto &edges = ToEdgeList(result[0]); - - std::vector datum; - datum.reserve(edges.size()); - - for (const auto &edge : edges) { - datum.push_back(edge.type); - } - - bool any_match = false; - for (const auto &expected : expected_results) { - if (expected == datum) { - any_match = true; - break; - } - } - - EXPECT_TRUE(any_match); - } - - Interpret("MATCH (n) DETACH DELETE n"); - }; - - static constexpr bool kUseNumeric{false}; - static constexpr bool kUseDuration{true}; - { - SCOPED_TRACE("Test with numeric values"); - test_shortest_path(kUseNumeric); - } - { - SCOPED_TRACE("Test with Duration values"); - test_shortest_path(kUseDuration); - } -} - -TEST_F(InterpreterTest, CreateLabelIndexInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE INDEX ON :X"), memgraph::query::v2::IndexInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, CreateLabelPropertyIndexInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE INDEX ON :X(y)"), memgraph::query::v2::IndexInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, CreateExistenceConstraintInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.a)"), - memgraph::query::v2::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, CreateUniqueConstraintInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE"), - memgraph::query::v2::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, ShowIndexInfoInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW INDEX INFO"), memgraph::query::v2::InfoInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, ShowConstraintInfoInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW CONSTRAINT INFO"), memgraph::query::v2::InfoInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, ShowStorageInfoInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW STORAGE INFO"), memgraph::query::v2::InfoInMulticommandTxException); - Interpret("ROLLBACK"); -} - -// // NOLINTNEXTLINE(hicpp-special-member-functions) -TEST_F(InterpreterTest, ExistenceConstraintTest) { - ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :A(a INTEGER);")); - - Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.b);"); - Interpret("CREATE (:A{a: 3, b:1})"); - Interpret("CREATE (:A{a: 3, b:2})"); - ASSERT_THROW(Interpret("CREATE (:A {a: 12})"), memgraph::query::v2::QueryException); - Interpret("MATCH (n:A{a:3, b: 2}) SET n.b=5"); - Interpret("CREATE (:A{a:2, b: 3})"); - Interpret("MATCH (n:A{a:3, b: 1}) DETACH DELETE n"); - Interpret("CREATE (n:A{a:2, b: 3})"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.c);"), - memgraph::query::v2::QueryRuntimeException); -} - -TEST_F(InterpreterTest, UniqueConstraintTest) { - ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :A(a INTEGER);")); - - // Empty property list should result with syntax exception. - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::v2::SyntaxException); - ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::v2::SyntaxException); - - // Too large list of properties should also result with syntax exception. - { - std::stringstream stream; - stream << " ON (n:A) ASSERT "; - for (size_t i = 0; i < 33; ++i) { - if (i > 0) stream << ", "; - stream << "n.prop" << i; - } - stream << " IS UNIQUE;"; - std::string create_query = "CREATE CONSTRAINT" + stream.str(); - std::string drop_query = "DROP CONSTRAINT" + stream.str(); - ASSERT_THROW(Interpret(create_query), memgraph::query::v2::SyntaxException); - ASSERT_THROW(Interpret(drop_query), memgraph::query::v2::SyntaxException); - } - - // Providing property list with duplicates results with syntax exception. - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), - memgraph::query::v2::SyntaxException); - ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), - memgraph::query::v2::SyntaxException); - - // Commit of vertex should fail if a constraint is violated. - Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); - Interpret("CREATE (:A{a:1, b:2})"); - Interpret("CREATE (:A{a:1, b:3})"); - ASSERT_THROW(Interpret("CREATE (:A{a:1, b:2})"), memgraph::query::v2::QueryException); - - // Attempt to create a constraint should fail if it's violated. - Interpret("CREATE (:A{a:1, c:2})"); - Interpret("CREATE (:A{a:1, c:2})"); - ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.c IS UNIQUE;"), - memgraph::query::v2::QueryRuntimeException); - - Interpret("MATCH (n:A{a:2, b:2}) SET n.a=1"); - Interpret("CREATE (:A{a:2})"); - Interpret("MATCH (n:A{a:2}) DETACH DELETE n"); - Interpret("CREATE (n:A{a:2})"); - - // Show constraint info. - { - auto stream = Interpret("SHOW CONSTRAINT INFO"); - ASSERT_EQ(stream.GetHeader().size(), 3U); - const auto &header = stream.GetHeader(); - ASSERT_EQ(header[0], "constraint type"); - ASSERT_EQ(header[1], "label"); - ASSERT_EQ(header[2], "properties"); - ASSERT_EQ(stream.GetResults().size(), 1U); - const auto &result = stream.GetResults().front(); - ASSERT_EQ(result.size(), 3U); - ASSERT_EQ(result[0].ValueString(), "unique"); - ASSERT_EQ(result[1].ValueString(), "A"); - const auto &properties = result[2].ValueList(); - ASSERT_EQ(properties.size(), 2U); - ASSERT_EQ(properties[0].ValueString(), "a"); - ASSERT_EQ(properties[1].ValueString(), "b"); - } - - // Drop constraint. - Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); - // Removing the same constraint twice should not throw any exception. - Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); -} - -TEST_F(InterpreterTest, ExplainQuery) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = Interpret("EXPLAIN MATCH (n) RETURN *;"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); - std::vector expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; - ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); - auto expected_it = expected_rows.begin(); - for (const auto &row : stream.GetResults()) { - ASSERT_EQ(row.size(), 1U); - EXPECT_EQ(row.front().ValueString(), *expected_it); - ++expected_it; - } - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ExplainQueryMultiplePulls) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto [stream, qid] = Prepare("EXPLAIN MATCH (n) RETURN *;"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); - std::vector expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; - Pull(&stream, 1); - ASSERT_EQ(stream.GetResults().size(), 1); - auto expected_it = expected_rows.begin(); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - EXPECT_EQ(stream.GetResults()[0].front().ValueString(), *expected_it); - ++expected_it; - - Pull(&stream, 1); - ASSERT_EQ(stream.GetResults().size(), 2); - ASSERT_EQ(stream.GetResults()[1].size(), 1U); - EXPECT_EQ(stream.GetResults()[1].front().ValueString(), *expected_it); - ++expected_it; - - Pull(&stream); - ASSERT_EQ(stream.GetResults().size(), 3); - ASSERT_EQ(stream.GetResults()[2].size(), 1U); - EXPECT_EQ(stream.GetResults()[2].front().ValueString(), *expected_it); - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ExplainQueryInMulticommandTransaction) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - Interpret("BEGIN"); - auto stream = Interpret("EXPLAIN MATCH (n) RETURN *;"); - Interpret("COMMIT"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); - std::vector expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; - ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); - auto expected_it = expected_rows.begin(); - for (const auto &row : stream.GetResults()) { - ASSERT_EQ(row.size(), 1U); - EXPECT_EQ(row.front().ValueString(), *expected_it); - ++expected_it; - } - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ExplainQueryWithParams) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = - Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue(42)}}); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); - std::vector expected_rows{" * Produce {n}", " * Filter", " * ScanAll (n)", " * Once"}; - ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); - auto expected_it = expected_rows.begin(); - for (const auto &row : stream.GetResults()) { - ASSERT_EQ(row.size(), 1U); - EXPECT_EQ(row.front().ValueString(), *expected_it); - ++expected_it; - } - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for EXPLAIN ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue("something else")}}); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ProfileQuery) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = Interpret("PROFILE MATCH (n) RETURN *;"); - std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; - EXPECT_EQ(stream.GetHeader(), expected_header); - std::vector expected_rows{"* Produce", "* ScanAll", "* Once"}; - ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); - auto expected_it = expected_rows.begin(); - for (const auto &row : stream.GetResults()) { - ASSERT_EQ(row.size(), 4U); - EXPECT_EQ(row.front().ValueString(), *expected_it); - ++expected_it; - } - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for PROFILE ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ProfileQueryMultiplePulls) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto [stream, qid] = Prepare("PROFILE MATCH (n) RETURN *;"); - std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; - EXPECT_EQ(stream.GetHeader(), expected_header); - - std::vector expected_rows{"* Produce", "* ScanAll", "* Once"}; - auto expected_it = expected_rows.begin(); - - Pull(&stream, 1); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0].size(), 4U); - ASSERT_EQ(stream.GetResults()[0][0].ValueString(), *expected_it); - ++expected_it; - - Pull(&stream, 1); - ASSERT_EQ(stream.GetResults().size(), 2U); - ASSERT_EQ(stream.GetResults()[1].size(), 4U); - ASSERT_EQ(stream.GetResults()[1][0].ValueString(), *expected_it); - ++expected_it; - - Pull(&stream); - ASSERT_EQ(stream.GetResults().size(), 3U); - ASSERT_EQ(stream.GetResults()[2].size(), 4U); - ASSERT_EQ(stream.GetResults()[2][0].ValueString(), *expected_it); - - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for PROFILE ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) RETURN *;"); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ProfileQueryInMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("PROFILE MATCH (n) RETURN *;"), memgraph::query::v2::ProfileInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, ProfileQueryWithParams) { - const auto &interpreter_context = default_interpreter.interpreter_context; - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); - auto stream = - Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue(42)}}); - std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; - EXPECT_EQ(stream.GetHeader(), expected_header); - std::vector expected_rows{"* Produce", "* Filter", "* ScanAll", "* Once"}; - ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); - auto expected_it = expected_rows.begin(); - for (const auto &row : stream.GetResults()) { - ASSERT_EQ(row.size(), 4U); - EXPECT_EQ(row.front().ValueString(), *expected_it); - ++expected_it; - } - // We should have a plan cache for MATCH ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for PROFILE ... and for inner MATCH ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); - Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue("something else")}}); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); -} - -TEST_F(InterpreterTest, ProfileQueryWithLiterals) { - const auto &interpreter_context = default_interpreter.interpreter_context; - ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :Node(id INTEGER)")); - - EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); - auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {}); - std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; - EXPECT_EQ(stream.GetHeader(), expected_header); - std::vector expected_rows{"* CreateNode", "* Unwind", "* Once"}; - ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); - auto expected_it = expected_rows.begin(); - for (const auto &row : stream.GetResults()) { - ASSERT_EQ(row.size(), 4U); - EXPECT_EQ(row.front().ValueString(), *expected_it); - ++expected_it; - } - // We should have a plan cache for UNWIND ... - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - // We should have AST cache for PROFILE ... and for inner UNWIND ... - EXPECT_EQ(interpreter_context.ast_cache.size(), 3U); - Interpret("UNWIND range(42, 4242) AS x CREATE (:Node {id: x});", {}); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - EXPECT_EQ(interpreter_context.ast_cache.size(), 3U); -} - -TEST_F(InterpreterTest, Transactions) { - auto &interpreter = default_interpreter.interpreter; - { - ASSERT_THROW(interpreter.CommitTransaction(), memgraph::query::v2::ExplicitTransactionUsageException); - ASSERT_THROW(interpreter.RollbackTransaction(), memgraph::query::v2::ExplicitTransactionUsageException); - interpreter.BeginTransaction(); - ASSERT_THROW(interpreter.BeginTransaction(), memgraph::query::v2::ExplicitTransactionUsageException); - auto [stream, qid] = Prepare("RETURN 2"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "2"); - Pull(&stream, 1); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 2); - interpreter.CommitTransaction(); - } - { - interpreter.BeginTransaction(); - auto [stream, qid] = Prepare("RETURN 2"); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "2"); - Pull(&stream, 1); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults()[0].size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 2); - interpreter.RollbackTransaction(); - } -} - -TEST_F(InterpreterTest, Qid) { - auto &interpreter = default_interpreter.interpreter; - { - interpreter.BeginTransaction(); - auto [stream, qid] = Prepare("RETURN 2"); - ASSERT_TRUE(qid); - ASSERT_THROW(Pull(&stream, {}, *qid + 1), memgraph::query::v2::InvalidArgumentsException); - interpreter.RollbackTransaction(); - } - { - interpreter.BeginTransaction(); - auto [stream1, qid1] = Prepare("UNWIND(range(1,3)) as n RETURN n"); - ASSERT_TRUE(qid1); - ASSERT_EQ(stream1.GetHeader().size(), 1U); - EXPECT_EQ(stream1.GetHeader()[0], "n"); - - auto [stream2, qid2] = Prepare("UNWIND(range(4,6)) as n RETURN n"); - ASSERT_TRUE(qid2); - ASSERT_EQ(stream2.GetHeader().size(), 1U); - EXPECT_EQ(stream2.GetHeader()[0], "n"); - - Pull(&stream1, 1, qid1); - ASSERT_EQ(stream1.GetSummary().count("has_more"), 1); - ASSERT_TRUE(stream1.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream1.GetResults().size(), 1U); - ASSERT_EQ(stream1.GetResults()[0].size(), 1U); - ASSERT_EQ(stream1.GetResults()[0][0].ValueInt(), 1); - - auto [stream3, qid3] = Prepare("UNWIND(range(7,9)) as n RETURN n"); - ASSERT_TRUE(qid3); - ASSERT_EQ(stream3.GetHeader().size(), 1U); - EXPECT_EQ(stream3.GetHeader()[0], "n"); - - Pull(&stream2, {}, qid2); - ASSERT_EQ(stream2.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream2.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream2.GetResults().size(), 3U); - ASSERT_EQ(stream2.GetResults()[0].size(), 1U); - ASSERT_EQ(stream2.GetResults()[0][0].ValueInt(), 4); - ASSERT_EQ(stream2.GetResults()[1][0].ValueInt(), 5); - ASSERT_EQ(stream2.GetResults()[2][0].ValueInt(), 6); - - Pull(&stream3, 1, qid3); - ASSERT_EQ(stream3.GetSummary().count("has_more"), 1); - ASSERT_TRUE(stream3.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream3.GetResults().size(), 1U); - ASSERT_EQ(stream3.GetResults()[0].size(), 1U); - ASSERT_EQ(stream3.GetResults()[0][0].ValueInt(), 7); - - Pull(&stream1, {}, qid1); - ASSERT_EQ(stream1.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream1.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream1.GetResults().size(), 3U); - ASSERT_EQ(stream1.GetResults()[1].size(), 1U); - ASSERT_EQ(stream1.GetResults()[1][0].ValueInt(), 2); - ASSERT_EQ(stream1.GetResults()[2][0].ValueInt(), 3); - - Pull(&stream3); - ASSERT_EQ(stream3.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream3.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream3.GetResults().size(), 3U); - ASSERT_EQ(stream3.GetResults()[1].size(), 1U); - ASSERT_EQ(stream3.GetResults()[1][0].ValueInt(), 8); - ASSERT_EQ(stream3.GetResults()[2][0].ValueInt(), 9); - - interpreter.CommitTransaction(); - } -} - -namespace { -// copied from utils_csv_parsing.cpp - tmp dir management and csv file writer -class TmpDirManager final { - public: - explicit TmpDirManager(const std::string_view directory) - : tmp_dir_{std::filesystem::temp_directory_path() / directory} { - CreateDir(); - } - ~TmpDirManager() { Clear(); } - - const std::filesystem::path &Path() const { return tmp_dir_; } - - private: - std::filesystem::path tmp_dir_; - - void CreateDir() { - if (!std::filesystem::exists(tmp_dir_)) { - std::filesystem::create_directory(tmp_dir_); - } - } - - void Clear() { - if (!std::filesystem::exists(tmp_dir_)) return; - std::filesystem::remove_all(tmp_dir_); - } -}; - -class FileWriter { - public: - explicit FileWriter(const std::filesystem::path path) { stream_.open(path); } - - FileWriter(const FileWriter &) = delete; - FileWriter &operator=(const FileWriter &) = delete; - - FileWriter(FileWriter &&) = delete; - FileWriter &operator=(FileWriter &&) = delete; - - void Close() { stream_.close(); } - - size_t WriteLine(const std::string_view line) { - if (!stream_.is_open()) { - return 0; - } - - stream_ << line << std::endl; - - // including the newline character - return line.size() + 1; - } - - private: - std::ofstream stream_; -}; - -std::string CreateRow(const std::vector &columns, const std::string_view delim) { - return memgraph::utils::Join(columns, delim); -} -} // namespace - -TEST_F(InterpreterTest, LoadCsvClause) { - auto dir_manager = TmpDirManager("csv_directory"); - const auto csv_path = dir_manager.Path() / "file.csv"; - auto writer = FileWriter(csv_path); - - const std::string delimiter{"|"}; - - const std::vector header{"A", "B", "C"}; - writer.WriteLine(CreateRow(header, delimiter)); - - const std::vector good_columns_1{"a", "b", "c"}; - writer.WriteLine(CreateRow(good_columns_1, delimiter)); - - const std::vector bad_columns{"\"\"1", "2", "3"}; - writer.WriteLine(CreateRow(bad_columns, delimiter)); - - const std::vector good_columns_2{"d", "e", "f"}; - writer.WriteLine(CreateRow(good_columns_2, delimiter)); - - writer.Close(); - - { - const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN - x.A)", - csv_path.string(), delimiter); - auto [stream, qid] = Prepare(query); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "x.A"); - - Pull(&stream, 1); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults().size(), 1U); - ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "a"); - - Pull(&stream, 1); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults().size(), 2U); - ASSERT_EQ(stream.GetResults()[1][0].ValueString(), "d"); - } - - { - const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN - x.C)", - csv_path.string(), delimiter); - auto [stream, qid] = Prepare(query); - ASSERT_EQ(stream.GetHeader().size(), 1U); - EXPECT_EQ(stream.GetHeader()[0], "x.C"); - - Pull(&stream); - ASSERT_EQ(stream.GetSummary().count("has_more"), 1); - ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); - ASSERT_EQ(stream.GetResults().size(), 2U); - ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "c"); - ASSERT_EQ(stream.GetResults()[1][0].ValueString(), "f"); - } -} - -TEST_F(InterpreterTest, CacheableQueries) { - const auto &interpreter_context = default_interpreter.interpreter_context; - // This should be cached - { - SCOPED_TRACE("Cacheable query"); - Interpret("RETURN 1"); - EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - } - - { - SCOPED_TRACE("Uncacheable query"); - // Queries which are calling procedure should not be cached because the - // result signature could be changed - Interpret("CALL mg.load_all()"); - EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); - EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); - } -} - -TEST_F(InterpreterTest, AllowLoadCsvConfig) { - const auto check_load_csv_queries = [&](const bool allow_load_csv) { - TmpDirManager directory_manager{"allow_load_csv"}; - const auto csv_path = directory_manager.Path() / "file.csv"; - auto writer = FileWriter(csv_path); - const std::vector data{"A", "B", "C"}; - writer.WriteLine(CreateRow(data, ",")); - writer.Close(); - - const std::array queries = { - fmt::format("LOAD CSV FROM \"{}\" WITH HEADER AS row RETURN row", csv_path.string()), - "CREATE TRIGGER trigger ON CREATE BEFORE COMMIT EXECUTE LOAD CSV FROM 'file.csv' WITH HEADER AS row RETURN " - "row"}; - - InterpreterFaker interpreter_faker{&db_, {.query = {.allow_load_csv = allow_load_csv}}, directory_manager.Path()}; - for (const auto &query : queries) { - if (allow_load_csv) { - SCOPED_TRACE(fmt::format("'{}' should not throw because LOAD CSV is allowed", query)); - ASSERT_NO_THROW(interpreter_faker.Interpret(query)); - } else { - SCOPED_TRACE(fmt::format("'{}' should throw becuase LOAD CSV is not allowed", query)); - ASSERT_THROW(interpreter_faker.Interpret(query), memgraph::utils::BasicException); - } - SCOPED_TRACE(fmt::format("Normal query should not throw (allow_load_csv: {})", allow_load_csv)); - ASSERT_NO_THROW(interpreter_faker.Interpret("RETURN 1")); - } - }; - - check_load_csv_queries(true); - check_load_csv_queries(false); -} - -void AssertAllValuesAreZero(const std::map &map, - const std::vector &exceptions) { - for (const auto &[key, value] : map) { - if (const auto it = std::find(exceptions.begin(), exceptions.end(), key); it != exceptions.end()) continue; - ASSERT_EQ(value.ValueInt(), 0) << "Value " << key << " actual: " << value.ValueInt() << ", expected 0"; - } -} - -TEST_F(InterpreterTest, ExecutionStatsIsValid) { - { - auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("stats"), 0); - } - { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)")); - std::array stats_keys{"nodes-created", "nodes-deleted", "relationships-created", "relationships-deleted", - "properties-set", "labels-added", "labels-removed"}; - auto [stream, qid] = Prepare("CREATE (:L1 {name: 'name1'});"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("stats"), 1); - ASSERT_TRUE(stream.GetSummary().at("stats").IsMap()); - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_TRUE( - std::all_of(stats_keys.begin(), stats_keys.end(), [&stats](const auto &key) { return stats.contains(key); })); - AssertAllValuesAreZero(stats, {"nodes-created"}); - } -} - -TEST_F(InterpreterTest, ExecutionStatsValues) { - EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)")); - { - auto [stream, qid] = - Prepare("CREATE (:L1{name: 'name1'}),(:L1{name: 'name2'}),(:L1{name: 'name3'}),(:L1{name: 'name4'});"); - - Pull(&stream); - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_EQ(stats["nodes-created"].ValueInt(), 4); - AssertAllValuesAreZero(stats, {"nodes-created", "labels-added"}); - } - { - auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); - Pull(&stream); - - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_EQ(stats["nodes-deleted"].ValueInt(), 4); - AssertAllValuesAreZero(stats, {"nodes-deleted"}); - } - { - auto [stream, qid] = - Prepare("CREATE (n:L1 {name: 'name5'})-[:TO]->(m:L1{name: 'name6'}), (n)-[:TO]->(m), (n)-[:TO]->(m);"); - - Pull(&stream); - - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_EQ(stats["nodes-created"].ValueInt(), 2); - ASSERT_EQ(stats["relationships-created"].ValueInt(), 3); - AssertAllValuesAreZero(stats, {"nodes-created", "relationships-created"}); - } - { - auto [stream, qid] = Prepare("MATCH (n) DETACH DELETE n;"); - Pull(&stream); - - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_EQ(stats["nodes-deleted"].ValueInt(), 2); - ASSERT_EQ(stats["relationships-deleted"].ValueInt(), 3); - AssertAllValuesAreZero(stats, {"nodes-deleted", "relationships-deleted"}); - } - { - auto [stream, qid] = Prepare("CREATE (n:L1 {name: 'name7'}) SET n:L2:L3:L4"); - Pull(&stream); - - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_EQ(stats["nodes-created"].ValueInt(), 1); - ASSERT_EQ(stats["labels-added"].ValueInt(), 3); - AssertAllValuesAreZero(stats, {"nodes-created", "labels-added"}); - } - { - auto [stream, qid] = Prepare("MATCH (n:L1) SET n.name2='test';"); - Pull(&stream); - - auto stats = stream.GetSummary().at("stats").ValueMap(); - ASSERT_EQ(stats["properties-set"].ValueInt(), 1); - AssertAllValuesAreZero(stats, {"properties-set"}); - } -} - -TEST_F(InterpreterTest, NotificationsValidStructure) { - { - auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 0); - } - { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); - Pull(&stream); - - // Assert notifications list - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - ASSERT_TRUE(stream.GetSummary().at("notifications").IsList()); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - // Assert one notification structure - ASSERT_EQ(notifications.size(), 1); - ASSERT_TRUE(notifications[0].IsMap()); - auto notification = notifications[0].ValueMap(); - ASSERT_TRUE(notification.contains("severity")); - ASSERT_TRUE(notification.contains("code")); - ASSERT_TRUE(notification.contains("title")); - ASSERT_TRUE(notification.contains("description")); - ASSERT_TRUE(notification["severity"].IsString()); - ASSERT_TRUE(notification["code"].IsString()); - ASSERT_TRUE(notification["title"].IsString()); - ASSERT_TRUE(notification["description"].IsString()); - } -} - -TEST_F(InterpreterTest, IndexInfoNotifications) { - { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "CreateIndex"); - ASSERT_EQ(notification["title"].ValueString(), "Created index on label Person on properties ."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "CreateIndex"); - ASSERT_EQ(notification["title"].ValueString(), "Created index on label Person on properties id."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "IndexAlreadyExists"); - ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id already exists."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "DropIndex"); - ASSERT_EQ(notification["title"].ValueString(), "Dropped index on label Person on properties id."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "IndexDoesNotExist"); - ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id doesn't exist."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } -} - -TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { - { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "CreateConstraint"); - ASSERT_EQ(notification["title"].ValueString(), - "Created UNIQUE constraint on label Person on properties email, id."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "ConstraintAlreadyExists"); - ASSERT_EQ(notification["title"].ValueString(), - "Constraint UNIQUE on label Person on properties email, id already exists."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "DropConstraint"); - ASSERT_EQ(notification["title"].ValueString(), - "Dropped UNIQUE constraint on label Person on properties email, id."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "ConstraintDoesNotExist"); - ASSERT_EQ(notification["title"].ValueString(), - "Constraint UNIQUE on label Person on properties email, id doesn't exist."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } -} - -TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { - { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "CreateConstraint"); - ASSERT_EQ(notification["title"].ValueString(), "Created EXISTS constraint on label L1 on properties name."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "ConstraintAlreadyExists"); - ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name already exists."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "DropConstraint"); - ASSERT_EQ(notification["title"].ValueString(), "Dropped EXISTS constraint on label L1 on properties name."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "ConstraintDoesNotExist"); - ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name doesn't exist."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } -} - -TEST_F(InterpreterTest, TriggerInfoNotifications) { - { - auto [stream, qid] = Prepare( - "CREATE TRIGGER bestTriggerEver ON CREATE AFTER COMMIT EXECUTE " - "CREATE ();"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "CreateTrigger"); - ASSERT_EQ(notification["title"].ValueString(), "Created trigger bestTriggerEver."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } - { - auto [stream, qid] = Prepare("DROP TRIGGER bestTriggerEver;"); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "DropTrigger"); - ASSERT_EQ(notification["title"].ValueString(), "Dropped trigger bestTriggerEver."); - ASSERT_EQ(notification["description"].ValueString(), ""); - } -} - -TEST_F(InterpreterTest, LoadCsvClauseNotification) { - auto dir_manager = TmpDirManager("csv_directory"); - const auto csv_path = dir_manager.Path() / "file.csv"; - auto writer = FileWriter(csv_path); - - const std::string delimiter{"|"}; - - const std::vector header{"A", "B", "C"}; - writer.WriteLine(CreateRow(header, delimiter)); - - const std::vector good_columns_1{"a", "b", "c"}; - writer.WriteLine(CreateRow(good_columns_1, delimiter)); - - writer.Close(); - - const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN x;)", - csv_path.string(), delimiter); - auto [stream, qid] = Prepare(query); - Pull(&stream); - - ASSERT_EQ(stream.GetSummary().count("notifications"), 1); - auto notifications = stream.GetSummary().at("notifications").ValueList(); - - auto notification = notifications[0].ValueMap(); - ASSERT_EQ(notification["severity"].ValueString(), "INFO"); - ASSERT_EQ(notification["code"].ValueString(), "LoadCSVTip"); - ASSERT_EQ(notification["title"].ValueString(), - "It's important to note that the parser parses the values as strings. It's up to the user to " - "convert the parsed row values to the appropriate type. This can be done using the built-in " - "conversion functions such as ToInteger, ToFloat, ToBoolean etc."); - ASSERT_EQ(notification["description"].ValueString(), ""); -} - -TEST_F(InterpreterTest, CreateSchemaMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"), - memgraph::query::v2::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, ShowSchemasMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW SCHEMAS"), memgraph::query::v2::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, ShowSchemaMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"), memgraph::query::v2::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, DropSchemaMulticommandTransaction) { - Interpret("BEGIN"); - ASSERT_THROW(Interpret("DROP SCHEMA ON :label"), memgraph::query::v2::ConstraintInMulticommandTxException); - Interpret("ROLLBACK"); -} - -TEST_F(InterpreterTest, SchemaTestCreateAndShow) { - // Empty schema type map should result with syntax exception. - ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"), memgraph::query::v2::SyntaxException); - - // Duplicate properties are should also cause an exception - ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name STRING);"), memgraph::query::v2::SemanticException); - ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name INTEGER);"), - memgraph::query::v2::SemanticException); - - { - // Cannot create same schema twice - Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"); - ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"), memgraph::query::v2::QueryException); - } - // Show schema - { - auto stream = Interpret("SHOW SCHEMA ON :label"); - ASSERT_EQ(stream.GetHeader().size(), 2U); - const auto &header = stream.GetHeader(); - ASSERT_EQ(header[0], "property_name"); - ASSERT_EQ(header[1], "property_type"); - ASSERT_EQ(stream.GetResults().size(), 2U); - std::unordered_map result_table{{"age", "Integer"}, {"name", "String"}}; - - const auto &result = stream.GetResults().front(); - ASSERT_EQ(result.size(), 2U); - const auto key1 = result[0].ValueString(); - ASSERT_TRUE(result_table.contains(key1)); - ASSERT_EQ(result[1].ValueString(), result_table[key1]); - - const auto &result2 = stream.GetResults().front(); - ASSERT_EQ(result2.size(), 2U); - const auto key2 = result2[0].ValueString(); - ASSERT_TRUE(result_table.contains(key2)); - ASSERT_EQ(result[1].ValueString(), result_table[key2]); - } - // Create Another Schema - Interpret("CREATE SCHEMA ON :label2(place STRING, dur DURATION)"); - - // Show schemas - { - auto stream = Interpret("SHOW SCHEMAS"); - ASSERT_EQ(stream.GetHeader().size(), 2U); - const auto &header = stream.GetHeader(); - ASSERT_EQ(header[0], "label"); - ASSERT_EQ(header[1], "primary_key"); - ASSERT_EQ(stream.GetResults().size(), 2U); - std::unordered_map> result_table{ - {"label", {"name::String", "age::Integer"}}, {"label2", {"place::String", "dur::Duration"}}}; - - const auto &result = stream.GetResults().front(); - ASSERT_EQ(result.size(), 2U); - const auto key1 = result[0].ValueString(); - ASSERT_TRUE(result_table.contains(key1)); - const auto primary_key_split = StringToUnorderedSet(result[1].ValueString()); - ASSERT_EQ(primary_key_split.size(), 2); - ASSERT_TRUE(primary_key_split == result_table[key1]) << "actual value is: " << result[1].ValueString(); - - const auto &result2 = stream.GetResults().front(); - ASSERT_EQ(result2.size(), 2U); - const auto key2 = result2[0].ValueString(); - ASSERT_TRUE(result_table.contains(key2)); - const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString()); - ASSERT_EQ(primary_key_split2.size(), 2); - ASSERT_TRUE(primary_key_split2 == result_table[key2]) << "Real value is: " << result[1].ValueString(); - } -} - -TEST_F(InterpreterTest, SchemaTestCreateDropAndShow) { - Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"); - // Wrong syntax for dropping schema. - ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"), memgraph::query::v2::SyntaxException); - // Cannot drop non existant schema. - ASSERT_THROW(Interpret("DROP SCHEMA ON :label1;"), memgraph::query::v2::QueryException); - - // Create Schema and Drop - auto get_number_of_schemas = [this]() { - auto stream = Interpret("SHOW SCHEMAS"); - return stream.GetResults().size(); - }; - - ASSERT_EQ(get_number_of_schemas(), 1); - Interpret("CREATE SCHEMA ON :label1(name STRING, age INTEGER)"); - ASSERT_EQ(get_number_of_schemas(), 2); - Interpret("CREATE SCHEMA ON :label2(name STRING, alive BOOL)"); - ASSERT_EQ(get_number_of_schemas(), 3); - Interpret("DROP SCHEMA ON :label1"); - ASSERT_EQ(get_number_of_schemas(), 2); - Interpret("CREATE SCHEMA ON :label3(name STRING, birthday LOCALDATETIME)"); - ASSERT_EQ(get_number_of_schemas(), 3); - Interpret("DROP SCHEMA ON :label2"); - ASSERT_EQ(get_number_of_schemas(), 2); - Interpret("CREATE SCHEMA ON :label4(name STRING, age DURATION)"); - ASSERT_EQ(get_number_of_schemas(), 3); - Interpret("DROP SCHEMA ON :label3"); - ASSERT_EQ(get_number_of_schemas(), 2); - Interpret("DROP SCHEMA ON :label"); - ASSERT_EQ(get_number_of_schemas(), 1); - - // Show schemas - auto stream = Interpret("SHOW SCHEMAS"); - ASSERT_EQ(stream.GetHeader().size(), 2U); - const auto &header = stream.GetHeader(); - ASSERT_EQ(header[0], "label"); - ASSERT_EQ(header[1], "primary_key"); - ASSERT_EQ(stream.GetResults().size(), 1U); - std::unordered_map> result_table{ - {"label4", {"name::String", "age::Duration"}}}; - - const auto &result = stream.GetResults().front(); - ASSERT_EQ(result.size(), 2U); - const auto key1 = result[0].ValueString(); - ASSERT_TRUE(result_table.contains(key1)); - const auto primary_key_split = StringToUnorderedSet(result[1].ValueString()); - ASSERT_EQ(primary_key_split.size(), 2); - ASSERT_TRUE(primary_key_split == result_table[key1]); -} +// TEST_F(InterpreterTest, MultiplePulls) { +// { +// auto [stream, qid] = Prepare("UNWIND [1,2,3,4,5] as n RETURN n"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "n"); +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 1); +// Pull(&stream, 2); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults().size(), 3U); +// ASSERT_EQ(stream.GetResults()[1][0].ValueInt(), 2); +// ASSERT_EQ(stream.GetResults()[2][0].ValueInt(), 3); +// Pull(&stream); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults().size(), 5U); +// ASSERT_EQ(stream.GetResults()[3][0].ValueInt(), 4); +// ASSERT_EQ(stream.GetResults()[4][0].ValueInt(), 5); +// } +// } + +// // Run query with different ast twice to see if query executes correctly when +// // ast is read from cache. +// TEST_F(InterpreterTest, AstCache) { +// { +// auto stream = Interpret("RETURN 2 + 3"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "2 + 3"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 5); +// } +// { +// // Cached ast, different literals. +// auto stream = Interpret("RETURN 5 + 4"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 9); +// } +// { +// // Different ast (because of different types). +// auto stream = Interpret("RETURN 5.5 + 4"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 9.5); +// } +// { +// // Cached ast, same literals. +// auto stream = Interpret("RETURN 2 + 3"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 5); +// } +// { +// // Cached ast, different literals. +// auto stream = Interpret("RETURN 10.5 + 1"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); +// } +// { +// // Cached ast, same literals, different whitespaces. +// auto stream = Interpret("RETURN 10.5 + 1"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); +// } +// { +// // Cached ast, same literals, different named header. +// auto stream = Interpret("RETURN 10.5+1"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "10.5+1"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueDouble(), 11.5); +// } +// } + +// // Run query with same ast multiple times with different parameters. +// TEST_F(InterpreterTest, Parameters) { +// { +// auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue(10)}, +// {"a b", memgraph::storage::v3::PropertyValue(15)}}); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 25); +// } +// { +// // Not needed parameter. +// auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue(10)}, +// {"a b", memgraph::storage::v3::PropertyValue(15)}, +// {"c", memgraph::storage::v3::PropertyValue(10)}}); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "$2 + $`a b`"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 25); +// } +// { +// // Cached ast, different parameters. +// auto stream = Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue("da")}, +// {"a b", memgraph::storage::v3::PropertyValue("ne")}}); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "dane"); +// } +// { +// // Non-primitive literal. +// auto stream = Interpret( +// "RETURN $2", {{"2", memgraph::storage::v3::PropertyValue(std::vector{ +// memgraph::storage::v3::PropertyValue(5), memgraph::storage::v3::PropertyValue(2), +// memgraph::storage::v3::PropertyValue(3)})}}); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// auto result = +// memgraph::query::v2::test_common::ToIntList(memgraph::glue::v2::ToTypedValue(stream.GetResults()[0][0])); +// ASSERT_THAT(result, testing::ElementsAre(5, 2, 3)); +// } +// { +// // Cached ast, unprovided parameter. +// ASSERT_THROW(Interpret("RETURN $2 + $`a b`", {{"2", memgraph::storage::v3::PropertyValue("da")}, +// {"ab", memgraph::storage::v3::PropertyValue("ne")}}), +// memgraph::query::v2::UnprovidedParameterError); +// } +// } + +// // Run CREATE/MATCH/MERGE queries with property map +// TEST_F(InterpreterTest, ParametersAsPropertyMap) { +// { +// std::map property_map{}; +// property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); +// property_map["age"] = memgraph::storage::v3::PropertyValue(25); +// auto stream = +// Interpret("CREATE (n:label $prop) RETURN n", { +// {"prop", +// memgraph::storage::v3::PropertyValue(property_map)}, +// }); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// ASSERT_EQ(stream.GetHeader()[0], "n"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// auto result = stream.GetResults()[0][0].ValueVertex(); +// EXPECT_EQ(result.properties["name"].ValueString(), "name1"); +// EXPECT_EQ(result.properties["age"].ValueInt(), 25); +// } +// { +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :Person(name STRING, age INTEGER)")); +// std::map property_map{}; +// property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); +// property_map["age"] = memgraph::storage::v3::PropertyValue(25); +// EXPECT_NO_THROW(Interpret("CREATE (:Person {name: 'test', age: 30})")); +// auto stream = Interpret("MATCH (m:Person) CREATE (n:Person $prop) RETURN n", +// { +// {"prop", memgraph::storage::v3::PropertyValue(property_map)}, +// }); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// ASSERT_EQ(stream.GetHeader()[0], "n"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// auto result = stream.GetResults()[0][0].ValueVertex(); +// EXPECT_EQ(result.properties["name"].ValueString(), "name1"); +// EXPECT_EQ(result.properties["age"].ValueInt(), 25); +// } +// { +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)")); +// std::map property_map{}; +// property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); +// property_map["weight"] = memgraph::storage::v3::PropertyValue(121); +// auto stream = Interpret("CREATE (:L1 {name: 'name1'})-[r:TO $prop]->(:L1 {name: 'name2'}) RETURN r", +// { +// {"prop", memgraph::storage::v3::PropertyValue(property_map)}, +// }); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// ASSERT_EQ(stream.GetHeader()[0], "r"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// auto result = stream.GetResults()[0][0].ValueEdge(); +// EXPECT_EQ(result.properties["name"].ValueString(), "name1"); +// EXPECT_EQ(result.properties["weight"].ValueInt(), 121); +// } +// { +// std::map property_map{}; +// property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); +// property_map["age"] = memgraph::storage::v3::PropertyValue(15); +// ASSERT_THROW(Interpret("MATCH (n $prop) RETURN n", +// { +// {"prop", memgraph::storage::v3::PropertyValue(property_map)}, +// }), +// memgraph::query::v2::SemanticException); +// } +// { +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L2(name STRING, age INTEGER)")); +// std::map property_map{}; +// property_map["name"] = memgraph::storage::v3::PropertyValue("name1"); +// property_map["age"] = memgraph::storage::v3::PropertyValue(15); +// ASSERT_THROW(Interpret("MERGE (n:L2 $prop) RETURN n", +// { +// {"prop", memgraph::storage::v3::PropertyValue(property_map)}, +// }), +// memgraph::query::v2::SemanticException); +// } +// } + +// // Test bfs end to end. +// TEST_F(InterpreterTest, Bfs) { +// srand(0); +// const auto kNumLevels = 10; +// const auto kNumNodesPerLevel = 100; +// const auto kNumEdgesPerNode = 100; +// const auto kNumUnreachableNodes = 1000; +// const auto kNumUnreachableEdges = 100000; +// const auto kReachable = "reachable"; +// const auto kId = "id"; + +// std::vector> levels(kNumLevels); +// int id = 0; + +// // Set up. +// { +// auto storage_dba = db_.Access(); +// memgraph::query::v2::DbAccessor dba(&storage_dba); +// auto add_node = [&](int level, bool reachable) { +// auto maybe_node = dba.InsertVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(id)}}); +// MG_ASSERT(maybe_node.HasValue()); +// auto node = maybe_node.GetValue(); +// MG_ASSERT( +// node.SetPropertyAndValidate(dba.NameToProperty(kId), +// memgraph::storage::v3::PropertyValue(id++)).HasValue()); +// MG_ASSERT( +// node.SetPropertyAndValidate(dba.NameToProperty(kReachable), +// memgraph::storage::v3::PropertyValue(reachable)) +// .HasValue()); +// levels[level].push_back(node); +// return node; +// }; + +// auto add_edge = [&](auto &v1, auto &v2, bool reachable) { +// auto edge = dba.InsertEdge(&v1, &v2, dba.NameToEdgeType("edge")); +// MG_ASSERT(edge->SetProperty(dba.NameToProperty(kReachable), memgraph::storage::v3::PropertyValue(reachable)) +// .HasValue()); +// }; + +// // Add source node. +// add_node(0, true); + +// // Add reachable nodes. +// for (int i = 1; i < kNumLevels; ++i) { +// for (int j = 0; j < kNumNodesPerLevel; ++j) { +// auto node = add_node(i, true); +// for (int k = 0; k < kNumEdgesPerNode; ++k) { +// auto &node2 = levels[i - 1][rand() % levels[i - 1].size()]; +// add_edge(node2, node, true); +// } +// } +// } + +// // Add unreachable nodes. +// for (int i = 0; i < kNumUnreachableNodes; ++i) { +// auto node = add_node(rand() % kNumLevels, // Not really important. +// false); +// for (int j = 0; j < kNumEdgesPerNode; ++j) { +// auto &level = levels[rand() % kNumLevels]; +// auto &node2 = level[rand() % level.size()]; +// add_edge(node2, node, true); +// add_edge(node, node2, true); +// } +// } + +// // Add unreachable edges. +// for (int i = 0; i < kNumUnreachableEdges; ++i) { +// auto &level1 = levels[rand() % kNumLevels]; +// auto &node1 = level1[rand() % level1.size()]; +// auto &level2 = levels[rand() % kNumLevels]; +// auto &node2 = level2[rand() % level2.size()]; +// add_edge(node1, node2, false); +// } + +// ASSERT_FALSE(dba.Commit().HasError()); +// } + +// auto stream = Interpret( +// "MATCH (n {id: 0})-[r *bfs..5 (e, n | n.reachable and " +// "e.reachable)]->(m) RETURN n, r, m"); + +// ASSERT_EQ(stream.GetHeader().size(), 3U); +// EXPECT_EQ(stream.GetHeader()[0], "n"); +// EXPECT_EQ(stream.GetHeader()[1], "r"); +// EXPECT_EQ(stream.GetHeader()[2], "m"); +// ASSERT_EQ(stream.GetResults().size(), 5 * kNumNodesPerLevel); + +// auto dba = db_.Access(); +// int expected_level = 1; +// int remaining_nodes_in_level = kNumNodesPerLevel; +// std::unordered_set matched_ids; + +// for (const auto &result : stream.GetResults()) { +// const auto &begin = result[0].ValueVertex(); +// const auto &edges = ToEdgeList(result[1]); +// const auto &end = result[2].ValueVertex(); + +// // Check that path is of expected length. Returned paths should be from +// // shorter to longer ones. +// EXPECT_EQ(edges.size(), expected_level); +// // Check that starting node is correct. +// EXPECT_EQ(edges.front().from, begin.id); +// EXPECT_EQ(begin.properties.at(kId).ValueInt(), 0); +// for (int i = 1; i < static_cast(edges.size()); ++i) { +// // Check that edges form a connected path. +// EXPECT_EQ(edges[i - 1].to.AsInt(), edges[i].from.AsInt()); +// } +// auto matched_id = end.properties.at(kId).ValueInt(); +// EXPECT_EQ(edges.back().to, end.id); +// // Check that we didn't match that node already. +// EXPECT_TRUE(matched_ids.insert(matched_id).second); +// // Check that shortest path was found. +// EXPECT_TRUE(matched_id > kNumNodesPerLevel * (expected_level - 1) && +// matched_id <= kNumNodesPerLevel * expected_level); +// if (!--remaining_nodes_in_level) { +// remaining_nodes_in_level = kNumNodesPerLevel; +// ++expected_level; +// } +// } +// } + +// // Test shortest path end to end. +// TEST_F(InterpreterTest, ShortestPath) { +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :A(x INTEGER)")); +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :B(x INTEGER)")); +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :C(x INTEGER)")); +// const auto test_shortest_path = [this](const bool use_duration) { +// const auto get_weight = [use_duration](const auto value) { +// return fmt::format(fmt::runtime(use_duration ? "DURATION('PT{}S')" : "{}"), value); +// }; + +// Interpret( +// fmt::format("CREATE (n:A {{x: 1}}), (m:B {{x: 2}}), (l:C {{x: 1}}), (n)-[:r1 {{w: {} " +// "}}]->(m)-[:r2 {{w: {}}}]->(l), (n)-[:r3 {{w: {}}}]->(l)", +// get_weight(1), get_weight(2), get_weight(4))); + +// auto stream = Interpret("MATCH (n)-[e *wshortest 5 (e, n | e.w) ]->(m) return e"); + +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "e"); +// ASSERT_EQ(stream.GetResults().size(), 3U); + +// auto dba = db_.Access(); +// std::vector> expected_results{{"r1"}, {"r2"}, {"r1", "r2"}}; + +// for (const auto &result : stream.GetResults()) { +// const auto &edges = ToEdgeList(result[0]); + +// std::vector datum; +// datum.reserve(edges.size()); + +// for (const auto &edge : edges) { +// datum.push_back(edge.type); +// } + +// bool any_match = false; +// for (const auto &expected : expected_results) { +// if (expected == datum) { +// any_match = true; +// break; +// } +// } + +// EXPECT_TRUE(any_match); +// } + +// Interpret("MATCH (n) DETACH DELETE n"); +// }; + +// static constexpr bool kUseNumeric{false}; +// static constexpr bool kUseDuration{true}; +// { +// SCOPED_TRACE("Test with numeric values"); +// test_shortest_path(kUseNumeric); +// } +// { +// SCOPED_TRACE("Test with Duration values"); +// test_shortest_path(kUseDuration); +// } +// } + +// TEST_F(InterpreterTest, CreateLabelIndexInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("CREATE INDEX ON :X"), memgraph::query::v2::IndexInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, CreateLabelPropertyIndexInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("CREATE INDEX ON :X(y)"), memgraph::query::v2::IndexInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, CreateExistenceConstraintInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.a)"), +// memgraph::query::v2::ConstraintInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, CreateUniqueConstraintInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE"), +// memgraph::query::v2::ConstraintInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, ShowIndexInfoInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("SHOW INDEX INFO"), memgraph::query::v2::InfoInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, ShowConstraintInfoInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("SHOW CONSTRAINT INFO"), memgraph::query::v2::InfoInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, ShowStorageInfoInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("SHOW STORAGE INFO"), memgraph::query::v2::InfoInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// // // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_F(InterpreterTest, ExistenceConstraintTest) { +// ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :A(a INTEGER);")); + +// Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.b);"); +// Interpret("CREATE (:A{a: 3, b:1})"); +// Interpret("CREATE (:A{a: 3, b:2})"); +// ASSERT_THROW(Interpret("CREATE (:A {a: 12})"), memgraph::query::v2::QueryException); +// Interpret("MATCH (n:A{a:3, b: 2}) SET n.b=5"); +// Interpret("CREATE (:A{a:2, b: 3})"); +// Interpret("MATCH (n:A{a:3, b: 1}) DETACH DELETE n"); +// Interpret("CREATE (n:A{a:2, b: 3})"); +// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT EXISTS (n.c);"), +// memgraph::query::v2::QueryRuntimeException); +// } + +// TEST_F(InterpreterTest, UniqueConstraintTest) { +// ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :A(a INTEGER);")); + +// // Empty property list should result with syntax exception. +// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::v2::SyntaxException); +// ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT IS UNIQUE;"), memgraph::query::v2::SyntaxException); + +// // Too large list of properties should also result with syntax exception. +// { +// std::stringstream stream; +// stream << " ON (n:A) ASSERT "; +// for (size_t i = 0; i < 33; ++i) { +// if (i > 0) stream << ", "; +// stream << "n.prop" << i; +// } +// stream << " IS UNIQUE;"; +// std::string create_query = "CREATE CONSTRAINT" + stream.str(); +// std::string drop_query = "DROP CONSTRAINT" + stream.str(); +// ASSERT_THROW(Interpret(create_query), memgraph::query::v2::SyntaxException); +// ASSERT_THROW(Interpret(drop_query), memgraph::query::v2::SyntaxException); +// } + +// // Providing property list with duplicates results with syntax exception. +// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), +// memgraph::query::v2::SyntaxException); +// ASSERT_THROW(Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b, n.a IS UNIQUE;"), +// memgraph::query::v2::SyntaxException); + +// // Commit of vertex should fail if a constraint is violated. +// Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); +// Interpret("CREATE (:A{a:1, b:2})"); +// Interpret("CREATE (:A{a:1, b:3})"); +// ASSERT_THROW(Interpret("CREATE (:A{a:1, b:2})"), memgraph::query::v2::QueryException); + +// // Attempt to create a constraint should fail if it's violated. +// Interpret("CREATE (:A{a:1, c:2})"); +// Interpret("CREATE (:A{a:1, c:2})"); +// ASSERT_THROW(Interpret("CREATE CONSTRAINT ON (n:A) ASSERT n.a, n.c IS UNIQUE;"), +// memgraph::query::v2::QueryRuntimeException); + +// Interpret("MATCH (n:A{a:2, b:2}) SET n.a=1"); +// Interpret("CREATE (:A{a:2})"); +// Interpret("MATCH (n:A{a:2}) DETACH DELETE n"); +// Interpret("CREATE (n:A{a:2})"); + +// // Show constraint info. +// { +// auto stream = Interpret("SHOW CONSTRAINT INFO"); +// ASSERT_EQ(stream.GetHeader().size(), 3U); +// const auto &header = stream.GetHeader(); +// ASSERT_EQ(header[0], "constraint type"); +// ASSERT_EQ(header[1], "label"); +// ASSERT_EQ(header[2], "properties"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// const auto &result = stream.GetResults().front(); +// ASSERT_EQ(result.size(), 3U); +// ASSERT_EQ(result[0].ValueString(), "unique"); +// ASSERT_EQ(result[1].ValueString(), "A"); +// const auto &properties = result[2].ValueList(); +// ASSERT_EQ(properties.size(), 2U); +// ASSERT_EQ(properties[0].ValueString(), "a"); +// ASSERT_EQ(properties[1].ValueString(), "b"); +// } + +// // Drop constraint. +// Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); +// // Removing the same constraint twice should not throw any exception. +// Interpret("DROP CONSTRAINT ON (n:A) ASSERT n.a, n.b IS UNIQUE;"); +// } + +// TEST_F(InterpreterTest, ExplainQuery) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// auto stream = Interpret("EXPLAIN MATCH (n) RETURN *;"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); +// std::vector expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; +// ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); +// auto expected_it = expected_rows.begin(); +// for (const auto &row : stream.GetResults()) { +// ASSERT_EQ(row.size(), 1U); +// EXPECT_EQ(row.front().ValueString(), *expected_it); +// ++expected_it; +// } +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for EXPLAIN ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) RETURN *;"); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// } + +// TEST_F(InterpreterTest, ExplainQueryMultiplePulls) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// auto [stream, qid] = Prepare("EXPLAIN MATCH (n) RETURN *;"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); +// std::vector expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetResults().size(), 1); +// auto expected_it = expected_rows.begin(); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// EXPECT_EQ(stream.GetResults()[0].front().ValueString(), *expected_it); +// ++expected_it; + +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetResults().size(), 2); +// ASSERT_EQ(stream.GetResults()[1].size(), 1U); +// EXPECT_EQ(stream.GetResults()[1].front().ValueString(), *expected_it); +// ++expected_it; + +// Pull(&stream); +// ASSERT_EQ(stream.GetResults().size(), 3); +// ASSERT_EQ(stream.GetResults()[2].size(), 1U); +// EXPECT_EQ(stream.GetResults()[2].front().ValueString(), *expected_it); +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for EXPLAIN ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) RETURN *;"); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// } + +// TEST_F(InterpreterTest, ExplainQueryInMulticommandTransaction) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// Interpret("BEGIN"); +// auto stream = Interpret("EXPLAIN MATCH (n) RETURN *;"); +// Interpret("COMMIT"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); +// std::vector expected_rows{" * Produce {n}", " * ScanAll (n)", " * Once"}; +// ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); +// auto expected_it = expected_rows.begin(); +// for (const auto &row : stream.GetResults()) { +// ASSERT_EQ(row.size(), 1U); +// EXPECT_EQ(row.front().ValueString(), *expected_it); +// ++expected_it; +// } +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for EXPLAIN ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) RETURN *;"); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// } + +// TEST_F(InterpreterTest, ExplainQueryWithParams) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// auto stream = +// Interpret("EXPLAIN MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue(42)}}); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader().front(), "QUERY PLAN"); +// std::vector expected_rows{" * Produce {n}", " * Filter", " * ScanAll (n)", " * Once"}; +// ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); +// auto expected_it = expected_rows.begin(); +// for (const auto &row : stream.GetResults()) { +// ASSERT_EQ(row.size(), 1U); +// EXPECT_EQ(row.front().ValueString(), *expected_it); +// ++expected_it; +// } +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for EXPLAIN ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue("something +// else")}}); EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); EXPECT_EQ(interpreter_context.ast_cache.size(), +// 2U); +// } + +// TEST_F(InterpreterTest, ProfileQuery) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// auto stream = Interpret("PROFILE MATCH (n) RETURN *;"); +// std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; +// EXPECT_EQ(stream.GetHeader(), expected_header); +// std::vector expected_rows{"* Produce", "* ScanAll", "* Once"}; +// ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); +// auto expected_it = expected_rows.begin(); +// for (const auto &row : stream.GetResults()) { +// ASSERT_EQ(row.size(), 4U); +// EXPECT_EQ(row.front().ValueString(), *expected_it); +// ++expected_it; +// } +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for PROFILE ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) RETURN *;"); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// } + +// TEST_F(InterpreterTest, ProfileQueryMultiplePulls) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// auto [stream, qid] = Prepare("PROFILE MATCH (n) RETURN *;"); +// std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; +// EXPECT_EQ(stream.GetHeader(), expected_header); + +// std::vector expected_rows{"* Produce", "* ScanAll", "* Once"}; +// auto expected_it = expected_rows.begin(); + +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0].size(), 4U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueString(), *expected_it); +// ++expected_it; + +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetResults().size(), 2U); +// ASSERT_EQ(stream.GetResults()[1].size(), 4U); +// ASSERT_EQ(stream.GetResults()[1][0].ValueString(), *expected_it); +// ++expected_it; + +// Pull(&stream); +// ASSERT_EQ(stream.GetResults().size(), 3U); +// ASSERT_EQ(stream.GetResults()[2].size(), 4U); +// ASSERT_EQ(stream.GetResults()[2][0].ValueString(), *expected_it); + +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for PROFILE ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) RETURN *;"); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// } + +// TEST_F(InterpreterTest, ProfileQueryInMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("PROFILE MATCH (n) RETURN *;"), memgraph::query::v2::ProfileInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, ProfileQueryWithParams) { +// const auto &interpreter_context = default_interpreter.interpreter_context; + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 0U); +// auto stream = +// Interpret("PROFILE MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue(42)}}); +// std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; +// EXPECT_EQ(stream.GetHeader(), expected_header); +// std::vector expected_rows{"* Produce", "* Filter", "* ScanAll", "* Once"}; +// ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); +// auto expected_it = expected_rows.begin(); +// for (const auto &row : stream.GetResults()) { +// ASSERT_EQ(row.size(), 4U); +// EXPECT_EQ(row.front().ValueString(), *expected_it); +// ++expected_it; +// } +// // We should have a plan cache for MATCH ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for PROFILE ... and for inner MATCH ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 2U); +// Interpret("MATCH (n) WHERE n.id = $id RETURN *;", {{"id", memgraph::storage::v3::PropertyValue("something +// else")}}); EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); EXPECT_EQ(interpreter_context.ast_cache.size(), +// 2U); +// } + +// TEST_F(InterpreterTest, ProfileQueryWithLiterals) { +// const auto &interpreter_context = default_interpreter.interpreter_context; +// ASSERT_NO_THROW(Interpret("CREATE SCHEMA ON :Node(id INTEGER)")); + +// EXPECT_EQ(interpreter_context.plan_cache.size(), 0U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); +// auto stream = Interpret("PROFILE UNWIND range(1, 1000) AS x CREATE (:Node {id: x});", {}); +// std::vector expected_header{"OPERATOR", "ACTUAL HITS", "RELATIVE TIME", "ABSOLUTE TIME"}; +// EXPECT_EQ(stream.GetHeader(), expected_header); +// std::vector expected_rows{"* CreateNode", "* Unwind", "* Once"}; +// ASSERT_EQ(stream.GetResults().size(), expected_rows.size()); +// auto expected_it = expected_rows.begin(); +// for (const auto &row : stream.GetResults()) { +// ASSERT_EQ(row.size(), 4U); +// EXPECT_EQ(row.front().ValueString(), *expected_it); +// ++expected_it; +// } +// // We should have a plan cache for UNWIND ... +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// // We should have AST cache for PROFILE ... and for inner UNWIND ... +// EXPECT_EQ(interpreter_context.ast_cache.size(), 3U); +// Interpret("UNWIND range(42, 4242) AS x CREATE (:Node {id: x});", {}); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 3U); +// } + +// TEST_F(InterpreterTest, Transactions) { +// auto &interpreter = default_interpreter.interpreter; +// { +// ASSERT_THROW(interpreter.CommitTransaction(), memgraph::query::v2::ExplicitTransactionUsageException); +// ASSERT_THROW(interpreter.RollbackTransaction(), memgraph::query::v2::ExplicitTransactionUsageException); +// interpreter.BeginTransaction(); +// ASSERT_THROW(interpreter.BeginTransaction(), memgraph::query::v2::ExplicitTransactionUsageException); +// auto [stream, qid] = Prepare("RETURN 2"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "2"); +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 2); +// interpreter.CommitTransaction(); +// } +// { +// interpreter.BeginTransaction(); +// auto [stream, qid] = Prepare("RETURN 2"); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "2"); +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueInt(), 2); +// interpreter.RollbackTransaction(); +// } +// } + +// TEST_F(InterpreterTest, Qid) { +// auto &interpreter = default_interpreter.interpreter; +// { +// interpreter.BeginTransaction(); +// auto [stream, qid] = Prepare("RETURN 2"); +// ASSERT_TRUE(qid); +// ASSERT_THROW(Pull(&stream, {}, *qid + 1), memgraph::query::v2::InvalidArgumentsException); +// interpreter.RollbackTransaction(); +// } +// { +// interpreter.BeginTransaction(); +// auto [stream1, qid1] = Prepare("UNWIND(range(1,3)) as n RETURN n"); +// ASSERT_TRUE(qid1); +// ASSERT_EQ(stream1.GetHeader().size(), 1U); +// EXPECT_EQ(stream1.GetHeader()[0], "n"); + +// auto [stream2, qid2] = Prepare("UNWIND(range(4,6)) as n RETURN n"); +// ASSERT_TRUE(qid2); +// ASSERT_EQ(stream2.GetHeader().size(), 1U); +// EXPECT_EQ(stream2.GetHeader()[0], "n"); + +// Pull(&stream1, 1, qid1); +// ASSERT_EQ(stream1.GetSummary().count("has_more"), 1); +// ASSERT_TRUE(stream1.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream1.GetResults().size(), 1U); +// ASSERT_EQ(stream1.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream1.GetResults()[0][0].ValueInt(), 1); + +// auto [stream3, qid3] = Prepare("UNWIND(range(7,9)) as n RETURN n"); +// ASSERT_TRUE(qid3); +// ASSERT_EQ(stream3.GetHeader().size(), 1U); +// EXPECT_EQ(stream3.GetHeader()[0], "n"); + +// Pull(&stream2, {}, qid2); +// ASSERT_EQ(stream2.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream2.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream2.GetResults().size(), 3U); +// ASSERT_EQ(stream2.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream2.GetResults()[0][0].ValueInt(), 4); +// ASSERT_EQ(stream2.GetResults()[1][0].ValueInt(), 5); +// ASSERT_EQ(stream2.GetResults()[2][0].ValueInt(), 6); + +// Pull(&stream3, 1, qid3); +// ASSERT_EQ(stream3.GetSummary().count("has_more"), 1); +// ASSERT_TRUE(stream3.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream3.GetResults().size(), 1U); +// ASSERT_EQ(stream3.GetResults()[0].size(), 1U); +// ASSERT_EQ(stream3.GetResults()[0][0].ValueInt(), 7); + +// Pull(&stream1, {}, qid1); +// ASSERT_EQ(stream1.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream1.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream1.GetResults().size(), 3U); +// ASSERT_EQ(stream1.GetResults()[1].size(), 1U); +// ASSERT_EQ(stream1.GetResults()[1][0].ValueInt(), 2); +// ASSERT_EQ(stream1.GetResults()[2][0].ValueInt(), 3); + +// Pull(&stream3); +// ASSERT_EQ(stream3.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream3.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream3.GetResults().size(), 3U); +// ASSERT_EQ(stream3.GetResults()[1].size(), 1U); +// ASSERT_EQ(stream3.GetResults()[1][0].ValueInt(), 8); +// ASSERT_EQ(stream3.GetResults()[2][0].ValueInt(), 9); + +// interpreter.CommitTransaction(); +// } +// } + +// namespace { +// // copied from utils_csv_parsing.cpp - tmp dir management and csv file writer +// class TmpDirManager final { +// public: +// explicit TmpDirManager(const std::string_view directory) +// : tmp_dir_{std::filesystem::temp_directory_path() / directory} { +// CreateDir(); +// } +// ~TmpDirManager() { Clear(); } + +// const std::filesystem::path &Path() const { return tmp_dir_; } + +// private: +// std::filesystem::path tmp_dir_; + +// void CreateDir() { +// if (!std::filesystem::exists(tmp_dir_)) { +// std::filesystem::create_directory(tmp_dir_); +// } +// } + +// void Clear() { +// if (!std::filesystem::exists(tmp_dir_)) return; +// std::filesystem::remove_all(tmp_dir_); +// } +// }; + +// class FileWriter { +// public: +// explicit FileWriter(const std::filesystem::path path) { stream_.open(path); } + +// FileWriter(const FileWriter &) = delete; +// FileWriter &operator=(const FileWriter &) = delete; + +// FileWriter(FileWriter &&) = delete; +// FileWriter &operator=(FileWriter &&) = delete; + +// void Close() { stream_.close(); } + +// size_t WriteLine(const std::string_view line) { +// if (!stream_.is_open()) { +// return 0; +// } + +// stream_ << line << std::endl; + +// // including the newline character +// return line.size() + 1; +// } + +// private: +// std::ofstream stream_; +// }; + +// std::string CreateRow(const std::vector &columns, const std::string_view delim) { +// return memgraph::utils::Join(columns, delim); +// } +// } // namespace + +// TEST_F(InterpreterTest, LoadCsvClause) { +// auto dir_manager = TmpDirManager("csv_directory"); +// const auto csv_path = dir_manager.Path() / "file.csv"; +// auto writer = FileWriter(csv_path); + +// const std::string delimiter{"|"}; + +// const std::vector header{"A", "B", "C"}; +// writer.WriteLine(CreateRow(header, delimiter)); + +// const std::vector good_columns_1{"a", "b", "c"}; +// writer.WriteLine(CreateRow(good_columns_1, delimiter)); + +// const std::vector bad_columns{"\"\"1", "2", "3"}; +// writer.WriteLine(CreateRow(bad_columns, delimiter)); + +// const std::vector good_columns_2{"d", "e", "f"}; +// writer.WriteLine(CreateRow(good_columns_2, delimiter)); + +// writer.Close(); + +// { +// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN +// x.A)", +// csv_path.string(), delimiter); +// auto [stream, qid] = Prepare(query); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "x.A"); + +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_TRUE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "a"); + +// Pull(&stream, 1); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults().size(), 2U); +// ASSERT_EQ(stream.GetResults()[1][0].ValueString(), "d"); +// } + +// { +// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN +// x.C)", +// csv_path.string(), delimiter); +// auto [stream, qid] = Prepare(query); +// ASSERT_EQ(stream.GetHeader().size(), 1U); +// EXPECT_EQ(stream.GetHeader()[0], "x.C"); + +// Pull(&stream); +// ASSERT_EQ(stream.GetSummary().count("has_more"), 1); +// ASSERT_FALSE(stream.GetSummary().at("has_more").ValueBool()); +// ASSERT_EQ(stream.GetResults().size(), 2U); +// ASSERT_EQ(stream.GetResults()[0][0].ValueString(), "c"); +// ASSERT_EQ(stream.GetResults()[1][0].ValueString(), "f"); +// } +// } + +// TEST_F(InterpreterTest, CacheableQueries) { +// const auto &interpreter_context = default_interpreter.interpreter_context; +// // This should be cached +// { +// SCOPED_TRACE("Cacheable query"); +// Interpret("RETURN 1"); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// } + +// { +// SCOPED_TRACE("Uncacheable query"); +// // Queries which are calling procedure should not be cached because the +// // result signature could be changed +// Interpret("CALL mg.load_all()"); +// EXPECT_EQ(interpreter_context.ast_cache.size(), 1U); +// EXPECT_EQ(interpreter_context.plan_cache.size(), 1U); +// } +// } + +// TEST_F(InterpreterTest, AllowLoadCsvConfig) { +// const auto check_load_csv_queries = [&](const bool allow_load_csv) { +// TmpDirManager directory_manager{"allow_load_csv"}; +// const auto csv_path = directory_manager.Path() / "file.csv"; +// auto writer = FileWriter(csv_path); +// const std::vector data{"A", "B", "C"}; +// writer.WriteLine(CreateRow(data, ",")); +// writer.Close(); + +// const std::array queries = { +// fmt::format("LOAD CSV FROM \"{}\" WITH HEADER AS row RETURN row", csv_path.string()), +// "CREATE TRIGGER trigger ON CREATE BEFORE COMMIT EXECUTE LOAD CSV FROM 'file.csv' WITH HEADER AS row RETURN " +// "row"}; + +// InterpreterFaker interpreter_faker{&db_, {.query = {.allow_load_csv = allow_load_csv}}, +// directory_manager.Path()}; for (const auto &query : queries) { +// if (allow_load_csv) { +// SCOPED_TRACE(fmt::format("'{}' should not throw because LOAD CSV is allowed", query)); +// ASSERT_NO_THROW(interpreter_faker.Interpret(query)); +// } else { +// SCOPED_TRACE(fmt::format("'{}' should throw becuase LOAD CSV is not allowed", query)); +// ASSERT_THROW(interpreter_faker.Interpret(query), memgraph::utils::BasicException); +// } +// SCOPED_TRACE(fmt::format("Normal query should not throw (allow_load_csv: {})", allow_load_csv)); +// ASSERT_NO_THROW(interpreter_faker.Interpret("RETURN 1")); +// } +// }; + +// check_load_csv_queries(true); +// check_load_csv_queries(false); +// } + +// void AssertAllValuesAreZero(const std::map &map, +// const std::vector &exceptions) { +// for (const auto &[key, value] : map) { +// if (const auto it = std::find(exceptions.begin(), exceptions.end(), key); it != exceptions.end()) continue; +// ASSERT_EQ(value.ValueInt(), 0) << "Value " << key << " actual: " << value.ValueInt() << ", expected 0"; +// } +// } + +// TEST_F(InterpreterTest, ExecutionStatsIsValid) { +// { +// auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("stats"), 0); +// } +// { +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)")); +// std::array stats_keys{"nodes-created", "nodes-deleted", "relationships-created", "relationships-deleted", +// "properties-set", "labels-added", "labels-removed"}; +// auto [stream, qid] = Prepare("CREATE (:L1 {name: 'name1'});"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("stats"), 1); +// ASSERT_TRUE(stream.GetSummary().at("stats").IsMap()); +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_TRUE( +// std::all_of(stats_keys.begin(), stats_keys.end(), [&stats](const auto &key) { return stats.contains(key); +// })); +// AssertAllValuesAreZero(stats, {"nodes-created"}); +// } +// } + +// TEST_F(InterpreterTest, ExecutionStatsValues) { +// EXPECT_NO_THROW(Interpret("CREATE SCHEMA ON :L1(name STRING)")); +// { +// auto [stream, qid] = +// Prepare("CREATE (:L1{name: 'name1'}),(:L1{name: 'name2'}),(:L1{name: 'name3'}),(:L1{name: 'name4'});"); + +// Pull(&stream); +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_EQ(stats["nodes-created"].ValueInt(), 4); +// AssertAllValuesAreZero(stats, {"nodes-created", "labels-added"}); +// } +// { +// auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); +// Pull(&stream); + +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_EQ(stats["nodes-deleted"].ValueInt(), 4); +// AssertAllValuesAreZero(stats, {"nodes-deleted"}); +// } +// { +// auto [stream, qid] = +// Prepare("CREATE (n:L1 {name: 'name5'})-[:TO]->(m:L1{name: 'name6'}), (n)-[:TO]->(m), (n)-[:TO]->(m);"); + +// Pull(&stream); + +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_EQ(stats["nodes-created"].ValueInt(), 2); +// ASSERT_EQ(stats["relationships-created"].ValueInt(), 3); +// AssertAllValuesAreZero(stats, {"nodes-created", "relationships-created"}); +// } +// { +// auto [stream, qid] = Prepare("MATCH (n) DETACH DELETE n;"); +// Pull(&stream); + +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_EQ(stats["nodes-deleted"].ValueInt(), 2); +// ASSERT_EQ(stats["relationships-deleted"].ValueInt(), 3); +// AssertAllValuesAreZero(stats, {"nodes-deleted", "relationships-deleted"}); +// } +// { +// auto [stream, qid] = Prepare("CREATE (n:L1 {name: 'name7'}) SET n:L2:L3:L4"); +// Pull(&stream); + +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_EQ(stats["nodes-created"].ValueInt(), 1); +// ASSERT_EQ(stats["labels-added"].ValueInt(), 3); +// AssertAllValuesAreZero(stats, {"nodes-created", "labels-added"}); +// } +// { +// auto [stream, qid] = Prepare("MATCH (n:L1) SET n.name2='test';"); +// Pull(&stream); + +// auto stats = stream.GetSummary().at("stats").ValueMap(); +// ASSERT_EQ(stats["properties-set"].ValueInt(), 1); +// AssertAllValuesAreZero(stats, {"properties-set"}); +// } +// } + +// TEST_F(InterpreterTest, NotificationsValidStructure) { +// { +// auto [stream, qid] = Prepare("MATCH (n) DELETE n;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 0); +// } +// { +// auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); +// Pull(&stream); + +// // Assert notifications list +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// ASSERT_TRUE(stream.GetSummary().at("notifications").IsList()); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// // Assert one notification structure +// ASSERT_EQ(notifications.size(), 1); +// ASSERT_TRUE(notifications[0].IsMap()); +// auto notification = notifications[0].ValueMap(); +// ASSERT_TRUE(notification.contains("severity")); +// ASSERT_TRUE(notification.contains("code")); +// ASSERT_TRUE(notification.contains("title")); +// ASSERT_TRUE(notification.contains("description")); +// ASSERT_TRUE(notification["severity"].IsString()); +// ASSERT_TRUE(notification["code"].IsString()); +// ASSERT_TRUE(notification["title"].IsString()); +// ASSERT_TRUE(notification["description"].IsString()); +// } +// } + +// TEST_F(InterpreterTest, IndexInfoNotifications) { +// { +// auto [stream, qid] = Prepare("CREATE INDEX ON :Person;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "CreateIndex"); +// ASSERT_EQ(notification["title"].ValueString(), "Created index on label Person on properties ."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "CreateIndex"); +// ASSERT_EQ(notification["title"].ValueString(), "Created index on label Person on properties id."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("CREATE INDEX ON :Person(id);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "IndexAlreadyExists"); +// ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id already exists."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "DropIndex"); +// ASSERT_EQ(notification["title"].ValueString(), "Dropped index on label Person on properties id."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP INDEX ON :Person(id);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "IndexDoesNotExist"); +// ASSERT_EQ(notification["title"].ValueString(), "Index on label Person on properties id doesn't exist."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// } + +// TEST_F(InterpreterTest, ConstraintUniqueInfoNotifications) { +// { +// auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "CreateConstraint"); +// ASSERT_EQ(notification["title"].ValueString(), +// "Created UNIQUE constraint on label Person on properties email, id."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "ConstraintAlreadyExists"); +// ASSERT_EQ(notification["title"].ValueString(), +// "Constraint UNIQUE on label Person on properties email, id already exists."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "DropConstraint"); +// ASSERT_EQ(notification["title"].ValueString(), +// "Dropped UNIQUE constraint on label Person on properties email, id."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:Person) ASSERT n.email, n.id IS UNIQUE;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "ConstraintDoesNotExist"); +// ASSERT_EQ(notification["title"].ValueString(), +// "Constraint UNIQUE on label Person on properties email, id doesn't exist."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// } + +// TEST_F(InterpreterTest, ConstraintExistsInfoNotifications) { +// { +// auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "CreateConstraint"); +// ASSERT_EQ(notification["title"].ValueString(), "Created EXISTS constraint on label L1 on properties name."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("CREATE CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "ConstraintAlreadyExists"); +// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name already +// exists."); ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "DropConstraint"); +// ASSERT_EQ(notification["title"].ValueString(), "Dropped EXISTS constraint on label L1 on properties name."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP CONSTRAINT ON (n:L1) ASSERT EXISTS (n.name);"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "ConstraintDoesNotExist"); +// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name doesn't +// exist."); ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// } + +// TEST_F(InterpreterTest, TriggerInfoNotifications) { +// { +// auto [stream, qid] = Prepare( +// "CREATE TRIGGER bestTriggerEver ON CREATE AFTER COMMIT EXECUTE " +// "CREATE ();"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "CreateTrigger"); +// ASSERT_EQ(notification["title"].ValueString(), "Created trigger bestTriggerEver."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// { +// auto [stream, qid] = Prepare("DROP TRIGGER bestTriggerEver;"); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "DropTrigger"); +// ASSERT_EQ(notification["title"].ValueString(), "Dropped trigger bestTriggerEver."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } +// } + +// TEST_F(InterpreterTest, LoadCsvClauseNotification) { +// auto dir_manager = TmpDirManager("csv_directory"); +// const auto csv_path = dir_manager.Path() / "file.csv"; +// auto writer = FileWriter(csv_path); + +// const std::string delimiter{"|"}; + +// const std::vector header{"A", "B", "C"}; +// writer.WriteLine(CreateRow(header, delimiter)); + +// const std::vector good_columns_1{"a", "b", "c"}; +// writer.WriteLine(CreateRow(good_columns_1, delimiter)); + +// writer.Close(); + +// const std::string query = fmt::format(R"(LOAD CSV FROM "{}" WITH HEADER IGNORE BAD DELIMITER "{}" AS x RETURN x;)", +// csv_path.string(), delimiter); +// auto [stream, qid] = Prepare(query); +// Pull(&stream); + +// ASSERT_EQ(stream.GetSummary().count("notifications"), 1); +// auto notifications = stream.GetSummary().at("notifications").ValueList(); + +// auto notification = notifications[0].ValueMap(); +// ASSERT_EQ(notification["severity"].ValueString(), "INFO"); +// ASSERT_EQ(notification["code"].ValueString(), "LoadCSVTip"); +// ASSERT_EQ(notification["title"].ValueString(), +// "It's important to note that the parser parses the values as strings. It's up to the user to " +// "convert the parsed row values to the appropriate type. This can be done using the built-in " +// "conversion functions such as ToInteger, ToFloat, ToBoolean etc."); +// ASSERT_EQ(notification["description"].ValueString(), ""); +// } + +// TEST_F(InterpreterTest, CreateSchemaMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"), +// memgraph::query::v2::ConstraintInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, ShowSchemasMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("SHOW SCHEMAS"), memgraph::query::v2::ConstraintInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, ShowSchemaMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"), memgraph::query::v2::ConstraintInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, DropSchemaMulticommandTransaction) { +// Interpret("BEGIN"); +// ASSERT_THROW(Interpret("DROP SCHEMA ON :label"), memgraph::query::v2::ConstraintInMulticommandTxException); +// Interpret("ROLLBACK"); +// } + +// TEST_F(InterpreterTest, SchemaTestCreateAndShow) { +// // Empty schema type map should result with syntax exception. +// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"), memgraph::query::v2::SyntaxException); + +// // Duplicate properties are should also cause an exception +// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name STRING);"), +// memgraph::query::v2::SemanticException); ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name +// INTEGER);"), +// memgraph::query::v2::SemanticException); + +// { +// // Cannot create same schema twice +// Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"); +// ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"), memgraph::query::v2::QueryException); +// } +// // Show schema +// { +// auto stream = Interpret("SHOW SCHEMA ON :label"); +// ASSERT_EQ(stream.GetHeader().size(), 2U); +// const auto &header = stream.GetHeader(); +// ASSERT_EQ(header[0], "property_name"); +// ASSERT_EQ(header[1], "property_type"); +// ASSERT_EQ(stream.GetResults().size(), 2U); +// std::unordered_map result_table{{"age", "Integer"}, {"name", "String"}}; + +// const auto &result = stream.GetResults().front(); +// ASSERT_EQ(result.size(), 2U); +// const auto key1 = result[0].ValueString(); +// ASSERT_TRUE(result_table.contains(key1)); +// ASSERT_EQ(result[1].ValueString(), result_table[key1]); + +// const auto &result2 = stream.GetResults().front(); +// ASSERT_EQ(result2.size(), 2U); +// const auto key2 = result2[0].ValueString(); +// ASSERT_TRUE(result_table.contains(key2)); +// ASSERT_EQ(result[1].ValueString(), result_table[key2]); +// } +// // Create Another Schema +// Interpret("CREATE SCHEMA ON :label2(place STRING, dur DURATION)"); + +// // Show schemas +// { +// auto stream = Interpret("SHOW SCHEMAS"); +// ASSERT_EQ(stream.GetHeader().size(), 2U); +// const auto &header = stream.GetHeader(); +// ASSERT_EQ(header[0], "label"); +// ASSERT_EQ(header[1], "primary_key"); +// ASSERT_EQ(stream.GetResults().size(), 2U); +// std::unordered_map> result_table{ +// {"label", {"name::String", "age::Integer"}}, {"label2", {"place::String", "dur::Duration"}}}; + +// const auto &result = stream.GetResults().front(); +// ASSERT_EQ(result.size(), 2U); +// const auto key1 = result[0].ValueString(); +// ASSERT_TRUE(result_table.contains(key1)); +// const auto primary_key_split = StringToUnorderedSet(result[1].ValueString()); +// ASSERT_EQ(primary_key_split.size(), 2); +// ASSERT_TRUE(primary_key_split == result_table[key1]) << "actual value is: " << result[1].ValueString(); + +// const auto &result2 = stream.GetResults().front(); +// ASSERT_EQ(result2.size(), 2U); +// const auto key2 = result2[0].ValueString(); +// ASSERT_TRUE(result_table.contains(key2)); +// const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString()); +// ASSERT_EQ(primary_key_split2.size(), 2); +// ASSERT_TRUE(primary_key_split2 == result_table[key2]) << "Real value is: " << result[1].ValueString(); +// } +// } + +// TEST_F(InterpreterTest, SchemaTestCreateDropAndShow) { +// Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"); +// // Wrong syntax for dropping schema. +// ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"), memgraph::query::v2::SyntaxException); +// // Cannot drop non existant schema. +// ASSERT_THROW(Interpret("DROP SCHEMA ON :label1;"), memgraph::query::v2::QueryException); + +// // Create Schema and Drop +// auto get_number_of_schemas = [this]() { +// auto stream = Interpret("SHOW SCHEMAS"); +// return stream.GetResults().size(); +// }; + +// ASSERT_EQ(get_number_of_schemas(), 1); +// Interpret("CREATE SCHEMA ON :label1(name STRING, age INTEGER)"); +// ASSERT_EQ(get_number_of_schemas(), 2); +// Interpret("CREATE SCHEMA ON :label2(name STRING, alive BOOL)"); +// ASSERT_EQ(get_number_of_schemas(), 3); +// Interpret("DROP SCHEMA ON :label1"); +// ASSERT_EQ(get_number_of_schemas(), 2); +// Interpret("CREATE SCHEMA ON :label3(name STRING, birthday LOCALDATETIME)"); +// ASSERT_EQ(get_number_of_schemas(), 3); +// Interpret("DROP SCHEMA ON :label2"); +// ASSERT_EQ(get_number_of_schemas(), 2); +// Interpret("CREATE SCHEMA ON :label4(name STRING, age DURATION)"); +// ASSERT_EQ(get_number_of_schemas(), 3); +// Interpret("DROP SCHEMA ON :label3"); +// ASSERT_EQ(get_number_of_schemas(), 2); +// Interpret("DROP SCHEMA ON :label"); +// ASSERT_EQ(get_number_of_schemas(), 1); + +// // Show schemas +// auto stream = Interpret("SHOW SCHEMAS"); +// ASSERT_EQ(stream.GetHeader().size(), 2U); +// const auto &header = stream.GetHeader(); +// ASSERT_EQ(header[0], "label"); +// ASSERT_EQ(header[1], "primary_key"); +// ASSERT_EQ(stream.GetResults().size(), 1U); +// std::unordered_map> result_table{ +// {"label4", {"name::String", "age::Duration"}}}; + +// const auto &result = stream.GetResults().front(); +// ASSERT_EQ(result.size(), 2U); +// const auto key1 = result[0].ValueString(); +// ASSERT_TRUE(result_table.contains(key1)); +// const auto primary_key_split = StringToUnorderedSet(result[1].ValueString()); +// ASSERT_EQ(primary_key_split.size(), 2); +// ASSERT_TRUE(primary_key_split == result_table[key1]); +// } +} // namespace memgraph::query::v2::tests diff --git a/tests/unit/query_v2_query_plan_v2_create_set_remove_delete.cpp b/tests/unit/query_v2_query_plan_v2_create_set_remove_delete.cpp index ed119722f..3d6e23df3 100644 --- a/tests/unit/query_v2_query_plan_v2_create_set_remove_delete.cpp +++ b/tests/unit/query_v2_query_plan_v2_create_set_remove_delete.cpp @@ -94,7 +94,7 @@ TEST_F(QueryPlanCRUDTest, ScanAll) { auto dba = db.Access(); for (int i = 0; i < 42; ++i) { auto v = *dba.CreateVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}}); - ASSERT_TRUE(v.SetProperty(property, storage::v3::PropertyValue(i)).HasValue()); + ASSERT_TRUE(v.SetPropertyAndValidate(property, storage::v3::PropertyValue(i)).HasValue()); } EXPECT_FALSE(dba.Commit().HasError()); } @@ -120,13 +120,13 @@ TEST_F(QueryPlanCRUDTest, ScanAllByLabel) { // Add some unlabeled vertices for (int i = 0; i < 12; ++i) { auto v = *dba.CreateVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}}); - ASSERT_TRUE(v.SetProperty(property, storage::v3::PropertyValue(i)).HasValue()); + ASSERT_TRUE(v.SetPropertyAndValidate(property, storage::v3::PropertyValue(i)).HasValue()); } // Add labeled vertices for (int i = 0; i < 42; ++i) { auto v = *dba.CreateVertexAndValidate(label, {}, {{property, storage::v3::PropertyValue(i)}}); - ASSERT_TRUE(v.SetProperty(property, storage::v3::PropertyValue(i)).HasValue()); - ASSERT_TRUE(v.AddLabel(label2).HasValue()); + ASSERT_TRUE(v.SetPropertyAndValidate(property, storage::v3::PropertyValue(i)).HasValue()); + ASSERT_TRUE(v.AddLabelAndValidate(label2).HasValue()); } EXPECT_FALSE(dba.Commit().HasError()); } diff --git a/tests/unit/storage_v3.cpp b/tests/unit/storage_v3.cpp index f3b3438e1..fbcd85704 100644 --- a/tests/unit/storage_v3.cpp +++ b/tests/unit/storage_v3.cpp @@ -10,11 +10,18 @@ // licenses/APL.txt. #include +#include #include +#include #include +#include "storage/v3/delta.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/key_store.hpp" #include "storage/v3/property_value.hpp" +#include "storage/v3/result.hpp" +#include "storage/v3/schema_validator.hpp" #include "storage/v3/storage.hpp" #include "storage/v3/vertex_accessor.hpp" #include "storage_v3_test_utils.hpp" @@ -25,33 +32,47 @@ namespace memgraph::storage::v3::tests { class StorageV3 : public ::testing::Test { protected: + void SetUp() override { + ASSERT_TRUE( + store.CreateSchema(primary_label, {storage::v3::SchemaProperty{primary_property, common::SchemaType::INT}})); + } + + VertexAccessor CreateVertexAndValidate(Storage::Accessor &acc, LabelId primary_label, + const std::vector &labels, + const std::vector> &properties) { + auto vtx = acc.CreateVertexAndValidate(primary_label, labels, properties); + EXPECT_TRUE(vtx.HasValue()); + return *vtx; + } + Storage store; + const LabelId primary_label{store.NameToLabel("label")}; + const PropertyId primary_property{store.NameToProperty("property")}; + const std::vector pk{PropertyValue{0}}; }; // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, Commit) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); ASSERT_FALSE(acc.Commit().HasError()); } { auto acc = store.Access(); - ASSERT_TRUE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); acc.Abort(); } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::NEW); + auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); auto res = acc.DeleteVertex(&*vertex); @@ -67,9 +88,9 @@ TEST_F(StorageV3, Commit) { } { auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_FALSE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 0U); acc.Abort(); } @@ -77,22 +98,20 @@ TEST_F(StorageV3, Commit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, Abort) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); acc.Abort(); } { auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_FALSE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 0U); acc.Abort(); } @@ -100,38 +119,37 @@ TEST_F(StorageV3, Abort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, AdvanceCommandCommit) { - Gid gid1 = Gid::FromUint(std::numeric_limits::max()); - Gid gid2 = Gid::FromUint(std::numeric_limits::max()); + std::vector pk1{PropertyValue{0}}; + std::vector pk2{PropertyValue(2)}; + { auto acc = store.Access(); - auto vertex1 = acc.CreateVertex(); - gid1 = vertex1.Gid(); - ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk1, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); acc.AdvanceCommand(); - auto vertex2 = acc.CreateVertex(); - gid2 = vertex2.Gid(); - ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(2)}}); + ASSERT_FALSE(acc.FindVertex(pk2, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk2, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 2U); - ASSERT_TRUE(acc.FindVertex(gid1, View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::NEW).has_value()); ASSERT_FALSE(acc.Commit().HasError()); } { auto acc = store.Access(); - ASSERT_TRUE(acc.FindVertex(gid1, View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); - ASSERT_TRUE(acc.FindVertex(gid2, View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk2, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk2, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 2U); EXPECT_EQ(CountVertices(acc, View::NEW), 2U); acc.Abort(); @@ -140,38 +158,36 @@ TEST_F(StorageV3, AdvanceCommandCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, AdvanceCommandAbort) { - Gid gid1 = Gid::FromUint(std::numeric_limits::max()); - Gid gid2 = Gid::FromUint(std::numeric_limits::max()); + std::vector pk1{PropertyValue{0}}; + std::vector pk2{PropertyValue(2)}; { auto acc = store.Access(); - auto vertex1 = acc.CreateVertex(); - gid1 = vertex1.Gid(); - ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk1, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); acc.AdvanceCommand(); - auto vertex2 = acc.CreateVertex(); - gid2 = vertex2.Gid(); - ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(2)}}); + ASSERT_FALSE(acc.FindVertex(pk2, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk2, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 2U); - ASSERT_TRUE(acc.FindVertex(gid1, View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::NEW).has_value()); acc.Abort(); } { auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid1, View::NEW).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(pk1, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk1, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(pk2, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk2, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); EXPECT_EQ(CountVertices(acc, View::NEW), 0U); acc.Abort(); @@ -183,60 +199,57 @@ TEST_F(StorageV3, SnapshotIsolation) { auto acc1 = store.Access(); auto acc2 = store.Access(); - auto vertex = acc1.CreateVertex(); - auto gid = vertex.Gid(); + CreateVertexAndValidate(acc1, primary_label, {}, {{primary_property, PropertyValue{0}}}); - ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc2.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); - ASSERT_FALSE(acc2.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc2.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc1, View::NEW), 1U); EXPECT_EQ(CountVertices(acc2, View::NEW), 0U); ASSERT_FALSE(acc1.Commit().HasError()); - ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc2.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); - ASSERT_FALSE(acc2.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc2.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc2, View::NEW), 0U); acc2.Abort(); auto acc3 = store.Access(); - ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); acc3.Abort(); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, AccessorMove) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); Storage::Accessor moved(std::move(acc)); - ASSERT_FALSE(moved.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(moved.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(moved, View::OLD), 0U); - ASSERT_TRUE(moved.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(moved.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(moved, View::NEW), 1U); ASSERT_FALSE(moved.Commit().HasError()); } { auto acc = store.Access(); - ASSERT_TRUE(acc.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 1U); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); acc.Abort(); } @@ -244,18 +257,15 @@ TEST_F(StorageV3, AccessorMove) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexDeleteCommit) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - auto acc1 = store.Access(); // read transaction auto acc2 = store.Access(); // write transaction // Create the vertex in transaction 2 { - auto vertex = acc2.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + CreateVertexAndValidate(acc2, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc2.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); - ASSERT_TRUE(acc2.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc2.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); ASSERT_FALSE(acc2.Commit().HasError()); } @@ -264,20 +274,20 @@ TEST_F(StorageV3, VertexDeleteCommit) { auto acc4 = store.Access(); // write transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); // Delete the vertex in transaction 4 { - auto vertex = acc4.FindVertex(gid, View::NEW); + auto vertex = acc4.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); EXPECT_EQ(CountVertices(acc4, View::OLD), 1U); EXPECT_EQ(CountVertices(acc4, View::NEW), 1U); @@ -297,38 +307,35 @@ TEST_F(StorageV3, VertexDeleteCommit) { auto acc5 = store.Access(); // read transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); // Check whether the vertex exists in transaction 5 - ASSERT_FALSE(acc5.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc5.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc5, View::OLD), 0U); - ASSERT_FALSE(acc5.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc5.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc5, View::NEW), 0U); } // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexDeleteAbort) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - auto acc1 = store.Access(); // read transaction auto acc2 = store.Access(); // write transaction // Create the vertex in transaction 2 { - auto vertex = acc2.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(acc2.FindVertex(gid, View::OLD).has_value()); + CreateVertexAndValidate(acc2, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc2.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc2, View::OLD), 0U); - ASSERT_TRUE(acc2.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc2.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); ASSERT_FALSE(acc2.Commit().HasError()); } @@ -337,20 +344,20 @@ TEST_F(StorageV3, VertexDeleteAbort) { auto acc4 = store.Access(); // write transaction (aborted) // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); // Delete the vertex in transaction 4, but abort the transaction { - auto vertex = acc4.FindVertex(gid, View::NEW); + auto vertex = acc4.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); EXPECT_EQ(CountVertices(acc4, View::OLD), 1U); EXPECT_EQ(CountVertices(acc4, View::NEW), 1U); @@ -371,26 +378,26 @@ TEST_F(StorageV3, VertexDeleteAbort) { auto acc6 = store.Access(); // write transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); // Check whether the vertex exists in transaction 5 - ASSERT_TRUE(acc5.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc5.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc5, View::OLD), 1U); - ASSERT_TRUE(acc5.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc5.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc5, View::NEW), 1U); // Delete the vertex in transaction 6 { - auto vertex = acc6.FindVertex(gid, View::NEW); + auto vertex = acc6.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); EXPECT_EQ(CountVertices(acc6, View::OLD), 1U); EXPECT_EQ(CountVertices(acc6, View::NEW), 1U); @@ -410,27 +417,27 @@ TEST_F(StorageV3, VertexDeleteAbort) { auto acc7 = store.Access(); // read transaction // Check whether the vertex exists in transaction 1 - ASSERT_FALSE(acc1.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc1, View::OLD), 0U); - ASSERT_FALSE(acc1.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc1.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc1, View::NEW), 0U); // Check whether the vertex exists in transaction 3 - ASSERT_TRUE(acc3.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc3, View::OLD), 1U); - ASSERT_TRUE(acc3.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc3.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc3, View::NEW), 1U); // Check whether the vertex exists in transaction 5 - ASSERT_TRUE(acc5.FindVertex(gid, View::OLD).has_value()); + ASSERT_TRUE(acc5.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc5, View::OLD), 1U); - ASSERT_TRUE(acc5.FindVertex(gid, View::NEW).has_value()); + ASSERT_TRUE(acc5.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc5, View::NEW), 1U); // Check whether the vertex exists in transaction 7 - ASSERT_FALSE(acc7.FindVertex(gid, View::OLD).has_value()); + ASSERT_FALSE(acc7.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc7, View::OLD), 0U); - ASSERT_FALSE(acc7.FindVertex(gid, View::NEW).has_value()); + ASSERT_FALSE(acc7.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc7, View::NEW), 0U); // Commit all accessors @@ -442,13 +449,10 @@ TEST_F(StorageV3, VertexDeleteAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexDeleteSerializationError) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - // Create vertex { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); ASSERT_FALSE(acc.Commit().HasError()); } @@ -457,7 +461,7 @@ TEST_F(StorageV3, VertexDeleteSerializationError) { // Delete vertex in accessor 1 { - auto vertex = acc1.FindVertex(gid, View::OLD); + auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); EXPECT_EQ(CountVertices(acc1, View::OLD), 1U); EXPECT_EQ(CountVertices(acc1, View::NEW), 1U); @@ -485,7 +489,7 @@ TEST_F(StorageV3, VertexDeleteSerializationError) { // Delete vertex in accessor 2 { - auto vertex = acc2.FindVertex(gid, View::OLD); + auto vertex = acc2.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); EXPECT_EQ(CountVertices(acc2, View::OLD), 1U); EXPECT_EQ(CountVertices(acc2, View::NEW), 1U); @@ -506,7 +510,7 @@ TEST_F(StorageV3, VertexDeleteSerializationError) { // Check whether the vertex exists { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_FALSE(vertex); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); EXPECT_EQ(CountVertices(acc, View::NEW), 0U); @@ -516,18 +520,17 @@ TEST_F(StorageV3, VertexDeleteSerializationError) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexDeleteSpecialCases) { - Gid gid1 = Gid::FromUint(std::numeric_limits::max()); - Gid gid2 = Gid::FromUint(std::numeric_limits::max()); + std::vector pk1{PropertyValue{0}}; + std::vector pk2{PropertyValue(2)}; // Create vertex and delete it in the same transaction, but abort the // transaction { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid1 = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk1, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid1, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk1, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); auto res = acc.DeleteVertex(&vertex); ASSERT_TRUE(res.HasValue()); @@ -543,11 +546,10 @@ TEST_F(StorageV3, VertexDeleteSpecialCases) { // Create vertex and delete it in the same transaction { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid2 = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(2)}}); + ASSERT_FALSE(acc.FindVertex(pk2, View::OLD).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); - ASSERT_TRUE(acc.FindVertex(gid2, View::NEW).has_value()); + ASSERT_TRUE(acc.FindVertex(pk2, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); auto res = acc.DeleteVertex(&vertex); ASSERT_TRUE(res.HasValue()); @@ -563,78 +565,81 @@ TEST_F(StorageV3, VertexDeleteSpecialCases) { // Check whether the vertices exist { auto acc = store.Access(); - ASSERT_FALSE(acc.FindVertex(gid1, View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid1, View::NEW).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, View::OLD).has_value()); - ASSERT_FALSE(acc.FindVertex(gid2, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(pk1, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk1, View::NEW).has_value()); + ASSERT_FALSE(acc.FindVertex(pk2, View::OLD).has_value()); + ASSERT_FALSE(acc.FindVertex(pk2, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::OLD), 0U); EXPECT_EQ(CountVertices(acc, View::NEW), 0U); acc.Abort(); } } +template +void AssertErrorInVariant(TResultHolder &holder, TError error_type) { + ASSERT_TRUE(holder.HasError()); + const auto error = holder.GetError(); + ASSERT_TRUE(std::holds_alternative(error)); + ASSERT_EQ(std::get(error), error_type); +} + // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexDeleteLabel) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - // Create the vertex { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); ASSERT_FALSE(acc.Commit().HasError()); } // Add label, delete the vertex and check the label API (same command) { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::NEW); + auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label5 = acc.NameToLabel("label5"); // Check whether label 5 exists - ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label5, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); // Add label 5 - ASSERT_TRUE(vertex->AddLabel(label).GetValue()); + ASSERT_TRUE(vertex->AddLabelAndValidate(label5).GetValue()); // Check whether label 5 exists - ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label5, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); { auto labels = vertex->Labels(View::NEW).GetValue(); ASSERT_EQ(labels.size(), 1); - ASSERT_EQ(labels[0], label); + ASSERT_EQ(labels[0], label5); } // Delete the vertex ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); // Check whether label 5 exists - ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_EQ(vertex->HasLabel(label, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_EQ(vertex->HasLabel(label5, View::NEW).GetError(), Error::DELETED_OBJECT); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(View::NEW).GetError(), Error::DELETED_OBJECT); // Try to add the label { - auto ret = vertex->AddLabel(label); - ASSERT_TRUE(ret.HasError()); - ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + auto ret = vertex->AddLabelAndValidate(label5); + AssertErrorInVariant(ret, Error::DELETED_OBJECT); } // Try to remove the label { - auto ret = vertex->RemoveLabel(label); - ASSERT_TRUE(ret.HasError()); - ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + auto ret = vertex->RemoveLabelAndValidate(label5); + AssertErrorInVariant(ret, Error::DELETED_OBJECT); } acc.Abort(); @@ -643,57 +648,57 @@ TEST_F(StorageV3, VertexDeleteLabel) { // Add label, delete the vertex and check the label API (different command) { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::NEW); + auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label5 = acc.NameToLabel("label5"); // Check whether label 5 exists - ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label5, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); // Add label 5 - ASSERT_TRUE(vertex->AddLabel(label).GetValue()); + ASSERT_TRUE(vertex->AddLabelAndValidate(label5).GetValue()); // Check whether label 5 exists - ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label5, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); { auto labels = vertex->Labels(View::NEW).GetValue(); ASSERT_EQ(labels.size(), 1); - ASSERT_EQ(labels[0], label); + ASSERT_EQ(labels[0], label5); } // Advance command acc.AdvanceCommand(); // Check whether label 5 exists - ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_TRUE(vertex->HasLabel(label, View::NEW).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_TRUE(vertex->HasLabel(label5, View::NEW).GetValue()); { auto labels = vertex->Labels(View::OLD).GetValue(); ASSERT_EQ(labels.size(), 1); - ASSERT_EQ(labels[0], label); + ASSERT_EQ(labels[0], label5); } { auto labels = vertex->Labels(View::NEW).GetValue(); ASSERT_EQ(labels.size(), 1); - ASSERT_EQ(labels[0], label); + ASSERT_EQ(labels[0], label5); } // Delete the vertex ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); // Check whether label 5 exists - ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); - ASSERT_EQ(vertex->HasLabel(label, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_TRUE(vertex->HasLabel(label5, View::OLD).GetValue()); + ASSERT_EQ(vertex->HasLabel(label5, View::NEW).GetError(), Error::DELETED_OBJECT); { auto labels = vertex->Labels(View::OLD).GetValue(); ASSERT_EQ(labels.size(), 1); - ASSERT_EQ(labels[0], label); + ASSERT_EQ(labels[0], label5); } ASSERT_EQ(vertex->Labels(View::NEW).GetError(), Error::DELETED_OBJECT); @@ -701,84 +706,78 @@ TEST_F(StorageV3, VertexDeleteLabel) { acc.AdvanceCommand(); // Check whether label 5 exists - ASSERT_EQ(vertex->HasLabel(label, View::OLD).GetError(), Error::DELETED_OBJECT); - ASSERT_EQ(vertex->HasLabel(label, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->HasLabel(label5, View::OLD).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->HasLabel(label5, View::NEW).GetError(), Error::DELETED_OBJECT); ASSERT_EQ(vertex->Labels(View::OLD).GetError(), Error::DELETED_OBJECT); ASSERT_EQ(vertex->Labels(View::NEW).GetError(), Error::DELETED_OBJECT); // Try to add the label { - auto ret = vertex->AddLabel(label); - ASSERT_TRUE(ret.HasError()); - ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + auto ret = vertex->AddLabelAndValidate(label5); + AssertErrorInVariant(ret, Error::DELETED_OBJECT); } // Try to remove the label { - auto ret = vertex->RemoveLabel(label); - ASSERT_TRUE(ret.HasError()); - ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + auto ret = vertex->RemoveLabelAndValidate(label5); + AssertErrorInVariant(ret, Error::DELETED_OBJECT); } acc.Abort(); } } -// NOLINTNEXTLINE(hicpp-special-member-functions) +// NOLINTNEXTLINE(hicpp - special - member - functions) TEST_F(StorageV3, VertexDeleteProperty) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - // Create the vertex { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(acc.FindVertex(gid, View::OLD).has_value()); - ASSERT_TRUE(acc.FindVertex(gid, View::NEW).has_value()); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(acc.FindVertex(pk, View::OLD).has_value()); + ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); ASSERT_FALSE(acc.Commit().HasError()); } // Set property, delete the vertex and check the property API (same command) { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::NEW); + auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property5 = acc.NameToProperty("property5"); // Check whether property 5 exists - ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); - ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property5, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); // Set property 5 to "nandare" - ASSERT_TRUE(vertex->SetProperty(property, PropertyValue("nandare"))->IsNull()); + ASSERT_TRUE(vertex->SetPropertyAndValidate(property5, PropertyValue("nandare"))->IsNull()); // Check whether property 5 exists - ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); - ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property5, View::NEW)->ValueString(), "nandare"); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); { auto properties = vertex->Properties(View::NEW).GetValue(); ASSERT_EQ(properties.size(), 1); - ASSERT_EQ(properties[property].ValueString(), "nandare"); + ASSERT_EQ(properties[property5].ValueString(), "nandare"); } // Delete the vertex ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); // Check whether label 5 exists - ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); - ASSERT_EQ(vertex->GetProperty(property, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property5, View::NEW).GetError(), Error::DELETED_OBJECT); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(View::NEW).GetError(), Error::DELETED_OBJECT); - // Try to set the property + // Try to set the property5 { - auto ret = vertex->SetProperty(property, PropertyValue("haihai")); - ASSERT_TRUE(ret.HasError()); - ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + auto ret = vertex->SetPropertyAndValidate(property5, PropertyValue("haihai")); + AssertErrorInVariant(ret, Error::DELETED_OBJECT); } acc.Abort(); @@ -788,57 +787,57 @@ TEST_F(StorageV3, VertexDeleteProperty) { // command) { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::NEW); + auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property5 = acc.NameToProperty("property5"); // Check whether property 5 exists - ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); - ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); + ASSERT_TRUE(vertex->GetProperty(property5, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); // Set property 5 to "nandare" - ASSERT_TRUE(vertex->SetProperty(property, PropertyValue("nandare"))->IsNull()); + ASSERT_TRUE(vertex->SetPropertyAndValidate(property5, PropertyValue("nandare"))->IsNull()); // Check whether property 5 exists - ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); - ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); + ASSERT_EQ(vertex->GetProperty(property5, View::NEW)->ValueString(), "nandare"); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); { auto properties = vertex->Properties(View::NEW).GetValue(); ASSERT_EQ(properties.size(), 1); - ASSERT_EQ(properties[property].ValueString(), "nandare"); + ASSERT_EQ(properties[property5].ValueString(), "nandare"); } // Advance command acc.AdvanceCommand(); // Check whether property 5 exists - ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); - ASSERT_EQ(vertex->GetProperty(property, View::NEW)->ValueString(), "nandare"); + ASSERT_EQ(vertex->GetProperty(property5, View::OLD)->ValueString(), "nandare"); + ASSERT_EQ(vertex->GetProperty(property5, View::NEW)->ValueString(), "nandare"); { auto properties = vertex->Properties(View::OLD).GetValue(); ASSERT_EQ(properties.size(), 1); - ASSERT_EQ(properties[property].ValueString(), "nandare"); + ASSERT_EQ(properties[property5].ValueString(), "nandare"); } { auto properties = vertex->Properties(View::NEW).GetValue(); ASSERT_EQ(properties.size(), 1); - ASSERT_EQ(properties[property].ValueString(), "nandare"); + ASSERT_EQ(properties[property5].ValueString(), "nandare"); } // Delete the vertex ASSERT_TRUE(acc.DeleteVertex(&*vertex).GetValue()); // Check whether property 5 exists - ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); - ASSERT_EQ(vertex->GetProperty(property, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->GetProperty(property5, View::OLD)->ValueString(), "nandare"); + ASSERT_EQ(vertex->GetProperty(property5, View::NEW).GetError(), Error::DELETED_OBJECT); { auto properties = vertex->Properties(View::OLD).GetValue(); ASSERT_EQ(properties.size(), 1); - ASSERT_EQ(properties[property].ValueString(), "nandare"); + ASSERT_EQ(properties[property5].ValueString(), "nandare"); } ASSERT_EQ(vertex->Properties(View::NEW).GetError(), Error::DELETED_OBJECT); @@ -846,16 +845,15 @@ TEST_F(StorageV3, VertexDeleteProperty) { acc.AdvanceCommand(); // Check whether property 5 exists - ASSERT_EQ(vertex->GetProperty(property, View::OLD).GetError(), Error::DELETED_OBJECT); - ASSERT_EQ(vertex->GetProperty(property, View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->GetProperty(property5, View::OLD).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex->GetProperty(property5, View::NEW).GetError(), Error::DELETED_OBJECT); ASSERT_EQ(vertex->Properties(View::OLD).GetError(), Error::DELETED_OBJECT); ASSERT_EQ(vertex->Properties(View::NEW).GetError(), Error::DELETED_OBJECT); // Try to set the property { - auto ret = vertex->SetProperty(property, PropertyValue("haihai")); - ASSERT_TRUE(ret.HasError()); - ASSERT_EQ(ret.GetError(), Error::DELETED_OBJECT); + auto ret = vertex->SetPropertyAndValidate(property5, PropertyValue("haihai")); + AssertErrorInVariant(ret, Error::DELETED_OBJECT); } acc.Abort(); @@ -864,11 +862,9 @@ TEST_F(StorageV3, VertexDeleteProperty) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexLabelCommit) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); auto label = acc.NameToLabel("label5"); @@ -876,7 +872,7 @@ TEST_F(StorageV3, VertexLabelCommit) { ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); { - auto res = vertex.AddLabel(label); + auto res = vertex.AddLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -889,7 +885,7 @@ TEST_F(StorageV3, VertexLabelCommit) { } { - auto res = vertex.AddLabel(label); + auto res = vertex.AddLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -898,7 +894,7 @@ TEST_F(StorageV3, VertexLabelCommit) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -926,13 +922,13 @@ TEST_F(StorageV3, VertexLabelCommit) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); { - auto res = vertex->RemoveLabel(label); + auto res = vertex->RemoveLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -948,7 +944,7 @@ TEST_F(StorageV3, VertexLabelCommit) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->RemoveLabel(label); + auto res = vertex->RemoveLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -957,7 +953,7 @@ TEST_F(StorageV3, VertexLabelCommit) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -978,20 +974,17 @@ TEST_F(StorageV3, VertexLabelCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexLabelAbort) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - // Create the vertex. { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); ASSERT_FALSE(acc.Commit().HasError()); } // Add label 5, but abort the transaction. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -1000,7 +993,7 @@ TEST_F(StorageV3, VertexLabelAbort) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->AddLabel(label); + auto res = vertex->AddLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -1013,7 +1006,7 @@ TEST_F(StorageV3, VertexLabelAbort) { } { - auto res = vertex->AddLabel(label); + auto res = vertex->AddLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -1024,7 +1017,7 @@ TEST_F(StorageV3, VertexLabelAbort) { // Check that label 5 doesn't exist. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -1045,7 +1038,7 @@ TEST_F(StorageV3, VertexLabelAbort) { // Add label 5. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -1054,7 +1047,7 @@ TEST_F(StorageV3, VertexLabelAbort) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->AddLabel(label); + auto res = vertex->AddLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -1067,7 +1060,7 @@ TEST_F(StorageV3, VertexLabelAbort) { } { - auto res = vertex->AddLabel(label); + auto res = vertex->AddLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -1078,7 +1071,7 @@ TEST_F(StorageV3, VertexLabelAbort) { // Check that label 5 exists. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -1108,13 +1101,13 @@ TEST_F(StorageV3, VertexLabelAbort) { // Remove label 5, but abort the transaction. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); { - auto res = vertex->RemoveLabel(label); + auto res = vertex->RemoveLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -1130,7 +1123,7 @@ TEST_F(StorageV3, VertexLabelAbort) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->RemoveLabel(label); + auto res = vertex->RemoveLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -1141,7 +1134,7 @@ TEST_F(StorageV3, VertexLabelAbort) { // Check that label 5 exists. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -1171,13 +1164,13 @@ TEST_F(StorageV3, VertexLabelAbort) { // Remove label 5. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); { - auto res = vertex->RemoveLabel(label); + auto res = vertex->RemoveLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -1193,7 +1186,7 @@ TEST_F(StorageV3, VertexLabelAbort) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->RemoveLabel(label); + auto res = vertex->RemoveLabelAndValidate(label); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -1204,7 +1197,7 @@ TEST_F(StorageV3, VertexLabelAbort) { // Check that label 5 doesn't exist. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label = acc.NameToLabel("label5"); @@ -1225,11 +1218,9 @@ TEST_F(StorageV3, VertexLabelAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexLabelSerializationError) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); ASSERT_FALSE(acc.Commit().HasError()); } @@ -1238,7 +1229,7 @@ TEST_F(StorageV3, VertexLabelSerializationError) { // Add label 1 in accessor 1. { - auto vertex = acc1.FindVertex(gid, View::OLD); + auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label1 = acc1.NameToLabel("label1"); @@ -1252,7 +1243,7 @@ TEST_F(StorageV3, VertexLabelSerializationError) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->AddLabel(label1); + auto res = vertex->AddLabelAndValidate(label1); ASSERT_TRUE(res.HasValue()); ASSERT_TRUE(res.GetValue()); } @@ -1269,7 +1260,7 @@ TEST_F(StorageV3, VertexLabelSerializationError) { } { - auto res = vertex->AddLabel(label1); + auto res = vertex->AddLabelAndValidate(label1); ASSERT_TRUE(res.HasValue()); ASSERT_FALSE(res.GetValue()); } @@ -1277,7 +1268,7 @@ TEST_F(StorageV3, VertexLabelSerializationError) { // Add label 2 in accessor 2. { - auto vertex = acc2.FindVertex(gid, View::OLD); + auto vertex = acc2.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label1 = acc2.NameToLabel("label1"); @@ -1291,9 +1282,8 @@ TEST_F(StorageV3, VertexLabelSerializationError) { ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); { - auto res = vertex->AddLabel(label1); - ASSERT_TRUE(res.HasError()); - ASSERT_EQ(res.GetError(), Error::SERIALIZATION_ERROR); + auto res = vertex->AddLabelAndValidate(label1); + AssertErrorInVariant(res, Error::SERIALIZATION_ERROR); } } @@ -1304,7 +1294,7 @@ TEST_F(StorageV3, VertexLabelSerializationError) { // Check which labels exist. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto label1 = acc.NameToLabel("label1"); @@ -1332,11 +1322,9 @@ TEST_F(StorageV3, VertexLabelSerializationError) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexPropertyCommit) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); auto property = acc.NameToProperty("property5"); @@ -1344,7 +1332,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); { - auto old_value = vertex.SetProperty(property, PropertyValue("temporary")); + auto old_value = vertex.SetPropertyAndValidate(property, PropertyValue("temporary")); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } @@ -1357,7 +1345,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { } { - auto old_value = vertex.SetProperty(property, PropertyValue("nandare")); + auto old_value = vertex.SetPropertyAndValidate(property, PropertyValue("nandare")); ASSERT_TRUE(old_value.HasValue()); ASSERT_FALSE(old_value->IsNull()); } @@ -1373,7 +1361,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1401,13 +1389,13 @@ TEST_F(StorageV3, VertexPropertyCommit) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); { - auto old_value = vertex->SetProperty(property, PropertyValue()); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue()); ASSERT_TRUE(old_value.HasValue()); ASSERT_FALSE(old_value->IsNull()); } @@ -1423,7 +1411,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); { - auto old_value = vertex->SetProperty(property, PropertyValue()); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue()); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } @@ -1432,7 +1420,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1453,20 +1441,17 @@ TEST_F(StorageV3, VertexPropertyCommit) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexPropertyAbort) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); - // Create the vertex. { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); ASSERT_FALSE(acc.Commit().HasError()); } // Set property 5 to "nandare", but abort the transaction. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1475,7 +1460,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); { - auto old_value = vertex->SetProperty(property, PropertyValue("temporary")); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue("temporary")); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } @@ -1488,7 +1473,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { } { - auto old_value = vertex->SetProperty(property, PropertyValue("nandare")); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue("nandare")); ASSERT_TRUE(old_value.HasValue()); ASSERT_FALSE(old_value->IsNull()); } @@ -1506,7 +1491,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Check that property 5 is null. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1527,7 +1512,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Set property 5 to "nandare". { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1536,7 +1521,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); { - auto old_value = vertex->SetProperty(property, PropertyValue("temporary")); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue("temporary")); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } @@ -1549,7 +1534,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { } { - auto old_value = vertex->SetProperty(property, PropertyValue("nandare")); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue("nandare")); ASSERT_TRUE(old_value.HasValue()); ASSERT_FALSE(old_value->IsNull()); } @@ -1567,7 +1552,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Check that property 5 is "nandare". { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1597,7 +1582,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Set property 5 to null, but abort the transaction. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1617,7 +1602,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { } { - auto old_value = vertex->SetProperty(property, PropertyValue()); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue()); ASSERT_TRUE(old_value.HasValue()); ASSERT_FALSE(old_value->IsNull()); } @@ -1638,7 +1623,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Check that property 5 is "nandare". { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1668,7 +1653,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Set property 5 to null. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1688,7 +1673,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { } { - auto old_value = vertex->SetProperty(property, PropertyValue()); + auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue()); ASSERT_TRUE(old_value.HasValue()); ASSERT_FALSE(old_value->IsNull()); } @@ -1709,7 +1694,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { // Check that property 5 is null. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property = acc.NameToProperty("property5"); @@ -1730,11 +1715,9 @@ TEST_F(StorageV3, VertexPropertyAbort) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexPropertySerializationError) { - Gid gid = Gid::FromUint(std::numeric_limits::max()); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); ASSERT_FALSE(acc.Commit().HasError()); } @@ -1743,7 +1726,7 @@ TEST_F(StorageV3, VertexPropertySerializationError) { // Set property 1 to 123 in accessor 1. { - auto vertex = acc1.FindVertex(gid, View::OLD); + auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property1 = acc1.NameToProperty("property1"); @@ -1757,7 +1740,7 @@ TEST_F(StorageV3, VertexPropertySerializationError) { ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); { - auto old_value = vertex->SetProperty(property1, PropertyValue(123)); + auto old_value = vertex->SetPropertyAndValidate(property1, PropertyValue(123)); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); } @@ -1776,7 +1759,7 @@ TEST_F(StorageV3, VertexPropertySerializationError) { // Set property 2 to "nandare" in accessor 2. { - auto vertex = acc2.FindVertex(gid, View::OLD); + auto vertex = acc2.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property1 = acc2.NameToProperty("property1"); @@ -1790,9 +1773,8 @@ TEST_F(StorageV3, VertexPropertySerializationError) { ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); { - auto res = vertex->SetProperty(property2, PropertyValue("nandare")); - ASSERT_TRUE(res.HasError()); - ASSERT_EQ(res.GetError(), Error::SERIALIZATION_ERROR); + auto res = vertex->SetPropertyAndValidate(property2, PropertyValue("nandare")); + AssertErrorInVariant(res, Error::SERIALIZATION_ERROR); } } @@ -1803,7 +1785,7 @@ TEST_F(StorageV3, VertexPropertySerializationError) { // Check which properties exist. { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto property1 = acc.NameToProperty("property1"); @@ -1832,7 +1814,7 @@ TEST_F(StorageV3, VertexPropertySerializationError) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, VertexLabelPropertyMixed) { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); auto label = acc.NameToLabel("label5"); auto property = acc.NameToProperty("property5"); @@ -1844,7 +1826,7 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); // Add label 5 - ASSERT_TRUE(vertex.AddLabel(label).GetValue()); + ASSERT_TRUE(vertex.AddLabelAndValidate(label).GetValue()); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, View::NEW).GetValue()); @@ -1878,7 +1860,7 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); // Set property 5 to "nandare" - ASSERT_TRUE(vertex.SetProperty(property, PropertyValue("nandare"))->IsNull()); + ASSERT_TRUE(vertex.SetPropertyAndValidate(property, PropertyValue("nandare"))->IsNull()); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); @@ -1932,7 +1914,7 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { } // Set property 5 to "haihai" - ASSERT_FALSE(vertex.SetProperty(property, PropertyValue("haihai"))->IsNull()); + ASSERT_FALSE(vertex.SetPropertyAndValidate(property, PropertyValue("haihai"))->IsNull()); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); @@ -1990,7 +1972,7 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { } // Remove label 5 - ASSERT_TRUE(vertex.RemoveLabel(label).GetValue()); + ASSERT_TRUE(vertex.RemoveLabelAndValidate(label).GetValue()); // Check whether label 5 and property 5 exist ASSERT_TRUE(vertex.HasLabel(label, View::OLD).GetValue()); @@ -2036,7 +2018,7 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { } // Set property 5 to null - ASSERT_FALSE(vertex.SetProperty(property, PropertyValue())->IsNull()); + ASSERT_FALSE(vertex.SetPropertyAndValidate(property, PropertyValue())->IsNull()); // Check whether label 5 and property 5 exist ASSERT_FALSE(vertex.HasLabel(label, View::OLD).GetValue()); @@ -2069,15 +2051,13 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { } TEST_F(StorageV3, VertexPropertyClear) { - Gid gid; auto property1 = store.NameToProperty("property1"); auto property2 = store.NameToProperty("property2"); { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); - auto old_value = vertex.SetProperty(property1, PropertyValue("value")); + auto old_value = vertex.SetPropertyAndValidate(property1, PropertyValue("value")); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); @@ -2085,7 +2065,7 @@ TEST_F(StorageV3, VertexPropertyClear) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); ASSERT_EQ(vertex->GetProperty(property1, View::OLD)->ValueString(), "value"); @@ -2117,10 +2097,10 @@ TEST_F(StorageV3, VertexPropertyClear) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto old_value = vertex->SetProperty(property2, PropertyValue(42)); + auto old_value = vertex->SetPropertyAndValidate(property2, PropertyValue(42)); ASSERT_TRUE(old_value.HasValue()); ASSERT_TRUE(old_value->IsNull()); @@ -2128,7 +2108,7 @@ TEST_F(StorageV3, VertexPropertyClear) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); ASSERT_EQ(vertex->GetProperty(property1, View::OLD)->ValueString(), "value"); @@ -2161,7 +2141,7 @@ TEST_F(StorageV3, VertexPropertyClear) { } { auto acc = store.Access(); - auto vertex = acc.FindVertex(gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); @@ -2173,17 +2153,17 @@ TEST_F(StorageV3, VertexPropertyClear) { } TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { - auto label = store.NameToLabel("label"); - auto property = store.NameToProperty("property"); + auto label1 = store.NameToLabel("label1"); + auto property1 = store.NameToProperty("property1"); auto acc = store.Access(); - auto vertex = acc.CreateVertex(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); // Check state before (OLD view). ASSERT_EQ(vertex.Labels(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); - ASSERT_EQ(vertex.HasLabel(label, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.HasLabel(label1, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.Properties(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); - ASSERT_EQ(vertex.GetProperty(property, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.GetProperty(property1, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.InEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.OutEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.InDegree(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); @@ -2191,24 +2171,24 @@ TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { // Check state before (NEW view). ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); - ASSERT_EQ(*vertex.HasLabel(label, View::NEW), false); + ASSERT_EQ(*vertex.HasLabel(label1, View::NEW), false); ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); - ASSERT_EQ(*vertex.GetProperty(property, View::NEW), PropertyValue()); + ASSERT_EQ(*vertex.GetProperty(property1, View::NEW), PropertyValue()); ASSERT_EQ(vertex.InEdges(View::NEW)->size(), 0); ASSERT_EQ(vertex.OutEdges(View::NEW)->size(), 0); ASSERT_EQ(*vertex.InDegree(View::NEW), 0); ASSERT_EQ(*vertex.OutDegree(View::NEW), 0); // Modify vertex. - ASSERT_TRUE(vertex.AddLabel(label).HasValue()); - ASSERT_TRUE(vertex.SetProperty(property, PropertyValue("value")).HasValue()); + ASSERT_TRUE(vertex.AddLabelAndValidate(label1).HasValue()); + ASSERT_TRUE(vertex.SetPropertyAndValidate(property1, PropertyValue("value")).HasValue()); ASSERT_TRUE(acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge")).HasValue()); // Check state after (OLD view). ASSERT_EQ(vertex.Labels(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); - ASSERT_EQ(vertex.HasLabel(label, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.HasLabel(label1, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.Properties(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); - ASSERT_EQ(vertex.GetProperty(property, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); + ASSERT_EQ(vertex.GetProperty(property1, View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.InEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.OutEdges(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); ASSERT_EQ(vertex.InDegree(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); @@ -2216,9 +2196,9 @@ TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { // Check state after (NEW view). ASSERT_EQ(vertex.Labels(View::NEW)->size(), 1); - ASSERT_EQ(*vertex.HasLabel(label, View::NEW), true); + ASSERT_EQ(*vertex.HasLabel(label1, View::NEW), true); ASSERT_EQ(vertex.Properties(View::NEW)->size(), 1); - ASSERT_EQ(*vertex.GetProperty(property, View::NEW), PropertyValue("value")); + ASSERT_EQ(*vertex.GetProperty(property1, View::NEW), PropertyValue("value")); ASSERT_EQ(vertex.InEdges(View::NEW)->size(), 1); ASSERT_EQ(vertex.OutEdges(View::NEW)->size(), 1); ASSERT_EQ(*vertex.InDegree(View::NEW), 1); @@ -2231,50 +2211,49 @@ TEST_F(StorageV3, VertexVisibilitySingleTransaction) { auto acc1 = store.Access(); auto acc2 = store.Access(); - auto vertex = acc1.CreateVertex(); - auto gid = vertex.Gid(); + auto vertex = CreateVertexAndValidate(acc1, primary_label, {}, {{primary_property, PropertyValue{0}}}); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex.AddLabel(acc1.NameToLabel("label")).HasValue()); + ASSERT_TRUE(vertex.AddLabelAndValidate(acc1.NameToLabel("label1")).HasValue()); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex.SetProperty(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex.SetPropertyAndValidate(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); auto acc3 = store.Access(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc3.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc3.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc3.FindVertex(pk, View::NEW)); ASSERT_TRUE(acc1.DeleteVertex(&vertex).HasValue()); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc3.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc3.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc3.FindVertex(pk, View::NEW)); acc1.AdvanceCommand(); acc3.AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc3.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc3.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc3.FindVertex(pk, View::NEW)); acc1.Abort(); acc2.Abort(); @@ -2282,33 +2261,30 @@ TEST_F(StorageV3, VertexVisibilitySingleTransaction) { } TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { - Gid gid; - { auto acc1 = store.Access(); auto acc2 = store.Access(); - auto vertex = acc1.CreateVertex(); - gid = vertex.Gid(); + CreateVertexAndValidate(acc1, primary_label, {}, {{primary_property, PropertyValue{0}}}); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); acc2.AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); acc1.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_FALSE(acc2.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); ASSERT_FALSE(acc1.Commit().HasError()); ASSERT_FALSE(acc2.Commit().HasError()); @@ -2318,72 +2294,72 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { auto acc1 = store.Access(); auto acc2 = store.Access(); - auto vertex = acc1.FindVertex(gid, View::OLD); + auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex->AddLabel(acc1.NameToLabel("label")).HasValue()); + ASSERT_TRUE(vertex->AddLabelAndValidate(acc1.NameToLabel("label1")).HasValue()); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); acc1.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); acc2.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex->SetProperty(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex->SetPropertyAndValidate(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); auto acc3 = store.Access(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc1.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc2.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc3.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); ASSERT_FALSE(acc1.Commit().HasError()); ASSERT_FALSE(acc2.Commit().HasError()); @@ -2394,46 +2370,46 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { auto acc1 = store.Access(); auto acc2 = store.Access(); - auto vertex = acc1.FindVertex(gid, View::OLD); + auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(acc1.DeleteVertex(&*vertex).HasValue()); auto acc3 = store.Access(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc2.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc1.AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc3.AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc1.Abort(); acc2.Abort(); @@ -2443,13 +2419,13 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { { auto acc = store.Access(); - EXPECT_TRUE(acc.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc.FindVertex(pk, View::NEW)); acc.AdvanceCommand(); - EXPECT_TRUE(acc.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc.FindVertex(pk, View::NEW)); acc.Abort(); } @@ -2458,46 +2434,46 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { auto acc1 = store.Access(); auto acc2 = store.Access(); - auto vertex = acc1.FindVertex(gid, View::OLD); + auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); ASSERT_TRUE(acc1.DeleteVertex(&*vertex).HasValue()); auto acc3 = store.Access(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc2.AdvanceCommand(); - EXPECT_TRUE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc1.AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); acc3.AdvanceCommand(); - EXPECT_FALSE(acc1.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc1.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc2.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc2.FindVertex(gid, View::NEW)); - EXPECT_TRUE(acc3.FindVertex(gid, View::OLD)); - EXPECT_TRUE(acc3.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc1.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); + EXPECT_TRUE(acc3.FindVertex(pk, View::OLD)); + EXPECT_TRUE(acc3.FindVertex(pk, View::NEW)); ASSERT_FALSE(acc1.Commit().HasError()); ASSERT_FALSE(acc2.Commit().HasError()); @@ -2507,13 +2483,13 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { { auto acc = store.Access(); - EXPECT_FALSE(acc.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc.FindVertex(pk, View::NEW)); acc.AdvanceCommand(); - EXPECT_FALSE(acc.FindVertex(gid, View::OLD)); - EXPECT_FALSE(acc.FindVertex(gid, View::NEW)); + EXPECT_FALSE(acc.FindVertex(pk, View::OLD)); + EXPECT_FALSE(acc.FindVertex(pk, View::NEW)); acc.Abort(); } @@ -2521,21 +2497,19 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, DeletedVertexAccessor) { - const auto property = store.NameToProperty("property"); + const auto property1 = store.NameToProperty("property1"); const PropertyValue property_value{"property_value"}; - std::optional gid; // Create the vertex { auto acc = store.Access(); - auto vertex = acc.CreateVertex(); - gid = vertex.Gid(); - ASSERT_FALSE(vertex.SetProperty(property, property_value).HasError()); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); + ASSERT_FALSE(vertex.SetPropertyAndValidate(property1, property_value).HasError()); ASSERT_FALSE(acc.Commit().HasError()); } auto acc = store.Access(); - auto vertex = acc.FindVertex(*gid, View::OLD); + auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); auto maybe_deleted_vertex = acc.DeleteVertex(&*vertex); ASSERT_FALSE(maybe_deleted_vertex.HasError()); @@ -2546,7 +2520,7 @@ TEST_F(StorageV3, DeletedVertexAccessor) { ASSERT_TRUE(deleted_vertex->ClearProperties().HasError()); // you can call read only methods - const auto maybe_property = deleted_vertex->GetProperty(property, View::OLD); + const auto maybe_property = deleted_vertex->GetProperty(property1, View::OLD); ASSERT_FALSE(maybe_property.HasError()); ASSERT_EQ(property_value, *maybe_property); ASSERT_FALSE(acc.Commit().HasError()); @@ -2555,9 +2529,89 @@ TEST_F(StorageV3, DeletedVertexAccessor) { // you can call read only methods and get valid results even after the // transaction which deleted the vertex committed, but only if the transaction // accessor is still alive - const auto maybe_property = deleted_vertex->GetProperty(property, View::OLD); + const auto maybe_property = deleted_vertex->GetProperty(property1, View::OLD); ASSERT_FALSE(maybe_property.HasError()); ASSERT_EQ(property_value, *maybe_property); } } + +TEST_F(StorageV3, TestCreateVertexAndValidate) { + { + auto acc = store.Access(); + const auto label1 = store.NameToLabel("label1"); + const auto prop1 = store.NameToProperty("prop1"); + auto vertex = acc.CreateVertexAndValidate(primary_label, {label1}, + {{primary_property, PropertyValue(0)}, {prop1, PropertyValue(111)}}); + ASSERT_TRUE(vertex.HasValue()); + ASSERT_TRUE(vertex->PrimaryLabel(View::NEW).HasValue()); + EXPECT_EQ(vertex->PrimaryLabel(View::NEW).GetValue(), primary_label); + ASSERT_TRUE(vertex->PrimaryKey(View::NEW).HasValue()); + EXPECT_EQ(vertex->PrimaryKey(View::NEW).GetValue(), PrimaryKey{{PropertyValue(0)}}); + ASSERT_TRUE(vertex->Properties(View::NEW).HasValue()); + EXPECT_EQ(vertex->Properties(View::NEW).GetValue(), + (std::map{{prop1, PropertyValue(111)}})); + } + { + const auto label1 = store.NameToLabel("new_primary_label"); + const auto prop1 = store.NameToProperty("key1"); + const auto prop2 = store.NameToProperty("key2"); + ASSERT_TRUE(store.CreateSchema( + label1, {SchemaProperty{prop1, common::SchemaType::INT}, SchemaProperty{prop2, common::SchemaType::STRING}})); + auto acc = store.Access(); + auto vertex = acc.CreateVertexAndValidate(label1, {}, {{prop1, PropertyValue(21)}, {prop2, PropertyValue("test")}}); + ASSERT_TRUE(vertex.HasValue()); + ASSERT_TRUE(vertex->PrimaryLabel(View::NEW).HasValue()); + EXPECT_EQ(vertex->PrimaryLabel(View::NEW).GetValue(), label1); + ASSERT_TRUE(vertex->PrimaryKey(View::NEW).HasValue()); + EXPECT_EQ(vertex->PrimaryKey(View::NEW).GetValue(), (PrimaryKey{{PropertyValue(21), PropertyValue("test")}})); + ASSERT_TRUE(vertex->Properties(View::NEW).HasValue()); + EXPECT_TRUE(vertex->Properties(View::NEW).GetValue().empty()); + } + { + ASSERT_DEATH( + { + Storage store; + ASSERT_TRUE(store.CreateSchema(primary_label, + {storage::v3::SchemaProperty{primary_property, common::SchemaType::INT}})); + auto acc = store.Access(); + auto vertex1 = acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, PropertyValue(0)}}); + auto vertex2 = acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, PropertyValue(0)}}); + }, + ""); + } + { + auto acc = store.Access(); + auto vertex = acc.CreateVertexAndValidate(primary_label, {primary_label}, {{primary_property, PropertyValue(0)}}); + ASSERT_TRUE(vertex.HasError()); + ASSERT_TRUE(std::holds_alternative(vertex.GetError())); + EXPECT_EQ(std::get(vertex.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY, primary_label)); + } + { + auto acc = store.Access(); + auto vertex = acc.CreateVertexAndValidate(primary_label, {primary_label}, {{primary_property, PropertyValue(0)}}); + ASSERT_TRUE(vertex.HasError()); + ASSERT_TRUE(std::holds_alternative(vertex.GetError())); + EXPECT_EQ(std::get(vertex.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY, primary_label)); + } + { + auto acc = store.Access(); + auto vertex = acc.CreateVertexAndValidate(primary_label, {}, {}); + ASSERT_TRUE(vertex.HasError()); + ASSERT_TRUE(std::holds_alternative(vertex.GetError())); + EXPECT_EQ(std::get(vertex.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_HAS_NO_PRIMARY_PROPERTY, primary_label, + {primary_property, common::SchemaType::INT})); + } + { + auto acc = store.Access(); + auto vertex = acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, PropertyValue("test")}}); + ASSERT_TRUE(vertex.HasError()); + ASSERT_TRUE(std::holds_alternative(vertex.GetError())); + EXPECT_EQ(std::get(vertex.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, + {primary_property, common::SchemaType::INT}, PropertyValue("test"))); + } +} } // namespace memgraph::storage::v3::tests diff --git a/tests/unit/storage_v3_schema.cpp b/tests/unit/storage_v3_schema.cpp index 3d090fc2f..472c2bb5c 100644 --- a/tests/unit/storage_v3_schema.cpp +++ b/tests/unit/storage_v3_schema.cpp @@ -281,13 +281,13 @@ TEST_F(SchemaValidatorTest, TestSchemaValidatePropertyUpdateLabel) { const auto schema_violation = schema_validator.ValidateLabelUpdate(label1); ASSERT_NE(schema_violation, std::nullopt); EXPECT_EQ(*schema_violation, - SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label1)); + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, label1)); } { const auto schema_violation = schema_validator.ValidateLabelUpdate(label2); ASSERT_NE(schema_violation, std::nullopt); EXPECT_EQ(*schema_violation, - SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_MODIFY_PRIMARY_LABEL, label2)); + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, label2)); } EXPECT_EQ(schema_validator.ValidateLabelUpdate(NameToLabel("test")), std::nullopt); } diff --git a/tests/unit/storage_v3_vertex_accessors.cpp b/tests/unit/storage_v3_vertex_accessors.cpp new file mode 100644 index 000000000..dcf94788a --- /dev/null +++ b/tests/unit/storage_v3_vertex_accessors.cpp @@ -0,0 +1,209 @@ +// Copyright 2022 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 +#include + +#include +#include + +#include "common/types.hpp" +#include "storage/v3/delta.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/result.hpp" +#include "storage/v3/schema_validator.hpp" +#include "storage/v3/storage.hpp" +#include "storage/v3/vertex_accessor.hpp" +#include "storage_v3_test_utils.hpp" + +using testing::UnorderedElementsAre; + +namespace memgraph::storage::v3::tests { + +class StorageV3Accessor : public ::testing::Test { + protected: + void SetUp() override { + ASSERT_TRUE(storage.CreateSchema(primary_label, {SchemaProperty{primary_property, common::SchemaType::INT}})); + } + + VertexAccessor CreateVertexAndValidate(Storage::Accessor &acc, LabelId primary_label, + const std::vector &labels, + const std::vector> &properties) { + auto vtx = acc.CreateVertexAndValidate(primary_label, labels, properties); + EXPECT_TRUE(vtx.HasValue()); + return *vtx; + } + + Storage storage; + const LabelId primary_label{storage.NameToLabel("label")}; + const PropertyId primary_property{storage.NameToProperty("property")}; +}; + +TEST_F(StorageV3Accessor, TestPrimaryLabel) { + { + auto acc = storage.Access(); + const auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(0)}}); + ASSERT_TRUE(vertex.PrimaryLabel(View::NEW).HasValue()); + const auto vertex_primary_label = vertex.PrimaryLabel(View::NEW).GetValue(); + ASSERT_FALSE(vertex.PrimaryLabel(View::OLD).HasValue()); + EXPECT_EQ(vertex_primary_label, primary_label); + } + { + auto acc = storage.Access(); + const auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(1)}}); + ASSERT_TRUE(vertex.PrimaryLabel(View::OLD).HasError()); + const auto error_primary_label = vertex.PrimaryLabel(View::OLD).GetError(); + ASSERT_FALSE(vertex.PrimaryLabel(View::NEW).HasError()); + EXPECT_EQ(error_primary_label, Error::NONEXISTENT_OBJECT); + } + { + auto acc = storage.Access(); + CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(2)}}); + ASSERT_FALSE(acc.Commit().HasError()); + } + { + auto acc = storage.Access(); + const auto vertex = acc.FindVertex({PropertyValue{2}}, View::OLD); + ASSERT_TRUE(vertex.has_value()); + ASSERT_TRUE(acc.FindVertex({PropertyValue{2}}, View::NEW).has_value()); + ASSERT_TRUE(vertex->PrimaryLabel(View::NEW).HasValue()); + ASSERT_TRUE(vertex->PrimaryLabel(View::OLD).HasValue()); + const auto vertex_primary_label = vertex->PrimaryLabel(View::NEW).GetValue(); + EXPECT_EQ(vertex_primary_label, primary_label); + } +} + +TEST_F(StorageV3Accessor, TestAddLabels) { + { + auto acc = storage.Access(); + const auto label1 = storage.NameToLabel("label1"); + const auto label2 = storage.NameToLabel("label2"); + const auto label3 = storage.NameToLabel("label3"); + const auto vertex = + CreateVertexAndValidate(acc, primary_label, {label1, label2, label3}, {{primary_property, PropertyValue(0)}}); + ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); + ASSERT_FALSE(vertex.Labels(View::OLD).HasValue()); + EXPECT_THAT(vertex.Labels(View::NEW).GetValue(), UnorderedElementsAre(label1, label2, label3)); + } + { + auto acc = storage.Access(); + const auto label1 = storage.NameToLabel("label1"); + const auto label2 = storage.NameToLabel("label2"); + const auto label3 = storage.NameToLabel("label3"); + auto vertex = CreateVertexAndValidate(acc, primary_label, {label1}, {{primary_property, PropertyValue(1)}}); + ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); + ASSERT_FALSE(vertex.Labels(View::OLD).HasValue()); + EXPECT_THAT(vertex.Labels(View::NEW).GetValue(), UnorderedElementsAre(label1)); + EXPECT_TRUE(vertex.AddLabelAndValidate(label2).HasValue()); + EXPECT_TRUE(vertex.AddLabelAndValidate(label3).HasValue()); + ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); + ASSERT_FALSE(vertex.Labels(View::OLD).HasValue()); + EXPECT_THAT(vertex.Labels(View::NEW).GetValue(), UnorderedElementsAre(label1, label2, label3)); + } + { + auto acc = storage.Access(); + const auto label1 = storage.NameToLabel("label"); + auto vertex = acc.CreateVertexAndValidate(primary_label, {label1}, {{primary_property, PropertyValue(2)}}); + ASSERT_TRUE(vertex.HasError()); + ASSERT_TRUE(std::holds_alternative(vertex.GetError())); + EXPECT_EQ(std::get(vertex.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_SECONDARY_LABEL_IS_PRIMARY, label1)); + } + { + auto acc = storage.Access(); + const auto label1 = storage.NameToLabel("label"); + auto vertex = acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, PropertyValue(3)}}); + ASSERT_TRUE(vertex.HasValue()); + const auto schema_violation = vertex->AddLabelAndValidate(label1); + ASSERT_TRUE(schema_violation.HasError()); + ASSERT_TRUE(std::holds_alternative(schema_violation.GetError())); + EXPECT_EQ(std::get(schema_violation.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, label1)); + } +} + +TEST_F(StorageV3Accessor, TestRemoveLabels) { + { + auto acc = storage.Access(); + const auto label1 = storage.NameToLabel("label1"); + const auto label2 = storage.NameToLabel("label2"); + const auto label3 = storage.NameToLabel("label3"); + auto vertex = + CreateVertexAndValidate(acc, primary_label, {label1, label2, label3}, {{primary_property, PropertyValue(0)}}); + ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); + EXPECT_THAT(vertex.Labels(View::NEW).GetValue(), UnorderedElementsAre(label1, label2, label3)); + const auto res1 = vertex.RemoveLabelAndValidate(label2); + ASSERT_TRUE(res1.HasValue()); + EXPECT_TRUE(res1.GetValue()); + EXPECT_THAT(vertex.Labels(View::NEW).GetValue(), UnorderedElementsAre(label1, label3)); + const auto res2 = vertex.RemoveLabelAndValidate(label1); + ASSERT_TRUE(res2.HasValue()); + EXPECT_TRUE(res2.GetValue()); + EXPECT_THAT(vertex.Labels(View::NEW).GetValue(), UnorderedElementsAre(label3)); + const auto res3 = vertex.RemoveLabelAndValidate(label3); + ASSERT_TRUE(res3.HasValue()); + ASSERT_TRUE(res3.HasValue()); + EXPECT_TRUE(res3.GetValue()); + EXPECT_TRUE(vertex.Labels(View::NEW).GetValue().empty()); + } + { + auto acc = storage.Access(); + const auto label1 = storage.NameToLabel("label1"); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(1)}}); + ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); + EXPECT_TRUE(vertex.Labels(View::NEW).GetValue().empty()); + const auto res1 = vertex.RemoveLabelAndValidate(label1); + ASSERT_TRUE(res1.HasValue()); + EXPECT_FALSE(res1.GetValue()); + } + { + auto acc = storage.Access(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(2)}}); + const auto res1 = vertex.RemoveLabelAndValidate(primary_label); + ASSERT_TRUE(res1.HasError()); + ASSERT_TRUE(std::holds_alternative(res1.GetError())); + EXPECT_EQ(std::get(res1.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_LABEL, primary_label)); + } +} + +TEST_F(StorageV3Accessor, TestSetKeysAndProperties) { + { + auto acc = storage.Access(); + const PropertyId prop1{storage.NameToProperty("prop1")}; + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(0)}}); + const auto res = vertex.SetPropertyAndValidate(prop1, PropertyValue(1)); + ASSERT_TRUE(res.HasValue()); + } + { + auto acc = storage.Access(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(1)}}); + const auto res = vertex.SetPropertyAndValidate(primary_property, PropertyValue(1)); + ASSERT_TRUE(res.HasError()); + ASSERT_TRUE(std::holds_alternative(res.GetError())); + EXPECT_EQ(std::get(res.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY, primary_label, + SchemaProperty{primary_property, common::SchemaType::INT})); + } + { + auto acc = storage.Access(); + auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(2)}}); + const auto res = vertex.SetPropertyAndValidate(primary_property, PropertyValue()); + ASSERT_TRUE(res.HasError()); + ASSERT_TRUE(std::holds_alternative(res.GetError())); + EXPECT_EQ(std::get(res.GetError()), + SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_UPDATE_PRIMARY_KEY, primary_label, + SchemaProperty{primary_property, common::SchemaType::INT})); + } +} + +} // namespace memgraph::storage::v3::tests From efb3c8d03deeae3906c3d8a4310905e5f189f55d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= Date: Tue, 30 Aug 2022 13:41:53 +0200 Subject: [PATCH 03/10] Remove multi-threaded related logic and variables (#460) * Remove logic that was necessary for optimal multi-threaded performance, such as accumulating deleted object in local containers and appending them to a global one, handling overlapping locking. * Remove background GC thread. * Remove mutexes, locks and atomics throughout storage. --- src/storage/v2/storage.cpp | 10 +- src/storage/v3/CMakeLists.txt | 1 + src/storage/v3/commit_log.cpp | 7 +- src/storage/v3/commit_log.hpp | 3 +- src/storage/v3/config.hpp | 5 +- src/storage/v3/constraints.cpp | 2 - src/storage/v3/durability/durability.cpp | 12 +- src/storage/v3/durability/durability.hpp | 13 +- src/storage/v3/durability/snapshot.cpp | 15 +- src/storage/v3/durability/snapshot.hpp | 2 +- src/storage/v3/durability/wal.cpp | 14 +- src/storage/v3/durability/wal.hpp | 2 +- src/storage/v3/edge.hpp | 1 - src/storage/v3/edge_accessor.cpp | 46 +- src/storage/v3/indices.cpp | 4 - .../v3/lexicographically_ordered_vertex.cpp | 14 + .../v3/replication/replication_client.cpp | 21 +- .../v3/replication/replication_server.cpp | 46 +- src/storage/v3/storage.cpp | 444 ++++++------------ src/storage/v3/storage.hpp | 31 +- src/storage/v3/vertex.hpp | 1 - src/storage/v3/vertex_accessor.cpp | 20 - 22 files changed, 241 insertions(+), 473 deletions(-) create mode 100644 src/storage/v3/lexicographically_ordered_vertex.cpp diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index cee74574d..ef32412c5 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -1110,8 +1110,9 @@ void Storage::Accessor::Abort() { { std::unique_lock engine_guard(storage_->engine_lock_); uint64_t mark_timestamp = storage_->timestamp_; - // Take garbage_undo_buffers lock while holding the engine lock to make - // sure that entries are sorted by mark timestamp in the list. + // Take garbage_undo_buffers lock while holding the engine lock to make sure that entries are sorted by mark + // timestamp in the list. This is necessary when a transaction is aborting simultaneously with a GC run: both of + // these operations acquire a mark timestamps and then modify the garbage deltas. storage_->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { // Release engine lock because we don't have to hold it anymore and // emplace back could take a long time. @@ -1517,8 +1518,9 @@ void Storage::CollectGarbage() { { std::unique_lock guard(engine_lock_); uint64_t mark_timestamp = timestamp_; - // Take garbage_undo_buffers lock while holding the engine lock to make - // sure that entries are sorted by mark timestamp in the list. + // Take garbage_undo_buffers lock while holding the engine lock to make sure that entries are sorted by mark + // timestamp in the list. This is necessary when a transaction is aborting simultaneously with a GC run: both of + // these operations acquire a mark timestamps and then modify the garbage deltas. garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { // Release engine lock because we don't have to hold it anymore and // this could take a long time. diff --git a/src/storage/v3/CMakeLists.txt b/src/storage/v3/CMakeLists.txt index c66d4a647..272d2cd03 100644 --- a/src/storage/v3/CMakeLists.txt +++ b/src/storage/v3/CMakeLists.txt @@ -9,6 +9,7 @@ set(storage_v3_src_files edge_accessor.cpp indices.cpp key_store.cpp + lexicographically_ordered_vertex.cpp property_store.cpp vertex_accessor.cpp schemas.cpp diff --git a/src/storage/v3/commit_log.cpp b/src/storage/v3/commit_log.cpp index 793e199d3..4ce4586be 100644 --- a/src/storage/v3/commit_log.cpp +++ b/src/storage/v3/commit_log.cpp @@ -47,8 +47,6 @@ CommitLog::~CommitLog() { } void CommitLog::MarkFinished(uint64_t id) { - std::lock_guard guard(lock_); - Block *block = FindOrCreateBlock(id); block->field[(id % kIdsInBlock) / kIdsInField] |= 1ULL << (id % kIdsInField); if (id == oldest_active_) { @@ -56,10 +54,7 @@ void CommitLog::MarkFinished(uint64_t id) { } } -uint64_t CommitLog::OldestActive() { - std::lock_guard guard(lock_); - return oldest_active_; -} +uint64_t CommitLog::OldestActive() const noexcept { return oldest_active_; } void CommitLog::UpdateOldestActive() { while (head_) { diff --git a/src/storage/v3/commit_log.hpp b/src/storage/v3/commit_log.hpp index c3f16e9ca..0ef3817dd 100644 --- a/src/storage/v3/commit_log.hpp +++ b/src/storage/v3/commit_log.hpp @@ -51,7 +51,7 @@ class CommitLog final { void MarkFinished(uint64_t id); /// Retrieve the oldest transaction still not marked as finished. - uint64_t OldestActive(); + uint64_t OldestActive() const noexcept; private: static constexpr uint64_t kBlockSize = 8192; @@ -72,7 +72,6 @@ class CommitLog final { uint64_t head_start_{0}; uint64_t next_start_{0}; uint64_t oldest_active_{0}; - utils::SpinLock lock_; utils::Allocator allocator_; }; diff --git a/src/storage/v3/config.hpp b/src/storage/v3/config.hpp index e0f878cc9..9d6dec759 100644 --- a/src/storage/v3/config.hpp +++ b/src/storage/v3/config.hpp @@ -23,9 +23,10 @@ namespace memgraph::storage::v3 { /// the storage. This class also defines the default behavior. struct Config { struct Gc { - enum class Type { NONE, PERIODIC }; + // TODO(antaljanosbenjamin): How to handle garbage collection? + enum class Type { NONE }; - Type type{Type::PERIODIC}; + Type type{Type::NONE}; std::chrono::milliseconds interval{std::chrono::milliseconds(1000)}; } gc; diff --git a/src/storage/v3/constraints.cpp b/src/storage/v3/constraints.cpp index 59006625e..17315165a 100644 --- a/src/storage/v3/constraints.cpp +++ b/src/storage/v3/constraints.cpp @@ -57,7 +57,6 @@ bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, c bool deleted{false}; bool has_label{false}; { - std::lock_guard guard(vertex.lock); delta = vertex.delta; deleted = vertex.deleted; has_label = VertexHasLabel(vertex, label); @@ -142,7 +141,6 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std:: bool deleted{false}; Delta *delta{nullptr}; { - std::lock_guard guard(vertex.lock); has_label = VertexHasLabel(vertex, label); deleted = vertex.deleted; delta = vertex.delta; diff --git a/src/storage/v3/durability/durability.cpp b/src/storage/v3/durability/durability.cpp index a96594258..65ef294f0 100644 --- a/src/storage/v3/durability/durability.cpp +++ b/src/storage/v3/durability/durability.cpp @@ -157,11 +157,13 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ spdlog::info("Constraints are recreated from metadata."); } -std::optional RecoverData( - const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, - std::string *epoch_id, std::deque> *epoch_history, VerticesSkipList *vertices, - utils::SkipList *edges, std::atomic *edge_count, NameIdMapper *name_id_mapper, Indices *indices, - Constraints *constraints, Config::Items items, uint64_t *wal_seq_num) { +std::optional RecoverData(const std::filesystem::path &snapshot_directory, + const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, + std::deque> *epoch_history, + VerticesSkipList *vertices, utils::SkipList *edges, uint64_t *edge_count, + NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, + Config::Items items, uint64_t *wal_seq_num) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; spdlog::info("Recovering persisted data using snapshot ({}) and WAL directory ({}).", snapshot_directory, wal_directory); diff --git a/src/storage/v3/durability/durability.hpp b/src/storage/v3/durability/durability.hpp index b7fc39005..b84c59b23 100644 --- a/src/storage/v3/durability/durability.hpp +++ b/src/storage/v3/durability/durability.hpp @@ -11,7 +11,6 @@ #pragma once -#include #include #include #include @@ -102,10 +101,12 @@ void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_ /// Recovers data either from a snapshot and/or WAL files. /// @throw RecoveryFailure /// @throw std::bad_alloc -std::optional RecoverData( - const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, - std::string *epoch_id, std::deque> *epoch_history, VerticesSkipList *vertices, - utils::SkipList *edges, std::atomic *edge_count, NameIdMapper *name_id_mapper, Indices *indices, - Constraints *constraints, Config::Items items, uint64_t *wal_seq_num); +std::optional RecoverData(const std::filesystem::path &snapshot_directory, + const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, + std::deque> *epoch_history, + VerticesSkipList *vertices, utils::SkipList *edges, uint64_t *edge_count, + NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, + Config::Items items, uint64_t *wal_seq_num); } // namespace memgraph::storage::v3::durability diff --git a/src/storage/v3/durability/snapshot.cpp b/src/storage/v3/durability/snapshot.cpp index 3c3ec9fe6..9ae4d00ab 100644 --- a/src/storage/v3/durability/snapshot.cpp +++ b/src/storage/v3/durability/snapshot.cpp @@ -162,7 +162,7 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices, utils::SkipList *edges, std::deque> *epoch_history, - NameIdMapper *name_id_mapper, std::atomic *edge_count, Config::Items items) { + NameIdMapper *name_id_mapper, uint64_t *edge_count, Config::Items items) { RecoveryInfo ret; RecoveredIndicesAndConstraints indices_constraints; @@ -226,7 +226,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi }; // Reset current edge count. - edge_count->store(0, std::memory_order_release); + *edge_count = 0; { // Recover edges. @@ -485,7 +485,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi } // Increment edge count. We only increment the count here because the // information is duplicated in in_edges. - edge_count->fetch_add(*out_size, std::memory_order_acq_rel); + *edge_count += *out_size; } } spdlog::info("Connectivity is recovered."); @@ -687,13 +687,8 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps for (auto &edge : acc) { // The edge visibility check must be done here manually because we don't // allow direct access to the edges through the public API. - bool is_visible = true; - Delta *delta = nullptr; - { - std::lock_guard guard(edge.lock); - is_visible = !edge.deleted; - delta = edge.delta; - } + auto is_visible = !edge.deleted; + auto *delta = edge.delta; ApplyDeltasForRead(transaction, delta, View::OLD, [&is_visible](const Delta &delta) { switch (delta.action) { case Delta::Action::ADD_LABEL: diff --git a/src/storage/v3/durability/snapshot.hpp b/src/storage/v3/durability/snapshot.hpp index 68871cfe6..34af826a7 100644 --- a/src/storage/v3/durability/snapshot.hpp +++ b/src/storage/v3/durability/snapshot.hpp @@ -63,7 +63,7 @@ SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path); RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices, utils::SkipList *edges, std::deque> *epoch_history, - NameIdMapper *name_id_mapper, std::atomic *edge_count, Config::Items items); + NameIdMapper *name_id_mapper, uint64_t *edge_count, Config::Items items); /// Function used to create a snapshot using the given transaction. void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snapshot_directory, diff --git a/src/storage/v3/durability/wal.cpp b/src/storage/v3/durability/wal.cpp index 7e1a0108c..52959379f 100644 --- a/src/storage/v3/durability/wal.cpp +++ b/src/storage/v3/durability/wal.cpp @@ -488,7 +488,6 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Config::Ite // actions. // encoder->WriteMarker(Marker::SECTION_DELTA); // encoder->WriteUint(timestamp); - // std::lock_guard guard(vertex.lock); // switch (delta.action) { // case Delta::Action::DELETE_OBJECT: // case Delta::Action::RECREATE_OBJECT: { @@ -540,10 +539,9 @@ void EncodeDelta(BaseEncoder *encoder, NameIdMapper *name_id_mapper, const Delta uint64_t timestamp) { // When converting a Delta to a WAL delta the logic is inverted. That is // because the Delta's represent undo actions and we want to store redo - // // actions. + // actions. // encoder->WriteMarker(Marker::SECTION_DELTA); // encoder->WriteUint(timestamp); - // std::lock_guard guard(edge.lock); // switch (delta.action) { // case Delta::Action::SET_PROPERTY: { // encoder->WriteMarker(Marker::DELTA_EDGE_SET_PROPERTY); @@ -619,7 +617,7 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, const std::optional last_loaded_timestamp, VerticesSkipList *vertices, - utils::SkipList *edges, NameIdMapper *name_id_mapper, std::atomic *edge_count, + utils::SkipList *edges, NameIdMapper * /*name_id_mapper*/, uint64_t * /*edge_count*/, Config::Items items) { spdlog::info("Trying to load WAL file {}.", path); RecoveryInfo ret; @@ -750,7 +748,7 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst // ret.next_edge_id = std::max(ret.next_edge_id, edge_gid.AsUint() + 1); // // Increment edge count. - // edge_count->fetch_add(1, std::memory_order_acq_rel); + // *edge_count += 1; // break; // } @@ -795,7 +793,7 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst // } // // Decrement edge count. - // edge_count->fetch_add(-1, std::memory_order_acq_rel); + // *edge_count += -1; // break; // } @@ -881,8 +879,8 @@ RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConst // } // } - spdlog::info("Applied {} deltas from WAL. Skipped {} deltas, because they were too old.", deltas_applied, - info.num_deltas - deltas_applied); + // spdlog::info("Applied {} deltas from WAL. Skipped {} deltas, because they were too old.", deltas_applied, + // info.num_deltas - deltas_applied); return ret; } diff --git a/src/storage/v3/durability/wal.hpp b/src/storage/v3/durability/wal.hpp index 0e80134a5..a59789edb 100644 --- a/src/storage/v3/durability/wal.hpp +++ b/src/storage/v3/durability/wal.hpp @@ -191,7 +191,7 @@ void EncodeOperation(BaseEncoder *encoder, NameIdMapper *name_id_mapper, Storage /// @throw RecoveryFailure RecoveryInfo LoadWal(const std::filesystem::path &path, RecoveredIndicesAndConstraints *indices_constraints, std::optional last_loaded_timestamp, VerticesSkipList *vertices, - utils::SkipList *edges, NameIdMapper *name_id_mapper, std::atomic *edge_count, + utils::SkipList *edges, NameIdMapper *name_id_mapper, uint64_t *edge_count, Config::Items items); /// WalFile class used to append deltas and operations to the WAL file. diff --git a/src/storage/v3/edge.hpp b/src/storage/v3/edge.hpp index d6440793a..df47b8416 100644 --- a/src/storage/v3/edge.hpp +++ b/src/storage/v3/edge.hpp @@ -33,7 +33,6 @@ struct Edge { PropertyStore properties; - mutable utils::SpinLock lock; bool deleted; // uint8_t PAD; // uint16_t PAD; diff --git a/src/storage/v3/edge_accessor.cpp b/src/storage/v3/edge_accessor.cpp index abb5597e5..b4d275d69 100644 --- a/src/storage/v3/edge_accessor.cpp +++ b/src/storage/v3/edge_accessor.cpp @@ -22,14 +22,10 @@ namespace memgraph::storage::v3 { bool EdgeAccessor::IsVisible(const View view) const { - bool deleted = true; - bool exists = true; - Delta *delta = nullptr; - { - std::lock_guard guard(edge_.ptr->lock); - deleted = edge_.ptr->deleted; - delta = edge_.ptr->delta; - } + auto deleted = edge_.ptr->deleted; + auto exists = true; + auto *delta = edge_.ptr->delta; + ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) { switch (delta.action) { case Delta::Action::ADD_LABEL: @@ -66,8 +62,6 @@ Result EdgeAccessor::SetProperty(PropertyId property, const Prope utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED; - std::lock_guard guard(edge_.ptr->lock); - if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; if (edge_.ptr->deleted) return Error::DELETED_OBJECT; @@ -88,8 +82,6 @@ Result EdgeAccessor::SetProperty(PropertyId property, const Prope Result> EdgeAccessor::ClearProperties() { if (!config_.properties_on_edges) return Error::PROPERTIES_DISABLED; - std::lock_guard guard(edge_.ptr->lock); - if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; if (edge_.ptr->deleted) return Error::DELETED_OBJECT; @@ -106,16 +98,11 @@ Result> EdgeAccessor::ClearProperties() { Result EdgeAccessor::GetProperty(PropertyId property, View view) const { if (!config_.properties_on_edges) return PropertyValue(); - bool exists = true; - bool deleted = false; - PropertyValue value; - Delta *delta = nullptr; - { - std::lock_guard guard(edge_.ptr->lock); - deleted = edge_.ptr->deleted; - value = edge_.ptr->properties.GetProperty(property); - delta = edge_.ptr->delta; - } + auto exists = true; + auto deleted = edge_.ptr->deleted; + auto value = edge_.ptr->properties.GetProperty(property); + auto *delta = edge_.ptr->delta; + ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) { switch (delta.action) { case Delta::Action::SET_PROPERTY: { @@ -148,16 +135,11 @@ Result EdgeAccessor::GetProperty(PropertyId property, View view) Result> EdgeAccessor::Properties(View view) const { if (!config_.properties_on_edges) return std::map{}; - bool exists = true; - bool deleted = false; - std::map properties; - Delta *delta = nullptr; - { - std::lock_guard guard(edge_.ptr->lock); - deleted = edge_.ptr->deleted; - properties = edge_.ptr->properties.Properties(); - delta = edge_.ptr->delta; - } + auto exists = true; + auto deleted = edge_.ptr->deleted; + auto properties = edge_.ptr->properties.Properties(); + auto *delta = edge_.ptr->delta; + ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) { switch (delta.action) { case Delta::Action::SET_PROPERTY: { diff --git a/src/storage/v3/indices.cpp b/src/storage/v3/indices.cpp index b9874d023..e69aa9d7e 100644 --- a/src/storage/v3/indices.cpp +++ b/src/storage/v3/indices.cpp @@ -53,7 +53,6 @@ bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t timestamp) bool deleted{false}; const Delta *delta{nullptr}; { - std::lock_guard guard(vertex.lock); has_label = utils::Contains(vertex.labels, label); deleted = vertex.deleted; delta = vertex.delta; @@ -106,7 +105,6 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, PropertyId bool deleted{false}; const Delta *delta{nullptr}; { - std::lock_guard guard(vertex.lock); has_label = utils::Contains(vertex.labels, label); current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); deleted = vertex.deleted; @@ -165,7 +163,6 @@ bool CurrentVersionHasLabel(const Vertex &vertex, LabelId label, Transaction *tr bool has_label{false}; const Delta *delta{nullptr}; { - std::lock_guard guard(vertex.lock); deleted = vertex.deleted; has_label = utils::Contains(vertex.labels, label); delta = vertex.delta; @@ -217,7 +214,6 @@ bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, Propert bool current_value_equal_to_value = value.IsNull(); const Delta *delta{nullptr}; { - std::lock_guard guard(vertex.lock); deleted = vertex.deleted; has_label = utils::Contains(vertex.labels, label); current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); diff --git a/src/storage/v3/lexicographically_ordered_vertex.cpp b/src/storage/v3/lexicographically_ordered_vertex.cpp new file mode 100644 index 000000000..04687f6e6 --- /dev/null +++ b/src/storage/v3/lexicographically_ordered_vertex.cpp @@ -0,0 +1,14 @@ +// Copyright 2022 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/v3/lexicographically_ordered_vertex.hpp" + +namespace memgraph::storage::v3 {} // namespace memgraph::storage::v3 diff --git a/src/storage/v3/replication/replication_client.cpp b/src/storage/v3/replication/replication_client.cpp index 4e893a314..ecc3ffbf4 100644 --- a/src/storage/v3/replication/replication_client.cpp +++ b/src/storage/v3/replication/replication_client.cpp @@ -90,14 +90,7 @@ void Storage::ReplicationClient::FrequentCheck() { void Storage::ReplicationClient::InitializeClient() { uint64_t current_commit_timestamp{kTimestampInitialId}; - std::optional epoch_id; - { - // epoch_id_ can be changed if we don't take this lock - std::unique_lock engine_guard(storage_->engine_lock_); - epoch_id.emplace(storage_->epoch_id_); - } - - auto stream{rpc_client_->Stream(storage_->last_commit_timestamp_, std::move(*epoch_id))}; + auto stream{rpc_client_->Stream(storage_->last_commit_timestamp_, storage_->epoch_id_)}; const auto response = stream.AwaitResponse(); std::optional branching_point; @@ -122,8 +115,8 @@ void Storage::ReplicationClient::InitializeClient() { current_commit_timestamp = response.current_commit_timestamp; spdlog::trace("Current timestamp on replica: {}", current_commit_timestamp); - spdlog::trace("Current timestamp on main: {}", storage_->last_commit_timestamp_.load()); - if (current_commit_timestamp == storage_->last_commit_timestamp_.load()) { + spdlog::trace("Current timestamp on main: {}", storage_->last_commit_timestamp_); + if (current_commit_timestamp == storage_->last_commit_timestamp_) { spdlog::debug("Replica '{}' up to date", name_); std::unique_lock client_guard{client_lock_}; replica_state_.store(replication::ReplicaState::READY); @@ -197,7 +190,7 @@ void Storage::ReplicationClient::StartTransactionReplication(const uint64_t curr case replication::ReplicaState::READY: MG_ASSERT(!replica_stream_); try { - replica_stream_.emplace(ReplicaStream{this, storage_->last_commit_timestamp_.load(), current_wal_seq_num}); + replica_stream_.emplace(ReplicaStream{this, storage_->last_commit_timestamp_, current_wal_seq_num}); replica_state_.store(replication::ReplicaState::REPLICATING); } catch (const rpc::RpcFailedException &) { replica_state_.store(replication::ReplicaState::INVALID); @@ -319,10 +312,8 @@ void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { auto response = TransferWalFiles(arg); replica_commit = response.current_commit_timestamp; } else if constexpr (std::is_same_v) { - std::unique_lock transaction_guard(storage_->engine_lock_); if (storage_->wal_file_ && storage_->wal_file_->SequenceNumber() == arg.current_wal_seq_num) { storage_->wal_file_->DisableFlushing(); - transaction_guard.unlock(); spdlog::debug("Sending current wal file"); replica_commit = ReplicateCurrentWal(); storage_->wal_file_->EnableFlushing(); @@ -355,7 +346,7 @@ void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { std::unique_lock client_guard{client_lock_}; SPDLOG_INFO("Replica timestamp: {}", replica_commit); SPDLOG_INFO("Last commit: {}", storage_->last_commit_timestamp_); - if (storage_->last_commit_timestamp_.load() == replica_commit) { + if (storage_->last_commit_timestamp_ == replica_commit) { replica_state_.store(replication::ReplicaState::READY); return; } @@ -403,7 +394,7 @@ std::vector Storage::ReplicationClient // This lock is also necessary to force the missed transaction to finish. std::optional current_wal_seq_num; std::optional current_wal_from_timestamp; - if (std::unique_lock transtacion_guard(storage_->engine_lock_); storage_->wal_file_) { + if (storage_->wal_file_) { current_wal_seq_num.emplace(storage_->wal_file_->SequenceNumber()); current_wal_from_timestamp.emplace(storage_->wal_file_->FromTimestamp()); } diff --git a/src/storage/v3/replication/replication_server.cpp b/src/storage/v3/replication/replication_server.cpp index 8f8fe10f8..552214b9a 100644 --- a/src/storage/v3/replication/replication_server.cpp +++ b/src/storage/v3/replication/replication_server.cpp @@ -87,7 +87,7 @@ Storage::ReplicationServer::ReplicationServer(Storage *storage, io::network::End void Storage::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::HeartbeatReq req; slk::Load(&req, req_reader); - replication::HeartbeatRes res{true, storage_->last_commit_timestamp_.load(), storage_->epoch_id_}; + replication::HeartbeatRes res{true, storage_->last_commit_timestamp_, storage_->epoch_id_}; slk::Save(res, res_builder); } @@ -125,7 +125,7 @@ void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, sl storage_->wal_seq_num_ = req.seq_num; } - if (req.previous_commit_timestamp != storage_->last_commit_timestamp_.load()) { + if (req.previous_commit_timestamp != storage_->last_commit_timestamp_) { // Empty the stream bool transaction_complete = false; while (!transaction_complete) { @@ -134,14 +134,14 @@ void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, sl transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type); } - replication::AppendDeltasRes res{false, storage_->last_commit_timestamp_.load()}; + replication::AppendDeltasRes res{false, storage_->last_commit_timestamp_}; slk::Save(res, res_builder); return; } ReadAndApplyDelta(&decoder); - replication::AppendDeltasRes res{true, storage_->last_commit_timestamp_.load()}; + replication::AppendDeltasRes res{true, storage_->last_commit_timestamp_}; slk::Save(res, res_builder); } @@ -157,7 +157,6 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!"); spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path); - std::unique_lock storage_guard(storage_->main_lock_); // Clear the database storage_->vertices_.clear(); storage_->edges_.clear(); @@ -188,9 +187,8 @@ void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::B } catch (const durability::RecoveryFailure &e) { LOG_FATAL("Couldn't load the snapshot because of: {}", e.what()); } - storage_guard.unlock(); - replication::SnapshotRes res{true, storage_->last_commit_timestamp_.load()}; + replication::SnapshotRes res{true, storage_->last_commit_timestamp_}; slk::Save(res, res_builder); // Delete other durability files @@ -226,7 +224,7 @@ void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::B LoadWal(&decoder); } - replication::WalFilesRes res{true, storage_->last_commit_timestamp_.load()}; + replication::WalFilesRes res{true, storage_->last_commit_timestamp_}; slk::Save(res, res_builder); } @@ -240,7 +238,7 @@ void Storage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk: LoadWal(&decoder); - replication::CurrentWalRes res{true, storage_->last_commit_timestamp_.load()}; + replication::CurrentWalRes res{true, storage_->last_commit_timestamp_}; slk::Save(res, res_builder); } @@ -298,17 +296,17 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // auto vertex_acc = storage_->vertices_.access(); std::optional> commit_timestamp_and_accessor; - auto get_transaction = [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) { - if (!commit_timestamp_and_accessor) { - commit_timestamp_and_accessor.emplace(commit_timestamp, storage_->Access()); - } else if (commit_timestamp_and_accessor->first != commit_timestamp) { - throw utils::BasicException("Received more than one transaction!"); - } - return &commit_timestamp_and_accessor->second; - }; + // auto get_transaction = [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) { + // if (!commit_timestamp_and_accessor) { + // commit_timestamp_and_accessor.emplace(commit_timestamp, storage_->Access()); + // } else if (commit_timestamp_and_accessor->first != commit_timestamp) { + // throw utils::BasicException("Received more than one transaction!"); + // } + // return &commit_timestamp_and_accessor->second; + // }; uint64_t applied_deltas = 0; - auto max_commit_timestamp = storage_->last_commit_timestamp_.load(); + auto max_commit_timestamp = storage_->last_commit_timestamp_; for (bool transaction_complete = false; !transaction_complete; ++applied_deltas) { const auto [timestamp, delta] = ReadDelta(decoder); @@ -423,13 +421,8 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // // The edge visibility check must be done here manually because we // // don't allow direct access to the edges through the public API. // { - // bool is_visible = true; - // Delta *delta = nullptr; - // { - // std::lock_guard guard(edge->lock); - // is_visible = !edge->deleted; - // delta = edge->delta; - // } + // auto is_visible = !edge->deleted; + // auto *delta = edge->delta; // ApplyDeltasForRead(&transaction->transaction_, delta, View::NEW, [&is_visible](const Delta &delta) { // switch (delta.action) { // case Delta::Action::ADD_LABEL: @@ -466,8 +459,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // &storage_->indices_, // &storage_->constraints_, // storage_->config_.items, - // storage_->schema_validator_, - // storage_->schemas_}; + // storage_->schema_validator_}; // auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property), // delta.vertex_edge_set_property.value); diff --git a/src/storage/v3/storage.cpp b/src/storage/v3/storage.cpp index bbe1aa0f4..23dd45427 100644 --- a/src/storage/v3/storage.cpp +++ b/src/storage/v3/storage.cpp @@ -401,19 +401,17 @@ Storage::Storage(Config config) } } if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] { - if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { - switch (maybe_error.GetError()) { - case CreateSnapshotError::DisabledForReplica: - spdlog::warn( - utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); - break; - } - } - }); - } - if (config_.gc.type == Config::Gc::Type::PERIODIC) { - gc_runner_.Run("Storage GC", config_.gc.interval, [this] { this->CollectGarbage(); }); + // TODO(antaljanosbenjamin): handle snapshots + // snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] { + // if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { + // switch (maybe_error.GetError()) { + // case CreateSnapshotError::DisabledForReplica: + // spdlog::warn( + // utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); + // break; + // } + // } + // }); } if (timestamp_ == kTimestampInitialId) { @@ -424,9 +422,6 @@ Storage::Storage(Config config) } Storage::~Storage() { - if (config_.gc.type == Config::Gc::Type::PERIODIC) { - gc_runner_.Stop(); - } { // Clear replication data replication_server_.reset(); @@ -437,7 +432,7 @@ Storage::~Storage() { wal_file_ = std::nullopt; } if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - snapshot_runner_.Stop(); + // TODO(antaljanosbenjamin): stop snapshot creation } if (config_.durability.snapshot_on_exit) { if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { @@ -452,17 +447,12 @@ Storage::~Storage() { Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level) : storage_(storage), - // The lock must be acquired before creating the transaction object to - // prevent freshly created transactions from dangling in an active state - // during exclusive operations. - storage_guard_(storage_->main_lock_), transaction_(storage->CreateTransaction(isolation_level)), is_transaction_active_(true), config_(storage->config_.items) {} Storage::Accessor::Accessor(Accessor &&other) noexcept : storage_(other.storage_), - storage_guard_(std::move(other.storage_guard_)), transaction_(std::move(other.transaction_)), commit_timestamp_(other.commit_timestamp_), is_transaction_active_(other.is_transaction_active_), @@ -533,8 +523,6 @@ Result> Storage::Accessor::DeleteVertex(VertexAcce "accessor when deleting a vertex!"); auto *vertex_ptr = vertex->vertex_; - std::lock_guard guard(vertex_ptr->lock); - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; if (vertex_ptr->deleted) { @@ -563,8 +551,6 @@ Result>>> Stor std::vector> out_edges; { - std::lock_guard guard(vertex_ptr->lock); - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; if (vertex_ptr->deleted) return std::optional{}; @@ -603,8 +589,6 @@ Result>>> Stor } } - std::lock_guard guard(vertex_ptr->lock); - // We need to check again for serialization errors because we unlocked the // vertex. Some other transaction could have modified the vertex in the // meantime if we didn't have any edges to delete. @@ -634,20 +618,6 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA auto *from_vertex = from->vertex_; auto *to_vertex = to->vertex_; - // Obtain the locks by `gid` order to avoid lock cycles. - std::unique_lock guard_from(from_vertex->lock, std::defer_lock); - std::unique_lock guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex < to_vertex) { - guard_from.lock(); - guard_to.lock(); - } else if (from_vertex > to_vertex) { - guard_to.lock(); - guard_from.lock(); - } else { - // The vertices are the same vertex, only lock one. - guard_from.lock(); - } - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; if (from_vertex->deleted) return Error::DELETED_OBJECT; @@ -656,7 +626,7 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA if (to_vertex->deleted) return Error::DELETED_OBJECT; } - auto gid = Gid::FromUint(storage_->edge_id_.fetch_add(1, std::memory_order_acq_rel)); + auto gid = Gid::FromUint(storage_->edge_id_++); EdgeRef edge(gid); if (config_.properties_on_edges) { auto acc = storage_->edges_.access(); @@ -675,7 +645,7 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); // Increment edge count. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + ++storage_->edge_count_; return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_); @@ -694,20 +664,6 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA auto *from_vertex = from->vertex_; auto *to_vertex = to->vertex_; - // Obtain the locks by `gid` order to avoid lock cycles. - std::unique_lock guard_from(from_vertex->lock, std::defer_lock); - std::unique_lock guard_to(to_vertex->lock, std::defer_lock); - if (&from_vertex < &to_vertex) { - guard_from.lock(); - guard_to.lock(); - } else if (&from_vertex > &to_vertex) { - guard_to.lock(); - guard_from.lock(); - } else { - // The vertices are the same vertex, only lock one. - guard_from.lock(); - } - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; if (from_vertex->deleted) return Error::DELETED_OBJECT; @@ -722,8 +678,7 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA // that runs single-threadedly and while this instance is set-up to apply // threads (it is the replica), it is guaranteed that no other writes are // possible. - storage_->edge_id_.store(std::max(storage_->edge_id_.load(std::memory_order_acquire), gid.AsUint() + 1), - std::memory_order_release); + storage_->edge_id_ = std::max(storage_->edge_id_, gid.AsUint() + 1); EdgeRef edge(gid); if (config_.properties_on_edges) { @@ -743,7 +698,7 @@ Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); // Increment edge count. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + ++storage_->edge_count_; return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_); @@ -756,10 +711,8 @@ Result> Storage::Accessor::DeleteEdge(EdgeAccessor * auto edge_ref = edge->edge_; auto edge_type = edge->edge_type_; - std::unique_lock guard; if (config_.properties_on_edges) { auto *edge_ptr = edge_ref.ptr; - guard = std::unique_lock(edge_ptr->lock); if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; @@ -769,20 +722,6 @@ Result> Storage::Accessor::DeleteEdge(EdgeAccessor * auto *from_vertex = edge->from_vertex_; auto *to_vertex = edge->to_vertex_; - // Obtain the locks by `gid` order to avoid lock cycles. - std::unique_lock guard_from(from_vertex->lock, std::defer_lock); - std::unique_lock guard_to(to_vertex->lock, std::defer_lock); - if (&from_vertex < &to_vertex) { - guard_from.lock(); - guard_to.lock(); - } else if (&from_vertex > &to_vertex) { - guard_to.lock(); - guard_from.lock(); - } else { - // The vertices are the same vertex, only lock one. - guard_from.lock(); - } - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); @@ -827,7 +766,7 @@ Result> Storage::Accessor::DeleteEdge(EdgeAccessor * CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); // Decrement edge count. - storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); + --storage_->edge_count_; return std::make_optional(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, @@ -887,75 +826,66 @@ utils::BasicResult Storage::Accessor::Commit( // Save these so we can mark them used in the commit log. uint64_t start_timestamp = transaction_.start_timestamp; - { - std::unique_lock engine_guard(storage_->engine_lock_); - commit_timestamp_.emplace(storage_->CommitTimestamp(desired_commit_timestamp)); + commit_timestamp_.emplace(storage_->CommitTimestamp(desired_commit_timestamp)); - // Before committing and validating vertices against unique constraints, - // we have to update unique constraints with the vertices that are going - // to be validated/committed. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - storage_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_); + // Before committing and validating vertices against unique constraints, + // we have to update unique constraints with the vertices that are going + // to be validated/committed. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + storage_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_); + } + + // Validate that unique constraints are satisfied for all modified + // vertices. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; } - // Validate that unique constraints are satisfied for all modified - // vertices. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } + // No need to take any locks here because we modified this vertex and no + // one else can touch it until we commit. + unique_constraint_violation = + storage_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_); + if (unique_constraint_violation) { + break; + } + } - // No need to take any locks here because we modified this vertex and no - // one else can touch it until we commit. - unique_constraint_violation = - storage_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_); - if (unique_constraint_violation) { - break; - } + if (!unique_constraint_violation) { + // Write transaction to WAL while holding the engine lock to make sure + // that committed transactions are sorted by the commit timestamp in the + // WAL files. We supply the new commit timestamp to the function so that + // it knows what will be the final commit timestamp. The WAL must be + // written before actually committing the transaction (before setting + // the commit timestamp) so that no other transaction can see the + // modifications before they are written to disk. + // Replica can log only the write transaction received from Main + // so the Wal files are consistent + if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { + storage_->AppendToWal(transaction_, *commit_timestamp_); } - if (!unique_constraint_violation) { - // Write transaction to WAL while holding the engine lock to make sure - // that committed transactions are sorted by the commit timestamp in the - // WAL files. We supply the new commit timestamp to the function so that - // it knows what will be the final commit timestamp. The WAL must be - // written before actually committing the transaction (before setting - // the commit timestamp) so that no other transaction can see the - // modifications before they are written to disk. - // Replica can log only the write transaction received from Main - // so the Wal files are consistent - if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { - storage_->AppendToWal(transaction_, *commit_timestamp_); - } - - // Take committed_transactions lock while holding the engine lock to - // make sure that committed transactions are sorted by the commit - // timestamp in the list. - storage_->committed_transactions_.WithLock([&](auto & /*committed_transactions*/) { - // TODO: release lock, and update all deltas to have a local copy - // of the commit timestamp - MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); - transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); - // Replica can only update the last commit timestamp with - // the commits received from main. - if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { - // Update the last commit timestamp - storage_->last_commit_timestamp_.store(*commit_timestamp_); - } - // Release engine lock because we don't have to hold it anymore - // and emplace back could take a long time. - engine_guard.unlock(); - }); - - storage_->commit_log_->MarkFinished(start_timestamp); + // TODO(antaljanosbenjamin): Figure out: + // 1. How the committed transactions are sorted in `committed_transactions_` + // 2. Why it was necessary to lock `committed_transactions_` when it was not accessed at all + // TODO: Update all deltas to have a local copy of the commit timestamp + MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); + transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); + // Replica can only update the last commit timestamp with + // the commits received from main. + if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { + // Update the last commit timestamp + storage_->last_commit_timestamp_ = *commit_timestamp_; } + + storage_->commit_log_->MarkFinished(start_timestamp); } if (unique_constraint_violation) { @@ -971,18 +901,11 @@ utils::BasicResult Storage::Accessor::Commit( void Storage::Accessor::Abort() { MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); - // We collect vertices and edges we've created here and then splice them into - // `deleted_vertices_` and `deleted_edges_` lists, instead of adding them one - // by one and acquiring lock every time. - std::list my_deleted_vertices; - std::list my_deleted_edges; - for (const auto &delta : transaction_.deltas) { auto prev = delta.prev.Get(); switch (prev.type) { case PreviousPtr::Type::VERTEX: { auto *vertex = prev.vertex; - std::lock_guard guard(vertex->lock); Delta *current = vertex->delta; while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { @@ -1022,7 +945,7 @@ void Storage::Accessor::Abort() { // the information in `ADD_IN_EDGE` and `Edge/RECREATE_OBJECT` is // redundant. Also, `Edge/RECREATE_OBJECT` isn't available when // edge properties are disabled. - storage_->edge_count_.fetch_add(1, std::memory_order_acq_rel); + ++storage_->edge_count_; break; } case Delta::Action::REMOVE_IN_EDGE: { @@ -1045,12 +968,12 @@ void Storage::Accessor::Abort() { // the information in `REMOVE_IN_EDGE` and `Edge/DELETE_OBJECT` is // redundant. Also, `Edge/DELETE_OBJECT` isn't available when edge // properties are disabled. - storage_->edge_count_.fetch_add(-1, std::memory_order_acq_rel); + --storage_->edge_count_; break; } case Delta::Action::DELETE_OBJECT: { vertex->deleted = true; - InsertVertexPKIntoList(my_deleted_vertices, vertex->keys.Keys()); + InsertVertexPKIntoList(storage_->deleted_vertices_, vertex->keys.Keys()); break; } case Delta::Action::RECREATE_OBJECT: { @@ -1069,7 +992,6 @@ void Storage::Accessor::Abort() { } case PreviousPtr::Type::EDGE: { auto *edge = prev.edge; - std::lock_guard guard(edge->lock); Delta *current = edge->delta; while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { @@ -1080,7 +1002,7 @@ void Storage::Accessor::Abort() { } case Delta::Action::DELETE_OBJECT: { edge->deleted = true; - my_deleted_edges.push_back(edge->gid); + storage_->deleted_edges_.push_back(edge->gid); break; } case Delta::Action::RECREATE_OBJECT: { @@ -1114,20 +1036,11 @@ void Storage::Accessor::Abort() { } { - std::unique_lock engine_guard(storage_->engine_lock_); uint64_t mark_timestamp = storage_->timestamp_; - // Take garbage_undo_buffers lock while holding the engine lock to make - // sure that entries are sorted by mark timestamp in the list. - storage_->garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { - // Release engine lock because we don't have to hold it anymore and - // emplace back could take a long time. - engine_guard.unlock(); - garbage_undo_buffers.emplace_back(mark_timestamp, std::move(transaction_.deltas)); - }); - storage_->deleted_vertices_.WithLock( - [&](auto &deleted_vertices) { deleted_vertices.splice(deleted_vertices.begin(), my_deleted_vertices); }); - storage_->deleted_edges_.WithLock( - [&](auto &deleted_edges) { deleted_edges.splice(deleted_edges.begin(), my_deleted_edges); }); + + // Release engine lock because we don't have to hold it anymore and + // emplace back could take a long time. + storage_->garbage_undo_buffers_.emplace_back(mark_timestamp, std::move(transaction_.deltas)); } storage_->commit_log_->MarkFinished(transaction_.start_timestamp); @@ -1137,8 +1050,7 @@ void Storage::Accessor::Abort() { void Storage::Accessor::FinalizeTransaction() { if (commit_timestamp_) { storage_->commit_log_->MarkFinished(*commit_timestamp_); - storage_->committed_transactions_.WithLock( - [&](auto &committed_transactions) { committed_transactions.emplace_back(std::move(transaction_)); }); + storage_->committed_transactions_.emplace_back(std::move(transaction_)); commit_timestamp_.reset(); } } @@ -1164,7 +1076,6 @@ EdgeTypeId Storage::NameToEdgeType(const std::string_view name) { } bool Storage::CreateIndex(LabelId label, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); // TODO Fix Index return false; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); @@ -1175,7 +1086,6 @@ bool Storage::CreateIndex(LabelId label, const std::optional desired_c } bool Storage::CreateIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); // TODO Fix Index // if (!indices_.label_property_index.CreateIndex(label, property, labelspace.access())) return false; return false; @@ -1187,7 +1097,6 @@ bool Storage::CreateIndex(LabelId label, PropertyId property, const std::optiona } bool Storage::DropIndex(LabelId label, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); if (!indices_.label_index.DropIndex(label)) return false; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp); @@ -1197,7 +1106,6 @@ bool Storage::DropIndex(LabelId label, const std::optional desired_com } bool Storage::DropIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); if (!indices_.label_property_index.DropIndex(label, property)) return false; // For a description why using `timestamp_` is correct, see // `CreateIndex(LabelId label)`. @@ -1209,13 +1117,11 @@ bool Storage::DropIndex(LabelId label, PropertyId property, const std::optional< } IndicesInfo Storage::ListAllIndices() const { - std::shared_lock storage_guard_(main_lock_); return {indices_.label_index.ListIndices(), indices_.label_property_index.ListIndices()}; } utils::BasicResult Storage::CreateExistenceConstraint( LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); // TODO Fix constraints // auto ret = ::memgraph::storage::v3::CreateExistenceConstraint(&constraints_, label, property, vertices_.access()); // if (ret.HasError() || !ret.GetValue()) return ret; @@ -1229,7 +1135,6 @@ utils::BasicResult Storage::CreateExistenceConstraint bool Storage::DropExistenceConstraint(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); if (!::memgraph::storage::v3::DropExistenceConstraint(&constraints_, label, property)) return false; const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, {property}, commit_timestamp); @@ -1240,7 +1145,6 @@ bool Storage::DropExistenceConstraint(LabelId label, PropertyId property, utils::BasicResult Storage::CreateUniqueConstraint( LabelId label, const std::set &properties, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); // TODO Fix constraints // auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access()); // if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { @@ -1256,7 +1160,6 @@ utils::BasicResult Stora UniqueConstraints::DeletionStatus Storage::DropUniqueConstraint( LabelId label, const std::set &properties, const std::optional desired_commit_timestamp) { - std::unique_lock storage_guard(main_lock_); auto ret = constraints_.unique_constraints.DropConstraint(label, properties); if (ret != UniqueConstraints::DeletionStatus::SUCCESS) { return ret; @@ -1271,17 +1174,12 @@ UniqueConstraints::DeletionStatus Storage::DropUniqueConstraint( const SchemaValidator &Storage::Accessor::GetSchemaValidator() const { return storage_->schema_validator_; } ConstraintsInfo Storage::ListAllConstraints() const { - std::shared_lock storage_guard_(main_lock_); return {ListExistenceConstraints(constraints_), constraints_.unique_constraints.ListConstraints()}; } -SchemasInfo Storage::ListAllSchemas() const { - std::shared_lock storage_guard_(main_lock_); - return {schemas_.ListSchemas()}; -} +SchemasInfo Storage::ListAllSchemas() const { return {schemas_.ListSchemas()}; } const Schemas::Schema *Storage::GetSchema(const LabelId primary_label) const { - std::shared_lock storage_guard_(main_lock_); return schemas_.GetSchema(primary_label); } @@ -1293,12 +1191,11 @@ bool Storage::DropSchema(const LabelId primary_label) { return schemas_.DropSche StorageInfo Storage::GetInfo() const { auto vertex_count = vertices_.size(); - auto edge_count = edge_count_.load(std::memory_order_acquire); double average_degree = 0.0; if (vertex_count) { - average_degree = 2.0 * static_cast(edge_count) / static_cast(vertex_count); + average_degree = 2.0 * static_cast(edge_count_) / static_cast(vertex_count); } - return {vertex_count, edge_count, average_degree, utils::GetMemoryUsage(), + return {vertex_count, edge_count_, average_degree, utils::GetMemoryUsage(), utils::GetDirDiskUsage(config_.durability.storage_directory)}; } @@ -1331,48 +1228,34 @@ Transaction Storage::CreateTransaction(IsolationLevel isolation_level) { // `timestamp`) below. uint64_t transaction_id{0}; uint64_t start_timestamp{0}; - { - std::lock_guard guard(engine_lock_); - transaction_id = transaction_id_++; - // Replica should have only read queries and the write queries - // can come from main instance with any past timestamp. - // To preserve snapshot isolation we set the start timestamp - // of any query on replica to the last commited transaction - // which is timestamp_ as only commit of transaction with writes - // can change the value of it. - if (replication_role_ == ReplicationRole::REPLICA) { - start_timestamp = timestamp_; - } else { - start_timestamp = timestamp_++; - } + + transaction_id = transaction_id_++; + // Replica should have only read queries and the write queries + // can come from main instance with any past timestamp. + // To preserve snapshot isolation we set the start timestamp + // of any query on replica to the last commited transaction + // which is timestamp_ as only commit of transaction with writes + // can change the value of it. + if (replication_role_ == ReplicationRole::REPLICA) { + start_timestamp = timestamp_; + } else { + start_timestamp = timestamp_++; } + return {transaction_id, start_timestamp, isolation_level}; } +// `force` means there are no active transactions, so everything can be deleted without worrying about removing some +// data that is used by an active transaction template void Storage::CollectGarbage() { if constexpr (force) { - // We take the unique lock on the main storage lock so we can forcefully clean - // everything we can - if (!main_lock_.try_lock()) { - CollectGarbage(); - return; - } - } else { - // Because the garbage collector iterates through the indices and constraints - // to clean them up, it must take the main lock for reading to make sure that - // the indices and constraints aren't concurrently being modified. - main_lock_.lock_shared(); + // TODO(antaljanosbenjamin): figure out whether is there any active transaction or not (probably accessors should + // increment/decrement a counter). If there are no transactions, then garbage collection can be forced + CollectGarbage(); + return; } - utils::OnScopeExit lock_releaser{[&] { - if constexpr (force) { - main_lock_.unlock(); - } else { - main_lock_.unlock_shared(); - } - }}; - // Garbage collection must be performed in two phases. In the first phase, // deltas that won't be applied by any transaction anymore are unlinked from // the version chains. They cannot be deleted immediately, because there @@ -1380,10 +1263,6 @@ void Storage::CollectGarbage() { // chain traversal. They are instead marked for deletion and will be deleted // in the second GC phase in this GC iteration or some of the following // ones. - std::unique_lock gc_guard(gc_lock_, std::try_to_lock); - if (!gc_guard.owns_lock()) { - return; - } uint64_t oldest_active_start_timestamp = commit_log_->OldestActive(); // We don't move undo buffers of unlinked transactions to garbage_undo_buffers @@ -1394,27 +1273,22 @@ void Storage::CollectGarbage() { // We will only free vertices deleted up until now in this GC cycle, and we // will do it after cleaning-up the indices. That way we are sure that all // vertices that appear in an index also exist in main storage. - std::list current_deleted_edges; - std::list current_deleted_vertices; - deleted_vertices_->swap(current_deleted_vertices); - deleted_edges_->swap(current_deleted_edges); // Flag that will be used to determine whether the Index GC should be run. It // should be run when there were any items that were cleaned up (there were // updates between this run of the GC and the previous run of the GC). This // eliminates high CPU usage when the GC doesn't have to clean up anything. - bool run_index_cleanup = !committed_transactions_->empty() || !garbage_undo_buffers_->empty(); + bool run_index_cleanup = !committed_transactions_.empty() || !garbage_undo_buffers_.empty(); while (true) { // We don't want to hold the lock on commited transactions for too long, // because that prevents other transactions from committing. Transaction *transaction{nullptr}; { - auto committed_transactions_ptr = committed_transactions_.Lock(); - if (committed_transactions_ptr->empty()) { + if (committed_transactions_.empty()) { break; } - transaction = &committed_transactions_ptr->front(); + transaction = &committed_transactions_.front(); } auto commit_timestamp = transaction->commit_timestamp->load(std::memory_order_acquire); @@ -1459,7 +1333,6 @@ void Storage::CollectGarbage() { switch (prev.type) { case PreviousPtr::Type::VERTEX: { Vertex *vertex = prev.vertex; - std::lock_guard vertex_guard(vertex->lock); if (vertex->delta != &delta) { // Something changed, we're not the first delta in the chain // anymore. @@ -1467,13 +1340,12 @@ void Storage::CollectGarbage() { } vertex->delta = nullptr; if (vertex->deleted) { - InsertVertexPKIntoList(current_deleted_vertices, vertex->keys.Keys()); + InsertVertexPKIntoList(deleted_vertices_, vertex->keys.Keys()); } break; } case PreviousPtr::Type::EDGE: { Edge *edge = prev.edge; - std::lock_guard edge_guard(edge->lock); if (edge->delta != &delta) { // Something changed, we're not the first delta in the chain // anymore. @@ -1481,7 +1353,7 @@ void Storage::CollectGarbage() { } edge->delta = nullptr; if (edge->deleted) { - current_deleted_edges.push_back(edge->gid); + deleted_edges_.push_back(edge->gid); } break; } @@ -1492,7 +1364,6 @@ void Storage::CollectGarbage() { // part of the suffix later. break; } - std::unique_lock guard; { // We need to find the parent object in order to be able to use // its lock. @@ -1502,21 +1373,13 @@ void Storage::CollectGarbage() { } switch (parent.type) { case PreviousPtr::Type::VERTEX: - guard = std::unique_lock(parent.vertex->lock); - break; case PreviousPtr::Type::EDGE: - guard = std::unique_lock(parent.edge->lock); break; case PreviousPtr::Type::DELTA: case PreviousPtr::Type::NULLPTR: LOG_FATAL("Invalid database state!"); } } - if (delta.prev.Get() != prev) { - // Something changed, we could now be the first delta in the - // chain. - continue; - } Delta *prev_delta = prev.delta; prev_delta->next.store(nullptr, std::memory_order_release); break; @@ -1529,10 +1392,8 @@ void Storage::CollectGarbage() { } } - committed_transactions_.WithLock([&](auto &committed_transactions) { - unlinked_undo_buffers.emplace_back(0, std::move(transaction->deltas)); - committed_transactions.pop_front(); - }); + unlinked_undo_buffers.emplace_back(0, std::move(transaction->deltas)); + committed_transactions_.pop_front(); } // After unlinking deltas from vertices, we refresh the indices. That way @@ -1547,38 +1408,26 @@ void Storage::CollectGarbage() { } { - std::unique_lock guard(engine_lock_); uint64_t mark_timestamp = timestamp_; - // Take garbage_undo_buffers lock while holding the engine lock to make - // sure that entries are sorted by mark timestamp in the list. - garbage_undo_buffers_.WithLock([&](auto &garbage_undo_buffers) { - // Release engine lock because we don't have to hold it anymore and - // this could take a long time. - guard.unlock(); - // TODO(mtomic): holding garbage_undo_buffers_ lock here prevents - // transactions from aborting until we're done marking, maybe we should - // add them one-by-one or something - for (auto &[timestamp, undo_buffer] : unlinked_undo_buffers) { - timestamp = mark_timestamp; - } - garbage_undo_buffers.splice(garbage_undo_buffers.end(), unlinked_undo_buffers); - }); - for (auto vertex : current_deleted_vertices) { + for (auto &[timestamp, undo_buffer] : unlinked_undo_buffers) { + timestamp = mark_timestamp; + } + garbage_undo_buffers_.splice(garbage_undo_buffers_.end(), unlinked_undo_buffers); + + for (const auto &vertex : deleted_vertices_) { garbage_vertices_.emplace_back(mark_timestamp, vertex); } } - garbage_undo_buffers_.WithLock([&](auto &undo_buffers) { - // if force is set to true we can simply delete all the leftover undos because - // no transaction is active - if constexpr (force) { - undo_buffers.clear(); - } else { - while (!undo_buffers.empty() && undo_buffers.front().first <= oldest_active_start_timestamp) { - undo_buffers.pop_front(); - } + // if force is set to true we can simply delete all the leftover undos because + // no transaction is active + if constexpr (force) { + garbage_undo_buffers_.clear(); + } else { + while (!garbage_undo_buffers_.empty() && garbage_undo_buffers_.front().first <= oldest_active_start_timestamp) { + garbage_undo_buffers_.pop_front(); } - }); + } { auto vertex_acc = vertices_.access(); @@ -1598,7 +1447,7 @@ void Storage::CollectGarbage() { } { auto edge_acc = edges_.access(); - for (auto edge : current_deleted_edges) { + for (auto edge : deleted_edges_) { MG_ASSERT(edge_acc.remove(edge), "Invalid database state!"); } } @@ -1643,7 +1492,7 @@ void Storage::AppendToWal(const Transaction &transaction, uint64_t final_commit_ // A single transaction will always be contained in a single WAL file. auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire); - if (replication_role_.load() == ReplicationRole::MAIN) { + if (replication_role_ == ReplicationRole::MAIN) { replication_clients_.WithLock([&](auto &clients) { for (auto &client : clients) { client->StartTransactionReplication(wal_file_->SequenceNumber()); @@ -1820,7 +1669,7 @@ void Storage::AppendToWal(durability::StorageGlobalOperation operation, LabelId if (!InitializeWalFile()) return; wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp); { - if (replication_role_.load() == ReplicationRole::MAIN) { + if (replication_role_ == ReplicationRole::MAIN) { replication_clients_.WithLock([&](auto &clients) { for (auto &client : clients) { client->StartTransactionReplication(wal_file_->SequenceNumber()); @@ -1835,15 +1684,10 @@ void Storage::AppendToWal(durability::StorageGlobalOperation operation, LabelId } utils::BasicResult Storage::CreateSnapshot() { - if (replication_role_.load() != ReplicationRole::MAIN) { + if (replication_role_ != ReplicationRole::MAIN) { return CreateSnapshotError::DisabledForReplica; } - std::lock_guard snapshot_guard(snapshot_lock_); - - // Take master RW lock (for reading). - std::shared_lock storage_guard(main_lock_); - // Create the transaction used to create the snapshot. auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION); @@ -1903,7 +1747,7 @@ bool Storage::SetReplicaRole(io::network::Endpoint endpoint, const replication:: replication_server_ = std::make_unique(this, std::move(endpoint), config); - replication_role_.store(ReplicationRole::REPLICA); + replication_role_ = ReplicationRole::REPLICA; return true; } @@ -1918,29 +1762,26 @@ bool Storage::SetMainReplicationRole() { // This should be always called first so we finalize everything replication_server_.reset(nullptr); - { - std::unique_lock engine_guard{engine_lock_}; - if (wal_file_) { - wal_file_->FinalizeWal(); - wal_file_.reset(); - } - - // Generate new epoch id and save the last one to the history. - if (epoch_history_.size() == kEpochHistoryRetention) { - epoch_history_.pop_front(); - } - epoch_history_.emplace_back(std::move(epoch_id_), last_commit_timestamp_); - epoch_id_ = utils::GenerateUUID(); + if (wal_file_) { + wal_file_->FinalizeWal(); + wal_file_.reset(); } - replication_role_.store(ReplicationRole::MAIN); + // Generate new epoch id and save the last one to the history. + if (epoch_history_.size() == kEpochHistoryRetention) { + epoch_history_.pop_front(); + } + epoch_history_.emplace_back(std::move(epoch_id_), last_commit_timestamp_); + epoch_id_ = utils::GenerateUUID(); + + replication_role_ = ReplicationRole::MAIN; return true; } utils::BasicResult Storage::RegisterReplica( std::string name, io::network::Endpoint endpoint, const replication::ReplicationMode replication_mode, const replication::ReplicationClientConfig &config) { - MG_ASSERT(replication_role_.load() == ReplicationRole::MAIN, "Only main instance can register a replica!"); + MG_ASSERT(replication_role_ == ReplicationRole::MAIN, "Only main instance can register a replica!"); const bool name_exists = replication_clients_.WithLock([&](auto &clients) { return std::any_of(clients.begin(), clients.end(), [&name](const auto &client) { return client->Name() == name; }); @@ -1986,7 +1827,7 @@ utils::BasicResult Storage::RegisterReplica( } bool Storage::UnregisterReplica(const std::string_view name) { - MG_ASSERT(replication_role_.load() == ReplicationRole::MAIN, "Only main instance can unregister a replica!"); + MG_ASSERT(replication_role_ == ReplicationRole::MAIN, "Only main instance can unregister a replica!"); return replication_clients_.WithLock([&](auto &clients) { return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); }); @@ -2017,9 +1858,6 @@ std::vector Storage::ReplicasInfo() { }); } -void Storage::SetIsolationLevel(IsolationLevel isolation_level) { - std::unique_lock main_guard{main_lock_}; - isolation_level_ = isolation_level; -} +void Storage::SetIsolationLevel(IsolationLevel isolation_level) { isolation_level_ = isolation_level; } } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/storage.hpp b/src/storage/v3/storage.hpp index 1862503d7..ed884c86e 100644 --- a/src/storage/v3/storage.hpp +++ b/src/storage/v3/storage.hpp @@ -355,7 +355,6 @@ class Storage final { Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Gid gid); Storage *storage_; - std::shared_lock storage_guard_; Transaction transaction_; std::optional commit_timestamp_; bool is_transaction_active_; @@ -510,22 +509,14 @@ class Storage final { uint64_t CommitTimestamp(std::optional desired_commit_timestamp = {}); - // Main storage lock. - // - // Accessors take a shared lock when starting, so it is possible to block - // creation of new accessors by taking a unique lock. This is used when doing - // operations on storage that affect the global state, for example index - // creation. - mutable utils::RWLock main_lock_{utils::RWLock::Priority::WRITE}; - // Main object storage VerticesSkipList vertices_; utils::SkipList edges_; - std::atomic edge_id_{0}; + 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 edge_count_{0}; + uint64_t edge_count_{0}; NameIdMapper name_id_mapper_; @@ -535,7 +526,6 @@ class Storage final { Schemas schemas_; // 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 @@ -544,19 +534,17 @@ class Storage final { // whatever. std::optional commit_log_; - utils::Synchronized, utils::SpinLock> committed_transactions_; + std::list 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>>, utils::SpinLock> garbage_undo_buffers_; + std::list>> 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, utils::SpinLock> deleted_vertices_; + std::list deleted_vertices_; // Vertices that are logically deleted and removed from indices and now wait // to be removed from the main storage. @@ -564,7 +552,7 @@ class Storage final { // Edges that are logically deleted and wait to be removed from the main // storage. - utils::Synchronized, utils::SpinLock> deleted_edges_; + std::list deleted_edges_; // Durability std::filesystem::path snapshot_directory_; @@ -572,9 +560,6 @@ class Storage final { std::filesystem::path lock_file_path_; utils::OutputFile lock_file_handle_; - 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. @@ -609,7 +594,7 @@ class Storage final { utils::FileRetainer::FileLocker global_locker_; // Last commited timestamp - std::atomic last_commit_timestamp_{kTimestampInitialId}; + uint64_t last_commit_timestamp_{kTimestampInitialId}; class ReplicationServer; std::unique_ptr replication_server_{nullptr}; @@ -628,7 +613,7 @@ class Storage final { using ReplicationClientList = utils::Synchronized>, utils::SpinLock>; ReplicationClientList replication_clients_; - std::atomic replication_role_{ReplicationRole::MAIN}; + ReplicationRole replication_role_{ReplicationRole::MAIN}; }; } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 44698381c..50cb0333d 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -69,7 +69,6 @@ struct Vertex { std::vector> in_edges; std::vector> out_edges; - mutable utils::SpinLock lock; bool deleted{false}; // uint8_t PAD; // uint16_t PAD; diff --git a/src/storage/v3/vertex_accessor.cpp b/src/storage/v3/vertex_accessor.cpp index 6b5dae827..ff8b34276 100644 --- a/src/storage/v3/vertex_accessor.cpp +++ b/src/storage/v3/vertex_accessor.cpp @@ -33,7 +33,6 @@ std::pair IsVisible(Vertex *vertex, Transaction *transaction, View v bool deleted = false; Delta *delta = nullptr; { - std::lock_guard guard(vertex->lock); deleted = vertex->deleted; delta = vertex->delta; } @@ -80,7 +79,6 @@ bool VertexAccessor::IsVisible(View view) const { Result VertexAccessor::AddLabel(LabelId label) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - std::lock_guard guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; @@ -102,7 +100,6 @@ ResultSchema VertexAccessor::AddLabelAndValidate(LabelId label) { return {*maybe_violation_error}; } utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - std::lock_guard guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) return {Error::SERIALIZATION_ERROR}; @@ -120,8 +117,6 @@ ResultSchema VertexAccessor::AddLabelAndValidate(LabelId label) { } Result VertexAccessor::RemoveLabel(LabelId label) { - std::lock_guard guard(vertex_->lock); - if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; if (vertex_->deleted) return Error::DELETED_OBJECT; @@ -140,7 +135,6 @@ ResultSchema VertexAccessor::RemoveLabelAndValidate(LabelId label) { if (const auto maybe_violation_error = vertex_validator_.ValidateRemoveLabel(label); maybe_violation_error) { return {*maybe_violation_error}; } - std::lock_guard guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) return {Error::SERIALIZATION_ERROR}; @@ -162,7 +156,6 @@ Result VertexAccessor::HasLabel(LabelId label, View view) const { bool has_label = false; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; has_label = VertexHasLabel(*vertex_, label); delta = vertex_->delta; @@ -209,7 +202,6 @@ Result VertexAccessor::PrimaryLabel(const View view) const { bool deleted = false; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; delta = vertex_->delta; } @@ -243,7 +235,6 @@ Result VertexAccessor::PrimaryKey(const View view) const { bool deleted = false; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; delta = vertex_->delta; } @@ -282,7 +273,6 @@ Result> VertexAccessor::Labels(View view) const { std::vector labels; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; labels = vertex_->labels; delta = vertex_->delta; @@ -327,7 +317,6 @@ Result> VertexAccessor::Labels(View view) const { Result VertexAccessor::SetProperty(PropertyId property, const PropertyValue &value) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - std::lock_guard guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; @@ -353,7 +342,6 @@ ResultSchema VertexAccessor::SetPropertyAndValidate(PropertyId pr return {*maybe_violation_error}; } utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; - std::lock_guard guard(vertex_->lock); if (!PrepareForWrite(transaction_, vertex_)) { return {Error::SERIALIZATION_ERROR}; @@ -379,8 +367,6 @@ ResultSchema VertexAccessor::SetPropertyAndValidate(PropertyId pr } Result> VertexAccessor::ClearProperties() { - std::lock_guard guard(vertex_->lock); - if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; if (vertex_->deleted) return Error::DELETED_OBJECT; @@ -402,7 +388,6 @@ Result VertexAccessor::GetProperty(PropertyId property, View view PropertyValue value; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; value = vertex_->properties.GetProperty(property); delta = vertex_->delta; @@ -443,7 +428,6 @@ Result> VertexAccessor::Properties(View view std::map properties; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; properties = vertex_->properties.Properties(); delta = vertex_->delta; @@ -495,7 +479,6 @@ Result> VertexAccessor::InEdges(View view, const std:: std::vector> in_edges; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; if (edge_types.empty() && !destination) { in_edges = vertex_->in_edges; @@ -576,7 +559,6 @@ Result> VertexAccessor::OutEdges(View view, const std: std::vector> out_edges; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; if (edge_types.empty() && !destination) { out_edges = vertex_->out_edges; @@ -655,7 +637,6 @@ Result VertexAccessor::InDegree(View view) const { size_t degree = 0; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; degree = vertex_->in_edges.size(); delta = vertex_->delta; @@ -693,7 +674,6 @@ Result VertexAccessor::OutDegree(View view) const { size_t degree = 0; Delta *delta = nullptr; { - std::lock_guard guard(vertex_->lock); deleted = vertex_->deleted; degree = vertex_->out_edges.size(); delta = vertex_->delta; From 7e84744d0754e9b4658c383f897a091dc3807cc4 Mon Sep 17 00:00:00 2001 From: Jure Bajic Date: Thu, 1 Sep 2022 09:10:40 +0200 Subject: [PATCH 04/10] Split storage and shards (#519) - Rename storage to shard - Add primary label and range for shard - Remove id_mapper functionality from shard - Adapt tests --- src/glue/v2/communication.cpp | 14 +- src/glue/v2/communication.hpp | 16 +- src/query/v2/db_accessor.hpp | 19 +- src/query/v2/interpreter.cpp | 37 +- src/query/v2/interpreter.hpp | 23 +- src/query/v2/stream/streams.cpp | 2 +- src/storage/v3/CMakeLists.txt | 1 + src/storage/v3/edge_accessor.hpp | 2 +- .../v3/replication/replication_client.cpp | 122 +- .../v3/replication/replication_client.hpp | 8 +- .../v3/replication/replication_server.cpp | 186 +- .../v3/replication/replication_server.hpp | 8 +- src/storage/v3/shard.cpp | 1849 +++++++++++++++++ src/storage/v3/shard.hpp | 617 ++++++ src/storage/v3/storage.cpp | 1847 +--------------- src/storage/v3/storage.hpp | 601 +----- src/storage/v3/vertex_accessor.hpp | 4 +- tests/unit/CMakeLists.txt | 6 +- tests/unit/query_v2_interpreter.cpp | 50 +- tests/unit/result_stream_faker.hpp | 4 +- tests/unit/storage_v3.cpp | 180 +- tests/unit/storage_v3_test_utils.cpp | 2 +- tests/unit/storage_v3_test_utils.hpp | 2 +- tests/unit/storage_v3_vertex_accessors.cpp | 44 +- 24 files changed, 2869 insertions(+), 2775 deletions(-) create mode 100644 src/storage/v3/shard.cpp create mode 100644 src/storage/v3/shard.hpp diff --git a/src/glue/v2/communication.cpp b/src/glue/v2/communication.cpp index 55dfd8838..d05b521fe 100644 --- a/src/glue/v2/communication.cpp +++ b/src/glue/v2/communication.cpp @@ -16,7 +16,7 @@ #include #include "storage/v3/edge_accessor.hpp" -#include "storage/v3/storage.hpp" +#include "storage/v3/shard.hpp" #include "storage/v3/vertex_accessor.hpp" #include "utils/temporal.hpp" @@ -64,16 +64,16 @@ query::v2::TypedValue ToTypedValue(const Value &value) { } storage::v3::Result ToBoltVertex(const query::v2::VertexAccessor &vertex, - const storage::v3::Storage &db, storage::v3::View view) { + const storage::v3::Shard &db, storage::v3::View view) { return ToBoltVertex(vertex.impl_, db, view); } storage::v3::Result ToBoltEdge(const query::v2::EdgeAccessor &edge, - const storage::v3::Storage &db, storage::v3::View view) { + const storage::v3::Shard &db, storage::v3::View view) { return ToBoltEdge(edge.impl_, db, view); } -storage::v3::Result ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Storage &db, +storage::v3::Result ToBoltValue(const query::v2::TypedValue &value, const storage::v3::Shard &db, storage::v3::View view) { switch (value.type()) { case query::v2::TypedValue::Type::Null: @@ -132,7 +132,7 @@ storage::v3::Result ToBoltValue(const query::v2::TypedValue &value, const } storage::v3::Result ToBoltVertex(const storage::v3::VertexAccessor &vertex, - const storage::v3::Storage &db, storage::v3::View view) { + const storage::v3::Shard &db, storage::v3::View view) { // TODO(jbajic) Fix bolt communication auto id = communication::bolt::Id::FromUint(0); auto maybe_labels = vertex.Labels(view); @@ -152,7 +152,7 @@ storage::v3::Result ToBoltVertex(const storage::v3: } storage::v3::Result ToBoltEdge(const storage::v3::EdgeAccessor &edge, - const storage::v3::Storage &db, storage::v3::View view) { + const storage::v3::Shard &db, storage::v3::View view) { // TODO(jbajic) Fix bolt communication auto id = communication::bolt::Id::FromUint(0); auto from = communication::bolt::Id::FromUint(0); @@ -167,7 +167,7 @@ storage::v3::Result ToBoltEdge(const storage::v3::Edg return communication::bolt::Edge{id, from, to, type, properties}; } -storage::v3::Result ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db, +storage::v3::Result ToBoltPath(const query::v2::Path &path, const storage::v3::Shard &db, storage::v3::View view) { std::vector vertices; vertices.reserve(path.vertices().size()); diff --git a/src/glue/v2/communication.hpp b/src/glue/v2/communication.hpp index 13bf96fca..794912724 100644 --- a/src/glue/v2/communication.hpp +++ b/src/glue/v2/communication.hpp @@ -28,36 +28,36 @@ namespace memgraph::glue::v2 { /// @param storage::v3::VertexAccessor for converting to /// communication::bolt::Vertex. -/// @param storage::v3::Storage for getting label and property names. +/// @param storage::v3::Shard for getting label and property names. /// @param storage::v3::View for deciding which vertex attributes are visible. /// /// @throw std::bad_alloc storage::v3::Result ToBoltVertex(const storage::v3::VertexAccessor &vertex, - const storage::v3::Storage &db, storage::v3::View view); + const storage::v3::Shard &db, storage::v3::View view); /// @param storage::v3::EdgeAccessor for converting to communication::bolt::Edge. -/// @param storage::v3::Storage for getting edge type and property names. +/// @param storage::v3::Shard for getting edge type and property names. /// @param storage::v3::View for deciding which edge attributes are visible. /// /// @throw std::bad_alloc storage::v3::Result ToBoltEdge(const storage::v3::EdgeAccessor &edge, - const storage::v3::Storage &db, storage::v3::View view); + const storage::v3::Shard &db, storage::v3::View view); /// @param query::v2::Path for converting to communication::bolt::Path. -/// @param storage::v3::Storage for ToBoltVertex and ToBoltEdge. +/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge. /// @param storage::v3::View for ToBoltVertex and ToBoltEdge. /// /// @throw std::bad_alloc -storage::v3::Result ToBoltPath(const query::v2::Path &path, const storage::v3::Storage &db, +storage::v3::Result ToBoltPath(const query::v2::Path &path, const storage::v3::Shard &db, storage::v3::View view); /// @param query::v2::TypedValue for converting to communication::bolt::Value. -/// @param storage::v3::Storage for ToBoltVertex and ToBoltEdge. +/// @param storage::v3::Shard for ToBoltVertex and ToBoltEdge. /// @param storage::v3::View for ToBoltVertex and ToBoltEdge. /// /// @throw std::bad_alloc storage::v3::Result ToBoltValue(const query::v2::TypedValue &value, - const storage::v3::Storage &db, storage::v3::View view); + const storage::v3::Shard &db, storage::v3::View view); query::v2::TypedValue ToTypedValue(const communication::bolt::Value &value); diff --git a/src/query/v2/db_accessor.hpp b/src/query/v2/db_accessor.hpp index beacf2138..a652ca656 100644 --- a/src/query/v2/db_accessor.hpp +++ b/src/query/v2/db_accessor.hpp @@ -212,7 +212,7 @@ inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.F inline bool EdgeAccessor::IsCycle() const { return To() == From(); } class DbAccessor final { - storage::v3::Storage::Accessor *accessor_; + storage::v3::Shard::Accessor *accessor_; class VerticesIterable final { storage::v3::VerticesIterable iterable_; @@ -244,7 +244,7 @@ class DbAccessor final { }; public: - explicit DbAccessor(storage::v3::Storage::Accessor *accessor) : accessor_(accessor) {} + explicit DbAccessor(storage::v3::Shard::Accessor *accessor) : accessor_(accessor) {} // TODO(jbajic) Fix Remove Gid // NOLINTNEXTLINE(readability-convert-member-functions-to-static) @@ -348,11 +348,20 @@ class DbAccessor final { return {std::make_optional(*value)}; } - storage::v3::PropertyId NameToProperty(const std::string_view name) { return accessor_->NameToProperty(name); } + // TODO(jbajic) Query engine should have a map of labels, properties and edge + // types + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + storage::v3::PropertyId NameToProperty(const std::string_view /*name*/) { + return storage::v3::PropertyId::FromUint(0); + } - storage::v3::LabelId NameToLabel(const std::string_view name) { return accessor_->NameToLabel(name); } + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + storage::v3::LabelId NameToLabel(const std::string_view /*name*/) { return storage::v3::LabelId::FromUint(0); } - storage::v3::EdgeTypeId NameToEdgeType(const std::string_view name) { return accessor_->NameToEdgeType(name); } + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) + storage::v3::EdgeTypeId NameToEdgeType(const std::string_view /*name*/) { + return storage::v3::EdgeTypeId::FromUint(0); + } const std::string &PropertyToName(storage::v3::PropertyId prop) const { return accessor_->PropertyToName(prop); } diff --git a/src/query/v2/interpreter.cpp b/src/query/v2/interpreter.cpp index c65b5e971..86825dd5e 100644 --- a/src/query/v2/interpreter.cpp +++ b/src/query/v2/interpreter.cpp @@ -43,6 +43,7 @@ #include "query/v2/trigger.hpp" #include "query/v2/typed_value.hpp" #include "storage/v3/property_value.hpp" +#include "storage/v3/shard.hpp" #include "storage/v3/storage.hpp" #include "utils/algorithm.hpp" #include "utils/csv_parsing.hpp" @@ -127,7 +128,7 @@ std::optional GetOptionalStringValue(query::v2::Expression *express class ReplQueryHandler final : public query::v2::ReplicationQueryHandler { public: - explicit ReplQueryHandler(storage::v3::Storage *db) : db_(db) {} + explicit ReplQueryHandler(storage::v3::Shard *db) : db_(db) {} /// @throw QueryRuntimeException if an error ocurred. void SetReplicationRole(ReplicationQuery::ReplicationRole replication_role, std::optional port) override { @@ -255,7 +256,7 @@ class ReplQueryHandler final : public query::v2::ReplicationQueryHandler { } private: - storage::v3::Storage *db_; + storage::v3::Shard *db_; }; /// returns false if the replication role can't be set /// @throw QueryRuntimeException if an error ocurred. @@ -913,7 +914,7 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp callback.header = {"property_name", "property_type"}; callback.fn = [interpreter_context, primary_label = schema_query->label_]() { auto *db = interpreter_context->db; - const auto label = db->NameToLabel(primary_label.name); + const auto label = interpreter_context->NameToLabelId(primary_label.name); const auto *schema = db->GetSchema(label); std::vector> results; if (schema) { @@ -938,11 +939,11 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp callback.fn = [interpreter_context, primary_label = schema_query->label_, schema_type_map = std::move(schema_type_map)]() { auto *db = interpreter_context->db; - const auto label = db->NameToLabel(primary_label.name); + const auto label = interpreter_context->NameToLabelId(primary_label.name); std::vector schemas_types; schemas_types.reserve(schema_type_map.size()); for (const auto &schema_type : schema_type_map) { - auto property_id = db->NameToProperty(schema_type.first.name); + auto property_id = interpreter_context->NameToPropertyId(schema_type.first.name); schemas_types.push_back({property_id, schema_type.second}); } if (!db->CreateSchema(label, schemas_types)) { @@ -957,7 +958,7 @@ Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interp case SchemaQuery::Action::DROP_SCHEMA: { callback.fn = [interpreter_context, primary_label = schema_query->label_]() { auto *db = interpreter_context->db; - const auto label = db->NameToLabel(primary_label.name); + const auto label = interpreter_context->NameToLabelId(primary_label.name); if (!db->DropSchema(label)) { throw QueryException(fmt::format("Schema on label :{} does not exist!", primary_label.name)); @@ -1138,7 +1139,7 @@ std::optional PullPlan::Pull(AnyStream *strea using RWType = plan::ReadWriteTypeChecker::RWType; } // namespace -InterpreterContext::InterpreterContext(storage::v3::Storage *db, const InterpreterConfig config, +InterpreterContext::InterpreterContext(storage::v3::Shard *db, const InterpreterConfig config, const std::filesystem::path &data_directory) : db(db), trigger_store(data_directory / "triggers"), config(config), streams{this, data_directory / "streams"} {} @@ -1157,8 +1158,8 @@ PreparedQuery Interpreter::PrepareTransactionQuery(std::string_view query_upper) in_explicit_transaction_ = true; expect_rollback_ = false; - db_accessor_ = std::make_unique( - interpreter_context_->db->Access(GetIsolationLevelOverride())); + db_accessor_ = + std::make_unique(interpreter_context_->db->Access(GetIsolationLevelOverride())); execution_db_accessor_.emplace(db_accessor_.get()); if (interpreter_context_->trigger_store.HasTriggers()) { @@ -1427,14 +1428,14 @@ PreparedQuery PrepareIndexQuery(ParsedQuery parsed_query, bool in_explicit_trans } }; - auto label = interpreter_context->db->NameToLabel(index_query->label_.name); + auto label = interpreter_context->NameToLabelId(index_query->label_.name); std::vector properties; std::vector properties_string; properties.reserve(index_query->properties_.size()); properties_string.reserve(index_query->properties_.size()); for (const auto &prop : index_query->properties_) { - properties.push_back(interpreter_context->db->NameToProperty(prop.name)); + properties.push_back(interpreter_context->NameToPropertyId(prop.name)); properties_string.push_back(prop.name); } auto properties_stringified = utils::Join(properties_string, ", "); @@ -1842,7 +1843,7 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli [interpreter_context](AnyStream *stream, std::optional n) -> std::optional { if (auto maybe_error = interpreter_context->db->CreateSnapshot(); maybe_error.HasError()) { switch (maybe_error.GetError()) { - case storage::v3::Storage::CreateSnapshotError::DisabledForReplica: + case storage::v3::Shard::CreateSnapshotError::DisabledForReplica: throw utils::BasicException( "Failed to create a snapshot. Replica instances are not allowed to create them."); } @@ -1897,8 +1898,8 @@ PreparedQuery PrepareVersionQuery(ParsedQuery parsed_query, const bool in_explic } PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transaction, - std::map *summary, InterpreterContext *interpreter_context, - storage::v3::Storage *db, utils::MemoryResource *execution_memory) { + std::map * /*summary*/, InterpreterContext *interpreter_context, + storage::v3::Shard *db, utils::MemoryResource * /*execution_memory*/) { if (in_explicit_transaction) { throw InfoInMulticommandTxException(); } @@ -1994,13 +1995,13 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_ auto *constraint_query = utils::Downcast(parsed_query.query); std::function handler; - auto label = interpreter_context->db->NameToLabel(constraint_query->constraint_.label.name); + auto label = interpreter_context->NameToLabelId(constraint_query->constraint_.label.name); std::vector properties; std::vector properties_string; properties.reserve(constraint_query->constraint_.properties.size()); properties_string.reserve(constraint_query->constraint_.properties.size()); for (const auto &prop : constraint_query->constraint_.properties) { - properties.push_back(interpreter_context->db->NameToProperty(prop.name)); + properties.push_back(interpreter_context->NameToPropertyId(prop.name)); properties_string.push_back(prop.name); } auto properties_stringified = utils::Join(properties_string, ", "); @@ -2259,8 +2260,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, (utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query) || utils::Downcast(parsed_query.query))) { - db_accessor_ = std::make_unique( - interpreter_context_->db->Access(GetIsolationLevelOverride())); + db_accessor_ = + std::make_unique(interpreter_context_->db->Access(GetIsolationLevelOverride())); execution_db_accessor_.emplace(db_accessor_.get()); if (utils::Downcast(parsed_query.query) && interpreter_context_->trigger_store.HasTriggers()) { diff --git a/src/query/v2/interpreter.hpp b/src/query/v2/interpreter.hpp index d3da9c724..75267ae88 100644 --- a/src/query/v2/interpreter.hpp +++ b/src/query/v2/interpreter.hpp @@ -31,6 +31,7 @@ #include "query/v2/trigger.hpp" #include "query/v2/typed_value.hpp" #include "storage/v3/isolation_level.hpp" +#include "storage/v3/name_id_mapper.hpp" #include "utils/event_counter.hpp" #include "utils/logging.hpp" #include "utils/memory.hpp" @@ -166,10 +167,10 @@ struct PreparedQuery { * been passed to an `Interpreter` instance. */ struct InterpreterContext { - explicit InterpreterContext(storage::v3::Storage *db, InterpreterConfig config, + explicit InterpreterContext(storage::v3::Shard *db, InterpreterConfig config, const std::filesystem::path &data_directory); - storage::v3::Storage *db; + storage::v3::Shard *db; std::optional tsc_frequency{utils::GetTSCFrequency()}; std::atomic is_shutting_down{false}; @@ -186,6 +187,22 @@ struct InterpreterContext { const InterpreterConfig config; query::v2::stream::Streams streams; + + storage::v3::LabelId NameToLabelId(std::string_view label_name) { + return storage::v3::LabelId::FromUint(query_id_mapper.NameToId(label_name)); + } + + storage::v3::PropertyId NameToPropertyId(std::string_view property_name) { + return storage::v3::PropertyId::FromUint(query_id_mapper.NameToId(property_name)); + } + + storage::v3::EdgeTypeId NameToEdgeTypeId(std::string_view edge_type_name) { + return storage::v3::EdgeTypeId::FromUint(query_id_mapper.NameToId(edge_type_name)); + } + + private: + // TODO Replace with local map of labels, properties and edge type ids + storage::v3::NameIdMapper query_id_mapper; }; /// Function that is used to tell all active interpreters that they should stop @@ -316,7 +333,7 @@ class Interpreter final { // This cannot be std::optional because we need to move this accessor later on into a lambda capture // which is assigned to std::function. std::function requires every object to be copyable, so we // move this unique_ptr into a shrared_ptr. - std::unique_ptr db_accessor_; + std::unique_ptr db_accessor_; std::optional execution_db_accessor_; std::optional trigger_context_collector_; bool in_explicit_transaction_{false}; diff --git a/src/query/v2/stream/streams.cpp b/src/query/v2/stream/streams.cpp index 563e1401f..026976127 100644 --- a/src/query/v2/stream/streams.cpp +++ b/src/query/v2/stream/streams.cpp @@ -83,7 +83,7 @@ std::pair ExtractTransformation template void CallCustomTransformation(const std::string &transformation_name, const std::vector &messages, - mgp_result &result, storage::v3::Storage::Accessor &storage_accessor, + mgp_result &result, storage::v3::Shard::Accessor &storage_accessor, utils::MemoryResource &memory_resource, const std::string &stream_name) { DbAccessor db_accessor{&storage_accessor}; { diff --git a/src/storage/v3/CMakeLists.txt b/src/storage/v3/CMakeLists.txt index 272d2cd03..bd93ba607 100644 --- a/src/storage/v3/CMakeLists.txt +++ b/src/storage/v3/CMakeLists.txt @@ -14,6 +14,7 @@ set(storage_v3_src_files vertex_accessor.cpp schemas.cpp schema_validator.cpp + shard.cpp storage.cpp) # #### Replication ##### diff --git a/src/storage/v3/edge_accessor.hpp b/src/storage/v3/edge_accessor.hpp index cf8b658d8..8a17b163c 100644 --- a/src/storage/v3/edge_accessor.hpp +++ b/src/storage/v3/edge_accessor.hpp @@ -31,7 +31,7 @@ struct Constraints; class EdgeAccessor final { private: - friend class Storage; + friend class Shard; public: EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction, diff --git a/src/storage/v3/replication/replication_client.cpp b/src/storage/v3/replication/replication_client.cpp index ecc3ffbf4..9a5594c5b 100644 --- a/src/storage/v3/replication/replication_client.cpp +++ b/src/storage/v3/replication/replication_client.cpp @@ -30,10 +30,10 @@ template } // namespace ////// ReplicationClient ////// -Storage::ReplicationClient::ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint, - const replication::ReplicationMode mode, - const replication::ReplicationClientConfig &config) - : name_(std::move(name)), storage_(storage), mode_(mode) { +Shard::ReplicationClient::ReplicationClient(std::string name, Shard *shard, const io::network::Endpoint &endpoint, + const replication::ReplicationMode mode, + const replication::ReplicationClientConfig &config) + : name_(std::move(name)), shard_(shard), mode_(mode) { if (config.ssl) { rpc_context_.emplace(config.ssl->key_file, config.ssl->cert_file); } else { @@ -54,14 +54,14 @@ Storage::ReplicationClient::ReplicationClient(std::string name, Storage *storage } } -void Storage::ReplicationClient::TryInitializeClientAsync() { +void Shard::ReplicationClient::TryInitializeClientAsync() { thread_pool_.AddTask([this] { rpc_client_->Abort(); this->TryInitializeClientSync(); }); } -void Storage::ReplicationClient::FrequentCheck() { +void Shard::ReplicationClient::FrequentCheck() { const auto is_success = std::invoke([this]() { try { auto stream{rpc_client_->Stream()}; @@ -87,15 +87,15 @@ void Storage::ReplicationClient::FrequentCheck() { } /// @throws rpc::RpcFailedException -void Storage::ReplicationClient::InitializeClient() { +void Shard::ReplicationClient::InitializeClient() { uint64_t current_commit_timestamp{kTimestampInitialId}; - auto stream{rpc_client_->Stream(storage_->last_commit_timestamp_, storage_->epoch_id_)}; + auto stream{rpc_client_->Stream(shard_->last_commit_timestamp_, shard_->epoch_id_)}; const auto response = stream.AwaitResponse(); std::optional branching_point; - if (response.epoch_id != storage_->epoch_id_ && response.current_commit_timestamp != kTimestampInitialId) { - const auto &epoch_history = storage_->epoch_history_; + if (response.epoch_id != shard_->epoch_id_ && response.current_commit_timestamp != kTimestampInitialId) { + const auto &epoch_history = shard_->epoch_history_; const auto epoch_info_iter = std::find_if(epoch_history.crbegin(), epoch_history.crend(), [&](const auto &epoch_info) { return epoch_info.first == response.epoch_id; }); @@ -115,8 +115,8 @@ void Storage::ReplicationClient::InitializeClient() { current_commit_timestamp = response.current_commit_timestamp; spdlog::trace("Current timestamp on replica: {}", current_commit_timestamp); - spdlog::trace("Current timestamp on main: {}", storage_->last_commit_timestamp_); - if (current_commit_timestamp == storage_->last_commit_timestamp_) { + spdlog::trace("Current timestamp on main: {}", shard_->last_commit_timestamp_); + if (current_commit_timestamp == shard_->last_commit_timestamp_) { spdlog::debug("Replica '{}' up to date", name_); std::unique_lock client_guard{client_lock_}; replica_state_.store(replication::ReplicaState::READY); @@ -130,7 +130,7 @@ void Storage::ReplicationClient::InitializeClient() { } } -void Storage::ReplicationClient::TryInitializeClientSync() { +void Shard::ReplicationClient::TryInitializeClientSync() { try { InitializeClient(); } catch (const rpc::RpcFailedException &) { @@ -141,19 +141,19 @@ void Storage::ReplicationClient::TryInitializeClientSync() { } } -void Storage::ReplicationClient::HandleRpcFailure() { +void Shard::ReplicationClient::HandleRpcFailure() { spdlog::error(utils::MessageWithLink("Couldn't replicate data to {}.", name_, "https://memgr.ph/replication")); TryInitializeClientAsync(); } -replication::SnapshotRes Storage::ReplicationClient::TransferSnapshot(const std::filesystem::path &path) { +replication::SnapshotRes Shard::ReplicationClient::TransferSnapshot(const std::filesystem::path &path) { auto stream{rpc_client_->Stream()}; replication::Encoder encoder(stream.GetBuilder()); encoder.WriteFile(path); return stream.AwaitResponse(); } -replication::WalFilesRes Storage::ReplicationClient::TransferWalFiles( +replication::WalFilesRes Shard::ReplicationClient::TransferWalFiles( const std::vector &wal_files) { MG_ASSERT(!wal_files.empty(), "Wal files list is empty!"); auto stream{rpc_client_->Stream(wal_files.size())}; @@ -166,7 +166,7 @@ replication::WalFilesRes Storage::ReplicationClient::TransferWalFiles( return stream.AwaitResponse(); } -void Storage::ReplicationClient::StartTransactionReplication(const uint64_t current_wal_seq_num) { +void Shard::ReplicationClient::StartTransactionReplication(const uint64_t current_wal_seq_num) { std::unique_lock guard(client_lock_); const auto status = replica_state_.load(); switch (status) { @@ -190,7 +190,7 @@ void Storage::ReplicationClient::StartTransactionReplication(const uint64_t curr case replication::ReplicaState::READY: MG_ASSERT(!replica_stream_); try { - replica_stream_.emplace(ReplicaStream{this, storage_->last_commit_timestamp_, current_wal_seq_num}); + replica_stream_.emplace(ReplicaStream{this, shard_->last_commit_timestamp_, current_wal_seq_num}); replica_state_.store(replication::ReplicaState::REPLICATING); } catch (const rpc::RpcFailedException &) { replica_state_.store(replication::ReplicaState::INVALID); @@ -200,7 +200,7 @@ void Storage::ReplicationClient::StartTransactionReplication(const uint64_t curr } } -void Storage::ReplicationClient::IfStreamingTransaction(const std::function &callback) { +void Shard::ReplicationClient::IfStreamingTransaction(const std::function &callback) { // We can only check the state because it guarantees to be only // valid during a single transaction replication (if the assumption // that this and other transaction replication functions can only be @@ -220,7 +220,7 @@ void Storage::ReplicationClient::IfStreamingTransaction(const std::functionFinalize(); @@ -293,9 +293,9 @@ void Storage::ReplicationClient::FinalizeTransactionReplicationInternal() { } } -void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { +void Shard::ReplicationClient::RecoverReplica(uint64_t replica_commit) { while (true) { - auto file_locker = storage_->file_retainer_.AddLocker(); + auto file_locker = shard_->file_retainer_.AddLocker(); const auto steps = GetRecoverySteps(replica_commit, &file_locker); for (const auto &recovery_step : steps) { @@ -312,11 +312,11 @@ void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { auto response = TransferWalFiles(arg); replica_commit = response.current_commit_timestamp; } else if constexpr (std::is_same_v) { - if (storage_->wal_file_ && storage_->wal_file_->SequenceNumber() == arg.current_wal_seq_num) { - storage_->wal_file_->DisableFlushing(); + if (shard_->wal_file_ && shard_->wal_file_->SequenceNumber() == arg.current_wal_seq_num) { + shard_->wal_file_->DisableFlushing(); spdlog::debug("Sending current wal file"); replica_commit = ReplicateCurrentWal(); - storage_->wal_file_->EnableFlushing(); + shard_->wal_file_->EnableFlushing(); } } else { static_assert(always_false_v, "Missing type from variant visitor"); @@ -345,20 +345,20 @@ void Storage::ReplicationClient::RecoverReplica(uint64_t replica_commit) { // By adding this lock, we can avoid that, and go to RECOVERY immediately. std::unique_lock client_guard{client_lock_}; SPDLOG_INFO("Replica timestamp: {}", replica_commit); - SPDLOG_INFO("Last commit: {}", storage_->last_commit_timestamp_); - if (storage_->last_commit_timestamp_ == replica_commit) { + SPDLOG_INFO("Last commit: {}", shard_->last_commit_timestamp_); + if (shard_->last_commit_timestamp_ == replica_commit) { replica_state_.store(replication::ReplicaState::READY); return; } } } -uint64_t Storage::ReplicationClient::ReplicateCurrentWal() { - const auto &wal_file = storage_->wal_file_; +uint64_t Shard::ReplicationClient::ReplicateCurrentWal() { + const auto &wal_file = shard_->wal_file_; auto stream = TransferCurrentWalFile(); stream.AppendFilename(wal_file->Path().filename()); utils::InputFile file; - MG_ASSERT(file.Open(storage_->wal_file_->Path()), "Failed to open current WAL file!"); + MG_ASSERT(file.Open(shard_->wal_file_->Path()), "Failed to open current WAL file!"); const auto [buffer, buffer_size] = wal_file->CurrentFileBuffer(); stream.AppendSize(file.GetSize() + buffer_size); stream.AppendFileData(&file); @@ -387,23 +387,23 @@ uint64_t Storage::ReplicationClient::ReplicateCurrentWal() { /// recovery steps, so we can safely send it to the replica. /// We assume that the property of preserving at least 1 WAL before the snapshot /// is satisfied as we extract the timestamp information from it. -std::vector Storage::ReplicationClient::GetRecoverySteps( +std::vector Shard::ReplicationClient::GetRecoverySteps( const uint64_t replica_commit, utils::FileRetainer::FileLocker *file_locker) { // First check if we can recover using the current wal file only // otherwise save the seq_num of the current wal file // This lock is also necessary to force the missed transaction to finish. std::optional current_wal_seq_num; std::optional current_wal_from_timestamp; - if (storage_->wal_file_) { - current_wal_seq_num.emplace(storage_->wal_file_->SequenceNumber()); - current_wal_from_timestamp.emplace(storage_->wal_file_->FromTimestamp()); + if (shard_->wal_file_) { + current_wal_seq_num.emplace(shard_->wal_file_->SequenceNumber()); + current_wal_from_timestamp.emplace(shard_->wal_file_->FromTimestamp()); } auto locker_acc = file_locker->Access(); - auto wal_files = durability::GetWalFiles(storage_->wal_directory_, storage_->uuid_, current_wal_seq_num); + auto wal_files = durability::GetWalFiles(shard_->wal_directory_, shard_->uuid_, current_wal_seq_num); MG_ASSERT(wal_files, "Wal files could not be loaded"); - auto snapshot_files = durability::GetSnapshotFiles(storage_->snapshot_directory_, storage_->uuid_); + auto snapshot_files = durability::GetSnapshotFiles(shard_->snapshot_directory_, shard_->uuid_); std::optional latest_snapshot; if (!snapshot_files.empty()) { std::sort(snapshot_files.begin(), snapshot_files.end()); @@ -529,13 +529,13 @@ std::vector Storage::ReplicationClient } ////// TimeoutDispatcher ////// -void Storage::ReplicationClient::TimeoutDispatcher::WaitForTaskToFinish() { +void Shard::ReplicationClient::TimeoutDispatcher::WaitForTaskToFinish() { // Wait for the previous timeout task to finish std::unique_lock main_guard(main_lock); main_cv.wait(main_guard, [&] { return finished; }); } -void Storage::ReplicationClient::TimeoutDispatcher::StartTimeoutTask(const double timeout) { +void Shard::ReplicationClient::TimeoutDispatcher::StartTimeoutTask(const double timeout) { timeout_pool.AddTask([timeout, this] { finished = false; using std::chrono::steady_clock; @@ -553,65 +553,65 @@ void Storage::ReplicationClient::TimeoutDispatcher::StartTimeoutTask(const doubl }); } ////// ReplicaStream ////// -Storage::ReplicationClient::ReplicaStream::ReplicaStream(ReplicationClient *self, - const uint64_t previous_commit_timestamp, - const uint64_t current_seq_num) +Shard::ReplicationClient::ReplicaStream::ReplicaStream(ReplicationClient *self, + const uint64_t previous_commit_timestamp, + const uint64_t current_seq_num) : self_(self), stream_(self_->rpc_client_->Stream(previous_commit_timestamp, current_seq_num)) { replication::Encoder encoder{stream_.GetBuilder()}; - encoder.WriteString(self_->storage_->epoch_id_); + encoder.WriteString(self_->shard_->epoch_id_); } -void Storage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, - uint64_t final_commit_timestamp) { +void Shard::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Vertex &vertex, + uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, self_->storage_->config_.items, delta, vertex, + EncodeDelta(&encoder, &self_->shard_->name_id_mapper_, self_->shard_->config_.items, delta, vertex, final_commit_timestamp); } -void Storage::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge, - uint64_t final_commit_timestamp) { +void Shard::ReplicationClient::ReplicaStream::AppendDelta(const Delta &delta, const Edge &edge, + uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeDelta(&encoder, &self_->storage_->name_id_mapper_, delta, edge, final_commit_timestamp); + EncodeDelta(&encoder, &self_->shard_->name_id_mapper_, delta, edge, final_commit_timestamp); } -void Storage::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) { +void Shard::ReplicationClient::ReplicaStream::AppendTransactionEnd(uint64_t final_commit_timestamp) { replication::Encoder encoder(stream_.GetBuilder()); EncodeTransactionEnd(&encoder, final_commit_timestamp); } -void Storage::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation, - LabelId label, const std::set &properties, - uint64_t timestamp) { +void Shard::ReplicationClient::ReplicaStream::AppendOperation(durability::StorageGlobalOperation operation, + LabelId label, const std::set &properties, + uint64_t timestamp) { replication::Encoder encoder(stream_.GetBuilder()); - EncodeOperation(&encoder, &self_->storage_->name_id_mapper_, operation, label, properties, timestamp); + EncodeOperation(&encoder, &self_->shard_->name_id_mapper_, operation, label, properties, timestamp); } -replication::AppendDeltasRes Storage::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); } +replication::AppendDeltasRes Shard::ReplicationClient::ReplicaStream::Finalize() { return stream_.AwaitResponse(); } ////// CurrentWalHandler ////// -Storage::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self) +Shard::ReplicationClient::CurrentWalHandler::CurrentWalHandler(ReplicationClient *self) : self_(self), stream_(self_->rpc_client_->Stream()) {} -void Storage::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) { +void Shard::ReplicationClient::CurrentWalHandler::AppendFilename(const std::string &filename) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteString(filename); } -void Storage::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) { +void Shard::ReplicationClient::CurrentWalHandler::AppendSize(const size_t size) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteUint(size); } -void Storage::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) { +void Shard::ReplicationClient::CurrentWalHandler::AppendFileData(utils::InputFile *file) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteFileData(file); } -void Storage::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, const size_t buffer_size) { +void Shard::ReplicationClient::CurrentWalHandler::AppendBufferData(const uint8_t *buffer, const size_t buffer_size) { replication::Encoder encoder(stream_.GetBuilder()); encoder.WriteBuffer(buffer, buffer_size); } -replication::CurrentWalRes Storage::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); } +replication::CurrentWalRes Shard::ReplicationClient::CurrentWalHandler::Finalize() { return stream_.AwaitResponse(); } } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/replication/replication_client.hpp b/src/storage/v3/replication/replication_client.hpp index f0c47f2e0..7a1731f5f 100644 --- a/src/storage/v3/replication/replication_client.hpp +++ b/src/storage/v3/replication/replication_client.hpp @@ -28,7 +28,7 @@ #include "storage/v3/replication/enums.hpp" #include "storage/v3/replication/rpc.hpp" #include "storage/v3/replication/serialization.hpp" -#include "storage/v3/storage.hpp" +#include "storage/v3/shard.hpp" #include "utils/file.hpp" #include "utils/file_locker.hpp" #include "utils/spin_lock.hpp" @@ -37,9 +37,9 @@ namespace memgraph::storage::v3 { -class Storage::ReplicationClient { +class Shard::ReplicationClient { public: - ReplicationClient(std::string name, Storage *storage, const io::network::Endpoint &endpoint, + ReplicationClient(std::string name, Shard *shard, const io::network::Endpoint &endpoint, replication::ReplicationMode mode, const replication::ReplicationClientConfig &config = {}); // Handler used for transfering the current transaction. @@ -149,7 +149,7 @@ class Storage::ReplicationClient { void HandleRpcFailure(); std::string name_; - Storage *storage_; + Shard *shard_; std::optional rpc_context_; std::optional rpc_client_; diff --git a/src/storage/v3/replication/replication_server.cpp b/src/storage/v3/replication/replication_server.cpp index 552214b9a..1905071ce 100644 --- a/src/storage/v3/replication/replication_server.cpp +++ b/src/storage/v3/replication/replication_server.cpp @@ -40,9 +40,9 @@ std::pair ReadDelta(durability::BaseDecoder }; } // namespace -Storage::ReplicationServer::ReplicationServer(Storage *storage, io::network::Endpoint endpoint, - const replication::ReplicationServerConfig &config) - : storage_(storage) { +Shard::ReplicationServer::ReplicationServer(Shard *shard, io::network::Endpoint endpoint, + const replication::ReplicationServerConfig &config) + : shard_(shard) { // Create RPC server. if (config.ssl) { rpc_server_context_.emplace(config.ssl->key_file, config.ssl->cert_file, config.ssl->ca_file, @@ -84,21 +84,21 @@ Storage::ReplicationServer::ReplicationServer(Storage *storage, io::network::End rpc_server_->Start(); } -void Storage::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void Shard::ReplicationServer::HeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::HeartbeatReq req; slk::Load(&req, req_reader); - replication::HeartbeatRes res{true, storage_->last_commit_timestamp_, storage_->epoch_id_}; + replication::HeartbeatRes res{true, shard_->last_commit_timestamp_, shard_->epoch_id_}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void Shard::ReplicationServer::FrequentHeartbeatHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::FrequentHeartbeatReq req; slk::Load(&req, req_reader); replication::FrequentHeartbeatRes res{true}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void Shard::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::AppendDeltasReq req; slk::Load(&req, req_reader); @@ -107,25 +107,25 @@ void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, sl auto maybe_epoch_id = decoder.ReadString(); MG_ASSERT(maybe_epoch_id, "Invalid replication message"); - if (*maybe_epoch_id != storage_->epoch_id_) { - storage_->epoch_history_.emplace_back(std::move(storage_->epoch_id_), storage_->last_commit_timestamp_); - storage_->epoch_id_ = std::move(*maybe_epoch_id); + if (*maybe_epoch_id != shard_->epoch_id_) { + shard_->epoch_history_.emplace_back(std::move(shard_->epoch_id_), shard_->last_commit_timestamp_); + shard_->epoch_id_ = std::move(*maybe_epoch_id); } - if (storage_->wal_file_) { - if (req.seq_num > storage_->wal_file_->SequenceNumber() || *maybe_epoch_id != storage_->epoch_id_) { - storage_->wal_file_->FinalizeWal(); - storage_->wal_file_.reset(); - storage_->wal_seq_num_ = req.seq_num; + if (shard_->wal_file_) { + if (req.seq_num > shard_->wal_file_->SequenceNumber() || *maybe_epoch_id != shard_->epoch_id_) { + shard_->wal_file_->FinalizeWal(); + shard_->wal_file_.reset(); + shard_->wal_seq_num_ = req.seq_num; } else { - MG_ASSERT(storage_->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file"); - storage_->wal_seq_num_ = req.seq_num + 1; + MG_ASSERT(shard_->wal_file_->SequenceNumber() == req.seq_num, "Invalid sequence number of current wal file"); + shard_->wal_seq_num_ = req.seq_num + 1; } } else { - storage_->wal_seq_num_ = req.seq_num; + shard_->wal_seq_num_ = req.seq_num; } - if (req.previous_commit_timestamp != storage_->last_commit_timestamp_) { + if (req.previous_commit_timestamp != shard_->last_commit_timestamp_) { // Empty the stream bool transaction_complete = false; while (!transaction_complete) { @@ -134,82 +134,82 @@ void Storage::ReplicationServer::AppendDeltasHandler(slk::Reader *req_reader, sl transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type); } - replication::AppendDeltasRes res{false, storage_->last_commit_timestamp_}; + replication::AppendDeltasRes res{false, shard_->last_commit_timestamp_}; slk::Save(res, res_builder); return; } ReadAndApplyDelta(&decoder); - replication::AppendDeltasRes res{true, storage_->last_commit_timestamp_}; + replication::AppendDeltasRes res{true, shard_->last_commit_timestamp_}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void Shard::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::SnapshotReq req; slk::Load(&req, req_reader); replication::Decoder decoder(req_reader); - utils::EnsureDirOrDie(storage_->snapshot_directory_); + utils::EnsureDirOrDie(shard_->snapshot_directory_); - const auto maybe_snapshot_path = decoder.ReadFile(storage_->snapshot_directory_); + const auto maybe_snapshot_path = decoder.ReadFile(shard_->snapshot_directory_); MG_ASSERT(maybe_snapshot_path, "Failed to load snapshot!"); spdlog::info("Received snapshot saved to {}", *maybe_snapshot_path); // Clear the database - storage_->vertices_.clear(); - storage_->edges_.clear(); + shard_->vertices_.clear(); + shard_->edges_.clear(); - storage_->constraints_ = Constraints(); - storage_->indices_.label_index = - LabelIndex(&storage_->indices_, &storage_->constraints_, storage_->config_.items, storage_->schema_validator_); - storage_->indices_.label_property_index = LabelPropertyIndex(&storage_->indices_, &storage_->constraints_, - storage_->config_.items, storage_->schema_validator_); + shard_->constraints_ = Constraints(); + shard_->indices_.label_index = + LabelIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->schema_validator_); + shard_->indices_.label_property_index = + LabelPropertyIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->schema_validator_); try { spdlog::debug("Loading snapshot"); auto recovered_snapshot = durability::RecoveredSnapshot{}; - // durability::LoadSnapshot(*maybe_snapshot_path, &storage_->vertices_, &storage_->edges_, - // &storage_->epoch_history_, - // &storage_->name_id_mapper_, &storage_->edge_count_, storage_->config_.items); + // durability::LoadSnapshot(*maybe_snapshot_path, &shard_->vertices_, &shard_->edges_, + // &shard_->epoch_history_, + // &shard_->name_id_mapper_, &shard_->edge_count_, shard_->config_.items); spdlog::debug("Snapshot loaded successfully"); // If this step is present it should always be the first step of // the recovery so we use the UUID we read from snasphost - storage_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid); - storage_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id); + shard_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid); + shard_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id); const auto &recovery_info = recovered_snapshot.recovery_info; - storage_->edge_id_ = recovery_info.next_edge_id; - storage_->timestamp_ = std::max(storage_->timestamp_, recovery_info.next_timestamp); + shard_->edge_id_ = recovery_info.next_edge_id; + shard_->timestamp_ = std::max(shard_->timestamp_, recovery_info.next_timestamp); - // durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &storage_->indices_, - // &storage_->constraints_, &storage_->vertices_); + // durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &shard_->indices_, + // &shard_->constraints_, &shard_->vertices_); } catch (const durability::RecoveryFailure &e) { LOG_FATAL("Couldn't load the snapshot because of: {}", e.what()); } - replication::SnapshotRes res{true, storage_->last_commit_timestamp_}; + replication::SnapshotRes res{true, shard_->last_commit_timestamp_}; slk::Save(res, res_builder); // Delete other durability files - auto snapshot_files = durability::GetSnapshotFiles(storage_->snapshot_directory_, storage_->uuid_); + auto snapshot_files = durability::GetSnapshotFiles(shard_->snapshot_directory_, shard_->uuid_); for (const auto &[path, uuid, _] : snapshot_files) { if (path != *maybe_snapshot_path) { - storage_->file_retainer_.DeleteFile(path); + shard_->file_retainer_.DeleteFile(path); } } - auto wal_files = durability::GetWalFiles(storage_->wal_directory_, storage_->uuid_); + auto wal_files = durability::GetWalFiles(shard_->wal_directory_, shard_->uuid_); if (wal_files) { for (const auto &wal_file : *wal_files) { - storage_->file_retainer_.DeleteFile(wal_file.path); + shard_->file_retainer_.DeleteFile(wal_file.path); } - storage_->wal_file_.reset(); + shard_->wal_file_.reset(); } } -void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void Shard::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::WalFilesReq req; slk::Load(&req, req_reader); @@ -218,31 +218,31 @@ void Storage::ReplicationServer::WalFilesHandler(slk::Reader *req_reader, slk::B replication::Decoder decoder(req_reader); - utils::EnsureDirOrDie(storage_->wal_directory_); + utils::EnsureDirOrDie(shard_->wal_directory_); for (auto i = 0; i < wal_file_number; ++i) { LoadWal(&decoder); } - replication::WalFilesRes res{true, storage_->last_commit_timestamp_}; + replication::WalFilesRes res{true, shard_->last_commit_timestamp_}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) { +void Shard::ReplicationServer::CurrentWalHandler(slk::Reader *req_reader, slk::Builder *res_builder) { replication::CurrentWalReq req; slk::Load(&req, req_reader); replication::Decoder decoder(req_reader); - utils::EnsureDirOrDie(storage_->wal_directory_); + utils::EnsureDirOrDie(shard_->wal_directory_); LoadWal(&decoder); - replication::CurrentWalRes res{true, storage_->last_commit_timestamp_}; + replication::CurrentWalRes res{true, shard_->last_commit_timestamp_}; slk::Save(res, res_builder); } -void Storage::ReplicationServer::LoadWal(replication::Decoder *decoder) { +void Shard::ReplicationServer::LoadWal(replication::Decoder *decoder) { const auto temp_wal_directory = std::filesystem::temp_directory_path() / "memgraph" / durability::kWalDirectory; utils::EnsureDir(temp_wal_directory); auto maybe_wal_path = decoder->ReadFile(temp_wal_directory); @@ -251,22 +251,22 @@ void Storage::ReplicationServer::LoadWal(replication::Decoder *decoder) { try { auto wal_info = durability::ReadWalInfo(*maybe_wal_path); if (wal_info.seq_num == 0) { - storage_->uuid_ = wal_info.uuid; + shard_->uuid_ = wal_info.uuid; } - if (wal_info.epoch_id != storage_->epoch_id_) { - storage_->epoch_history_.emplace_back(wal_info.epoch_id, storage_->last_commit_timestamp_); - storage_->epoch_id_ = std::move(wal_info.epoch_id); + if (wal_info.epoch_id != shard_->epoch_id_) { + shard_->epoch_history_.emplace_back(wal_info.epoch_id, shard_->last_commit_timestamp_); + shard_->epoch_id_ = std::move(wal_info.epoch_id); } - if (storage_->wal_file_) { - if (storage_->wal_file_->SequenceNumber() != wal_info.seq_num) { - storage_->wal_file_->FinalizeWal(); - storage_->wal_seq_num_ = wal_info.seq_num; - storage_->wal_file_.reset(); + if (shard_->wal_file_) { + if (shard_->wal_file_->SequenceNumber() != wal_info.seq_num) { + shard_->wal_file_->FinalizeWal(); + shard_->wal_seq_num_ = wal_info.seq_num; + shard_->wal_file_.reset(); } } else { - storage_->wal_seq_num_ = wal_info.seq_num; + shard_->wal_seq_num_ = wal_info.seq_num; } durability::Decoder wal; @@ -285,20 +285,20 @@ void Storage::ReplicationServer::LoadWal(replication::Decoder *decoder) { } } -Storage::ReplicationServer::~ReplicationServer() { +Shard::ReplicationServer::~ReplicationServer() { if (rpc_server_) { rpc_server_->Shutdown(); rpc_server_->AwaitShutdown(); } } -uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) { - auto edge_acc = storage_->edges_.access(); - // auto vertex_acc = storage_->vertices_.access(); +uint64_t Shard::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder *decoder) { + auto edge_acc = shard_->edges_.access(); + // auto vertex_acc = shard_->vertices_.access(); - std::optional> commit_timestamp_and_accessor; + std::optional> commit_timestamp_and_accessor; // auto get_transaction = [this, &commit_timestamp_and_accessor](uint64_t commit_timestamp) { // if (!commit_timestamp_and_accessor) { - // commit_timestamp_and_accessor.emplace(commit_timestamp, storage_->Access()); + // commit_timestamp_and_accessor.emplace(commit_timestamp, shard_->Access()); // } else if (commit_timestamp_and_accessor->first != commit_timestamp) { // throw utils::BasicException("Received more than one transaction!"); // } @@ -306,7 +306,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // }; uint64_t applied_deltas = 0; - auto max_commit_timestamp = storage_->last_commit_timestamp_; + auto max_commit_timestamp = shard_->last_commit_timestamp_; for (bool transaction_complete = false; !transaction_complete; ++applied_deltas) { const auto [timestamp, delta] = ReadDelta(decoder); @@ -316,7 +316,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * transaction_complete = durability::IsWalDeltaDataTypeTransactionEnd(delta.type); - if (timestamp < storage_->timestamp_) { + if (timestamp < shard_->timestamp_) { continue; } @@ -406,12 +406,12 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // spdlog::trace(" Edge {} set property {} to {}", delta.vertex_edge_set_property.gid.AsUint(), // delta.vertex_edge_set_property.property, delta.vertex_edge_set_property.value); - // if (!storage_->config_.items.properties_on_edges) + // if (!shard_->config_.items.properties_on_edges) // throw utils::BasicException( // "Can't set properties on edges because properties on edges " // "are disabled!"); - // auto *transaction = get_transaction(timestamp); + // // auto *transaction = get_transaction(timestamp); // // The following block of code effectively implements `FindEdge` and // // yields an accessor that is only valid for managing the edge's @@ -456,10 +456,10 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // nullptr, // nullptr, // &transaction->transaction_, - // &storage_->indices_, - // &storage_->constraints_, - // storage_->config_.items, - // storage_->schema_validator_}; + // &shard_->indices_, + // &shard_->constraints_, + // shard_->config_.items, + // shard_->schema_validator_}; // auto ret = ea.SetProperty(transaction->NameToProperty(delta.vertex_edge_set_property.property), // delta.vertex_edge_set_property.value); @@ -481,14 +481,14 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // spdlog::trace(" Create label index on :{}", delta.operation_label.label); // // Need to send the timestamp // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - // if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label.label), timestamp)) + // if (!shard_->CreateIndex(shard_->NameToLabel(delta.operation_label.label), timestamp)) // throw utils::BasicException("Invalid transaction!"); // break; // } // case durability::WalDeltaData::Type::LABEL_INDEX_DROP: { // spdlog::trace(" Drop label index on :{}", delta.operation_label.label); // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - // if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label.label), timestamp)) + // if (!shard_->DropIndex(shard_->NameToLabel(delta.operation_label.label), timestamp)) // throw utils::BasicException("Invalid transaction!"); // break; // } @@ -496,8 +496,8 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // spdlog::trace(" Create label+property index on :{} ({})", delta.operation_label_property.label, // delta.operation_label_property.property); // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - // if (!storage_->CreateIndex(storage_->NameToLabel(delta.operation_label_property.label), - // storage_->NameToProperty(delta.operation_label_property.property), timestamp)) + // if (!shard_->CreateIndex(shard_->NameToLabel(delta.operation_label_property.label), + // shard_->NameToProperty(delta.operation_label_property.property), timestamp)) // throw utils::BasicException("Invalid transaction!"); // break; // } @@ -505,8 +505,8 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // spdlog::trace(" Drop label+property index on :{} ({})", delta.operation_label_property.label, // delta.operation_label_property.property); // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - // if (!storage_->DropIndex(storage_->NameToLabel(delta.operation_label_property.label), - // storage_->NameToProperty(delta.operation_label_property.property), timestamp)) + // if (!shard_->DropIndex(shard_->NameToLabel(delta.operation_label_property.label), + // shard_->NameToProperty(delta.operation_label_property.property), timestamp)) // throw utils::BasicException("Invalid transaction!"); // break; // } @@ -514,9 +514,9 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // spdlog::trace(" Create existence constraint on :{} ({})", delta.operation_label_property.label, // delta.operation_label_property.property); // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - // auto ret = storage_->CreateExistenceConstraint( - // storage_->NameToLabel(delta.operation_label_property.label), - // storage_->NameToProperty(delta.operation_label_property.property), timestamp); + // auto ret = shard_->CreateExistenceConstraint( + // shard_->NameToLabel(delta.operation_label_property.label), + // shard_->NameToProperty(delta.operation_label_property.property), timestamp); // if (!ret.HasValue() || !ret.GetValue()) throw utils::BasicException("Invalid transaction!"); // break; // } @@ -524,8 +524,8 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // spdlog::trace(" Drop existence constraint on :{} ({})", delta.operation_label_property.label, // delta.operation_label_property.property); // if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); - // if (!storage_->DropExistenceConstraint(storage_->NameToLabel(delta.operation_label_property.label), - // storage_->NameToProperty(delta.operation_label_property.property), + // if (!shard_->DropExistenceConstraint(shard_->NameToLabel(delta.operation_label_property.label), + // shard_->NameToProperty(delta.operation_label_property.property), // timestamp)) // throw utils::BasicException("Invalid transaction!"); // break; @@ -537,9 +537,9 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // ss.str()); if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); // std::set properties; // for (const auto &prop : delta.operation_label_properties.properties) { - // properties.emplace(storage_->NameToProperty(prop)); + // properties.emplace(shard_->NameToProperty(prop)); // } - // auto ret = storage_->CreateUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label), + // auto ret = shard_->CreateUniqueConstraint(shard_->NameToLabel(delta.operation_label_properties.label), // properties, timestamp); // if (!ret.HasValue() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) // throw utils::BasicException("Invalid transaction!"); @@ -552,9 +552,9 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * // ss.str()); if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid transaction!"); // std::set properties; // for (const auto &prop : delta.operation_label_properties.properties) { - // properties.emplace(storage_->NameToProperty(prop)); + // properties.emplace(shard_->NameToProperty(prop)); // } - // auto ret = storage_->DropUniqueConstraint(storage_->NameToLabel(delta.operation_label_properties.label), + // auto ret = shard_->DropUniqueConstraint(shard_->NameToLabel(delta.operation_label_properties.label), // properties, timestamp); // if (ret != UniqueConstraints::DeletionStatus::SUCCESS) throw utils::BasicException("Invalid transaction!"); // break; @@ -564,7 +564,7 @@ uint64_t Storage::ReplicationServer::ReadAndApplyDelta(durability::BaseDecoder * if (commit_timestamp_and_accessor) throw utils::BasicException("Invalid data!"); - storage_->last_commit_timestamp_ = max_commit_timestamp; + shard_->last_commit_timestamp_ = max_commit_timestamp; return applied_deltas; } diff --git a/src/storage/v3/replication/replication_server.hpp b/src/storage/v3/replication/replication_server.hpp index 6d6cc256f..6b278a103 100644 --- a/src/storage/v3/replication/replication_server.hpp +++ b/src/storage/v3/replication/replication_server.hpp @@ -11,13 +11,13 @@ #pragma once -#include "storage/v3/storage.hpp" +#include "storage/v3/shard.hpp" namespace memgraph::storage::v3 { -class Storage::ReplicationServer { +class Shard::ReplicationServer { public: - explicit ReplicationServer(Storage *storage, io::network::Endpoint endpoint, + explicit ReplicationServer(Shard *shard, io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config); ReplicationServer(const ReplicationServer &) = delete; ReplicationServer(ReplicationServer &&) = delete; @@ -41,7 +41,7 @@ class Storage::ReplicationServer { std::optional rpc_server_context_; std::optional rpc_server_; - Storage *storage_; + Shard *shard_; }; } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/shard.cpp b/src/storage/v3/shard.cpp new file mode 100644 index 000000000..48040a80c --- /dev/null +++ b/src/storage/v3/shard.cpp @@ -0,0 +1,1849 @@ +// Copyright 2022 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/v3/shard.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "io/network/endpoint.hpp" +#include "storage/v3/constraints.hpp" +#include "storage/v3/durability/durability.hpp" +#include "storage/v3/durability/metadata.hpp" +#include "storage/v3/durability/paths.hpp" +#include "storage/v3/durability/snapshot.hpp" +#include "storage/v3/durability/wal.hpp" +#include "storage/v3/edge_accessor.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/indices.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/replication/config.hpp" +#include "storage/v3/replication/replication_client.hpp" +#include "storage/v3/replication/replication_server.hpp" +#include "storage/v3/replication/rpc.hpp" +#include "storage/v3/schema_validator.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertices_skip_list.hpp" +#include "utils/exceptions.hpp" +#include "utils/file.hpp" +#include "utils/logging.hpp" +#include "utils/memory_tracker.hpp" +#include "utils/message.hpp" +#include "utils/result.hpp" +#include "utils/rw_lock.hpp" +#include "utils/skip_list.hpp" +#include "utils/spin_lock.hpp" +#include "utils/stat.hpp" +#include "utils/uuid.hpp" + +namespace memgraph::storage::v3 { + +using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; + +namespace { +inline constexpr uint16_t kEpochHistoryRetention = 1000; + +void InsertVertexPKIntoList(auto &container, const PrimaryKey &primary_key) { container.push_back(primary_key); } +} // namespace + +auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end, + std::optional *vertex, Transaction *tx, View view, Indices *indices, + Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator) { + while (it != end) { + *vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, schema_validator, view); + if (!*vertex) { + ++it; + continue; + } + break; + } + return it; +} + +AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it) + : self_(self), + it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, + self->indices_, self_->constraints_, self->config_, *self_->schema_validator_)) {} + +VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; } + +AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() { + ++it_; + it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), &self_->vertex_, self_->transaction_, self_->view_, + self_->indices_, self_->constraints_, self_->config_, *self_->schema_validator_); + return *this; +} + +VerticesIterable::VerticesIterable(AllVerticesIterable vertices) : type_(Type::ALL) { + new (&all_vertices_) AllVerticesIterable(std::move(vertices)); +} + +VerticesIterable::VerticesIterable(LabelIndex::Iterable vertices) : type_(Type::BY_LABEL) { + new (&vertices_by_label_) LabelIndex::Iterable(std::move(vertices)); +} + +VerticesIterable::VerticesIterable(LabelPropertyIndex::Iterable vertices) : type_(Type::BY_LABEL_PROPERTY) { + new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(vertices)); +} + +VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::ALL: + new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); + break; + case Type::BY_LABEL: + new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_)); + break; + case Type::BY_LABEL_PROPERTY: + new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(other.vertices_by_label_property_)); + break; + } +} + +VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept { + switch (type_) { + case Type::ALL: + all_vertices_.AllVerticesIterable::~AllVerticesIterable(); + break; + case Type::BY_LABEL: + vertices_by_label_.LabelIndex::Iterable::~Iterable(); + break; + case Type::BY_LABEL_PROPERTY: + vertices_by_label_property_.LabelPropertyIndex::Iterable::~Iterable(); + break; + } + type_ = other.type_; + switch (other.type_) { + case Type::ALL: + new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); + break; + case Type::BY_LABEL: + new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_)); + break; + case Type::BY_LABEL_PROPERTY: + new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(other.vertices_by_label_property_)); + break; + } + return *this; +} + +VerticesIterable::~VerticesIterable() { + switch (type_) { + case Type::ALL: + all_vertices_.AllVerticesIterable::~AllVerticesIterable(); + break; + case Type::BY_LABEL: + vertices_by_label_.LabelIndex::Iterable::~Iterable(); + break; + case Type::BY_LABEL_PROPERTY: + vertices_by_label_property_.LabelPropertyIndex::Iterable::~Iterable(); + break; + } +} + +VerticesIterable::Iterator VerticesIterable::begin() { + switch (type_) { + case Type::ALL: + return Iterator(all_vertices_.begin()); + case Type::BY_LABEL: + return Iterator(vertices_by_label_.begin()); + case Type::BY_LABEL_PROPERTY: + return Iterator(vertices_by_label_property_.begin()); + } +} + +VerticesIterable::Iterator VerticesIterable::end() { + switch (type_) { + case Type::ALL: + return Iterator(all_vertices_.end()); + case Type::BY_LABEL: + return Iterator(vertices_by_label_.end()); + case Type::BY_LABEL_PROPERTY: + return Iterator(vertices_by_label_property_.end()); + } +} + +VerticesIterable::Iterator::Iterator(AllVerticesIterable::Iterator it) : type_(Type::ALL) { + new (&all_it_) AllVerticesIterable::Iterator(it); +} + +VerticesIterable::Iterator::Iterator(LabelIndex::Iterable::Iterator it) : type_(Type::BY_LABEL) { + new (&by_label_it_) LabelIndex::Iterable::Iterator(it); +} + +VerticesIterable::Iterator::Iterator(LabelPropertyIndex::Iterable::Iterator it) : type_(Type::BY_LABEL_PROPERTY) { + new (&by_label_property_it_) 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_); + break; + case Type::BY_LABEL: + new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); + break; + case Type::BY_LABEL_PROPERTY: + new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); + break; + } +} + +VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const VerticesIterable::Iterator &other) { + if (this == &other) { + return *this; + } + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::ALL: + new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); + break; + case Type::BY_LABEL: + new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); + break; + case Type::BY_LABEL_PROPERTY: + new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); + break; + } + return *this; +} + +VerticesIterable::Iterator::Iterator(VerticesIterable::Iterator &&other) noexcept : type_(other.type_) { + switch (other.type_) { + case Type::ALL: + new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); + break; + case Type::BY_LABEL: + new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); + break; + case Type::BY_LABEL_PROPERTY: + new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); + break; + } +} + +VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(VerticesIterable::Iterator &&other) noexcept { + Destroy(); + type_ = other.type_; + switch (other.type_) { + case Type::ALL: + new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); + break; + case Type::BY_LABEL: + new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); + break; + case Type::BY_LABEL_PROPERTY: + new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); + break; + } + return *this; +} + +VerticesIterable::Iterator::~Iterator() { Destroy(); } + +void VerticesIterable::Iterator::Destroy() noexcept { + switch (type_) { + case Type::ALL: + all_it_.AllVerticesIterable::Iterator::~Iterator(); + break; + case Type::BY_LABEL: + by_label_it_.LabelIndex::Iterable::Iterator::~Iterator(); + break; + case Type::BY_LABEL_PROPERTY: + by_label_property_it_.LabelPropertyIndex::Iterable::Iterator::~Iterator(); + break; + } +} + +VertexAccessor VerticesIterable::Iterator::operator*() const { + switch (type_) { + case Type::ALL: + return *all_it_; + case Type::BY_LABEL: + return *by_label_it_; + case Type::BY_LABEL_PROPERTY: + return *by_label_property_it_; + } +} + +VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() { + switch (type_) { + case Type::ALL: + ++all_it_; + break; + case Type::BY_LABEL: + ++by_label_it_; + break; + case Type::BY_LABEL_PROPERTY: + ++by_label_property_it_; + break; + } + return *this; +} + +bool VerticesIterable::Iterator::operator==(const Iterator &other) const { + switch (type_) { + case Type::ALL: + return all_it_ == other.all_it_; + case Type::BY_LABEL: + return by_label_it_ == other.by_label_it_; + case Type::BY_LABEL_PROPERTY: + return by_label_property_it_ == other.by_label_property_it_; + } +} + +Shard::Shard(const LabelId primary_label, const PrimaryKey min_primary_key, + const std::optional max_primary_key, Config config) + : primary_label_{primary_label}, + min_primary_key_{min_primary_key}, + max_primary_key_{max_primary_key}, + schema_validator_{schemas_}, + indices_{&constraints_, config.items, schema_validator_}, + isolation_level_{config.transaction.isolation_level}, + config_{config}, + snapshot_directory_{config_.durability.storage_directory / durability::kSnapshotDirectory}, + wal_directory_{config_.durability.storage_directory / durability::kWalDirectory}, + lock_file_path_{config_.durability.storage_directory / durability::kLockFile}, + uuid_{utils::GenerateUUID()}, + epoch_id_{utils::GenerateUUID()}, + global_locker_{file_retainer_.AddLocker()} { + if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED && + replication_role_ == ReplicationRole::MAIN) { + spdlog::warn( + "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider " + "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because " + "without write-ahead logs this instance is not replicating any data."); + } + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || + config_.durability.snapshot_on_exit || config_.durability.recover_on_startup) { + // Create the directory initially to crash the database in case of + // permission errors. This is done early to crash the database on startup + // instead of crashing the database for the first time during runtime (which + // could be an unpleasant surprise). + utils::EnsureDirOrDie(snapshot_directory_); + // Same reasoning as above. + utils::EnsureDirOrDie(wal_directory_); + + // Verify that the user that started the process is the same user that is + // the owner of the storage directory. + durability::VerifyStorageDirectoryOwnerAndProcessUserOrDie(config_.durability.storage_directory); + + // Create the lock file and open a handle to it. This will crash the + // database if it can't open the file for writing or if any other process is + // holding the file opened. + lock_file_handle_.Open(lock_file_path_, utils::OutputFile::Mode::OVERWRITE_EXISTING); + MG_ASSERT(lock_file_handle_.AcquireLock(), + "Couldn't acquire lock on the storage directory {}!\n" + "Another Memgraph process is currently running with the same " + "storage directory, please stop it first before starting this " + "process!", + config_.durability.storage_directory); + } + if (config_.durability.recover_on_startup) { + auto info = std::optional{}; + + // durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, &vertices_, + // &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, + // &wal_seq_num_); + if (info) { + edge_id_ = info->next_edge_id; + timestamp_ = std::max(timestamp_, info->next_timestamp); + if (info->last_commit_timestamp) { + last_commit_timestamp_ = *info->last_commit_timestamp; + } + } + } else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || + config_.durability.snapshot_on_exit) { + bool files_moved = false; + auto backup_root = config_.durability.storage_directory / durability::kBackupDirectory; + for (const auto &[path, dirname, what] : + {std::make_tuple(snapshot_directory_, durability::kSnapshotDirectory, "snapshot"), + std::make_tuple(wal_directory_, durability::kWalDirectory, "WAL")}) { + if (!utils::DirExists(path)) continue; + auto backup_curr = backup_root / dirname; + std::error_code error_code; + for (const auto &item : std::filesystem::directory_iterator(path, error_code)) { + utils::EnsureDirOrDie(backup_root); + utils::EnsureDirOrDie(backup_curr); + std::error_code item_error_code; + std::filesystem::rename(item.path(), backup_curr / item.path().filename(), item_error_code); + MG_ASSERT(!item_error_code, "Couldn't move {} file {} because of: {}", what, item.path(), + item_error_code.message()); + files_moved = true; + } + MG_ASSERT(!error_code, "Couldn't backup {} files because of: {}", what, error_code.message()); + } + if (files_moved) { + spdlog::warn( + "Since Memgraph was not supposed to recover on startup and " + "durability is enabled, your current durability files will likely " + "be overridden. To prevent important data loss, Memgraph has stored " + "those files into a .backup directory inside the storage directory."); + } + } + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { + // TODO(antaljanosbenjamin): handle snapshots + // snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] { + // if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { + // switch (maybe_error.GetError()) { + // case CreateSnapshotError::DisabledForReplica: + // spdlog::warn( + // utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); + // break; + // } + // } + // }); + } + + if (timestamp_ == kTimestampInitialId) { + commit_log_.emplace(); + } else { + commit_log_.emplace(timestamp_); + } +} + +Shard::~Shard() { + { + // Clear replication data + replication_server_.reset(); + replication_clients_.WithLock([&](auto &clients) { clients.clear(); }); + } + if (wal_file_) { + wal_file_->FinalizeWal(); + wal_file_ = std::nullopt; + } + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { + // TODO(antaljanosbenjamin): stop snapshot creation + } + if (config_.durability.snapshot_on_exit) { + if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { + switch (maybe_error.GetError()) { + case CreateSnapshotError::DisabledForReplica: + spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); + break; + } + } + } +} + +Shard::Accessor::Accessor(Shard *shard, IsolationLevel isolation_level) + : shard_(shard), + transaction_(shard->CreateTransaction(isolation_level)), + is_transaction_active_(true), + config_(shard->config_.items) {} + +Shard::Accessor::Accessor(Accessor &&other) noexcept + : shard_(other.shard_), + transaction_(std::move(other.transaction_)), + commit_timestamp_(other.commit_timestamp_), + is_transaction_active_(other.is_transaction_active_), + config_(other.config_) { + // Don't allow the other accessor to abort our transaction in destructor. + other.is_transaction_active_ = false; + other.commit_timestamp_.reset(); +} + +Shard::Accessor::~Accessor() { + if (is_transaction_active_) { + Abort(); + } + + FinalizeTransaction(); +} + +ResultSchema Shard::Accessor::CreateVertexAndValidate( + LabelId primary_label, const std::vector &labels, + const std::vector> &properties) { + if (primary_label != shard_->primary_label_) { + throw utils::BasicException("Cannot add vertex to shard which does not hold the given primary label!"); + } + auto maybe_schema_violation = GetSchemaValidator().ValidateVertexCreate(primary_label, labels, properties); + if (maybe_schema_violation) { + return {std::move(*maybe_schema_violation)}; + } + OOMExceptionEnabler oom_exception; + // Extract key properties + std::vector primary_properties; + for ([[maybe_unused]] const auto &[property_id, property_type] : shard_->GetSchema(primary_label)->second) { + // We know there definitely is key in properties since we have validated + primary_properties.push_back( + std::ranges::find_if(properties, [property_id = property_id](const auto &property_pair) { + return property_pair.first == property_id; + })->second); + } + + // Get secondary properties + std::vector> secondary_properties; + for (const auto &[property_id, property_value] : properties) { + if (!shard_->schemas_.IsPropertyKey(primary_label, property_id)) { + secondary_properties.emplace_back(property_id, property_value); + } + } + + auto acc = shard_->vertices_.access(); + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert({Vertex{delta, primary_label, primary_properties, labels, secondary_properties}}); + MG_ASSERT(inserted, "The vertex must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); + delta->prev.Set(&it->vertex); + return VertexAccessor{&it->vertex, &transaction_, &shard_->indices_, + &shard_->constraints_, config_, shard_->schema_validator_}; +} + +std::optional Shard::Accessor::FindVertex(std::vector primary_key, View view) { + auto acc = shard_->vertices_.access(); + // Later on use label space + auto it = acc.find(primary_key); + if (it == acc.end()) { + return std::nullopt; + } + return VertexAccessor::Create(&it->vertex, &transaction_, &shard_->indices_, &shard_->constraints_, config_, + shard_->schema_validator_, view); +} + +Result> Shard::Accessor::DeleteVertex(VertexAccessor *vertex) { + MG_ASSERT(vertex->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + auto *vertex_ptr = vertex->vertex_; + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + if (vertex_ptr->deleted) { + return std::optional{}; + } + + if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) return Error::VERTEX_HAS_EDGES; + + CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + + return std::make_optional(vertex_ptr, &transaction_, &shard_->indices_, &shard_->constraints_, + config_, shard_->schema_validator_, true); +} + +Result>>> Shard::Accessor::DetachDeleteVertex( + VertexAccessor *vertex) { + using ReturnType = std::pair>; + + MG_ASSERT(vertex->transaction_ == &transaction_, + "VertexAccessor must be from the same transaction as the storage " + "accessor when deleting a vertex!"); + auto *vertex_ptr = vertex->vertex_; + + std::vector> in_edges; + std::vector> out_edges; + + { + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + if (vertex_ptr->deleted) return std::optional{}; + + in_edges = vertex_ptr->in_edges; + out_edges = vertex_ptr->out_edges; + } + + std::vector deleted_edges; + for (const auto &item : in_edges) { + auto [edge_type, from_vertex, edge] = item; + EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &shard_->indices_, &shard_->constraints_, + config_, shard_->schema_validator_); + auto ret = DeleteEdge(&e); + if (ret.HasError()) { + MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); + return ret.GetError(); + } + + if (ret.GetValue()) { + deleted_edges.push_back(*ret.GetValue()); + } + } + for (const auto &item : out_edges) { + auto [edge_type, to_vertex, edge] = item; + EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, + config_, shard_->schema_validator_); + auto ret = DeleteEdge(&e); + if (ret.HasError()) { + MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); + return ret.GetError(); + } + + if (ret.GetValue()) { + deleted_edges.push_back(*ret.GetValue()); + } + } + + // We need to check again for serialization errors because we unlocked the + // vertex. Some other transaction could have modified the vertex in the + // meantime if we didn't have any edges to delete. + + if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; + + MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); + + CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); + vertex_ptr->deleted = true; + + return std::make_optional(VertexAccessor{vertex_ptr, &transaction_, &shard_->indices_, + &shard_->constraints_, config_, shard_->schema_validator_, true}, + std::move(deleted_edges)); +} + +Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) { + OOMExceptionEnabler oom_exception; + MG_ASSERT(from->transaction_ == to->transaction_, + "VertexAccessors must be from the same transaction when creating " + "an edge!"); + MG_ASSERT(from->transaction_ == &transaction_, + "VertexAccessors must be from the same transaction in when " + "creating an edge!"); + + auto *from_vertex = from->vertex_; + auto *to_vertex = to->vertex_; + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (to_vertex->deleted) return Error::DELETED_OBJECT; + } + + auto gid = Gid::FromUint(shard_->edge_id_++); + EdgeRef edge(gid); + if (config_.properties_on_edges) { + auto acc = shard_->edges_.access(); + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Edge(gid, delta)); + MG_ASSERT(inserted, "The edge must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); + edge = EdgeRef(&*it); + delta->prev.Set(&*it); + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + // Increment edge count. + ++shard_->edge_count_; + + return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, + config_, shard_->schema_validator_); +} + +Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, + Gid gid) { + OOMExceptionEnabler oom_exception; + MG_ASSERT(from->transaction_ == to->transaction_, + "VertexAccessors must be from the same transaction when creating " + "an edge!"); + MG_ASSERT(from->transaction_ == &transaction_, + "VertexAccessors must be from the same transaction in when " + "creating an edge!"); + + auto *from_vertex = from->vertex_; + auto *to_vertex = to->vertex_; + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (to_vertex->deleted) return Error::DELETED_OBJECT; + } + + // NOTE: When we update the next `edge_id_` here we perform a RMW + // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue + // because this function is only called from the replication delta applier + // that runs single-threadedly and while this instance is set-up to apply + // threads (it is the replica), it is guaranteed that no other writes are + // possible. + shard_->edge_id_ = std::max(shard_->edge_id_, gid.AsUint() + 1); + + EdgeRef edge(gid); + if (config_.properties_on_edges) { + auto acc = shard_->edges_.access(); + auto *delta = CreateDeleteObjectDelta(&transaction_); + auto [it, inserted] = acc.insert(Edge(gid, delta)); + MG_ASSERT(inserted, "The edge must be inserted here!"); + MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); + edge = EdgeRef(&*it); + delta->prev.Set(&*it); + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); + + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); + + // Increment edge count. + ++shard_->edge_count_; + + return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, + config_, shard_->schema_validator_); +} + +Result> Shard::Accessor::DeleteEdge(EdgeAccessor *edge) { + MG_ASSERT(edge->transaction_ == &transaction_, + "EdgeAccessor must be from the same transaction as the storage " + "accessor when deleting an edge!"); + auto edge_ref = edge->edge_; + auto edge_type = edge->edge_type_; + + if (config_.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + + if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; + + if (edge_ptr->deleted) return std::optional{}; + } + + auto *from_vertex = edge->from_vertex_; + auto *to_vertex = edge->to_vertex_; + + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + + if (to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); + } + + auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { + std::tuple link(edge_type, vertex, edge_ref); + auto it = std::find(edges->begin(), edges->end(), link); + if (config_.properties_on_edges) { + MG_ASSERT(it != edges->end(), "Invalid database state!"); + } else if (it == edges->end()) { + return false; + } + std::swap(*it, *edges->rbegin()); + edges->pop_back(); + return true; + }; + + auto op1 = delete_edge_from_storage(to_vertex, &from_vertex->out_edges); + auto op2 = delete_edge_from_storage(from_vertex, &to_vertex->in_edges); + + if (config_.properties_on_edges) { + MG_ASSERT((op1 && op2), "Invalid database state!"); + } else { + MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!"); + if (!op1 && !op2) { + // The edge is already deleted. + return std::optional{}; + } + } + + if (config_.properties_on_edges) { + auto *edge_ptr = edge_ref.ptr; + CreateAndLinkDelta(&transaction_, edge_ptr, Delta::RecreateObjectTag()); + edge_ptr->deleted = true; + } + + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + + // Decrement edge count. + --shard_->edge_count_; + + return std::make_optional(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, + &shard_->constraints_, config_, shard_->schema_validator_, true); +} + +const std::string &Shard::Accessor::LabelToName(LabelId label) const { return shard_->LabelToName(label); } + +const std::string &Shard::Accessor::PropertyToName(PropertyId property) const { + return shard_->PropertyToName(property); +} + +const std::string &Shard::Accessor::EdgeTypeToName(EdgeTypeId edge_type) const { + return shard_->EdgeTypeToName(edge_type); +} + +void Shard::Accessor::AdvanceCommand() { ++transaction_.command_id; } + +utils::BasicResult Shard::Accessor::Commit( + const std::optional desired_commit_timestamp) { + MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); + MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); + + if (transaction_.deltas.empty()) { + // We don't have to update the commit timestamp here because no one reads + // it. + shard_->commit_log_->MarkFinished(transaction_.start_timestamp); + } else { + // Validate that existence constraints are satisfied for all modified + // vertices. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + // No need to take any locks here because we modified this vertex and no + // one else can touch it until we commit. + auto validation_result = ValidateExistenceConstraints(*prev.vertex, shard_->constraints_); + if (validation_result) { + Abort(); + return {*validation_result}; + } + } + + // Result of validating the vertex against unique constraints. It has to be + // declared outside of the critical section scope because its value is + // tested for Abort call which has to be done out of the scope. + std::optional unique_constraint_violation; + + // Save these so we can mark them used in the commit log. + uint64_t start_timestamp = transaction_.start_timestamp; + + commit_timestamp_.emplace(shard_->CommitTimestamp(desired_commit_timestamp)); + + // Before committing and validating vertices against unique constraints, + // we have to update unique constraints with the vertices that are going + // to be validated/committed. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + shard_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_); + } + + // Validate that unique constraints are satisfied for all modified + // vertices. + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) { + continue; + } + + // No need to take any locks here because we modified this vertex and no + // one else can touch it until we commit. + unique_constraint_violation = + shard_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_); + if (unique_constraint_violation) { + break; + } + } + + if (!unique_constraint_violation) { + // Write transaction to WAL while holding the engine lock to make sure + // that committed transactions are sorted by the commit timestamp in the + // WAL files. We supply the new commit timestamp to the function so that + // it knows what will be the final commit timestamp. The WAL must be + // written before actually committing the transaction (before setting + // the commit timestamp) so that no other transaction can see the + // modifications before they are written to disk. + // Replica can log only the write transaction received from Main + // so the Wal files are consistent + if (shard_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { + shard_->AppendToWal(transaction_, *commit_timestamp_); + } + + // TODO(antaljanosbenjamin): Figure out: + // 1. How the committed transactions are sorted in `committed_transactions_` + // 2. Why it was necessary to lock `committed_transactions_` when it was not accessed at all + // TODO: Update all deltas to have a local copy of the commit timestamp + MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); + transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); + // Replica can only update the last commit timestamp with + // the commits received from main. + if (shard_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { + // Update the last commit timestamp + shard_->last_commit_timestamp_ = *commit_timestamp_; + } + + shard_->commit_log_->MarkFinished(start_timestamp); + } + + if (unique_constraint_violation) { + Abort(); + return {*unique_constraint_violation}; + } + } + is_transaction_active_ = false; + + return {}; +} + +void Shard::Accessor::Abort() { + MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); + + for (const auto &delta : transaction_.deltas) { + auto prev = delta.prev.Get(); + switch (prev.type) { + case PreviousPtr::Type::VERTEX: { + auto *vertex = prev.vertex; + Delta *current = vertex->delta; + while (current != nullptr && + current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { + switch (current->action) { + case Delta::Action::REMOVE_LABEL: { + auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); + MG_ASSERT(it != vertex->labels.end(), "Invalid database state!"); + std::swap(*it, *vertex->labels.rbegin()); + vertex->labels.pop_back(); + break; + } + case Delta::Action::ADD_LABEL: { + auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); + MG_ASSERT(it == vertex->labels.end(), "Invalid database state!"); + vertex->labels.push_back(current->label); + break; + } + case Delta::Action::SET_PROPERTY: { + vertex->properties.SetProperty(current->property.key, current->property.value); + break; + } + case Delta::Action::ADD_IN_EDGE: { + std::tuple link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + MG_ASSERT(it == vertex->in_edges.end(), "Invalid database state!"); + vertex->in_edges.push_back(link); + break; + } + case Delta::Action::ADD_OUT_EDGE: { + std::tuple link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); + MG_ASSERT(it == vertex->out_edges.end(), "Invalid database state!"); + vertex->out_edges.push_back(link); + // Increment edge count. We only increment the count here because + // the information in `ADD_IN_EDGE` and `Edge/RECREATE_OBJECT` is + // redundant. Also, `Edge/RECREATE_OBJECT` isn't available when + // edge properties are disabled. + ++shard_->edge_count_; + break; + } + case Delta::Action::REMOVE_IN_EDGE: { + std::tuple link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); + MG_ASSERT(it != vertex->in_edges.end(), "Invalid database state!"); + std::swap(*it, *vertex->in_edges.rbegin()); + vertex->in_edges.pop_back(); + break; + } + case Delta::Action::REMOVE_OUT_EDGE: { + std::tuple link{current->vertex_edge.edge_type, + current->vertex_edge.vertex, current->vertex_edge.edge}; + auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); + MG_ASSERT(it != vertex->out_edges.end(), "Invalid database state!"); + std::swap(*it, *vertex->out_edges.rbegin()); + vertex->out_edges.pop_back(); + // Decrement edge count. We only decrement the count here because + // the information in `REMOVE_IN_EDGE` and `Edge/DELETE_OBJECT` is + // redundant. Also, `Edge/DELETE_OBJECT` isn't available when edge + // properties are disabled. + --shard_->edge_count_; + break; + } + case Delta::Action::DELETE_OBJECT: { + vertex->deleted = true; + InsertVertexPKIntoList(shard_->deleted_vertices_, vertex->keys.Keys()); + break; + } + case Delta::Action::RECREATE_OBJECT: { + vertex->deleted = false; + break; + } + } + current = current->next.load(std::memory_order_acquire); + } + vertex->delta = current; + if (current != nullptr) { + current->prev.Set(vertex); + } + + break; + } + case PreviousPtr::Type::EDGE: { + auto *edge = prev.edge; + Delta *current = edge->delta; + while (current != nullptr && + current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { + switch (current->action) { + case Delta::Action::SET_PROPERTY: { + edge->properties.SetProperty(current->property.key, current->property.value); + break; + } + case Delta::Action::DELETE_OBJECT: { + edge->deleted = true; + shard_->deleted_edges_.push_back(edge->gid); + break; + } + case Delta::Action::RECREATE_OBJECT: { + edge->deleted = false; + break; + } + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_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: { + LOG_FATAL("Invalid database state!"); + break; + } + } + current = current->next.load(std::memory_order_acquire); + } + edge->delta = current; + if (current != nullptr) { + current->prev.Set(edge); + } + + break; + } + case PreviousPtr::Type::DELTA: + // pointer probably couldn't be set because allocation failed + case PreviousPtr::Type::NULLPTR: + break; + } + } + + { + uint64_t mark_timestamp = shard_->timestamp_; + + // Release engine lock because we don't have to hold it anymore and + // emplace back could take a long time. + shard_->garbage_undo_buffers_.emplace_back(mark_timestamp, std::move(transaction_.deltas)); + } + + shard_->commit_log_->MarkFinished(transaction_.start_timestamp); + is_transaction_active_ = false; +} + +void Shard::Accessor::FinalizeTransaction() { + if (commit_timestamp_) { + shard_->commit_log_->MarkFinished(*commit_timestamp_); + shard_->committed_transactions_.emplace_back(std::move(transaction_)); + commit_timestamp_.reset(); + } +} + +const std::string &Shard::LabelToName(LabelId label) const { return name_id_mapper_.IdToName(label.AsUint()); } + +const std::string &Shard::PropertyToName(PropertyId property) const { + return name_id_mapper_.IdToName(property.AsUint()); +} + +const std::string &Shard::EdgeTypeToName(EdgeTypeId edge_type) const { + return name_id_mapper_.IdToName(edge_type.AsUint()); +} + +bool Shard::CreateIndex(LabelId label, const std::optional desired_commit_timestamp) { + // TODO Fix Index + return false; + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return true; +} + +bool Shard::CreateIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { + // TODO Fix Index + // if (!indices_.label_property_index.CreateIndex(label, property, labelspace.access())) return false; + return false; + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return true; +} + +bool Shard::DropIndex(LabelId label, const std::optional desired_commit_timestamp) { + if (!indices_.label_index.DropIndex(label)) return false; + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return true; +} + +bool Shard::DropIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { + if (!indices_.label_property_index.DropIndex(label, property)) return false; + // For a description why using `timestamp_` is correct, see + // `CreateIndex(LabelId label)`. + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label, {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return true; +} + +IndicesInfo Shard::ListAllIndices() const { + return {indices_.label_index.ListIndices(), indices_.label_property_index.ListIndices()}; +} + +utils::BasicResult Shard::CreateExistenceConstraint( + LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { + // TODO Fix constraints + // auto ret = ::memgraph::storage::v3::CreateExistenceConstraint(&constraints_, label, property, vertices_.access()); + // if (ret.HasError() || !ret.GetValue()) return ret; + return false; + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return true; +} + +bool Shard::DropExistenceConstraint(LabelId label, PropertyId property, + const std::optional desired_commit_timestamp) { + if (!memgraph::storage::v3::DropExistenceConstraint(&constraints_, label, property)) return false; + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, {property}, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return true; +} + +utils::BasicResult Shard::CreateUniqueConstraint( + LabelId label, const std::set &properties, const std::optional desired_commit_timestamp) { + // TODO Fix constraints + // auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access()); + // if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { + // return ret; + // } + return UniqueConstraints::CreationStatus::ALREADY_EXISTS; + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, properties, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return UniqueConstraints::CreationStatus::SUCCESS; +} + +UniqueConstraints::DeletionStatus Shard::DropUniqueConstraint(LabelId label, const std::set &properties, + const std::optional desired_commit_timestamp) { + auto ret = constraints_.unique_constraints.DropConstraint(label, properties); + if (ret != UniqueConstraints::DeletionStatus::SUCCESS) { + return ret; + } + const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); + AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, properties, commit_timestamp); + commit_log_->MarkFinished(commit_timestamp); + last_commit_timestamp_ = commit_timestamp; + return UniqueConstraints::DeletionStatus::SUCCESS; +} + +const SchemaValidator &Shard::Accessor::GetSchemaValidator() const { return shard_->schema_validator_; } + +ConstraintsInfo Shard::ListAllConstraints() const { + return {ListExistenceConstraints(constraints_), constraints_.unique_constraints.ListConstraints()}; +} + +SchemasInfo Shard::ListAllSchemas() const { return {schemas_.ListSchemas()}; } + +const Schemas::Schema *Shard::GetSchema(const LabelId primary_label) const { return schemas_.GetSchema(primary_label); } + +bool Shard::CreateSchema(const LabelId primary_label, const std::vector &schemas_types) { + return schemas_.CreateSchema(primary_label, schemas_types); +} + +bool Shard::DropSchema(const LabelId primary_label) { return schemas_.DropSchema(primary_label); } + +StorageInfo Shard::GetInfo() const { + auto vertex_count = vertices_.size(); + double average_degree = 0.0; + if (vertex_count) { + average_degree = 2.0 * static_cast(edge_count_) / static_cast(vertex_count); + } + return {vertex_count, edge_count_, average_degree, utils::GetMemoryUsage(), + utils::GetDirDiskUsage(config_.durability.storage_directory)}; +} + +VerticesIterable Shard::Accessor::Vertices(LabelId label, View view) { + return VerticesIterable(shard_->indices_.label_index.Vertices(label, view, &transaction_)); +} + +VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, View view) { + return VerticesIterable(shard_->indices_.label_property_index.Vertices( + label, property, std::nullopt, std::nullopt, view, &transaction_, shard_->schema_validator_)); +} + +VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) { + return VerticesIterable(shard_->indices_.label_property_index.Vertices( + label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_, + shard_->schema_validator_)); +} + +VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, + const std::optional> &lower_bound, + const std::optional> &upper_bound, View view) { + return VerticesIterable(shard_->indices_.label_property_index.Vertices( + label, property, lower_bound, upper_bound, view, &transaction_, shard_->schema_validator_)); +} + +Transaction Shard::CreateTransaction(IsolationLevel isolation_level) { + // We acquire the transaction engine lock here because we access (and + // modify) the transaction engine variables (`transaction_id` and + // `timestamp`) below. + uint64_t transaction_id{0}; + uint64_t start_timestamp{0}; + + transaction_id = transaction_id_++; + // Replica should have only read queries and the write queries + // can come from main instance with any past timestamp. + // To preserve snapshot isolation we set the start timestamp + // of any query on replica to the last commited transaction + // which is timestamp_ as only commit of transaction with writes + // can change the value of it. + if (replication_role_ == ReplicationRole::REPLICA) { + start_timestamp = timestamp_; + } else { + start_timestamp = timestamp_++; + } + + return {transaction_id, start_timestamp, isolation_level}; +} + +// `force` means there are no active transactions, so everything can be deleted without worrying about removing some +// data that is used by an active transaction +template +void Shard::CollectGarbage() { + if constexpr (force) { + // TODO(antaljanosbenjamin): figure out whether is there any active transaction or not (probably accessors should + // increment/decrement a counter). If there are no transactions, then garbage collection can be forced + CollectGarbage(); + return; + } + + // Garbage collection must be performed in two phases. In the first phase, + // deltas that won't be applied by any transaction anymore are unlinked from + // the version chains. They cannot be deleted immediately, because there + // might be a transaction that still needs them to terminate the version + // chain traversal. They are instead marked for deletion and will be deleted + // in the second GC phase in this GC iteration or some of the following + // ones. + + uint64_t oldest_active_start_timestamp = commit_log_->OldestActive(); + // We don't move undo buffers of unlinked transactions to garbage_undo_buffers + // list immediately, because we would have to repeatedly take + // garbage_undo_buffers lock. + std::list>> unlinked_undo_buffers; + + // We will only free vertices deleted up until now in this GC cycle, and we + // will do it after cleaning-up the indices. That way we are sure that all + // vertices that appear in an index also exist in main storage. + + // Flag that will be used to determine whether the Index GC should be run. It + // should be run when there were any items that were cleaned up (there were + // updates between this run of the GC and the previous run of the GC). This + // eliminates high CPU usage when the GC doesn't have to clean up anything. + bool run_index_cleanup = !committed_transactions_.empty() || !garbage_undo_buffers_.empty(); + + while (true) { + // We don't want to hold the lock on commited transactions for too long, + // because that prevents other transactions from committing. + Transaction *transaction{nullptr}; + { + if (committed_transactions_.empty()) { + break; + } + transaction = &committed_transactions_.front(); + } + + auto commit_timestamp = transaction->commit_timestamp->load(std::memory_order_acquire); + if (commit_timestamp >= oldest_active_start_timestamp) { + break; + } + + // When unlinking a delta which is the first delta in its version chain, + // special care has to be taken to avoid the following race condition: + // + // [Vertex] --> [Delta A] + // + // GC thread: Delta A is the first in its chain, it must be unlinked from + // vertex and marked for deletion + // TX thread: Update vertex and add Delta B with Delta A as next + // + // [Vertex] --> [Delta B] <--> [Delta A] + // + // GC thread: Unlink delta from Vertex + // + // [Vertex] --> (nullptr) + // + // When processing a delta that is the first one in its chain, we + // obtain the corresponding vertex or edge lock, and then verify that this + // delta still is the first in its chain. + // When processing a delta that is in the middle of the chain we only + // process the final delta of the given transaction in that chain. We + // determine the owner of the chain (either a vertex or an edge), obtain the + // corresponding lock, and then verify that this delta is still in the same + // position as it was before taking the lock. + // + // Even though the delta chain is lock-free (both `next` and `prev`) the + // chain should not be modified without taking the lock from the object that + // owns the chain (either a vertex or an edge). Modifying the chain without + // taking the lock will cause subtle race conditions that will leave the + // chain in a broken state. + // The chain can be only read without taking any locks. + + for (Delta &delta : transaction->deltas) { + while (true) { + auto prev = delta.prev.Get(); + switch (prev.type) { + case PreviousPtr::Type::VERTEX: { + Vertex *vertex = prev.vertex; + if (vertex->delta != &delta) { + // Something changed, we're not the first delta in the chain + // anymore. + continue; + } + vertex->delta = nullptr; + if (vertex->deleted) { + InsertVertexPKIntoList(deleted_vertices_, vertex->keys.Keys()); + } + break; + } + case PreviousPtr::Type::EDGE: { + Edge *edge = prev.edge; + if (edge->delta != &delta) { + // Something changed, we're not the first delta in the chain + // anymore. + continue; + } + edge->delta = nullptr; + if (edge->deleted) { + deleted_edges_.push_back(edge->gid); + } + break; + } + case PreviousPtr::Type::DELTA: { + if (prev.delta->timestamp->load(std::memory_order_acquire) == commit_timestamp) { + // The delta that is newer than this one is also a delta from this + // transaction. We skip the current delta and will remove it as a + // part of the suffix later. + break; + } + { + // We need to find the parent object in order to be able to use + // its lock. + auto parent = prev; + while (parent.type == PreviousPtr::Type::DELTA) { + parent = parent.delta->prev.Get(); + } + switch (parent.type) { + case PreviousPtr::Type::VERTEX: + case PreviousPtr::Type::EDGE: + break; + case PreviousPtr::Type::DELTA: + case PreviousPtr::Type::NULLPTR: + LOG_FATAL("Invalid database state!"); + } + } + Delta *prev_delta = prev.delta; + prev_delta->next.store(nullptr, std::memory_order_release); + break; + } + case PreviousPtr::Type::NULLPTR: { + LOG_FATAL("Invalid pointer!"); + } + } + break; + } + } + + unlinked_undo_buffers.emplace_back(0, std::move(transaction->deltas)); + committed_transactions_.pop_front(); + } + + // After unlinking deltas from vertices, we refresh the indices. That way + // we're sure that none of the vertices from `current_deleted_vertices` + // appears in an index, and we can safely remove the from the main storage + // after the last currently active transaction is finished. + if (run_index_cleanup) { + // This operation is very expensive as it traverses through all of the items + // in every index every time. + RemoveObsoleteEntries(&indices_, oldest_active_start_timestamp); + constraints_.unique_constraints.RemoveObsoleteEntries(oldest_active_start_timestamp); + } + + { + uint64_t mark_timestamp = timestamp_; + for (auto &[timestamp, undo_buffer] : unlinked_undo_buffers) { + timestamp = mark_timestamp; + } + garbage_undo_buffers_.splice(garbage_undo_buffers_.end(), unlinked_undo_buffers); + + for (const auto &vertex : deleted_vertices_) { + garbage_vertices_.emplace_back(mark_timestamp, vertex); + } + } + + // if force is set to true we can simply delete all the leftover undos because + // no transaction is active + if constexpr (force) { + garbage_undo_buffers_.clear(); + } else { + while (!garbage_undo_buffers_.empty() && garbage_undo_buffers_.front().first <= oldest_active_start_timestamp) { + garbage_undo_buffers_.pop_front(); + } + } + + { + auto vertex_acc = vertices_.access(); + if constexpr (force) { + // if force is set to true, then we have unique_lock and no transactions are active + // so we can clean all of the deleted vertices + while (!garbage_vertices_.empty()) { + MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); + garbage_vertices_.pop_front(); + } + } else { + while (!garbage_vertices_.empty() && garbage_vertices_.front().first < oldest_active_start_timestamp) { + MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); + garbage_vertices_.pop_front(); + } + } + } + { + auto edge_acc = edges_.access(); + for (auto edge : deleted_edges_) { + MG_ASSERT(edge_acc.remove(edge), "Invalid database state!"); + } + } +} + +// tell the linker he can find the CollectGarbage definitions here +template void Shard::CollectGarbage(); +template void Shard::CollectGarbage(); + +bool Shard::InitializeWalFile() { + if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL) + return false; + if (!wal_file_) { + wal_file_.emplace(wal_directory_, uuid_, epoch_id_, config_.items, &name_id_mapper_, wal_seq_num_++, + &file_retainer_); + } + return true; +} + +void Shard::FinalizeWalFile() { + ++wal_unsynced_transactions_; + if (wal_unsynced_transactions_ >= config_.durability.wal_file_flush_every_n_tx) { + wal_file_->Sync(); + wal_unsynced_transactions_ = 0; + } + if (wal_file_->GetSize() / 1024 >= config_.durability.wal_file_size_kibibytes) { + wal_file_->FinalizeWal(); + wal_file_ = std::nullopt; + wal_unsynced_transactions_ = 0; + } else { + // Try writing the internal buffer if possible, if not + // the data should be written as soon as it's possible + // (triggered by the new transaction commit, or some + // reading thread EnabledFlushing) + wal_file_->TryFlushing(); + } +} + +void Shard::AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp) { + if (!InitializeWalFile()) return; + // Traverse deltas and append them to the WAL file. + // A single transaction will always be contained in a single WAL file. + auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire); + + if (replication_role_ == ReplicationRole::MAIN) { + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->StartTransactionReplication(wal_file_->SequenceNumber()); + } + }); + } + + // Helper lambda that traverses the delta chain on order to find the first + // delta that should be processed and then appends all discovered deltas. + auto find_and_apply_deltas = [&](const auto *delta, const auto &parent, auto filter) { + while (true) { + auto *older = delta->next.load(std::memory_order_acquire); + if (older == nullptr || older->timestamp->load(std::memory_order_acquire) != current_commit_timestamp) break; + delta = older; + } + while (true) { + if (filter(delta->action)) { + wal_file_->AppendDelta(*delta, parent, final_commit_timestamp); + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->IfStreamingTransaction( + [&](auto &stream) { stream.AppendDelta(*delta, parent, final_commit_timestamp); }); + } + }); + } + auto prev = delta->prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::DELTA) break; + delta = prev.delta; + } + }; + + // The deltas are ordered correctly in the `transaction.deltas` buffer, but we + // don't traverse them in that order. That is because for each delta we need + // information about the vertex or edge they belong to and that information + // isn't stored in the deltas themselves. In order to find out information + // about the corresponding vertex or edge it is necessary to traverse the + // delta chain for each delta until a vertex or edge is encountered. This + // operation is very expensive as the chain grows. + // Instead, we traverse the edges until we find a vertex or edge and traverse + // their delta chains. This approach has a drawback because we lose the + // correct order of the operations. Because of that, we need to traverse the + // deltas several times and we have to manually ensure that the stored deltas + // will be ordered correctly. + + // 1. Process all Vertex deltas and store all operations that create vertices + // and modify vertex data. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::DELETE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + return true; + + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + // 2. Process all Vertex deltas and store all operations that create edges. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::REMOVE_OUT_EDGE: + return true; + + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + return false; + } + }); + } + // 3. Process all Edge deltas and store all operations that modify edge data. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::EDGE) continue; + find_and_apply_deltas(&delta, *prev.edge, [](auto action) { + switch (action) { + case Delta::Action::SET_PROPERTY: + return true; + + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + 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: + return false; + } + }); + } + // 4. Process all Vertex deltas and store all operations that delete edges. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::ADD_OUT_EDGE: + return true; + + case Delta::Action::DELETE_OBJECT: + case Delta::Action::RECREATE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + // 5. Process all Vertex deltas and store all operations that delete vertices. + for (const auto &delta : transaction.deltas) { + auto prev = delta.prev.Get(); + MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); + if (prev.type != PreviousPtr::Type::VERTEX) continue; + find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { + switch (action) { + case Delta::Action::RECREATE_OBJECT: + return true; + + case Delta::Action::DELETE_OBJECT: + case Delta::Action::SET_PROPERTY: + case Delta::Action::ADD_LABEL: + case Delta::Action::REMOVE_LABEL: + case Delta::Action::ADD_IN_EDGE: + case Delta::Action::ADD_OUT_EDGE: + case Delta::Action::REMOVE_IN_EDGE: + case Delta::Action::REMOVE_OUT_EDGE: + return false; + } + }); + } + + // Add a delta that indicates that the transaction is fully written to the WAL + // file. + wal_file_->AppendTransactionEnd(final_commit_timestamp); + + FinalizeWalFile(); + + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(final_commit_timestamp); }); + client->FinalizeTransactionReplication(); + } + }); +} + +void Shard::AppendToWal(durability::StorageGlobalOperation operation, LabelId label, + const std::set &properties, uint64_t final_commit_timestamp) { + if (!InitializeWalFile()) return; + wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp); + { + if (replication_role_ == ReplicationRole::MAIN) { + replication_clients_.WithLock([&](auto &clients) { + for (auto &client : clients) { + client->StartTransactionReplication(wal_file_->SequenceNumber()); + client->IfStreamingTransaction( + [&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); }); + client->FinalizeTransactionReplication(); + } + }); + } + } + FinalizeWalFile(); +} + +utils::BasicResult Shard::CreateSnapshot() { + if (replication_role_ != ReplicationRole::MAIN) { + return CreateSnapshotError::DisabledForReplica; + } + + // Create the transaction used to create the snapshot. + auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION); + + // Create snapshot. + // durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, + // config_.durability.snapshot_retention_count, &vertices_, &edges_, + // &name_id_mapper_, &indices_, &constraints_, config_.items, schema_validator_, + // uuid_, epoch_id_, epoch_history_, &file_retainer_); + + // Finalize snapshot transaction. + commit_log_->MarkFinished(transaction.start_timestamp); + return {}; +} + +bool Shard::LockPath() { + auto locker_accessor = global_locker_.Access(); + return locker_accessor.AddPath(config_.durability.storage_directory); +} + +bool Shard::UnlockPath() { + { + auto locker_accessor = global_locker_.Access(); + if (!locker_accessor.RemovePath(config_.durability.storage_directory)) { + return false; + } + } + + // We use locker accessor in seperate scope so we don't produce deadlock + // after we call clean queue. + file_retainer_.CleanQueue(); + return true; +} + +void Shard::FreeMemory() { + CollectGarbage(); + + // SkipList is already threadsafe + vertices_.run_gc(); + edges_.run_gc(); + indices_.label_index.RunGC(); + indices_.label_property_index.RunGC(); +} + +uint64_t Shard::CommitTimestamp(const std::optional desired_commit_timestamp) { + if (!desired_commit_timestamp) { + return timestamp_++; + } + timestamp_ = std::max(timestamp_, *desired_commit_timestamp + 1); + return *desired_commit_timestamp; +} + +bool Shard::SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config) { + // We don't want to restart the server if we're already a REPLICA + if (replication_role_ == ReplicationRole::REPLICA) { + return false; + } + + replication_server_ = std::make_unique(this, std::move(endpoint), config); + + replication_role_ = ReplicationRole::REPLICA; + return true; +} + +bool Shard::SetMainReplicationRole() { + // We don't want to generate new epoch_id and do the + // cleanup if we're already a MAIN + if (replication_role_ == ReplicationRole::MAIN) { + return false; + } + + // Main instance does not need replication server + // This should be always called first so we finalize everything + replication_server_.reset(nullptr); + + if (wal_file_) { + wal_file_->FinalizeWal(); + wal_file_.reset(); + } + + // Generate new epoch id and save the last one to the history. + if (epoch_history_.size() == kEpochHistoryRetention) { + epoch_history_.pop_front(); + } + epoch_history_.emplace_back(std::move(epoch_id_), last_commit_timestamp_); + epoch_id_ = utils::GenerateUUID(); + + replication_role_ = ReplicationRole::MAIN; + return true; +} + +utils::BasicResult Shard::RegisterReplica( + std::string name, io::network::Endpoint endpoint, const replication::ReplicationMode replication_mode, + const replication::ReplicationClientConfig &config) { + MG_ASSERT(replication_role_ == ReplicationRole::MAIN, "Only main instance can register a replica!"); + + const bool name_exists = replication_clients_.WithLock([&](auto &clients) { + return std::any_of(clients.begin(), clients.end(), [&name](const auto &client) { return client->Name() == name; }); + }); + + if (name_exists) { + return RegisterReplicaError::NAME_EXISTS; + } + + const auto end_point_exists = replication_clients_.WithLock([&endpoint](auto &clients) { + return std::any_of(clients.begin(), clients.end(), + [&endpoint](const auto &client) { return client->Endpoint() == endpoint; }); + }); + + if (end_point_exists) { + return RegisterReplicaError::END_POINT_EXISTS; + } + + MG_ASSERT(replication_mode == replication::ReplicationMode::SYNC || !config.timeout, + "Only SYNC mode can have a timeout set"); + + auto client = std::make_unique(std::move(name), this, endpoint, replication_mode, config); + if (client->State() == replication::ReplicaState::INVALID) { + return RegisterReplicaError::CONNECTION_FAILED; + } + + return replication_clients_.WithLock([&](auto &clients) -> utils::BasicResult { + // Another thread could have added a client with same name while + // we were connecting to this client. + if (std::any_of(clients.begin(), clients.end(), + [&](const auto &other_client) { return client->Name() == other_client->Name(); })) { + return RegisterReplicaError::NAME_EXISTS; + } + + if (std::any_of(clients.begin(), clients.end(), + [&client](const auto &other_client) { return client->Endpoint() == other_client->Endpoint(); })) { + return RegisterReplicaError::END_POINT_EXISTS; + } + + clients.push_back(std::move(client)); + return {}; + }); +} + +bool Shard::UnregisterReplica(const std::string_view name) { + MG_ASSERT(replication_role_ == ReplicationRole::MAIN, "Only main instance can unregister a replica!"); + return replication_clients_.WithLock([&](auto &clients) { + return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); + }); +} + +std::optional Shard::GetReplicaState(const std::string_view name) { + return replication_clients_.WithLock([&](auto &clients) -> std::optional { + const auto client_it = + std::find_if(clients.cbegin(), clients.cend(), [name](auto &client) { return client->Name() == name; }); + if (client_it == clients.cend()) { + return std::nullopt; + } + return (*client_it)->State(); + }); +} + +ReplicationRole Shard::GetReplicationRole() const { return replication_role_; } + +std::vector Shard::ReplicasInfo() { + return replication_clients_.WithLock([](auto &clients) { + std::vector replica_info; + replica_info.reserve(clients.size()); + std::transform(clients.begin(), clients.end(), std::back_inserter(replica_info), + [](const auto &client) -> ReplicaInfo { + return {client->Name(), client->Mode(), client->Timeout(), client->Endpoint(), client->State()}; + }); + return replica_info; + }); +} + +void Shard::SetIsolationLevel(IsolationLevel isolation_level) { isolation_level_ = isolation_level; } + +} // namespace memgraph::storage::v3 diff --git a/src/storage/v3/shard.hpp b/src/storage/v3/shard.hpp new file mode 100644 index 000000000..8a42e542d --- /dev/null +++ b/src/storage/v3/shard.hpp @@ -0,0 +1,617 @@ +// Copyright 2022 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include "io/network/endpoint.hpp" +#include "kvstore/kvstore.hpp" +#include "storage/v3/commit_log.hpp" +#include "storage/v3/config.hpp" +#include "storage/v3/constraints.hpp" +#include "storage/v3/durability/metadata.hpp" +#include "storage/v3/durability/wal.hpp" +#include "storage/v3/edge.hpp" +#include "storage/v3/edge_accessor.hpp" +#include "storage/v3/id_types.hpp" +#include "storage/v3/indices.hpp" +#include "storage/v3/isolation_level.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/lexicographically_ordered_vertex.hpp" +#include "storage/v3/mvcc.hpp" +#include "storage/v3/name_id_mapper.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/result.hpp" +#include "storage/v3/schema_validator.hpp" +#include "storage/v3/schemas.hpp" +#include "storage/v3/transaction.hpp" +#include "storage/v3/vertex.hpp" +#include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertices_skip_list.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/v3/replication/config.hpp" +#include "storage/v3/replication/enums.hpp" +#include "storage/v3/replication/rpc.hpp" +#include "storage/v3/replication/serialization.hpp" + +namespace memgraph::storage::v3 { + +// 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 { + VerticesSkipList::Accessor vertices_accessor_; + Transaction *transaction_; + View view_; + Indices *indices_; + Constraints *constraints_; + Config::Items config_; + const SchemaValidator *schema_validator_; + const Schemas *schemas_; + std::optional vertex_; + + public: + class Iterator final { + AllVerticesIterable *self_; + VerticesSkipList::Iterator it_; + + public: + Iterator(AllVerticesIterable *self, VerticesSkipList::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(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view, + Indices *indices, Constraints *constraints, Config::Items config, + const SchemaValidator &schema_validator) + : vertices_accessor_(std::move(vertices_accessor)), + transaction_(transaction), + view_(view), + indices_(indices), + constraints_(constraints), + config_(config), + schema_validator_{&schema_validator} {} + + Iterator begin() { return {this, vertices_accessor_.begin()}; } + Iterator end() { return {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 }; + + Type type_; + union { + AllVerticesIterable all_vertices_; + LabelIndex::Iterable vertices_by_label_; + LabelPropertyIndex::Iterable vertices_by_label_property_; + }; + + public: + explicit VerticesIterable(AllVerticesIterable); + explicit VerticesIterable(LabelIndex::Iterable); + explicit VerticesIterable(LabelPropertyIndex::Iterable); + + VerticesIterable(const VerticesIterable &) = delete; + VerticesIterable &operator=(const VerticesIterable &) = delete; + + VerticesIterable(VerticesIterable &&) noexcept; + VerticesIterable &operator=(VerticesIterable &&) noexcept; + + ~VerticesIterable(); + + class Iterator final { + Type type_; + union { + AllVerticesIterable::Iterator all_it_; + LabelIndex::Iterable::Iterator by_label_it_; + LabelPropertyIndex::Iterable::Iterator by_label_property_it_; + }; + + void Destroy() noexcept; + + public: + explicit Iterator(AllVerticesIterable::Iterator); + explicit Iterator(LabelIndex::Iterable::Iterator); + explicit Iterator(LabelPropertyIndex::Iterable::Iterator); + + Iterator(const Iterator &); + Iterator &operator=(const Iterator &); + + Iterator(Iterator &&) noexcept; + Iterator &operator=(Iterator &&) noexcept; + + ~Iterator(); + + VertexAccessor operator*() const; + + Iterator &operator++(); + + bool operator==(const Iterator &other) const; + bool operator!=(const Iterator &other) const { return !(*this == other); } + }; + + Iterator begin(); + Iterator end(); +}; + +/// Structure used to return information about existing indices in the storage. +struct IndicesInfo { + std::vector label; + std::vector> label_property; +}; + +/// Structure used to return information about existing constraints in the +/// storage. +struct ConstraintsInfo { + std::vector> existence; + std::vector>> unique; +}; + +/// Structure used to return information about existing schemas in the storage +struct SchemasInfo { + Schemas::SchemasList schemas; +}; + +/// Structure used to return information about the storage. +struct StorageInfo { + uint64_t vertex_count; + uint64_t edge_count; + double average_degree; + uint64_t memory_usage; + uint64_t disk_usage; +}; + +enum class ReplicationRole : uint8_t { MAIN, REPLICA }; + +class Shard final { + public: + /// @throw std::system_error + /// @throw std::bad_alloc + explicit Shard(LabelId primary_label, PrimaryKey min_primary_key, std::optional max_primary_key, + Config config = Config()); + + Shard(const Shard &) = delete; + Shard(Shard &&) noexcept = delete; + Shard &operator=(const Shard &) = delete; + Shard operator=(Shard &&) noexcept = delete; + ~Shard(); + + class Accessor final { + private: + friend class Shard; + + explicit Accessor(Shard *shard, IsolationLevel isolation_level); + + public: + Accessor(const Accessor &) = delete; + Accessor &operator=(const Accessor &) = delete; + Accessor &operator=(Accessor &&other) = delete; + + // NOTE: After the accessor is moved, all objects derived from it (accessors + // and iterators) are *invalid*. You have to get all derived objects again. + Accessor(Accessor &&other) noexcept; + + ~Accessor(); + + /// @throw std::bad_alloc + ResultSchema CreateVertexAndValidate( + LabelId primary_label, const std::vector &labels, + const std::vector> &properties); + + std::optional FindVertex(std::vector primary_key, View view); + + VerticesIterable Vertices(View view) { + return VerticesIterable(AllVerticesIterable(shard_->vertices_.access(), &transaction_, view, &shard_->indices_, + &shard_->constraints_, shard_->config_.items, + shard_->schema_validator_)); + } + + VerticesIterable Vertices(LabelId label, View view); + + VerticesIterable Vertices(LabelId label, PropertyId property, View view); + + VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view); + + VerticesIterable Vertices(LabelId label, PropertyId property, + const std::optional> &lower_bound, + const std::optional> &upper_bound, View view); + + /// Return approximate number of all vertices in the database. + /// Note that this is always an over-estimate and never an under-estimate. + int64_t ApproximateVertexCount() const { return static_cast(shard_->vertices_.size()); } + + /// Return approximate number of vertices with the given label. + /// Note that this is always an over-estimate and never an under-estimate. + int64_t ApproximateVertexCount(LabelId label) const { + return shard_->indices_.label_index.ApproximateVertexCount(label); + } + + /// Return approximate number of vertices with the given label and property. + /// Note that this is always an over-estimate and never an under-estimate. + int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { + return shard_->indices_.label_property_index.ApproximateVertexCount(label, property); + } + + /// Return approximate number of vertices with the given label and the given + /// value for the given property. Note that this is always an over-estimate + /// and never an under-estimate. + int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const { + return shard_->indices_.label_property_index.ApproximateVertexCount(label, property, value); + } + + /// Return approximate number of vertices with the given label and value for + /// the given property in the range defined by provided upper and lower + /// bounds. + int64_t ApproximateVertexCount(LabelId label, PropertyId property, + const std::optional> &lower, + const std::optional> &upper) const { + return shard_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper); + } + + /// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise + /// @throw std::bad_alloc + Result> DeleteVertex(VertexAccessor *vertex); + + /// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise + /// @throw std::bad_alloc + Result>>> DetachDeleteVertex( + VertexAccessor *vertex); + + /// @throw std::bad_alloc + Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type); + + /// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise + /// @throw std::bad_alloc + Result> DeleteEdge(EdgeAccessor *edge); + + const std::string &LabelToName(LabelId label) const; + const std::string &PropertyToName(PropertyId property) const; + const std::string &EdgeTypeToName(EdgeTypeId edge_type) const; + + bool LabelIndexExists(LabelId label) const { return shard_->indices_.label_index.IndexExists(label); } + + bool LabelPropertyIndexExists(LabelId label, PropertyId property) const { + return shard_->indices_.label_property_index.IndexExists(label, property); + } + + IndicesInfo ListAllIndices() const { + return {shard_->indices_.label_index.ListIndices(), shard_->indices_.label_property_index.ListIndices()}; + } + + ConstraintsInfo ListAllConstraints() const { + return {ListExistenceConstraints(shard_->constraints_), + shard_->constraints_.unique_constraints.ListConstraints()}; + } + + const SchemaValidator &GetSchemaValidator() const; + + SchemasInfo ListAllSchemas() const { return {shard_->schemas_.ListSchemas()}; } + + void AdvanceCommand(); + + /// Commit returns `ConstraintViolation` if the changes made by this + /// transaction violate an existence or unique constraint. In that case the + /// transaction is automatically aborted. Otherwise, void is returned. + /// @throw std::bad_alloc + utils::BasicResult Commit(std::optional desired_commit_timestamp = {}); + + /// @throw std::bad_alloc + void Abort(); + + void FinalizeTransaction(); + + private: + /// @throw std::bad_alloc + VertexAccessor CreateVertex(Gid gid, LabelId primary_label); + + /// @throw std::bad_alloc + Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Gid gid); + + Shard *shard_; + Transaction transaction_; + std::optional commit_timestamp_; + bool is_transaction_active_; + Config::Items config_; + }; + + Accessor Access(std::optional override_isolation_level = {}) { + return Accessor{this, override_isolation_level.value_or(isolation_level_)}; + } + + const std::string &LabelToName(LabelId label) const; + const std::string &PropertyToName(PropertyId property) const; + const std::string &EdgeTypeToName(EdgeTypeId edge_type) const; + + /// @throw std::bad_alloc if unable to insert a new mapping + LabelId NameToLabel(std::string_view name); + + /// @throw std::bad_alloc if unable to insert a new mapping + PropertyId NameToProperty(std::string_view name); + + /// @throw std::bad_alloc if unable to insert a new mapping + EdgeTypeId NameToEdgeType(std::string_view name); + + /// @throw std::bad_alloc + bool CreateIndex(LabelId label, std::optional desired_commit_timestamp = {}); + + /// @throw std::bad_alloc + bool CreateIndex(LabelId label, PropertyId property, std::optional desired_commit_timestamp = {}); + + bool DropIndex(LabelId label, std::optional desired_commit_timestamp = {}); + + bool DropIndex(LabelId label, PropertyId property, std::optional desired_commit_timestamp = {}); + + IndicesInfo ListAllIndices() const; + + /// Creates an existence constraint. Returns true if the constraint was + /// successfully added, false if it already exists and a `ConstraintViolation` + /// if there is an existing vertex violating the constraint. + /// + /// @throw std::bad_alloc + /// @throw std::length_error + utils::BasicResult CreateExistenceConstraint( + LabelId label, PropertyId property, std::optional desired_commit_timestamp = {}); + + /// Removes an existence constraint. Returns true if the constraint was + /// removed, and false if it doesn't exist. + bool DropExistenceConstraint(LabelId label, PropertyId property, + std::optional desired_commit_timestamp = {}); + + /// Creates a unique constraint. In the case of two vertices violating the + /// constraint, it returns `ConstraintViolation`. Otherwise returns a + /// `UniqueConstraints::CreationStatus` enum with the following possibilities: + /// * `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 CreateUniqueConstraint( + LabelId label, const std::set &properties, std::optional desired_commit_timestamp = {}); + + /// Removes a unique constraint. Returns `UniqueConstraints::DeletionStatus` + /// enum with the following possibilities: + /// * `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. + UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set &properties, + std::optional desired_commit_timestamp = {}); + + ConstraintsInfo ListAllConstraints() const; + + SchemasInfo ListAllSchemas() const; + + const Schemas::Schema *GetSchema(LabelId primary_label) const; + + bool CreateSchema(LabelId primary_label, const std::vector &schemas_types); + + bool DropSchema(LabelId primary_label); + + StorageInfo GetInfo() const; + + bool LockPath(); + bool UnlockPath(); + + bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {}); + + bool SetMainReplicationRole(); + + enum class RegisterReplicaError : uint8_t { + NAME_EXISTS, + END_POINT_EXISTS, + CONNECTION_FAILED, + COULD_NOT_BE_PERSISTED + }; + + /// @pre The instance should have a MAIN role + /// @pre Timeout can only be set for SYNC replication + utils::BasicResult RegisterReplica( + std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode, + const replication::ReplicationClientConfig &config = {}); + /// @pre The instance should have a MAIN role + bool UnregisterReplica(std::string_view name); + + std::optional GetReplicaState(std::string_view name); + + ReplicationRole GetReplicationRole() const; + + struct ReplicaInfo { + std::string name; + replication::ReplicationMode mode; + std::optional timeout; + io::network::Endpoint endpoint; + replication::ReplicaState state; + }; + + std::vector ReplicasInfo(); + + void FreeMemory(); + + void SetIsolationLevel(IsolationLevel isolation_level); + + enum class CreateSnapshotError : uint8_t { DisabledForReplica }; + + utils::BasicResult CreateSnapshot(); + + private: + Transaction CreateTransaction(IsolationLevel isolation_level); + + /// The force parameter determines the behaviour of the garbage collector. + /// If it's set to true, it will behave as a global operation, i.e. it can't + /// be part of a transaction, and no other transaction can be active at the same time. + /// This allows it to delete immediately vertices without worrying that some other + /// transaction is possibly using it. If there are active transactions when this method + /// is called with force set to true, it will fallback to the same method with the force + /// set to false. + /// If it's set to false, it will execute in parallel with other transactions, ensuring + /// that no object in use can be deleted. + /// @throw std::system_error + /// @throw std::bad_alloc + template + void CollectGarbage(); + + bool InitializeWalFile(); + void FinalizeWalFile(); + + void AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp); + void AppendToWal(durability::StorageGlobalOperation operation, LabelId label, const std::set &properties, + uint64_t final_commit_timestamp); + + uint64_t CommitTimestamp(std::optional desired_commit_timestamp = {}); + + // Main object storage + NameIdMapper name_id_mapper_; + LabelId primary_label_; + PrimaryKey min_primary_key_; + std::optional max_primary_key_; + VerticesSkipList vertices_; + utils::SkipList edges_; + 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. + uint64_t edge_count_{0}; + + SchemaValidator schema_validator_; + Constraints constraints_; + Indices indices_; + Schemas schemas_; + + // Transaction engine + 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 commit_log_; + + std::list committed_transactions_; + IsolationLevel isolation_level_; + + Config config_; + + // Undo buffers that were unlinked and now are waiting to be freed. + std::list>> garbage_undo_buffers_; + + // Vertices that are logically deleted but still have to be removed from + // indices before removing them from the main storage. + std::list deleted_vertices_; + + // Vertices that are logically deleted and removed from indices and now wait + // to be removed from the main storage. + std::list> garbage_vertices_; + + // Edges that are logically deleted and wait to be removed from the main + // storage. + std::list deleted_edges_; + + // Durability + std::filesystem::path snapshot_directory_; + std::filesystem::path wal_directory_; + std::filesystem::path lock_file_path_; + utils::OutputFile lock_file_handle_; + + // 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> epoch_history_; + + std::optional 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 + uint64_t last_commit_timestamp_{kTimestampInitialId}; + + class ReplicationServer; + std::unique_ptr 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>, utils::SpinLock>; + ReplicationClientList replication_clients_; + + ReplicationRole replication_role_{ReplicationRole::MAIN}; +}; + +} // namespace memgraph::storage::v3 diff --git a/src/storage/v3/storage.cpp b/src/storage/v3/storage.cpp index 23dd45427..28fd8b330 100644 --- a/src/storage/v3/storage.cpp +++ b/src/storage/v3/storage.cpp @@ -11,1853 +11,10 @@ #include "storage/v3/storage.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "io/network/endpoint.hpp" -#include "storage/v3/constraints.hpp" -#include "storage/v3/durability/durability.hpp" -#include "storage/v3/durability/metadata.hpp" -#include "storage/v3/durability/paths.hpp" -#include "storage/v3/durability/snapshot.hpp" -#include "storage/v3/durability/wal.hpp" -#include "storage/v3/edge_accessor.hpp" -#include "storage/v3/id_types.hpp" -#include "storage/v3/indices.hpp" -#include "storage/v3/key_store.hpp" -#include "storage/v3/mvcc.hpp" -#include "storage/v3/property_value.hpp" -#include "storage/v3/replication/config.hpp" -#include "storage/v3/replication/replication_client.hpp" -#include "storage/v3/replication/replication_server.hpp" -#include "storage/v3/replication/rpc.hpp" -#include "storage/v3/schema_validator.hpp" -#include "storage/v3/transaction.hpp" -#include "storage/v3/vertex.hpp" -#include "storage/v3/vertex_accessor.hpp" -#include "storage/v3/vertices_skip_list.hpp" -#include "utils/exceptions.hpp" -#include "utils/file.hpp" -#include "utils/logging.hpp" -#include "utils/memory_tracker.hpp" -#include "utils/message.hpp" -#include "utils/result.hpp" -#include "utils/rw_lock.hpp" -#include "utils/skip_list.hpp" -#include "utils/spin_lock.hpp" -#include "utils/stat.hpp" -#include "utils/uuid.hpp" +#include "storage/v3/config.hpp" namespace memgraph::storage::v3 { -using OOMExceptionEnabler = utils::MemoryTracker::OutOfMemoryExceptionEnabler; - -namespace { -inline constexpr uint16_t kEpochHistoryRetention = 1000; - -void InsertVertexPKIntoList(auto &container, const PrimaryKey &primary_key) { container.push_back(primary_key); } -} // namespace - -auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end, - std::optional *vertex, Transaction *tx, View view, Indices *indices, - Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator) { - while (it != end) { - *vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, schema_validator, view); - if (!*vertex) { - ++it; - continue; - } - break; - } - return it; -} - -AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it) - : self_(self), - it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, - self->indices_, self_->constraints_, self->config_, *self_->schema_validator_)) {} - -VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; } - -AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() { - ++it_; - it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), &self_->vertex_, self_->transaction_, self_->view_, - self_->indices_, self_->constraints_, self_->config_, *self_->schema_validator_); - return *this; -} - -VerticesIterable::VerticesIterable(AllVerticesIterable vertices) : type_(Type::ALL) { - new (&all_vertices_) AllVerticesIterable(std::move(vertices)); -} - -VerticesIterable::VerticesIterable(LabelIndex::Iterable vertices) : type_(Type::BY_LABEL) { - new (&vertices_by_label_) LabelIndex::Iterable(std::move(vertices)); -} - -VerticesIterable::VerticesIterable(LabelPropertyIndex::Iterable vertices) : type_(Type::BY_LABEL_PROPERTY) { - new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(vertices)); -} - -VerticesIterable::VerticesIterable(VerticesIterable &&other) noexcept : type_(other.type_) { - switch (other.type_) { - case Type::ALL: - new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); - break; - case Type::BY_LABEL: - new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_)); - break; - case Type::BY_LABEL_PROPERTY: - new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(other.vertices_by_label_property_)); - break; - } -} - -VerticesIterable &VerticesIterable::operator=(VerticesIterable &&other) noexcept { - switch (type_) { - case Type::ALL: - all_vertices_.AllVerticesIterable::~AllVerticesIterable(); - break; - case Type::BY_LABEL: - vertices_by_label_.LabelIndex::Iterable::~Iterable(); - break; - case Type::BY_LABEL_PROPERTY: - vertices_by_label_property_.LabelPropertyIndex::Iterable::~Iterable(); - break; - } - type_ = other.type_; - switch (other.type_) { - case Type::ALL: - new (&all_vertices_) AllVerticesIterable(std::move(other.all_vertices_)); - break; - case Type::BY_LABEL: - new (&vertices_by_label_) LabelIndex::Iterable(std::move(other.vertices_by_label_)); - break; - case Type::BY_LABEL_PROPERTY: - new (&vertices_by_label_property_) LabelPropertyIndex::Iterable(std::move(other.vertices_by_label_property_)); - break; - } - return *this; -} - -VerticesIterable::~VerticesIterable() { - switch (type_) { - case Type::ALL: - all_vertices_.AllVerticesIterable::~AllVerticesIterable(); - break; - case Type::BY_LABEL: - vertices_by_label_.LabelIndex::Iterable::~Iterable(); - break; - case Type::BY_LABEL_PROPERTY: - vertices_by_label_property_.LabelPropertyIndex::Iterable::~Iterable(); - break; - } -} - -VerticesIterable::Iterator VerticesIterable::begin() { - switch (type_) { - case Type::ALL: - return Iterator(all_vertices_.begin()); - case Type::BY_LABEL: - return Iterator(vertices_by_label_.begin()); - case Type::BY_LABEL_PROPERTY: - return Iterator(vertices_by_label_property_.begin()); - } -} - -VerticesIterable::Iterator VerticesIterable::end() { - switch (type_) { - case Type::ALL: - return Iterator(all_vertices_.end()); - case Type::BY_LABEL: - return Iterator(vertices_by_label_.end()); - case Type::BY_LABEL_PROPERTY: - return Iterator(vertices_by_label_property_.end()); - } -} - -VerticesIterable::Iterator::Iterator(AllVerticesIterable::Iterator it) : type_(Type::ALL) { - new (&all_it_) AllVerticesIterable::Iterator(it); -} - -VerticesIterable::Iterator::Iterator(LabelIndex::Iterable::Iterator it) : type_(Type::BY_LABEL) { - new (&by_label_it_) LabelIndex::Iterable::Iterator(it); -} - -VerticesIterable::Iterator::Iterator(LabelPropertyIndex::Iterable::Iterator it) : type_(Type::BY_LABEL_PROPERTY) { - new (&by_label_property_it_) 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_); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); - break; - } -} - -VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(const VerticesIterable::Iterator &other) { - if (this == &other) { - return *this; - } - Destroy(); - type_ = other.type_; - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); - break; - } - return *this; -} - -VerticesIterable::Iterator::Iterator(VerticesIterable::Iterator &&other) noexcept : type_(other.type_) { - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); - break; - } -} - -VerticesIterable::Iterator &VerticesIterable::Iterator::operator=(VerticesIterable::Iterator &&other) noexcept { - Destroy(); - type_ = other.type_; - switch (other.type_) { - case Type::ALL: - new (&all_it_) AllVerticesIterable::Iterator(other.all_it_); - break; - case Type::BY_LABEL: - new (&by_label_it_) LabelIndex::Iterable::Iterator(other.by_label_it_); - break; - case Type::BY_LABEL_PROPERTY: - new (&by_label_property_it_) LabelPropertyIndex::Iterable::Iterator(other.by_label_property_it_); - break; - } - return *this; -} - -VerticesIterable::Iterator::~Iterator() { Destroy(); } - -void VerticesIterable::Iterator::Destroy() noexcept { - switch (type_) { - case Type::ALL: - all_it_.AllVerticesIterable::Iterator::~Iterator(); - break; - case Type::BY_LABEL: - by_label_it_.LabelIndex::Iterable::Iterator::~Iterator(); - break; - case Type::BY_LABEL_PROPERTY: - by_label_property_it_.LabelPropertyIndex::Iterable::Iterator::~Iterator(); - break; - } -} - -VertexAccessor VerticesIterable::Iterator::operator*() const { - switch (type_) { - case Type::ALL: - return *all_it_; - case Type::BY_LABEL: - return *by_label_it_; - case Type::BY_LABEL_PROPERTY: - return *by_label_property_it_; - } -} - -VerticesIterable::Iterator &VerticesIterable::Iterator::operator++() { - switch (type_) { - case Type::ALL: - ++all_it_; - break; - case Type::BY_LABEL: - ++by_label_it_; - break; - case Type::BY_LABEL_PROPERTY: - ++by_label_property_it_; - break; - } - return *this; -} - -bool VerticesIterable::Iterator::operator==(const Iterator &other) const { - switch (type_) { - case Type::ALL: - return all_it_ == other.all_it_; - case Type::BY_LABEL: - return by_label_it_ == other.by_label_it_; - case Type::BY_LABEL_PROPERTY: - return by_label_property_it_ == other.by_label_property_it_; - } -} - -Storage::Storage(Config config) - : schema_validator_(schemas_), - indices_(&constraints_, config.items, schema_validator_), - isolation_level_(config.transaction.isolation_level), - config_(config), - snapshot_directory_(config_.durability.storage_directory / durability::kSnapshotDirectory), - wal_directory_(config_.durability.storage_directory / durability::kWalDirectory), - lock_file_path_(config_.durability.storage_directory / durability::kLockFile), - uuid_(utils::GenerateUUID()), - epoch_id_(utils::GenerateUUID()), - global_locker_(file_retainer_.AddLocker()) { - if (config_.durability.snapshot_wal_mode == Config::Durability::SnapshotWalMode::DISABLED && - replication_role_ == ReplicationRole::MAIN) { - spdlog::warn( - "The instance has the MAIN replication role, but durability logs and snapshots are disabled. Please consider " - "enabling durability by using --storage-snapshot-interval-sec and --storage-wal-enabled flags because " - "without write-ahead logs this instance is not replicating any data."); - } - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || - config_.durability.snapshot_on_exit || config_.durability.recover_on_startup) { - // Create the directory initially to crash the database in case of - // permission errors. This is done early to crash the database on startup - // instead of crashing the database for the first time during runtime (which - // could be an unpleasant surprise). - utils::EnsureDirOrDie(snapshot_directory_); - // Same reasoning as above. - utils::EnsureDirOrDie(wal_directory_); - - // Verify that the user that started the process is the same user that is - // the owner of the storage directory. - durability::VerifyStorageDirectoryOwnerAndProcessUserOrDie(config_.durability.storage_directory); - - // Create the lock file and open a handle to it. This will crash the - // database if it can't open the file for writing or if any other process is - // holding the file opened. - lock_file_handle_.Open(lock_file_path_, utils::OutputFile::Mode::OVERWRITE_EXISTING); - MG_ASSERT(lock_file_handle_.AcquireLock(), - "Couldn't acquire lock on the storage directory {}" - "!\nAnother Memgraph process is currently running with the same " - "storage directory, please stop it first before starting this " - "process!", - config_.durability.storage_directory); - } - if (config_.durability.recover_on_startup) { - auto info = std::optional{}; - - // durability::RecoverData(snapshot_directory_, wal_directory_, &uuid_, &epoch_id_, &epoch_history_, &vertices_, - // &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, - // &wal_seq_num_); - if (info) { - edge_id_ = info->next_edge_id; - timestamp_ = std::max(timestamp_, info->next_timestamp); - if (info->last_commit_timestamp) { - last_commit_timestamp_ = *info->last_commit_timestamp; - } - } - } else if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED || - config_.durability.snapshot_on_exit) { - bool files_moved = false; - auto backup_root = config_.durability.storage_directory / durability::kBackupDirectory; - for (const auto &[path, dirname, what] : - {std::make_tuple(snapshot_directory_, durability::kSnapshotDirectory, "snapshot"), - std::make_tuple(wal_directory_, durability::kWalDirectory, "WAL")}) { - if (!utils::DirExists(path)) continue; - auto backup_curr = backup_root / dirname; - std::error_code error_code; - for (const auto &item : std::filesystem::directory_iterator(path, error_code)) { - utils::EnsureDirOrDie(backup_root); - utils::EnsureDirOrDie(backup_curr); - std::error_code item_error_code; - std::filesystem::rename(item.path(), backup_curr / item.path().filename(), item_error_code); - MG_ASSERT(!item_error_code, "Couldn't move {} file {} because of: {}", what, item.path(), - item_error_code.message()); - files_moved = true; - } - MG_ASSERT(!error_code, "Couldn't backup {} files because of: {}", what, error_code.message()); - } - if (files_moved) { - spdlog::warn( - "Since Memgraph was not supposed to recover on startup and " - "durability is enabled, your current durability files will likely " - "be overridden. To prevent important data loss, Memgraph has stored " - "those files into a .backup directory inside the storage directory."); - } - } - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - // TODO(antaljanosbenjamin): handle snapshots - // snapshot_runner_.Run("Snapshot", config_.durability.snapshot_interval, [this] { - // if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { - // switch (maybe_error.GetError()) { - // case CreateSnapshotError::DisabledForReplica: - // spdlog::warn( - // utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); - // break; - // } - // } - // }); - } - - if (timestamp_ == kTimestampInitialId) { - commit_log_.emplace(); - } else { - commit_log_.emplace(timestamp_); - } -} - -Storage::~Storage() { - { - // Clear replication data - replication_server_.reset(); - replication_clients_.WithLock([&](auto &clients) { clients.clear(); }); - } - if (wal_file_) { - wal_file_->FinalizeWal(); - wal_file_ = std::nullopt; - } - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::DISABLED) { - // TODO(antaljanosbenjamin): stop snapshot creation - } - if (config_.durability.snapshot_on_exit) { - if (auto maybe_error = this->CreateSnapshot(); maybe_error.HasError()) { - switch (maybe_error.GetError()) { - case CreateSnapshotError::DisabledForReplica: - spdlog::warn(utils::MessageWithLink("Snapshots are disabled for replicas.", "https://memgr.ph/replication")); - break; - } - } - } -} - -Storage::Accessor::Accessor(Storage *storage, IsolationLevel isolation_level) - : storage_(storage), - transaction_(storage->CreateTransaction(isolation_level)), - is_transaction_active_(true), - config_(storage->config_.items) {} - -Storage::Accessor::Accessor(Accessor &&other) noexcept - : storage_(other.storage_), - transaction_(std::move(other.transaction_)), - commit_timestamp_(other.commit_timestamp_), - is_transaction_active_(other.is_transaction_active_), - config_(other.config_) { - // Don't allow the other accessor to abort our transaction in destructor. - other.is_transaction_active_ = false; - other.commit_timestamp_.reset(); -} - -Storage::Accessor::~Accessor() { - if (is_transaction_active_) { - Abort(); - } - - FinalizeTransaction(); -} - -ResultSchema Storage::Accessor::CreateVertexAndValidate( - LabelId primary_label, const std::vector &labels, - const std::vector> &properties) { - auto maybe_schema_violation = GetSchemaValidator().ValidateVertexCreate(primary_label, labels, properties); - if (maybe_schema_violation) { - return {std::move(*maybe_schema_violation)}; - } - OOMExceptionEnabler oom_exception; - // Extract key properties - std::vector primary_properties; - for ([[maybe_unused]] const auto &[property_id, property_type] : storage_->GetSchema(primary_label)->second) { - // We know there definitely is key in properties since we have validated - primary_properties.push_back( - std::ranges::find_if(properties, [property_id = property_id](const auto &property_pair) { - return property_pair.first == property_id; - })->second); - } - - // Get secondary properties - std::vector> secondary_properties; - for (const auto &[property_id, property_value] : properties) { - if (!storage_->schemas_.IsPropertyKey(primary_label, property_id)) { - secondary_properties.emplace_back(property_id, property_value); - } - } - - auto acc = storage_->vertices_.access(); - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert({Vertex{delta, primary_label, primary_properties, labels, secondary_properties}}); - MG_ASSERT(inserted, "The vertex must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); - delta->prev.Set(&it->vertex); - return VertexAccessor{ - &it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, storage_->schema_validator_}; -} - -std::optional Storage::Accessor::FindVertex(std::vector primary_key, View view) { - auto acc = storage_->vertices_.access(); - // Later on use label space - auto it = acc.find(primary_key); - if (it == acc.end()) { - return std::nullopt; - } - return VertexAccessor::Create(&it->vertex, &transaction_, &storage_->indices_, &storage_->constraints_, config_, - storage_->schema_validator_, view); -} - -Result> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) { - MG_ASSERT(vertex->transaction_ == &transaction_, - "VertexAccessor must be from the same transaction as the storage " - "accessor when deleting a vertex!"); - auto *vertex_ptr = vertex->vertex_; - - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - - if (vertex_ptr->deleted) { - return std::optional{}; - } - - if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) return Error::VERTEX_HAS_EDGES; - - CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); - vertex_ptr->deleted = true; - - return std::make_optional(vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, - config_, storage_->schema_validator_, true); -} - -Result>>> Storage::Accessor::DetachDeleteVertex( - VertexAccessor *vertex) { - using ReturnType = std::pair>; - - MG_ASSERT(vertex->transaction_ == &transaction_, - "VertexAccessor must be from the same transaction as the storage " - "accessor when deleting a vertex!"); - auto *vertex_ptr = vertex->vertex_; - - std::vector> in_edges; - std::vector> out_edges; - - { - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - - if (vertex_ptr->deleted) return std::optional{}; - - in_edges = vertex_ptr->in_edges; - out_edges = vertex_ptr->out_edges; - } - - std::vector deleted_edges; - for (const auto &item : in_edges) { - auto [edge_type, from_vertex, edge] = item; - EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &storage_->indices_, - &storage_->constraints_, config_, storage_->schema_validator_); - auto ret = DeleteEdge(&e); - if (ret.HasError()) { - MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); - return ret.GetError(); - } - - if (ret.GetValue()) { - deleted_edges.push_back(*ret.GetValue()); - } - } - for (const auto &item : out_edges) { - auto [edge_type, to_vertex, edge] = item; - EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &storage_->indices_, &storage_->constraints_, - config_, storage_->schema_validator_); - auto ret = DeleteEdge(&e); - if (ret.HasError()) { - MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); - return ret.GetError(); - } - - if (ret.GetValue()) { - deleted_edges.push_back(*ret.GetValue()); - } - } - - // We need to check again for serialization errors because we unlocked the - // vertex. Some other transaction could have modified the vertex in the - // meantime if we didn't have any edges to delete. - - if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - - MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); - - CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); - vertex_ptr->deleted = true; - - return std::make_optional( - VertexAccessor{vertex_ptr, &transaction_, &storage_->indices_, &storage_->constraints_, config_, - storage_->schema_validator_, true}, - std::move(deleted_edges)); -} - -Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) { - OOMExceptionEnabler oom_exception; - MG_ASSERT(from->transaction_ == to->transaction_, - "VertexAccessors must be from the same transaction when creating " - "an edge!"); - MG_ASSERT(from->transaction_ == &transaction_, - "VertexAccessors must be from the same transaction in when " - "creating an edge!"); - - auto *from_vertex = from->vertex_; - auto *to_vertex = to->vertex_; - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; - } - - auto gid = Gid::FromUint(storage_->edge_id_++); - EdgeRef edge(gid); - if (config_.properties_on_edges) { - auto acc = storage_->edges_.access(); - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Edge(gid, delta)); - MG_ASSERT(inserted, "The edge must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); - edge = EdgeRef(&*it); - delta->prev.Set(&*it); - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - - // Increment edge count. - ++storage_->edge_count_; - - return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, - &storage_->constraints_, config_, storage_->schema_validator_); -} - -Result Storage::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, - Gid gid) { - OOMExceptionEnabler oom_exception; - MG_ASSERT(from->transaction_ == to->transaction_, - "VertexAccessors must be from the same transaction when creating " - "an edge!"); - MG_ASSERT(from->transaction_ == &transaction_, - "VertexAccessors must be from the same transaction in when " - "creating an edge!"); - - auto *from_vertex = from->vertex_; - auto *to_vertex = to->vertex_; - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; - } - - // NOTE: When we update the next `edge_id_` here we perform a RMW - // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue - // because this function is only called from the replication delta applier - // that runs single-threadedly and while this instance is set-up to apply - // threads (it is the replica), it is guaranteed that no other writes are - // possible. - storage_->edge_id_ = std::max(storage_->edge_id_, gid.AsUint() + 1); - - EdgeRef edge(gid); - if (config_.properties_on_edges) { - auto acc = storage_->edges_.access(); - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Edge(gid, delta)); - MG_ASSERT(inserted, "The edge must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); - edge = EdgeRef(&*it); - delta->prev.Set(&*it); - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - - // Increment edge count. - ++storage_->edge_count_; - - return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &storage_->indices_, - &storage_->constraints_, config_, storage_->schema_validator_); -} - -Result> Storage::Accessor::DeleteEdge(EdgeAccessor *edge) { - MG_ASSERT(edge->transaction_ == &transaction_, - "EdgeAccessor must be from the same transaction as the storage " - "accessor when deleting an edge!"); - auto edge_ref = edge->edge_; - auto edge_type = edge->edge_type_; - - if (config_.properties_on_edges) { - auto *edge_ptr = edge_ref.ptr; - - if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; - - if (edge_ptr->deleted) return std::optional{}; - } - - auto *from_vertex = edge->from_vertex_; - auto *to_vertex = edge->to_vertex_; - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); - } - - auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { - std::tuple link(edge_type, vertex, edge_ref); - auto it = std::find(edges->begin(), edges->end(), link); - if (config_.properties_on_edges) { - MG_ASSERT(it != edges->end(), "Invalid database state!"); - } else if (it == edges->end()) { - return false; - } - std::swap(*it, *edges->rbegin()); - edges->pop_back(); - return true; - }; - - auto op1 = delete_edge_from_storage(to_vertex, &from_vertex->out_edges); - auto op2 = delete_edge_from_storage(from_vertex, &to_vertex->in_edges); - - if (config_.properties_on_edges) { - MG_ASSERT((op1 && op2), "Invalid database state!"); - } else { - MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!"); - if (!op1 && !op2) { - // The edge is already deleted. - return std::optional{}; - } - } - - if (config_.properties_on_edges) { - auto *edge_ptr = edge_ref.ptr; - CreateAndLinkDelta(&transaction_, edge_ptr, Delta::RecreateObjectTag()); - edge_ptr->deleted = true; - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); - - // Decrement edge count. - --storage_->edge_count_; - - return std::make_optional(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, - &storage_->indices_, &storage_->constraints_, config_, - storage_->schema_validator_, true); -} - -const std::string &Storage::Accessor::LabelToName(LabelId label) const { return storage_->LabelToName(label); } - -const std::string &Storage::Accessor::PropertyToName(PropertyId property) const { - return storage_->PropertyToName(property); -} - -const std::string &Storage::Accessor::EdgeTypeToName(EdgeTypeId edge_type) const { - return storage_->EdgeTypeToName(edge_type); -} - -LabelId Storage::Accessor::NameToLabel(const std::string_view name) { return storage_->NameToLabel(name); } - -PropertyId Storage::Accessor::NameToProperty(const std::string_view name) { return storage_->NameToProperty(name); } - -EdgeTypeId Storage::Accessor::NameToEdgeType(const std::string_view name) { return storage_->NameToEdgeType(name); } - -void Storage::Accessor::AdvanceCommand() { ++transaction_.command_id; } - -utils::BasicResult Storage::Accessor::Commit( - const std::optional desired_commit_timestamp) { - MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); - MG_ASSERT(!transaction_.must_abort, "The transaction can't be committed!"); - - if (transaction_.deltas.empty()) { - // We don't have to update the commit timestamp here because no one reads - // it. - storage_->commit_log_->MarkFinished(transaction_.start_timestamp); - } else { - // Validate that existence constraints are satisfied for all modified - // vertices. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - // No need to take any locks here because we modified this vertex and no - // one else can touch it until we commit. - auto validation_result = ValidateExistenceConstraints(*prev.vertex, storage_->constraints_); - if (validation_result) { - Abort(); - return {*validation_result}; - } - } - - // Result of validating the vertex against unique constraints. It has to be - // declared outside of the critical section scope because its value is - // tested for Abort call which has to be done out of the scope. - std::optional unique_constraint_violation; - - // Save these so we can mark them used in the commit log. - uint64_t start_timestamp = transaction_.start_timestamp; - - commit_timestamp_.emplace(storage_->CommitTimestamp(desired_commit_timestamp)); - - // Before committing and validating vertices against unique constraints, - // we have to update unique constraints with the vertices that are going - // to be validated/committed. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - storage_->constraints_.unique_constraints.UpdateBeforeCommit(prev.vertex, transaction_); - } - - // Validate that unique constraints are satisfied for all modified - // vertices. - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) { - continue; - } - - // No need to take any locks here because we modified this vertex and no - // one else can touch it until we commit. - unique_constraint_violation = - storage_->constraints_.unique_constraints.Validate(*prev.vertex, transaction_, *commit_timestamp_); - if (unique_constraint_violation) { - break; - } - } - - if (!unique_constraint_violation) { - // Write transaction to WAL while holding the engine lock to make sure - // that committed transactions are sorted by the commit timestamp in the - // WAL files. We supply the new commit timestamp to the function so that - // it knows what will be the final commit timestamp. The WAL must be - // written before actually committing the transaction (before setting - // the commit timestamp) so that no other transaction can see the - // modifications before they are written to disk. - // Replica can log only the write transaction received from Main - // so the Wal files are consistent - if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { - storage_->AppendToWal(transaction_, *commit_timestamp_); - } - - // TODO(antaljanosbenjamin): Figure out: - // 1. How the committed transactions are sorted in `committed_transactions_` - // 2. Why it was necessary to lock `committed_transactions_` when it was not accessed at all - // TODO: Update all deltas to have a local copy of the commit timestamp - MG_ASSERT(transaction_.commit_timestamp != nullptr, "Invalid database state!"); - transaction_.commit_timestamp->store(*commit_timestamp_, std::memory_order_release); - // Replica can only update the last commit timestamp with - // the commits received from main. - if (storage_->replication_role_ == ReplicationRole::MAIN || desired_commit_timestamp.has_value()) { - // Update the last commit timestamp - storage_->last_commit_timestamp_ = *commit_timestamp_; - } - - storage_->commit_log_->MarkFinished(start_timestamp); - } - - if (unique_constraint_violation) { - Abort(); - return {*unique_constraint_violation}; - } - } - is_transaction_active_ = false; - - return {}; -} - -void Storage::Accessor::Abort() { - MG_ASSERT(is_transaction_active_, "The transaction is already terminated!"); - - for (const auto &delta : transaction_.deltas) { - auto prev = delta.prev.Get(); - switch (prev.type) { - case PreviousPtr::Type::VERTEX: { - auto *vertex = prev.vertex; - Delta *current = vertex->delta; - while (current != nullptr && - current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { - switch (current->action) { - case Delta::Action::REMOVE_LABEL: { - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); - MG_ASSERT(it != vertex->labels.end(), "Invalid database state!"); - std::swap(*it, *vertex->labels.rbegin()); - vertex->labels.pop_back(); - break; - } - case Delta::Action::ADD_LABEL: { - auto it = std::find(vertex->labels.begin(), vertex->labels.end(), current->label); - MG_ASSERT(it == vertex->labels.end(), "Invalid database state!"); - vertex->labels.push_back(current->label); - break; - } - case Delta::Action::SET_PROPERTY: { - vertex->properties.SetProperty(current->property.key, current->property.value); - break; - } - case Delta::Action::ADD_IN_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); - MG_ASSERT(it == vertex->in_edges.end(), "Invalid database state!"); - vertex->in_edges.push_back(link); - break; - } - case Delta::Action::ADD_OUT_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); - MG_ASSERT(it == vertex->out_edges.end(), "Invalid database state!"); - vertex->out_edges.push_back(link); - // Increment edge count. We only increment the count here because - // the information in `ADD_IN_EDGE` and `Edge/RECREATE_OBJECT` is - // redundant. Also, `Edge/RECREATE_OBJECT` isn't available when - // edge properties are disabled. - ++storage_->edge_count_; - break; - } - case Delta::Action::REMOVE_IN_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); - MG_ASSERT(it != vertex->in_edges.end(), "Invalid database state!"); - std::swap(*it, *vertex->in_edges.rbegin()); - vertex->in_edges.pop_back(); - break; - } - case Delta::Action::REMOVE_OUT_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; - auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); - MG_ASSERT(it != vertex->out_edges.end(), "Invalid database state!"); - std::swap(*it, *vertex->out_edges.rbegin()); - vertex->out_edges.pop_back(); - // Decrement edge count. We only decrement the count here because - // the information in `REMOVE_IN_EDGE` and `Edge/DELETE_OBJECT` is - // redundant. Also, `Edge/DELETE_OBJECT` isn't available when edge - // properties are disabled. - --storage_->edge_count_; - break; - } - case Delta::Action::DELETE_OBJECT: { - vertex->deleted = true; - InsertVertexPKIntoList(storage_->deleted_vertices_, vertex->keys.Keys()); - break; - } - case Delta::Action::RECREATE_OBJECT: { - vertex->deleted = false; - break; - } - } - current = current->next.load(std::memory_order_acquire); - } - vertex->delta = current; - if (current != nullptr) { - current->prev.Set(vertex); - } - - break; - } - case PreviousPtr::Type::EDGE: { - auto *edge = prev.edge; - Delta *current = edge->delta; - while (current != nullptr && - current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { - switch (current->action) { - case Delta::Action::SET_PROPERTY: { - edge->properties.SetProperty(current->property.key, current->property.value); - break; - } - case Delta::Action::DELETE_OBJECT: { - edge->deleted = true; - storage_->deleted_edges_.push_back(edge->gid); - break; - } - case Delta::Action::RECREATE_OBJECT: { - edge->deleted = false; - break; - } - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_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: { - LOG_FATAL("Invalid database state!"); - break; - } - } - current = current->next.load(std::memory_order_acquire); - } - edge->delta = current; - if (current != nullptr) { - current->prev.Set(edge); - } - - break; - } - case PreviousPtr::Type::DELTA: - // pointer probably couldn't be set because allocation failed - case PreviousPtr::Type::NULLPTR: - break; - } - } - - { - uint64_t mark_timestamp = storage_->timestamp_; - - // Release engine lock because we don't have to hold it anymore and - // emplace back could take a long time. - storage_->garbage_undo_buffers_.emplace_back(mark_timestamp, std::move(transaction_.deltas)); - } - - storage_->commit_log_->MarkFinished(transaction_.start_timestamp); - is_transaction_active_ = false; -} - -void Storage::Accessor::FinalizeTransaction() { - if (commit_timestamp_) { - storage_->commit_log_->MarkFinished(*commit_timestamp_); - storage_->committed_transactions_.emplace_back(std::move(transaction_)); - commit_timestamp_.reset(); - } -} - -const std::string &Storage::LabelToName(LabelId label) const { return name_id_mapper_.IdToName(label.AsUint()); } - -const std::string &Storage::PropertyToName(PropertyId property) const { - return name_id_mapper_.IdToName(property.AsUint()); -} - -const std::string &Storage::EdgeTypeToName(EdgeTypeId edge_type) const { - return name_id_mapper_.IdToName(edge_type.AsUint()); -} - -LabelId Storage::NameToLabel(const std::string_view name) { return LabelId::FromUint(name_id_mapper_.NameToId(name)); } - -PropertyId Storage::NameToProperty(const std::string_view name) { - return PropertyId::FromUint(name_id_mapper_.NameToId(name)); -} - -EdgeTypeId Storage::NameToEdgeType(const std::string_view name) { - return EdgeTypeId::FromUint(name_id_mapper_.NameToId(name)); -} - -bool Storage::CreateIndex(LabelId label, const std::optional desired_commit_timestamp) { - // TODO Fix Index - return false; - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return true; -} - -bool Storage::CreateIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - // TODO Fix Index - // if (!indices_.label_property_index.CreateIndex(label, property, labelspace.access())) return false; - return false; - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return true; -} - -bool Storage::DropIndex(LabelId label, const std::optional desired_commit_timestamp) { - if (!indices_.label_index.DropIndex(label)) return false; - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_DROP, label, {}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return true; -} - -bool Storage::DropIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - if (!indices_.label_property_index.DropIndex(label, property)) return false; - // For a description why using `timestamp_` is correct, see - // `CreateIndex(LabelId label)`. - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_DROP, label, {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return true; -} - -IndicesInfo Storage::ListAllIndices() const { - return {indices_.label_index.ListIndices(), indices_.label_property_index.ListIndices()}; -} - -utils::BasicResult Storage::CreateExistenceConstraint( - LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - // TODO Fix constraints - // auto ret = ::memgraph::storage::v3::CreateExistenceConstraint(&constraints_, label, property, vertices_.access()); - // if (ret.HasError() || !ret.GetValue()) return ret; - return false; - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_CREATE, label, {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return true; -} - -bool Storage::DropExistenceConstraint(LabelId label, PropertyId property, - const std::optional desired_commit_timestamp) { - if (!::memgraph::storage::v3::DropExistenceConstraint(&constraints_, label, property)) return false; - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::EXISTENCE_CONSTRAINT_DROP, label, {property}, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return true; -} - -utils::BasicResult Storage::CreateUniqueConstraint( - LabelId label, const std::set &properties, const std::optional desired_commit_timestamp) { - // TODO Fix constraints - // auto ret = constraints_.unique_constraints.CreateConstraint(label, properties, vertices_.access()); - // if (ret.HasError() || ret.GetValue() != UniqueConstraints::CreationStatus::SUCCESS) { - // return ret; - // } - return UniqueConstraints::CreationStatus::ALREADY_EXISTS; - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_CREATE, label, properties, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return UniqueConstraints::CreationStatus::SUCCESS; -} - -UniqueConstraints::DeletionStatus Storage::DropUniqueConstraint( - LabelId label, const std::set &properties, const std::optional desired_commit_timestamp) { - auto ret = constraints_.unique_constraints.DropConstraint(label, properties); - if (ret != UniqueConstraints::DeletionStatus::SUCCESS) { - return ret; - } - const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); - AppendToWal(durability::StorageGlobalOperation::UNIQUE_CONSTRAINT_DROP, label, properties, commit_timestamp); - commit_log_->MarkFinished(commit_timestamp); - last_commit_timestamp_ = commit_timestamp; - return UniqueConstraints::DeletionStatus::SUCCESS; -} - -const SchemaValidator &Storage::Accessor::GetSchemaValidator() const { return storage_->schema_validator_; } - -ConstraintsInfo Storage::ListAllConstraints() const { - return {ListExistenceConstraints(constraints_), constraints_.unique_constraints.ListConstraints()}; -} - -SchemasInfo Storage::ListAllSchemas() const { return {schemas_.ListSchemas()}; } - -const Schemas::Schema *Storage::GetSchema(const LabelId primary_label) const { - return schemas_.GetSchema(primary_label); -} - -bool Storage::CreateSchema(const LabelId primary_label, const std::vector &schemas_types) { - return schemas_.CreateSchema(primary_label, schemas_types); -} - -bool Storage::DropSchema(const LabelId primary_label) { return schemas_.DropSchema(primary_label); } - -StorageInfo Storage::GetInfo() const { - auto vertex_count = vertices_.size(); - double average_degree = 0.0; - if (vertex_count) { - average_degree = 2.0 * static_cast(edge_count_) / static_cast(vertex_count); - } - return {vertex_count, edge_count_, average_degree, utils::GetMemoryUsage(), - utils::GetDirDiskUsage(config_.durability.storage_directory)}; -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, View view) { - return VerticesIterable(storage_->indices_.label_index.Vertices(label, view, &transaction_)); -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, View view) { - return VerticesIterable(storage_->indices_.label_property_index.Vertices( - label, property, std::nullopt, std::nullopt, view, &transaction_, storage_->schema_validator_)); -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value, - View view) { - return VerticesIterable(storage_->indices_.label_property_index.Vertices( - label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_, - storage_->schema_validator_)); -} - -VerticesIterable Storage::Accessor::Vertices(LabelId label, PropertyId property, - const std::optional> &lower_bound, - const std::optional> &upper_bound, View view) { - return VerticesIterable(storage_->indices_.label_property_index.Vertices( - label, property, lower_bound, upper_bound, view, &transaction_, storage_->schema_validator_)); -} - -Transaction Storage::CreateTransaction(IsolationLevel isolation_level) { - // We acquire the transaction engine lock here because we access (and - // modify) the transaction engine variables (`transaction_id` and - // `timestamp`) below. - uint64_t transaction_id{0}; - uint64_t start_timestamp{0}; - - transaction_id = transaction_id_++; - // Replica should have only read queries and the write queries - // can come from main instance with any past timestamp. - // To preserve snapshot isolation we set the start timestamp - // of any query on replica to the last commited transaction - // which is timestamp_ as only commit of transaction with writes - // can change the value of it. - if (replication_role_ == ReplicationRole::REPLICA) { - start_timestamp = timestamp_; - } else { - start_timestamp = timestamp_++; - } - - return {transaction_id, start_timestamp, isolation_level}; -} - -// `force` means there are no active transactions, so everything can be deleted without worrying about removing some -// data that is used by an active transaction -template -void Storage::CollectGarbage() { - if constexpr (force) { - // TODO(antaljanosbenjamin): figure out whether is there any active transaction or not (probably accessors should - // increment/decrement a counter). If there are no transactions, then garbage collection can be forced - CollectGarbage(); - return; - } - - // Garbage collection must be performed in two phases. In the first phase, - // deltas that won't be applied by any transaction anymore are unlinked from - // the version chains. They cannot be deleted immediately, because there - // might be a transaction that still needs them to terminate the version - // chain traversal. They are instead marked for deletion and will be deleted - // in the second GC phase in this GC iteration or some of the following - // ones. - - uint64_t oldest_active_start_timestamp = commit_log_->OldestActive(); - // We don't move undo buffers of unlinked transactions to garbage_undo_buffers - // list immediately, because we would have to repeatedly take - // garbage_undo_buffers lock. - std::list>> unlinked_undo_buffers; - - // We will only free vertices deleted up until now in this GC cycle, and we - // will do it after cleaning-up the indices. That way we are sure that all - // vertices that appear in an index also exist in main storage. - - // Flag that will be used to determine whether the Index GC should be run. It - // should be run when there were any items that were cleaned up (there were - // updates between this run of the GC and the previous run of the GC). This - // eliminates high CPU usage when the GC doesn't have to clean up anything. - bool run_index_cleanup = !committed_transactions_.empty() || !garbage_undo_buffers_.empty(); - - while (true) { - // We don't want to hold the lock on commited transactions for too long, - // because that prevents other transactions from committing. - Transaction *transaction{nullptr}; - { - if (committed_transactions_.empty()) { - break; - } - transaction = &committed_transactions_.front(); - } - - auto commit_timestamp = transaction->commit_timestamp->load(std::memory_order_acquire); - if (commit_timestamp >= oldest_active_start_timestamp) { - break; - } - - // When unlinking a delta which is the first delta in its version chain, - // special care has to be taken to avoid the following race condition: - // - // [Vertex] --> [Delta A] - // - // GC thread: Delta A is the first in its chain, it must be unlinked from - // vertex and marked for deletion - // TX thread: Update vertex and add Delta B with Delta A as next - // - // [Vertex] --> [Delta B] <--> [Delta A] - // - // GC thread: Unlink delta from Vertex - // - // [Vertex] --> (nullptr) - // - // When processing a delta that is the first one in its chain, we - // obtain the corresponding vertex or edge lock, and then verify that this - // delta still is the first in its chain. - // When processing a delta that is in the middle of the chain we only - // process the final delta of the given transaction in that chain. We - // determine the owner of the chain (either a vertex or an edge), obtain the - // corresponding lock, and then verify that this delta is still in the same - // position as it was before taking the lock. - // - // Even though the delta chain is lock-free (both `next` and `prev`) the - // chain should not be modified without taking the lock from the object that - // owns the chain (either a vertex or an edge). Modifying the chain without - // taking the lock will cause subtle race conditions that will leave the - // chain in a broken state. - // The chain can be only read without taking any locks. - - for (Delta &delta : transaction->deltas) { - while (true) { - auto prev = delta.prev.Get(); - switch (prev.type) { - case PreviousPtr::Type::VERTEX: { - Vertex *vertex = prev.vertex; - if (vertex->delta != &delta) { - // Something changed, we're not the first delta in the chain - // anymore. - continue; - } - vertex->delta = nullptr; - if (vertex->deleted) { - InsertVertexPKIntoList(deleted_vertices_, vertex->keys.Keys()); - } - break; - } - case PreviousPtr::Type::EDGE: { - Edge *edge = prev.edge; - if (edge->delta != &delta) { - // Something changed, we're not the first delta in the chain - // anymore. - continue; - } - edge->delta = nullptr; - if (edge->deleted) { - deleted_edges_.push_back(edge->gid); - } - break; - } - case PreviousPtr::Type::DELTA: { - if (prev.delta->timestamp->load(std::memory_order_acquire) == commit_timestamp) { - // The delta that is newer than this one is also a delta from this - // transaction. We skip the current delta and will remove it as a - // part of the suffix later. - break; - } - { - // We need to find the parent object in order to be able to use - // its lock. - auto parent = prev; - while (parent.type == PreviousPtr::Type::DELTA) { - parent = parent.delta->prev.Get(); - } - switch (parent.type) { - case PreviousPtr::Type::VERTEX: - case PreviousPtr::Type::EDGE: - break; - case PreviousPtr::Type::DELTA: - case PreviousPtr::Type::NULLPTR: - LOG_FATAL("Invalid database state!"); - } - } - Delta *prev_delta = prev.delta; - prev_delta->next.store(nullptr, std::memory_order_release); - break; - } - case PreviousPtr::Type::NULLPTR: { - LOG_FATAL("Invalid pointer!"); - } - } - break; - } - } - - unlinked_undo_buffers.emplace_back(0, std::move(transaction->deltas)); - committed_transactions_.pop_front(); - } - - // After unlinking deltas from vertices, we refresh the indices. That way - // we're sure that none of the vertices from `current_deleted_vertices` - // appears in an index, and we can safely remove the from the main storage - // after the last currently active transaction is finished. - if (run_index_cleanup) { - // This operation is very expensive as it traverses through all of the items - // in every index every time. - RemoveObsoleteEntries(&indices_, oldest_active_start_timestamp); - constraints_.unique_constraints.RemoveObsoleteEntries(oldest_active_start_timestamp); - } - - { - uint64_t mark_timestamp = timestamp_; - for (auto &[timestamp, undo_buffer] : unlinked_undo_buffers) { - timestamp = mark_timestamp; - } - garbage_undo_buffers_.splice(garbage_undo_buffers_.end(), unlinked_undo_buffers); - - for (const auto &vertex : deleted_vertices_) { - garbage_vertices_.emplace_back(mark_timestamp, vertex); - } - } - - // if force is set to true we can simply delete all the leftover undos because - // no transaction is active - if constexpr (force) { - garbage_undo_buffers_.clear(); - } else { - while (!garbage_undo_buffers_.empty() && garbage_undo_buffers_.front().first <= oldest_active_start_timestamp) { - garbage_undo_buffers_.pop_front(); - } - } - - { - auto vertex_acc = vertices_.access(); - if constexpr (force) { - // if force is set to true, then we have unique_lock and no transactions are active - // so we can clean all of the deleted vertices - while (!garbage_vertices_.empty()) { - MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); - garbage_vertices_.pop_front(); - } - } else { - while (!garbage_vertices_.empty() && garbage_vertices_.front().first < oldest_active_start_timestamp) { - MG_ASSERT(vertex_acc.remove(garbage_vertices_.front().second), "Invalid database state!"); - garbage_vertices_.pop_front(); - } - } - } - { - auto edge_acc = edges_.access(); - for (auto edge : deleted_edges_) { - MG_ASSERT(edge_acc.remove(edge), "Invalid database state!"); - } - } -} - -// tell the linker he can find the CollectGarbage definitions here -template void Storage::CollectGarbage(); -template void Storage::CollectGarbage(); - -bool Storage::InitializeWalFile() { - if (config_.durability.snapshot_wal_mode != Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT_WITH_WAL) - return false; - if (!wal_file_) { - wal_file_.emplace(wal_directory_, uuid_, epoch_id_, config_.items, &name_id_mapper_, wal_seq_num_++, - &file_retainer_); - } - return true; -} - -void Storage::FinalizeWalFile() { - ++wal_unsynced_transactions_; - if (wal_unsynced_transactions_ >= config_.durability.wal_file_flush_every_n_tx) { - wal_file_->Sync(); - wal_unsynced_transactions_ = 0; - } - if (wal_file_->GetSize() / 1024 >= config_.durability.wal_file_size_kibibytes) { - wal_file_->FinalizeWal(); - wal_file_ = std::nullopt; - wal_unsynced_transactions_ = 0; - } else { - // Try writing the internal buffer if possible, if not - // the data should be written as soon as it's possible - // (triggered by the new transaction commit, or some - // reading thread EnabledFlushing) - wal_file_->TryFlushing(); - } -} - -void Storage::AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp) { - if (!InitializeWalFile()) return; - // Traverse deltas and append them to the WAL file. - // A single transaction will always be contained in a single WAL file. - auto current_commit_timestamp = transaction.commit_timestamp->load(std::memory_order_acquire); - - if (replication_role_ == ReplicationRole::MAIN) { - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->StartTransactionReplication(wal_file_->SequenceNumber()); - } - }); - } - - // Helper lambda that traverses the delta chain on order to find the first - // delta that should be processed and then appends all discovered deltas. - auto find_and_apply_deltas = [&](const auto *delta, const auto &parent, auto filter) { - while (true) { - auto *older = delta->next.load(std::memory_order_acquire); - if (older == nullptr || older->timestamp->load(std::memory_order_acquire) != current_commit_timestamp) break; - delta = older; - } - while (true) { - if (filter(delta->action)) { - wal_file_->AppendDelta(*delta, parent, final_commit_timestamp); - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->IfStreamingTransaction( - [&](auto &stream) { stream.AppendDelta(*delta, parent, final_commit_timestamp); }); - } - }); - } - auto prev = delta->prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::DELTA) break; - delta = prev.delta; - } - }; - - // The deltas are ordered correctly in the `transaction.deltas` buffer, but we - // don't traverse them in that order. That is because for each delta we need - // information about the vertex or edge they belong to and that information - // isn't stored in the deltas themselves. In order to find out information - // about the corresponding vertex or edge it is necessary to traverse the - // delta chain for each delta until a vertex or edge is encountered. This - // operation is very expensive as the chain grows. - // Instead, we traverse the edges until we find a vertex or edge and traverse - // their delta chains. This approach has a drawback because we lose the - // correct order of the operations. Because of that, we need to traverse the - // deltas several times and we have to manually ensure that the stored deltas - // will be ordered correctly. - - // 1. Process all Vertex deltas and store all operations that create vertices - // and modify vertex data. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::DELETE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - return true; - - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - // 2. Process all Vertex deltas and store all operations that create edges. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::REMOVE_OUT_EDGE: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - return false; - } - }); - } - // 3. Process all Edge deltas and store all operations that modify edge data. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::EDGE) continue; - find_and_apply_deltas(&delta, *prev.edge, [](auto action) { - switch (action) { - case Delta::Action::SET_PROPERTY: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - 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: - return false; - } - }); - } - // 4. Process all Vertex deltas and store all operations that delete edges. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::ADD_OUT_EDGE: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::RECREATE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - // 5. Process all Vertex deltas and store all operations that delete vertices. - for (const auto &delta : transaction.deltas) { - auto prev = delta.prev.Get(); - MG_ASSERT(prev.type != PreviousPtr::Type::NULLPTR, "Invalid pointer!"); - if (prev.type != PreviousPtr::Type::VERTEX) continue; - find_and_apply_deltas(&delta, *prev.vertex, [](auto action) { - switch (action) { - case Delta::Action::RECREATE_OBJECT: - return true; - - case Delta::Action::DELETE_OBJECT: - case Delta::Action::SET_PROPERTY: - case Delta::Action::ADD_LABEL: - case Delta::Action::REMOVE_LABEL: - case Delta::Action::ADD_IN_EDGE: - case Delta::Action::ADD_OUT_EDGE: - case Delta::Action::REMOVE_IN_EDGE: - case Delta::Action::REMOVE_OUT_EDGE: - return false; - } - }); - } - - // Add a delta that indicates that the transaction is fully written to the WAL - // file. - wal_file_->AppendTransactionEnd(final_commit_timestamp); - - FinalizeWalFile(); - - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->IfStreamingTransaction([&](auto &stream) { stream.AppendTransactionEnd(final_commit_timestamp); }); - client->FinalizeTransactionReplication(); - } - }); -} - -void Storage::AppendToWal(durability::StorageGlobalOperation operation, LabelId label, - const std::set &properties, uint64_t final_commit_timestamp) { - if (!InitializeWalFile()) return; - wal_file_->AppendOperation(operation, label, properties, final_commit_timestamp); - { - if (replication_role_ == ReplicationRole::MAIN) { - replication_clients_.WithLock([&](auto &clients) { - for (auto &client : clients) { - client->StartTransactionReplication(wal_file_->SequenceNumber()); - client->IfStreamingTransaction( - [&](auto &stream) { stream.AppendOperation(operation, label, properties, final_commit_timestamp); }); - client->FinalizeTransactionReplication(); - } - }); - } - } - FinalizeWalFile(); -} - -utils::BasicResult Storage::CreateSnapshot() { - if (replication_role_ != ReplicationRole::MAIN) { - return CreateSnapshotError::DisabledForReplica; - } - - // Create the transaction used to create the snapshot. - auto transaction = CreateTransaction(IsolationLevel::SNAPSHOT_ISOLATION); - - // Create snapshot. - // durability::CreateSnapshot(&transaction, snapshot_directory_, wal_directory_, - // config_.durability.snapshot_retention_count, &vertices_, &edges_, - // &name_id_mapper_, &indices_, &constraints_, config_.items, schema_validator_, - // uuid_, epoch_id_, epoch_history_, &file_retainer_); - - // Finalize snapshot transaction. - commit_log_->MarkFinished(transaction.start_timestamp); - return {}; -} - -bool Storage::LockPath() { - auto locker_accessor = global_locker_.Access(); - return locker_accessor.AddPath(config_.durability.storage_directory); -} - -bool Storage::UnlockPath() { - { - auto locker_accessor = global_locker_.Access(); - if (!locker_accessor.RemovePath(config_.durability.storage_directory)) { - return false; - } - } - - // We use locker accessor in seperate scope so we don't produce deadlock - // after we call clean queue. - file_retainer_.CleanQueue(); - return true; -} - -void Storage::FreeMemory() { - CollectGarbage(); - - // SkipList is already threadsafe - vertices_.run_gc(); - edges_.run_gc(); - indices_.label_index.RunGC(); - indices_.label_property_index.RunGC(); -} - -uint64_t Storage::CommitTimestamp(const std::optional desired_commit_timestamp) { - if (!desired_commit_timestamp) { - return timestamp_++; - } - timestamp_ = std::max(timestamp_, *desired_commit_timestamp + 1); - return *desired_commit_timestamp; -} - -bool Storage::SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config) { - // We don't want to restart the server if we're already a REPLICA - if (replication_role_ == ReplicationRole::REPLICA) { - return false; - } - - replication_server_ = std::make_unique(this, std::move(endpoint), config); - - replication_role_ = ReplicationRole::REPLICA; - return true; -} - -bool Storage::SetMainReplicationRole() { - // We don't want to generate new epoch_id and do the - // cleanup if we're already a MAIN - if (replication_role_ == ReplicationRole::MAIN) { - return false; - } - - // Main instance does not need replication server - // This should be always called first so we finalize everything - replication_server_.reset(nullptr); - - if (wal_file_) { - wal_file_->FinalizeWal(); - wal_file_.reset(); - } - - // Generate new epoch id and save the last one to the history. - if (epoch_history_.size() == kEpochHistoryRetention) { - epoch_history_.pop_front(); - } - epoch_history_.emplace_back(std::move(epoch_id_), last_commit_timestamp_); - epoch_id_ = utils::GenerateUUID(); - - replication_role_ = ReplicationRole::MAIN; - return true; -} - -utils::BasicResult Storage::RegisterReplica( - std::string name, io::network::Endpoint endpoint, const replication::ReplicationMode replication_mode, - const replication::ReplicationClientConfig &config) { - MG_ASSERT(replication_role_ == ReplicationRole::MAIN, "Only main instance can register a replica!"); - - const bool name_exists = replication_clients_.WithLock([&](auto &clients) { - return std::any_of(clients.begin(), clients.end(), [&name](const auto &client) { return client->Name() == name; }); - }); - - if (name_exists) { - return RegisterReplicaError::NAME_EXISTS; - } - - const auto end_point_exists = replication_clients_.WithLock([&endpoint](auto &clients) { - return std::any_of(clients.begin(), clients.end(), - [&endpoint](const auto &client) { return client->Endpoint() == endpoint; }); - }); - - if (end_point_exists) { - return RegisterReplicaError::END_POINT_EXISTS; - } - - MG_ASSERT(replication_mode == replication::ReplicationMode::SYNC || !config.timeout, - "Only SYNC mode can have a timeout set"); - - auto client = std::make_unique(std::move(name), this, endpoint, replication_mode, config); - if (client->State() == replication::ReplicaState::INVALID) { - return RegisterReplicaError::CONNECTION_FAILED; - } - - return replication_clients_.WithLock([&](auto &clients) -> utils::BasicResult { - // Another thread could have added a client with same name while - // we were connecting to this client. - if (std::any_of(clients.begin(), clients.end(), - [&](const auto &other_client) { return client->Name() == other_client->Name(); })) { - return RegisterReplicaError::NAME_EXISTS; - } - - if (std::any_of(clients.begin(), clients.end(), - [&client](const auto &other_client) { return client->Endpoint() == other_client->Endpoint(); })) { - return RegisterReplicaError::END_POINT_EXISTS; - } - - clients.push_back(std::move(client)); - return {}; - }); -} - -bool Storage::UnregisterReplica(const std::string_view name) { - MG_ASSERT(replication_role_ == ReplicationRole::MAIN, "Only main instance can unregister a replica!"); - return replication_clients_.WithLock([&](auto &clients) { - return std::erase_if(clients, [&](const auto &client) { return client->Name() == name; }); - }); -} - -std::optional Storage::GetReplicaState(const std::string_view name) { - return replication_clients_.WithLock([&](auto &clients) -> std::optional { - const auto client_it = - std::find_if(clients.cbegin(), clients.cend(), [name](auto &client) { return client->Name() == name; }); - if (client_it == clients.cend()) { - return std::nullopt; - } - return (*client_it)->State(); - }); -} - -ReplicationRole Storage::GetReplicationRole() const { return replication_role_; } - -std::vector Storage::ReplicasInfo() { - return replication_clients_.WithLock([](auto &clients) { - std::vector replica_info; - replica_info.reserve(clients.size()); - std::transform(clients.begin(), clients.end(), std::back_inserter(replica_info), - [](const auto &client) -> ReplicaInfo { - return {client->Name(), client->Mode(), client->Timeout(), client->Endpoint(), client->State()}; - }); - return replica_info; - }); -} - -void Storage::SetIsolationLevel(IsolationLevel isolation_level) { isolation_level_ = isolation_level; } +Storage::Storage(Config config) : config_{config} {} } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/storage.hpp b/src/storage/v3/storage.hpp index ed884c86e..9305ba707 100644 --- a/src/storage/v3/storage.hpp +++ b/src/storage/v3/storage.hpp @@ -11,609 +11,24 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include #include -#include "io/network/endpoint.hpp" -#include "kvstore/kvstore.hpp" -#include "storage/v3/commit_log.hpp" -#include "storage/v3/config.hpp" -#include "storage/v3/constraints.hpp" -#include "storage/v3/durability/metadata.hpp" -#include "storage/v3/durability/wal.hpp" -#include "storage/v3/edge.hpp" -#include "storage/v3/edge_accessor.hpp" -#include "storage/v3/id_types.hpp" -#include "storage/v3/indices.hpp" -#include "storage/v3/isolation_level.hpp" -#include "storage/v3/key_store.hpp" -#include "storage/v3/lexicographically_ordered_vertex.hpp" -#include "storage/v3/mvcc.hpp" -#include "storage/v3/name_id_mapper.hpp" -#include "storage/v3/property_value.hpp" -#include "storage/v3/result.hpp" -#include "storage/v3/schema_validator.hpp" -#include "storage/v3/schemas.hpp" -#include "storage/v3/transaction.hpp" -#include "storage/v3/vertex.hpp" -#include "storage/v3/vertex_accessor.hpp" -#include "storage/v3/vertices_skip_list.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" +#include -/// REPLICATION /// -#include "rpc/server.hpp" -#include "storage/v3/replication/config.hpp" -#include "storage/v3/replication/enums.hpp" -#include "storage/v3/replication/rpc.hpp" -#include "storage/v3/replication/serialization.hpp" +#include "storage/v3/shard.hpp" namespace memgraph::storage::v3 { -// 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 { - VerticesSkipList::Accessor vertices_accessor_; - Transaction *transaction_; - View view_; - Indices *indices_; - Constraints *constraints_; - Config::Items config_; - const SchemaValidator *schema_validator_; - const Schemas *schemas_; - std::optional vertex_; - +class Storage { public: - class Iterator final { - AllVerticesIterable *self_; - VerticesSkipList::Iterator it_; - - public: - Iterator(AllVerticesIterable *self, VerticesSkipList::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(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view, - Indices *indices, Constraints *constraints, Config::Items config, - const SchemaValidator &schema_validator) - : vertices_accessor_(std::move(vertices_accessor)), - transaction_(transaction), - view_(view), - indices_(indices), - constraints_(constraints), - config_(config), - schema_validator_{&schema_validator} {} - - Iterator begin() { return {this, vertices_accessor_.begin()}; } - Iterator end() { return {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 }; - - Type type_; - union { - AllVerticesIterable all_vertices_; - LabelIndex::Iterable vertices_by_label_; - LabelPropertyIndex::Iterable vertices_by_label_property_; - }; - - public: - explicit VerticesIterable(AllVerticesIterable); - explicit VerticesIterable(LabelIndex::Iterable); - explicit VerticesIterable(LabelPropertyIndex::Iterable); - - VerticesIterable(const VerticesIterable &) = delete; - VerticesIterable &operator=(const VerticesIterable &) = delete; - - VerticesIterable(VerticesIterable &&) noexcept; - VerticesIterable &operator=(VerticesIterable &&) noexcept; - - ~VerticesIterable(); - - class Iterator final { - Type type_; - union { - AllVerticesIterable::Iterator all_it_; - LabelIndex::Iterable::Iterator by_label_it_; - LabelPropertyIndex::Iterable::Iterator by_label_property_it_; - }; - - void Destroy() noexcept; - - public: - explicit Iterator(AllVerticesIterable::Iterator); - explicit Iterator(LabelIndex::Iterable::Iterator); - explicit Iterator(LabelPropertyIndex::Iterable::Iterator); - - Iterator(const Iterator &); - Iterator &operator=(const Iterator &); - - Iterator(Iterator &&) noexcept; - Iterator &operator=(Iterator &&) noexcept; - - ~Iterator(); - - VertexAccessor operator*() const; - - Iterator &operator++(); - - bool operator==(const Iterator &other) const; - bool operator!=(const Iterator &other) const { return !(*this == other); } - }; - - Iterator begin(); - Iterator end(); -}; - -/// Structure used to return information about existing indices in the storage. -struct IndicesInfo { - std::vector label; - std::vector> label_property; -}; - -/// Structure used to return information about existing constraints in the -/// storage. -struct ConstraintsInfo { - std::vector> existence; - std::vector>> unique; -}; - -/// Structure used to return information about existing schemas in the storage -struct SchemasInfo { - Schemas::SchemasList schemas; -}; - -/// Structure used to return information about the storage. -struct StorageInfo { - uint64_t vertex_count; - uint64_t edge_count; - double average_degree; - uint64_t memory_usage; - uint64_t disk_usage; -}; - -enum class ReplicationRole : uint8_t { MAIN, REPLICA }; - -class Storage final { - public: - /// @throw std::system_error - /// @throw std::bad_alloc - explicit Storage(Config config = Config()); - - ~Storage(); - - class Accessor final { - private: - friend class Storage; - - explicit Accessor(Storage *storage, IsolationLevel isolation_level); - - public: - Accessor(const Accessor &) = delete; - Accessor &operator=(const Accessor &) = delete; - Accessor &operator=(Accessor &&other) = delete; - - // NOTE: After the accessor is moved, all objects derived from it (accessors - // and iterators) are *invalid*. You have to get all derived objects again. - Accessor(Accessor &&other) noexcept; - - ~Accessor(); - - /// @throw std::bad_alloc - ResultSchema CreateVertexAndValidate( - LabelId primary_label, const std::vector &labels, - const std::vector> &properties); - - std::optional FindVertex(std::vector primary_key, View view); - - VerticesIterable Vertices(View view) { - return VerticesIterable(AllVerticesIterable(storage_->vertices_.access(), &transaction_, view, - &storage_->indices_, &storage_->constraints_, storage_->config_.items, - storage_->schema_validator_)); - } - - VerticesIterable Vertices(LabelId label, View view); - - VerticesIterable Vertices(LabelId label, PropertyId property, View view); - - VerticesIterable Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view); - - VerticesIterable Vertices(LabelId label, PropertyId property, - const std::optional> &lower_bound, - const std::optional> &upper_bound, View view); - - /// Return approximate number of all vertices in the database. - /// Note that this is always an over-estimate and never an under-estimate. - int64_t ApproximateVertexCount() const { return static_cast(storage_->vertices_.size()); } - - /// Return approximate number of vertices with the given label. - /// Note that this is always an over-estimate and never an under-estimate. - int64_t ApproximateVertexCount(LabelId label) const { - return storage_->indices_.label_index.ApproximateVertexCount(label); - } - - /// Return approximate number of vertices with the given label and property. - /// Note that this is always an over-estimate and never an under-estimate. - int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { - return storage_->indices_.label_property_index.ApproximateVertexCount(label, property); - } - - /// Return approximate number of vertices with the given label and the given - /// value for the given property. Note that this is always an over-estimate - /// and never an under-estimate. - int64_t ApproximateVertexCount(LabelId label, PropertyId property, const PropertyValue &value) const { - return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, value); - } - - /// Return approximate number of vertices with the given label and value for - /// the given property in the range defined by provided upper and lower - /// bounds. - int64_t ApproximateVertexCount(LabelId label, PropertyId property, - const std::optional> &lower, - const std::optional> &upper) const { - return storage_->indices_.label_property_index.ApproximateVertexCount(label, property, lower, upper); - } - - /// @return Accessor to the deleted vertex if a deletion took place, std::nullopt otherwise - /// @throw std::bad_alloc - Result> DeleteVertex(VertexAccessor *vertex); - - /// @return Accessor to the deleted vertex and deleted edges if a deletion took place, std::nullopt otherwise - /// @throw std::bad_alloc - Result>>> DetachDeleteVertex( - VertexAccessor *vertex); - - /// @throw std::bad_alloc - Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type); - - /// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise - /// @throw std::bad_alloc - Result> DeleteEdge(EdgeAccessor *edge); - - const std::string &LabelToName(LabelId label) const; - const std::string &PropertyToName(PropertyId property) const; - const std::string &EdgeTypeToName(EdgeTypeId edge_type) const; - - /// @throw std::bad_alloc if unable to insert a new mapping - LabelId NameToLabel(std::string_view name); - - /// @throw std::bad_alloc if unable to insert a new mapping - PropertyId NameToProperty(std::string_view name); - - /// @throw std::bad_alloc if unable to insert a new mapping - EdgeTypeId NameToEdgeType(std::string_view name); - - bool LabelIndexExists(LabelId label) const { return storage_->indices_.label_index.IndexExists(label); } - - bool LabelPropertyIndexExists(LabelId label, PropertyId property) const { - return storage_->indices_.label_property_index.IndexExists(label, property); - } - - IndicesInfo ListAllIndices() const { - return {storage_->indices_.label_index.ListIndices(), storage_->indices_.label_property_index.ListIndices()}; - } - - ConstraintsInfo ListAllConstraints() const { - return {ListExistenceConstraints(storage_->constraints_), - storage_->constraints_.unique_constraints.ListConstraints()}; - } - - const SchemaValidator &GetSchemaValidator() const; - - SchemasInfo ListAllSchemas() const { return {storage_->schemas_.ListSchemas()}; } - - void AdvanceCommand(); - - /// Commit returns `ConstraintViolation` if the changes made by this - /// transaction violate an existence or unique constraint. In that case the - /// transaction is automatically aborted. Otherwise, void is returned. - /// @throw std::bad_alloc - utils::BasicResult Commit(std::optional desired_commit_timestamp = {}); - - /// @throw std::bad_alloc - void Abort(); - - void FinalizeTransaction(); - - private: - /// @throw std::bad_alloc - VertexAccessor CreateVertex(Gid gid, LabelId primary_label); - - /// @throw std::bad_alloc - Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Gid gid); - - Storage *storage_; - Transaction transaction_; - std::optional commit_timestamp_; - bool is_transaction_active_; - Config::Items config_; - }; - - Accessor Access(std::optional override_isolation_level = {}) { - return Accessor{this, override_isolation_level.value_or(isolation_level_)}; - } - - const std::string &LabelToName(LabelId label) const; - const std::string &PropertyToName(PropertyId property) const; - const std::string &EdgeTypeToName(EdgeTypeId edge_type) const; - - /// @throw std::bad_alloc if unable to insert a new mapping - LabelId NameToLabel(std::string_view name); - - /// @throw std::bad_alloc if unable to insert a new mapping - PropertyId NameToProperty(std::string_view name); - - /// @throw std::bad_alloc if unable to insert a new mapping - EdgeTypeId NameToEdgeType(std::string_view name); - - /// @throw std::bad_alloc - bool CreateIndex(LabelId label, std::optional desired_commit_timestamp = {}); - - /// @throw std::bad_alloc - bool CreateIndex(LabelId label, PropertyId property, std::optional desired_commit_timestamp = {}); - - bool DropIndex(LabelId label, std::optional desired_commit_timestamp = {}); - - bool DropIndex(LabelId label, PropertyId property, std::optional desired_commit_timestamp = {}); - - IndicesInfo ListAllIndices() const; - - /// Creates an existence constraint. Returns true if the constraint was - /// successfully added, false if it already exists and a `ConstraintViolation` - /// if there is an existing vertex violating the constraint. - /// - /// @throw std::bad_alloc - /// @throw std::length_error - utils::BasicResult CreateExistenceConstraint( - LabelId label, PropertyId property, std::optional desired_commit_timestamp = {}); - - /// Removes an existence constraint. Returns true if the constraint was - /// removed, and false if it doesn't exist. - bool DropExistenceConstraint(LabelId label, PropertyId property, - std::optional desired_commit_timestamp = {}); - - /// Creates a unique constraint. In the case of two vertices violating the - /// constraint, it returns `ConstraintViolation`. Otherwise returns a - /// `UniqueConstraints::CreationStatus` enum with the following possibilities: - /// * `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 CreateUniqueConstraint( - LabelId label, const std::set &properties, std::optional desired_commit_timestamp = {}); - - /// Removes a unique constraint. Returns `UniqueConstraints::DeletionStatus` - /// enum with the following possibilities: - /// * `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. - UniqueConstraints::DeletionStatus DropUniqueConstraint(LabelId label, const std::set &properties, - std::optional desired_commit_timestamp = {}); - - ConstraintsInfo ListAllConstraints() const; - - SchemasInfo ListAllSchemas() const; - - const Schemas::Schema *GetSchema(LabelId primary_label) const; - - bool CreateSchema(LabelId primary_label, const std::vector &schemas_types); - - bool DropSchema(LabelId primary_label); - - StorageInfo GetInfo() const; - - bool LockPath(); - bool UnlockPath(); - - bool SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config = {}); - - bool SetMainReplicationRole(); - - enum class RegisterReplicaError : uint8_t { - NAME_EXISTS, - END_POINT_EXISTS, - CONNECTION_FAILED, - COULD_NOT_BE_PERSISTED - }; - - /// @pre The instance should have a MAIN role - /// @pre Timeout can only be set for SYNC replication - utils::BasicResult RegisterReplica( - std::string name, io::network::Endpoint endpoint, replication::ReplicationMode replication_mode, - const replication::ReplicationClientConfig &config = {}); - /// @pre The instance should have a MAIN role - bool UnregisterReplica(std::string_view name); - - std::optional GetReplicaState(std::string_view name); - - ReplicationRole GetReplicationRole() const; - - struct ReplicaInfo { - std::string name; - replication::ReplicationMode mode; - std::optional timeout; - io::network::Endpoint endpoint; - replication::ReplicaState state; - }; - - std::vector ReplicasInfo(); - - void FreeMemory(); - - void SetIsolationLevel(IsolationLevel isolation_level); - - enum class CreateSnapshotError : uint8_t { DisabledForReplica }; - - utils::BasicResult CreateSnapshot(); + explicit Storage(Config config); + // Interface toward shard manipulation + // Shard handler -> will use rsm client 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 - void CollectGarbage(); - - bool InitializeWalFile(); - void FinalizeWalFile(); - - void AppendToWal(const Transaction &transaction, uint64_t final_commit_timestamp); - void AppendToWal(durability::StorageGlobalOperation operation, LabelId label, const std::set &properties, - uint64_t final_commit_timestamp); - - uint64_t CommitTimestamp(std::optional desired_commit_timestamp = {}); - - // Main object storage - VerticesSkipList vertices_; - utils::SkipList edges_; - 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. - uint64_t edge_count_{0}; - - NameIdMapper name_id_mapper_; - - SchemaValidator schema_validator_; - Constraints constraints_; - Indices indices_; - Schemas schemas_; - - // Transaction engine - 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 commit_log_; - - std::list committed_transactions_; - IsolationLevel isolation_level_; - + std::vector shards_; + boost::asio::thread_pool shard_handlers_; Config config_; - - // Undo buffers that were unlinked and now are waiting to be freed. - std::list>> garbage_undo_buffers_; - - // Vertices that are logically deleted but still have to be removed from - // indices before removing them from the main storage. - std::list deleted_vertices_; - - // Vertices that are logically deleted and removed from indices and now wait - // to be removed from the main storage. - std::list> garbage_vertices_; - - // Edges that are logically deleted and wait to be removed from the main - // storage. - std::list deleted_edges_; - - // Durability - std::filesystem::path snapshot_directory_; - std::filesystem::path wal_directory_; - std::filesystem::path lock_file_path_; - utils::OutputFile lock_file_handle_; - - // 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> epoch_history_; - - std::optional 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 - uint64_t last_commit_timestamp_{kTimestampInitialId}; - - class ReplicationServer; - std::unique_ptr 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>, utils::SpinLock>; - ReplicationClientList replication_clients_; - - ReplicationRole replication_role_{ReplicationRole::MAIN}; }; } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/vertex_accessor.hpp b/src/storage/v3/vertex_accessor.hpp index 061cf399d..71e297478 100644 --- a/src/storage/v3/vertex_accessor.hpp +++ b/src/storage/v3/vertex_accessor.hpp @@ -25,7 +25,7 @@ namespace memgraph::storage::v3 { class EdgeAccessor; -class Storage; +class Shard; struct Indices; struct Constraints; @@ -46,7 +46,7 @@ class VertexAccessor final { private: const Vertex *vertex_; }; - friend class Storage; + friend class Shard; public: // Be careful when using VertexAccessor since it can be instantiated with diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index afd1f8dd9..cceade081 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -337,15 +337,15 @@ target_link_libraries(${test_prefix}storage_v3_key_store mg-storage-v3 rapidchec add_unit_test(storage_v3_vertex_accessors.cpp) target_link_libraries(${test_prefix}storage_v3_vertex_accessors mg-storage-v3) -# Test mg-query-v3 +# Test mg-query-v2 add_unit_test(query_v2_interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) target_link_libraries(${test_prefix}query_v2_interpreter mg-storage-v3 mg-query-v2 mg-communication) # add_unit_test(query_v2_query_plan_accumulate_aggregate.cpp) # target_link_libraries(${test_prefix}query_v2_query_plan_accumulate_aggregate mg-query-v2) -# add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp) -# target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2) +# # add_unit_test(query_v2_query_plan_create_set_remove_delete.cpp) +# # target_link_libraries(${test_prefix}query_v2_query_plan_create_set_remove_delete mg-query-v2) # add_unit_test(query_v2_query_plan_bag_semantics.cpp) # target_link_libraries(${test_prefix}query_v2_query_plan_bag_semantics mg-query-v2) diff --git a/tests/unit/query_v2_interpreter.cpp b/tests/unit/query_v2_interpreter.cpp index fdc15b032..c24f2b995 100644 --- a/tests/unit/query_v2_interpreter.cpp +++ b/tests/unit/query_v2_interpreter.cpp @@ -29,27 +29,29 @@ #include "query_v2_query_common.hpp" #include "result_stream_faker.hpp" #include "storage/v3/isolation_level.hpp" +#include "storage/v3/key_store.hpp" +#include "storage/v3/name_id_mapper.hpp" #include "storage/v3/property_value.hpp" #include "utils/csv_parsing.hpp" #include "utils/logging.hpp" namespace memgraph::query::v2::tests { -// auto ToEdgeList(const memgraph::communication::bolt::Value &v) { -// std::vector list; -// for (auto x : v.ValueList()) { -// list.push_back(x.ValueEdge()); -// } -// return list; -// } +auto ToEdgeList(const memgraph::communication::bolt::Value &v) { + std::vector list; + for (auto x : v.ValueList()) { + list.push_back(x.ValueEdge()); + } + return list; +} -// auto StringToUnorderedSet(const std::string &element) { -// const auto element_split = memgraph::utils::Split(element, ", "); -// return std::unordered_set(element_split.begin(), element_split.end()); -// }; +auto StringToUnorderedSet(const std::string &element) { + const auto element_split = memgraph::utils::Split(element, ", "); + return std::unordered_set(element_split.begin(), element_split.end()); +}; struct InterpreterFaker { - InterpreterFaker(memgraph::storage::v3::Storage *db, const memgraph::query::v2::InterpreterConfig config, + InterpreterFaker(memgraph::storage::v3::Shard *db, const memgraph::query::v2::InterpreterConfig config, const std::filesystem::path &data_directory) : interpreter_context(db, config, data_directory), interpreter(&interpreter_context) { interpreter_context.auth_checker = &auth_checker; @@ -113,10 +115,20 @@ class InterpreterTest : public ::testing::Test { return default_interpreter.Interpret(query, params); } - memgraph::storage::v3::Storage db_; + storage::v3::LabelId NameToLabelId(std::string_view label_name) { + return storage::v3::LabelId::FromUint(id_mapper.NameToId(label_name)); + } + + storage::v3::PropertyId NameToPropertyId(std::string_view property_name) { + return storage::v3::PropertyId::FromUint(id_mapper.NameToId(property_name)); + } + + storage::v3::PrimaryKey pk{storage::v3::PropertyValue(0)}; + memgraph::storage::v3::NameIdMapper id_mapper; + const storage::v3::LabelId label{NameToLabelId("label")}; + storage::v3::Shard db_{label, pk, std::nullopt}; std::filesystem::path data_directory{std::filesystem::temp_directory_path() / "MG_tests_unit_query_v2_interpreter"}; - const storage::v3::LabelId label{db_.NameToLabel("label")}; - const storage::v3::PropertyId property{db_.NameToProperty("property")}; + const storage::v3::PropertyId property{NameToPropertyId("property")}; InterpreterFaker default_interpreter{&db_, {}, data_directory}; }; @@ -150,8 +162,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) { // } // } -// // Run query with different ast twice to see if query executes correctly when -// // ast is read from cache. +// Run query with different ast twice to see if query executes correctly when +// ast is read from cache. // TEST_F(InterpreterTest, AstCache) { // { // auto stream = Interpret("RETURN 2 + 3"); @@ -1447,8 +1459,8 @@ TEST_F(InterpreterTest, DummyTestToForceQueryV2Compilation) { // auto notification = notifications[0].ValueMap(); // ASSERT_EQ(notification["severity"].ValueString(), "INFO"); // ASSERT_EQ(notification["code"].ValueString(), "ConstraintDoesNotExist"); -// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name doesn't -// exist."); ASSERT_EQ(notification["description"].ValueString(), ""); +// ASSERT_EQ(notification["title"].ValueString(), "Constraint EXISTS on label L1 on properties name doesn'texist."); +// ASSERT_EQ(notification["description"].ValueString(), ""); // } // } diff --git a/tests/unit/result_stream_faker.hpp b/tests/unit/result_stream_faker.hpp index 60c5884e7..6e7aab261 100644 --- a/tests/unit/result_stream_faker.hpp +++ b/tests/unit/result_stream_faker.hpp @@ -26,7 +26,7 @@ */ class ResultStreamFaker { public: - explicit ResultStreamFaker(memgraph::storage::v3::Storage *store) : store_(store) {} + explicit ResultStreamFaker(memgraph::storage::v3::Shard *store) : store_(store) {} ResultStreamFaker(const ResultStreamFaker &) = delete; ResultStreamFaker &operator=(const ResultStreamFaker &) = delete; @@ -124,7 +124,7 @@ class ResultStreamFaker { } private: - memgraph::storage::v3::Storage *store_; + memgraph::storage::v3::Shard *store_; // the data that the record stream can accept std::vector header_; std::vector> results_; diff --git a/tests/unit/storage_v3.cpp b/tests/unit/storage_v3.cpp index fbcd85704..4163c96a2 100644 --- a/tests/unit/storage_v3.cpp +++ b/tests/unit/storage_v3.cpp @@ -19,12 +19,14 @@ #include "storage/v3/delta.hpp" #include "storage/v3/id_types.hpp" #include "storage/v3/key_store.hpp" +#include "storage/v3/name_id_mapper.hpp" #include "storage/v3/property_value.hpp" #include "storage/v3/result.hpp" #include "storage/v3/schema_validator.hpp" -#include "storage/v3/storage.hpp" +#include "storage/v3/shard.hpp" #include "storage/v3/vertex_accessor.hpp" #include "storage_v3_test_utils.hpp" +#include "utils/exceptions.hpp" using testing::UnorderedElementsAre; @@ -37,7 +39,7 @@ class StorageV3 : public ::testing::Test { store.CreateSchema(primary_label, {storage::v3::SchemaProperty{primary_property, common::SchemaType::INT}})); } - VertexAccessor CreateVertexAndValidate(Storage::Accessor &acc, LabelId primary_label, + VertexAccessor CreateVertexAndValidate(Shard::Accessor &acc, LabelId primary_label, const std::vector &labels, const std::vector> &properties) { auto vtx = acc.CreateVertexAndValidate(primary_label, labels, properties); @@ -45,10 +47,21 @@ class StorageV3 : public ::testing::Test { return *vtx; } - Storage store; - const LabelId primary_label{store.NameToLabel("label")}; - const PropertyId primary_property{store.NameToProperty("property")}; + LabelId NameToLabelId(std::string_view label_name) { return LabelId::FromUint(id_mapper.NameToId(label_name)); } + + PropertyId NameToPropertyId(std::string_view property_name) { + return PropertyId::FromUint(id_mapper.NameToId(property_name)); + } + + EdgeTypeId NameToEdgeTypeId(std::string_view edge_type_name) { + return EdgeTypeId::FromUint(id_mapper.NameToId(edge_type_name)); + } + + NameIdMapper id_mapper; const std::vector pk{PropertyValue{0}}; + const LabelId primary_label{NameToLabelId("label")}; + Shard store{primary_label, pk, std::nullopt}; + const PropertyId primary_property{NameToPropertyId("property")}; }; // NOLINTNEXTLINE(hicpp-special-member-functions) @@ -236,7 +249,7 @@ TEST_F(StorageV3, AccessorMove) { ASSERT_TRUE(acc.FindVertex(pk, View::NEW).has_value()); EXPECT_EQ(CountVertices(acc, View::NEW), 1U); - Storage::Accessor moved(std::move(acc)); + Shard::Accessor moved(std::move(acc)); ASSERT_FALSE(moved.FindVertex(pk, View::OLD).has_value()); EXPECT_EQ(CountVertices(moved, View::OLD), 0U); @@ -600,7 +613,7 @@ TEST_F(StorageV3, VertexDeleteLabel) { auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto label5 = acc.NameToLabel("label5"); + auto label5 = NameToLabelId("label5"); // Check whether label 5 exists ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); @@ -651,7 +664,7 @@ TEST_F(StorageV3, VertexDeleteLabel) { auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto label5 = acc.NameToLabel("label5"); + auto label5 = NameToLabelId("label5"); // Check whether label 5 exists ASSERT_FALSE(vertex->HasLabel(label5, View::OLD).GetValue()); @@ -744,7 +757,7 @@ TEST_F(StorageV3, VertexDeleteProperty) { auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto property5 = acc.NameToProperty("property5"); + auto property5 = NameToPropertyId("property5"); // Check whether property 5 exists ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); @@ -790,7 +803,7 @@ TEST_F(StorageV3, VertexDeleteProperty) { auto vertex = acc.FindVertex(pk, View::NEW); ASSERT_TRUE(vertex); - auto property5 = acc.NameToProperty("property5"); + auto property5 = NameToPropertyId("property5"); // Check whether property 5 exists ASSERT_TRUE(vertex->GetProperty(property5, View::OLD)->IsNull()); @@ -866,7 +879,7 @@ TEST_F(StorageV3, VertexLabelCommit) { auto acc = store.Access(); auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); ASSERT_EQ(vertex.Labels(View::NEW)->size(), 0); @@ -897,7 +910,7 @@ TEST_F(StorageV3, VertexLabelCommit) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); { @@ -913,7 +926,7 @@ TEST_F(StorageV3, VertexLabelCommit) { ASSERT_EQ(labels[0], label); } - auto other_label = acc.NameToLabel("other"); + auto other_label = NameToLabelId("other"); ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); @@ -925,7 +938,7 @@ TEST_F(StorageV3, VertexLabelCommit) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); { auto res = vertex->RemoveLabelAndValidate(label); @@ -956,14 +969,14 @@ TEST_F(StorageV3, VertexLabelCommit) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); - auto other_label = acc.NameToLabel("other"); + auto other_label = NameToLabelId("other"); ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); @@ -987,7 +1000,7 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); @@ -1020,14 +1033,14 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); - auto other_label = acc.NameToLabel("other"); + auto other_label = NameToLabelId("other"); ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); @@ -1041,7 +1054,7 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); @@ -1074,7 +1087,7 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); { @@ -1090,7 +1103,7 @@ TEST_F(StorageV3, VertexLabelAbort) { ASSERT_EQ(labels[0], label); } - auto other_label = acc.NameToLabel("other"); + auto other_label = NameToLabelId("other"); ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); @@ -1104,7 +1117,7 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); { auto res = vertex->RemoveLabelAndValidate(label); @@ -1137,7 +1150,7 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_TRUE(vertex->HasLabel(label, View::OLD).GetValue()); { @@ -1153,7 +1166,7 @@ TEST_F(StorageV3, VertexLabelAbort) { ASSERT_EQ(labels[0], label); } - auto other_label = acc.NameToLabel("other"); + auto other_label = NameToLabelId("other"); ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); @@ -1167,7 +1180,7 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); { auto res = vertex->RemoveLabelAndValidate(label); @@ -1200,14 +1213,14 @@ TEST_F(StorageV3, VertexLabelAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label = acc.NameToLabel("label5"); + auto label = NameToLabelId("label5"); ASSERT_FALSE(vertex->HasLabel(label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label, View::NEW).GetValue()); ASSERT_EQ(vertex->Labels(View::OLD)->size(), 0); ASSERT_EQ(vertex->Labels(View::NEW)->size(), 0); - auto other_label = acc.NameToLabel("other"); + auto other_label = NameToLabelId("other"); ASSERT_FALSE(vertex->HasLabel(other_label, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(other_label, View::NEW).GetValue()); @@ -1232,8 +1245,8 @@ TEST_F(StorageV3, VertexLabelSerializationError) { auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label1 = acc1.NameToLabel("label1"); - auto label2 = acc1.NameToLabel("label2"); + auto label1 = NameToLabelId("label1"); + auto label2 = NameToLabelId("label2"); ASSERT_FALSE(vertex->HasLabel(label1, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label1, View::NEW).GetValue()); @@ -1271,8 +1284,8 @@ TEST_F(StorageV3, VertexLabelSerializationError) { auto vertex = acc2.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label1 = acc2.NameToLabel("label1"); - auto label2 = acc2.NameToLabel("label2"); + auto label1 = NameToLabelId("label1"); + auto label2 = NameToLabelId("label2"); ASSERT_FALSE(vertex->HasLabel(label1, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label1, View::NEW).GetValue()); @@ -1297,8 +1310,8 @@ TEST_F(StorageV3, VertexLabelSerializationError) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto label1 = acc.NameToLabel("label1"); - auto label2 = acc.NameToLabel("label2"); + auto label1 = NameToLabelId("label1"); + auto label2 = NameToLabelId("label2"); ASSERT_TRUE(vertex->HasLabel(label1, View::OLD).GetValue()); ASSERT_FALSE(vertex->HasLabel(label2, View::OLD).GetValue()); @@ -1326,7 +1339,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { auto acc = store.Access(); auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_TRUE(vertex.GetProperty(property, View::NEW)->IsNull()); ASSERT_EQ(vertex.Properties(View::NEW)->size(), 0); @@ -1364,7 +1377,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); { @@ -1380,7 +1393,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = NameToPropertyId("other"); ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); @@ -1392,7 +1405,7 @@ TEST_F(StorageV3, VertexPropertyCommit) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); { auto old_value = vertex->SetPropertyAndValidate(property, PropertyValue()); @@ -1423,14 +1436,14 @@ TEST_F(StorageV3, VertexPropertyCommit) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = NameToPropertyId("other"); ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); @@ -1454,7 +1467,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); @@ -1494,14 +1507,14 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = NameToPropertyId("other"); ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); @@ -1515,7 +1528,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); @@ -1555,7 +1568,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); { @@ -1571,7 +1584,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = NameToPropertyId("other"); ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); @@ -1585,7 +1598,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); { @@ -1626,7 +1639,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); { @@ -1642,7 +1655,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { ASSERT_EQ(properties[property].ValueString(), "nandare"); } - auto other_property = acc.NameToProperty("other"); + auto other_property = NameToPropertyId("other"); ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); @@ -1656,7 +1669,7 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_EQ(vertex->GetProperty(property, View::OLD)->ValueString(), "nandare"); { @@ -1697,14 +1710,14 @@ TEST_F(StorageV3, VertexPropertyAbort) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property = acc.NameToProperty("property5"); + auto property = NameToPropertyId("property5"); ASSERT_TRUE(vertex->GetProperty(property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property, View::NEW)->IsNull()); ASSERT_EQ(vertex->Properties(View::OLD)->size(), 0); ASSERT_EQ(vertex->Properties(View::NEW)->size(), 0); - auto other_property = acc.NameToProperty("other"); + auto other_property = NameToPropertyId("other"); ASSERT_TRUE(vertex->GetProperty(other_property, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(other_property, View::NEW)->IsNull()); @@ -1729,8 +1742,8 @@ TEST_F(StorageV3, VertexPropertySerializationError) { auto vertex = acc1.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property1 = acc1.NameToProperty("property1"); - auto property2 = acc1.NameToProperty("property2"); + auto property1 = NameToPropertyId("property1"); + auto property2 = NameToPropertyId("property2"); ASSERT_TRUE(vertex->GetProperty(property1, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); @@ -1762,8 +1775,8 @@ TEST_F(StorageV3, VertexPropertySerializationError) { auto vertex = acc2.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property1 = acc2.NameToProperty("property1"); - auto property2 = acc2.NameToProperty("property2"); + auto property1 = NameToPropertyId("property1"); + auto property2 = NameToPropertyId("property2"); ASSERT_TRUE(vertex->GetProperty(property1, View::OLD)->IsNull()); ASSERT_TRUE(vertex->GetProperty(property1, View::NEW)->IsNull()); @@ -1788,8 +1801,8 @@ TEST_F(StorageV3, VertexPropertySerializationError) { auto vertex = acc.FindVertex(pk, View::OLD); ASSERT_TRUE(vertex); - auto property1 = acc.NameToProperty("property1"); - auto property2 = acc.NameToProperty("property2"); + auto property1 = NameToPropertyId("property1"); + auto property2 = NameToPropertyId("property2"); ASSERT_EQ(vertex->GetProperty(property1, View::OLD)->ValueInt(), 123); ASSERT_TRUE(vertex->GetProperty(property2, View::OLD)->IsNull()); @@ -1816,8 +1829,8 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { auto acc = store.Access(); auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); - auto label = acc.NameToLabel("label5"); - auto property = acc.NameToProperty("property5"); + auto label = NameToLabelId("label5"); + auto property = NameToPropertyId("property5"); // Check whether label 5 and property 5 exist ASSERT_FALSE(vertex.HasLabel(label, View::NEW).GetValue()); @@ -2051,8 +2064,8 @@ TEST_F(StorageV3, VertexLabelPropertyMixed) { } TEST_F(StorageV3, VertexPropertyClear) { - auto property1 = store.NameToProperty("property1"); - auto property2 = store.NameToProperty("property2"); + auto property1 = NameToPropertyId("property1"); + auto property2 = NameToPropertyId("property2"); { auto acc = store.Access(); auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); @@ -2153,8 +2166,8 @@ TEST_F(StorageV3, VertexPropertyClear) { } TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { - auto label1 = store.NameToLabel("label1"); - auto property1 = store.NameToProperty("property1"); + auto label1 = NameToLabelId("label1"); + auto property1 = NameToPropertyId("property1"); auto acc = store.Access(); auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue{0}}}); @@ -2182,7 +2195,7 @@ TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { // Modify vertex. ASSERT_TRUE(vertex.AddLabelAndValidate(label1).HasValue()); ASSERT_TRUE(vertex.SetPropertyAndValidate(property1, PropertyValue("value")).HasValue()); - ASSERT_TRUE(acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge")).HasValue()); + ASSERT_TRUE(acc.CreateEdge(&vertex, &vertex, NameToEdgeTypeId("edge")).HasValue()); // Check state after (OLD view). ASSERT_EQ(vertex.Labels(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); @@ -2218,14 +2231,14 @@ TEST_F(StorageV3, VertexVisibilitySingleTransaction) { EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex.AddLabelAndValidate(acc1.NameToLabel("label1")).HasValue()); + ASSERT_TRUE(vertex.AddLabelAndValidate(NameToLabelId("label1")).HasValue()); EXPECT_FALSE(acc1.FindVertex(pk, View::OLD)); EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); EXPECT_FALSE(acc2.FindVertex(pk, View::OLD)); EXPECT_FALSE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex.SetPropertyAndValidate(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex.SetPropertyAndValidate(NameToPropertyId("meaning"), PropertyValue(42)).HasValue()); auto acc3 = store.Access(); @@ -2302,7 +2315,7 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex->AddLabelAndValidate(acc1.NameToLabel("label1")).HasValue()); + ASSERT_TRUE(vertex->AddLabelAndValidate(NameToLabelId("label1")).HasValue()); EXPECT_TRUE(acc1.FindVertex(pk, View::OLD)); EXPECT_TRUE(acc1.FindVertex(pk, View::NEW)); @@ -2323,7 +2336,7 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { EXPECT_TRUE(acc2.FindVertex(pk, View::OLD)); EXPECT_TRUE(acc2.FindVertex(pk, View::NEW)); - ASSERT_TRUE(vertex->SetPropertyAndValidate(acc1.NameToProperty("meaning"), PropertyValue(42)).HasValue()); + ASSERT_TRUE(vertex->SetPropertyAndValidate(NameToPropertyId("meaning"), PropertyValue(42)).HasValue()); auto acc3 = store.Access(); @@ -2497,7 +2510,7 @@ TEST_F(StorageV3, VertexVisibilityMultipleTransactions) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(StorageV3, DeletedVertexAccessor) { - const auto property1 = store.NameToProperty("property1"); + const auto property1 = NameToPropertyId("property1"); const PropertyValue property_value{"property_value"}; // Create the vertex @@ -2538,8 +2551,8 @@ TEST_F(StorageV3, DeletedVertexAccessor) { TEST_F(StorageV3, TestCreateVertexAndValidate) { { auto acc = store.Access(); - const auto label1 = store.NameToLabel("label1"); - const auto prop1 = store.NameToProperty("prop1"); + const auto label1 = NameToLabelId("label1"); + const auto prop1 = NameToPropertyId("prop1"); auto vertex = acc.CreateVertexAndValidate(primary_label, {label1}, {{primary_property, PropertyValue(0)}, {prop1, PropertyValue(111)}}); ASSERT_TRUE(vertex.HasValue()); @@ -2552,25 +2565,20 @@ TEST_F(StorageV3, TestCreateVertexAndValidate) { (std::map{{prop1, PropertyValue(111)}})); } { - const auto label1 = store.NameToLabel("new_primary_label"); - const auto prop1 = store.NameToProperty("key1"); - const auto prop2 = store.NameToProperty("key2"); - ASSERT_TRUE(store.CreateSchema( - label1, {SchemaProperty{prop1, common::SchemaType::INT}, SchemaProperty{prop2, common::SchemaType::STRING}})); auto acc = store.Access(); - auto vertex = acc.CreateVertexAndValidate(label1, {}, {{prop1, PropertyValue(21)}, {prop2, PropertyValue("test")}}); - ASSERT_TRUE(vertex.HasValue()); - ASSERT_TRUE(vertex->PrimaryLabel(View::NEW).HasValue()); - EXPECT_EQ(vertex->PrimaryLabel(View::NEW).GetValue(), label1); - ASSERT_TRUE(vertex->PrimaryKey(View::NEW).HasValue()); - EXPECT_EQ(vertex->PrimaryKey(View::NEW).GetValue(), (PrimaryKey{{PropertyValue(21), PropertyValue("test")}})); - ASSERT_TRUE(vertex->Properties(View::NEW).HasValue()); - EXPECT_TRUE(vertex->Properties(View::NEW).GetValue().empty()); + const auto label1 = NameToLabelId("label1"); + const auto prop1 = NameToPropertyId("prop1"); + EXPECT_THROW( + { + auto vertex = acc.CreateVertexAndValidate( + label1, {}, {{primary_property, PropertyValue(0)}, {prop1, PropertyValue(111)}}); + }, + utils::BasicException); } { ASSERT_DEATH( { - Storage store; + Shard store(primary_label, pk, std::nullopt); ASSERT_TRUE(store.CreateSchema(primary_label, {storage::v3::SchemaProperty{primary_property, common::SchemaType::INT}})); auto acc = store.Access(); diff --git a/tests/unit/storage_v3_test_utils.cpp b/tests/unit/storage_v3_test_utils.cpp index b1a59b7aa..4a8c7b310 100644 --- a/tests/unit/storage_v3_test_utils.cpp +++ b/tests/unit/storage_v3_test_utils.cpp @@ -13,7 +13,7 @@ namespace memgraph::storage::v3::tests { -size_t CountVertices(Storage::Accessor &storage_accessor, View view) { +size_t CountVertices(Shard::Accessor &storage_accessor, View view) { auto vertices = storage_accessor.Vertices(view); size_t count = 0U; for (auto it = vertices.begin(); it != vertices.end(); ++it, ++count) diff --git a/tests/unit/storage_v3_test_utils.hpp b/tests/unit/storage_v3_test_utils.hpp index 1cc772036..7cbb7414b 100644 --- a/tests/unit/storage_v3_test_utils.hpp +++ b/tests/unit/storage_v3_test_utils.hpp @@ -16,6 +16,6 @@ namespace memgraph::storage::v3::tests { -size_t CountVertices(Storage::Accessor &storage_accessor, View view); +size_t CountVertices(Shard::Accessor &storage_accessor, View view); } // namespace memgraph::storage::v3::tests diff --git a/tests/unit/storage_v3_vertex_accessors.cpp b/tests/unit/storage_v3_vertex_accessors.cpp index dcf94788a..8dea70e59 100644 --- a/tests/unit/storage_v3_vertex_accessors.cpp +++ b/tests/unit/storage_v3_vertex_accessors.cpp @@ -21,7 +21,7 @@ #include "storage/v3/property_value.hpp" #include "storage/v3/result.hpp" #include "storage/v3/schema_validator.hpp" -#include "storage/v3/storage.hpp" +#include "storage/v3/shard.hpp" #include "storage/v3/vertex_accessor.hpp" #include "storage_v3_test_utils.hpp" @@ -35,7 +35,7 @@ class StorageV3Accessor : public ::testing::Test { ASSERT_TRUE(storage.CreateSchema(primary_label, {SchemaProperty{primary_property, common::SchemaType::INT}})); } - VertexAccessor CreateVertexAndValidate(Storage::Accessor &acc, LabelId primary_label, + VertexAccessor CreateVertexAndValidate(Shard::Accessor &acc, LabelId primary_label, const std::vector &labels, const std::vector> &properties) { auto vtx = acc.CreateVertexAndValidate(primary_label, labels, properties); @@ -43,9 +43,17 @@ class StorageV3Accessor : public ::testing::Test { return *vtx; } - Storage storage; - const LabelId primary_label{storage.NameToLabel("label")}; - const PropertyId primary_property{storage.NameToProperty("property")}; + LabelId NameToLabelId(std::string_view label_name) { return LabelId::FromUint(id_mapper.NameToId(label_name)); } + + PropertyId NameToPropertyId(std::string_view property_name) { + return PropertyId::FromUint(id_mapper.NameToId(property_name)); + } + + const std::vector pk{PropertyValue{0}}; + NameIdMapper id_mapper; + Shard storage{NameToLabelId("label"), pk, std::nullopt}; + const LabelId primary_label{NameToLabelId("label")}; + const PropertyId primary_property{NameToPropertyId("property")}; }; TEST_F(StorageV3Accessor, TestPrimaryLabel) { @@ -85,9 +93,9 @@ TEST_F(StorageV3Accessor, TestPrimaryLabel) { TEST_F(StorageV3Accessor, TestAddLabels) { { auto acc = storage.Access(); - const auto label1 = storage.NameToLabel("label1"); - const auto label2 = storage.NameToLabel("label2"); - const auto label3 = storage.NameToLabel("label3"); + const auto label1 = NameToLabelId("label1"); + const auto label2 = NameToLabelId("label2"); + const auto label3 = NameToLabelId("label3"); const auto vertex = CreateVertexAndValidate(acc, primary_label, {label1, label2, label3}, {{primary_property, PropertyValue(0)}}); ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); @@ -96,9 +104,9 @@ TEST_F(StorageV3Accessor, TestAddLabels) { } { auto acc = storage.Access(); - const auto label1 = storage.NameToLabel("label1"); - const auto label2 = storage.NameToLabel("label2"); - const auto label3 = storage.NameToLabel("label3"); + const auto label1 = NameToLabelId("label1"); + const auto label2 = NameToLabelId("label2"); + const auto label3 = NameToLabelId("label3"); auto vertex = CreateVertexAndValidate(acc, primary_label, {label1}, {{primary_property, PropertyValue(1)}}); ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); ASSERT_FALSE(vertex.Labels(View::OLD).HasValue()); @@ -111,7 +119,7 @@ TEST_F(StorageV3Accessor, TestAddLabels) { } { auto acc = storage.Access(); - const auto label1 = storage.NameToLabel("label"); + const auto label1 = NameToLabelId("label"); auto vertex = acc.CreateVertexAndValidate(primary_label, {label1}, {{primary_property, PropertyValue(2)}}); ASSERT_TRUE(vertex.HasError()); ASSERT_TRUE(std::holds_alternative(vertex.GetError())); @@ -120,7 +128,7 @@ TEST_F(StorageV3Accessor, TestAddLabels) { } { auto acc = storage.Access(); - const auto label1 = storage.NameToLabel("label"); + const auto label1 = NameToLabelId("label"); auto vertex = acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, PropertyValue(3)}}); ASSERT_TRUE(vertex.HasValue()); const auto schema_violation = vertex->AddLabelAndValidate(label1); @@ -134,9 +142,9 @@ TEST_F(StorageV3Accessor, TestAddLabels) { TEST_F(StorageV3Accessor, TestRemoveLabels) { { auto acc = storage.Access(); - const auto label1 = storage.NameToLabel("label1"); - const auto label2 = storage.NameToLabel("label2"); - const auto label3 = storage.NameToLabel("label3"); + const auto label1 = NameToLabelId("label1"); + const auto label2 = NameToLabelId("label2"); + const auto label3 = NameToLabelId("label3"); auto vertex = CreateVertexAndValidate(acc, primary_label, {label1, label2, label3}, {{primary_property, PropertyValue(0)}}); ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); @@ -157,7 +165,7 @@ TEST_F(StorageV3Accessor, TestRemoveLabels) { } { auto acc = storage.Access(); - const auto label1 = storage.NameToLabel("label1"); + const auto label1 = NameToLabelId("label1"); auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(1)}}); ASSERT_TRUE(vertex.Labels(View::NEW).HasValue()); EXPECT_TRUE(vertex.Labels(View::NEW).GetValue().empty()); @@ -179,7 +187,7 @@ TEST_F(StorageV3Accessor, TestRemoveLabels) { TEST_F(StorageV3Accessor, TestSetKeysAndProperties) { { auto acc = storage.Access(); - const PropertyId prop1{storage.NameToProperty("prop1")}; + const PropertyId prop1{NameToPropertyId("prop1")}; auto vertex = CreateVertexAndValidate(acc, primary_label, {}, {{primary_property, PropertyValue(0)}}); const auto res = vertex.SetPropertyAndValidate(prop1, PropertyValue(1)); ASSERT_TRUE(res.HasValue()); From e9f0360fb3c11fb96b7d7b8454a22b9e3161ed9a Mon Sep 17 00:00:00 2001 From: Jure Bajic Date: Tue, 6 Sep 2022 22:48:25 +0200 Subject: [PATCH 05/10] Enable indices for storage v3 (#528) - Enable indices in storage v3 - Add new test cases - Change `CreateVertexAndValidate` to call `SetProperty` and `AddLabel` --- src/storage/v3/key_store.cpp | 3 + src/storage/v3/key_store.hpp | 3 + src/storage/v3/shard.cpp | 50 +- src/storage/v3/vertex.hpp | 21 - tests/unit/CMakeLists.txt | 3 + tests/unit/storage_v3_indices.cpp | 989 ++++++++++++++++++++++++++++++ 6 files changed, 1031 insertions(+), 38 deletions(-) create mode 100644 tests/unit/storage_v3_indices.cpp diff --git a/src/storage/v3/key_store.cpp b/src/storage/v3/key_store.cpp index 7f8e69ec9..4e65f5fd1 100644 --- a/src/storage/v3/key_store.cpp +++ b/src/storage/v3/key_store.cpp @@ -13,6 +13,7 @@ #include #include +#include "storage/v3/id_types.hpp" #include "storage/v3/key_store.hpp" #include "storage/v3/property_value.hpp" @@ -27,6 +28,8 @@ KeyStore::KeyStore(const PrimaryKey &key_values) { PropertyValue KeyStore::GetKey(const size_t index) const { return store_.GetProperty(PropertyId::FromUint(index)); } +PropertyValue KeyStore::GetKey(const PropertyId property_id) const { return store_.GetProperty(property_id); } + PrimaryKey KeyStore::Keys() const { auto keys_map = store_.Properties(); PrimaryKey keys; diff --git a/src/storage/v3/key_store.hpp b/src/storage/v3/key_store.hpp index 034e6c280..c65132d9a 100644 --- a/src/storage/v3/key_store.hpp +++ b/src/storage/v3/key_store.hpp @@ -15,6 +15,7 @@ #include #include +#include "storage/v3/id_types.hpp" #include "storage/v3/property_store.hpp" #include "storage/v3/property_value.hpp" @@ -36,6 +37,8 @@ class KeyStore { PropertyValue GetKey(size_t index) const; + PropertyValue GetKey(PropertyId property) const; + PrimaryKey Keys() const; friend bool operator<(const KeyStore &lhs, const KeyStore &rhs) { diff --git a/src/storage/v3/shard.cpp b/src/storage/v3/shard.cpp index 48040a80c..bd90327c9 100644 --- a/src/storage/v3/shard.cpp +++ b/src/storage/v3/shard.cpp @@ -494,23 +494,31 @@ ResultSchema Shard::Accessor::CreateVertexAndValidate( return property_pair.first == property_id; })->second); } - - // Get secondary properties - std::vector> secondary_properties; - for (const auto &[property_id, property_value] : properties) { - if (!shard_->schemas_.IsPropertyKey(primary_label, property_id)) { - secondary_properties.emplace_back(property_id, property_value); - } - } - auto acc = shard_->vertices_.access(); auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert({Vertex{delta, primary_label, primary_properties, labels, secondary_properties}}); + auto [it, inserted] = acc.insert({Vertex{delta, primary_label, primary_properties}}); + + VertexAccessor vertex_acc{&it->vertex, &transaction_, &shard_->indices_, + &shard_->constraints_, config_, shard_->schema_validator_}; MG_ASSERT(inserted, "The vertex must be inserted here!"); MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); + + // TODO(jbajic) Improve, maybe delay index update + for (const auto &[property_id, property_value] : properties) { + if (!shard_->schemas_.IsPropertyKey(primary_label, property_id)) { + if (const auto err = vertex_acc.SetProperty(property_id, property_value); err.HasError()) { + return {err.GetError()}; + } + } + } + // Set secondary labels + for (auto label : labels) { + if (const auto err = vertex_acc.AddLabel(label); err.HasError()) { + return {err.GetError()}; + } + } delta->prev.Set(&it->vertex); - return VertexAccessor{&it->vertex, &transaction_, &shard_->indices_, - &shard_->constraints_, config_, shard_->schema_validator_}; + return vertex_acc; } std::optional Shard::Accessor::FindVertex(std::vector primary_key, View view) { @@ -1065,8 +1073,10 @@ const std::string &Shard::EdgeTypeToName(EdgeTypeId edge_type) const { } bool Shard::CreateIndex(LabelId label, const std::optional desired_commit_timestamp) { - // TODO Fix Index - return false; + // TODO(jbajic) response should be different when label == primary_label + if (label == primary_label_ || !indices_.label_index.CreateIndex(label, vertices_.access())) { + return false; + } const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::LABEL_INDEX_CREATE, label, {}, commit_timestamp); commit_log_->MarkFinished(commit_timestamp); @@ -1075,9 +1085,15 @@ bool Shard::CreateIndex(LabelId label, const std::optional desired_com } bool Shard::CreateIndex(LabelId label, PropertyId property, const std::optional desired_commit_timestamp) { - // TODO Fix Index - // if (!indices_.label_property_index.CreateIndex(label, property, labelspace.access())) return false; - return false; + // TODO(jbajic) response should be different when index conflicts with schema + if (label == primary_label_ && schemas_.GetSchema(primary_label_)->second.size() == 1 && + schemas_.GetSchema(primary_label_)->second[0].property_id == property) { + // Index already exists on primary key + return false; + } + if (!indices_.label_property_index.CreateIndex(label, property, vertices_.access())) { + return false; + } const auto commit_timestamp = CommitTimestamp(desired_commit_timestamp); AppendToWal(durability::StorageGlobalOperation::LABEL_PROPERTY_INDEX_CREATE, label, {property}, commit_timestamp); commit_log_->MarkFinished(commit_timestamp); diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 50cb0333d..08ae747b3 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -34,32 +34,11 @@ struct Vertex { "Vertex must be created with an initial DELETE_OBJECT delta!"); } - Vertex(Delta *delta, LabelId primary_label, const std::vector &primary_properties, - const std::vector &secondary_labels, - const std::vector> &secondary_properties) - : primary_label{primary_label}, keys{primary_properties}, labels{secondary_labels}, delta{delta} { - MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, - "Vertex must be created with an initial DELETE_OBJECT delta!"); - for (const auto &[property_id, property_value] : secondary_properties) { - properties.SetProperty(property_id, property_value); - } - } - Vertex(LabelId primary_label, const std::vector &primary_properties) : primary_label{primary_label}, keys(primary_properties) { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } - Vertex(LabelId primary_label, const std::vector &primary_properties, - const std::vector &secondary_labels, - const std::vector> &secondary_properties) - : primary_label{primary_label}, keys{primary_properties}, labels{secondary_labels} { - MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, - "Vertex must be created with an initial DELETE_OBJECT delta!"); - for (const auto &[property_id, property_value] : secondary_properties) { - properties.SetProperty(property_id, property_value); - } - } LabelId primary_label; KeyStore keys; diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index c112fe9b2..5a9d41f98 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -334,6 +334,9 @@ target_link_libraries(${test_prefix}storage_v3_property_store mg-storage-v3 fmt) add_unit_test(storage_v3_key_store.cpp) target_link_libraries(${test_prefix}storage_v3_key_store mg-storage-v3 rapidcheck rapidcheck_gtest) +add_unit_test(storage_v3_indices.cpp) +target_link_libraries(${test_prefix}storage_v3_indices mg-storage-v3) + add_unit_test(storage_v3_vertex_accessors.cpp) target_link_libraries(${test_prefix}storage_v3_vertex_accessors mg-storage-v3) diff --git a/tests/unit/storage_v3_indices.cpp b/tests/unit/storage_v3_indices.cpp new file mode 100644 index 000000000..708fae156 --- /dev/null +++ b/tests/unit/storage_v3_indices.cpp @@ -0,0 +1,989 @@ +// Copyright 2022 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 +#include +#include +#include + +#include "storage/v3/id_types.hpp" +#include "storage/v3/name_id_mapper.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/storage.hpp" +#include "storage/v3/temporal.hpp" + +// NOLINTNEXTLINE(google-build-using-namespace) + +using testing::IsEmpty; +using testing::Pair; +using testing::UnorderedElementsAre; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define ASSERT_NO_ERROR(result) ASSERT_FALSE((result).HasError()) + +namespace memgraph::storage::v3::tests { + +class IndexTest : public testing::Test { + protected: + void SetUp() override { + ASSERT_TRUE( + storage.CreateSchema(primary_label, {storage::v3::SchemaProperty{primary_property, common::SchemaType::INT}})); + } + + NameIdMapper id_mapper; + const std::vector pk{PropertyValue{0}}; + const LabelId primary_label{NameToLabelId("label")}; + Shard storage{primary_label, pk, std::nullopt}; + const PropertyId primary_property{NameToPropertyId("property")}; + + const PropertyId prop_id{NameToPropertyId("id")}; + const PropertyId prop_val{NameToPropertyId("val")}; + const LabelId label1{NameToLabelId("label1")}; + const LabelId label2{NameToLabelId("label2")}; + int primary_key_id{0}; + int vertex_id{0}; + + LabelId NameToLabelId(std::string_view label_name) { return LabelId::FromUint(id_mapper.NameToId(label_name)); } + + PropertyId NameToPropertyId(std::string_view property_name) { + return PropertyId::FromUint(id_mapper.NameToId(property_name)); + } + + VertexAccessor CreateVertex(Shard::Accessor *accessor) { + auto vertex = *accessor->CreateVertexAndValidate( + primary_label, {}, + {{primary_property, PropertyValue(primary_key_id++)}, {prop_id, PropertyValue(vertex_id++)}}); + return vertex; + } + + template + std::vector GetIds(TIterable iterable, View view = View::OLD) { + std::vector ret; + for (auto vertex : iterable) { + ret.push_back(vertex.GetProperty(prop_id, view)->ValueInt()); + } + return ret; + } + + template + std::vector GetPrimaryKeyIds(TIterable iterable, View view = View::OLD) { + std::vector ret; + for (auto vertex : iterable) { + EXPECT_TRUE(vertex.PrimaryKey(view).HasValue()); + const auto pk = vertex.PrimaryKey(view).GetValue(); + EXPECT_EQ(pk.size(), 1); + ret.push_back(pk[0].ValueInt()); + } + return ret; + } +}; + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelIndexCreate) { + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelIndexExists(label1)); + } + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + + { + auto acc = storage.Access(); + for (int i = 0; i < 10; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + } + ASSERT_NO_ERROR(acc.Commit()); + } + + EXPECT_TRUE(storage.CreateIndex(label1)); + + { + auto acc = storage.Access(); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + } + + { + auto acc = storage.Access(); + for (int i = 10; i < 20; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + + acc.Abort(); + } + + { + auto acc = storage.Access(); + for (int i = 10; i < 20; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + ASSERT_NO_ERROR(acc.Commit()); + } + + { + auto acc = storage.Access(); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29)); + + ASSERT_NO_ERROR(acc.Commit()); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelIndexDrop) { + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelIndexExists(label1)); + } + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + + { + auto acc = storage.Access(); + for (int i = 0; i < 10; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + } + ASSERT_NO_ERROR(acc.Commit()); + } + + EXPECT_TRUE(storage.CreateIndex(label1)); + + { + auto acc = storage.Access(); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + } + + EXPECT_TRUE(storage.DropIndex(label1)); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelIndexExists(label1)); + } + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + + EXPECT_FALSE(storage.DropIndex(label1)); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelIndexExists(label1)); + } + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + + { + auto acc = storage.Access(); + for (int i = 10; i < 20; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + } + ASSERT_NO_ERROR(acc.Commit()); + } + + EXPECT_TRUE(storage.CreateIndex(label1)); + { + auto acc = storage.Access(); + EXPECT_TRUE(acc.LabelIndexExists(label1)); + } + EXPECT_THAT(storage.ListAllIndices().label, UnorderedElementsAre(label1)); + + { + auto acc = storage.Access(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19)); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelIndexBasic) { + // The following steps are performed and index correctness is validated after + // each step: + // 1. Create 10 vertices numbered from 0 to 9. + // 2. Add Label1 to odd numbered, and Label2 to even numbered vertices. + // 3. Remove Label1 from odd numbered vertices, and add it to even numbered + // vertices. + // 4. Delete even numbered vertices. + EXPECT_TRUE(storage.CreateIndex(label1)); + EXPECT_TRUE(storage.CreateIndex(label2)); + + auto acc = storage.Access(); + EXPECT_THAT(storage.ListAllIndices().label, UnorderedElementsAre(label1, label2)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty()); + + for (int i = 0; i < 10; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + + acc.AdvanceCommand(); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc.Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + if (id % 2) { + ASSERT_NO_ERROR(vertex.RemoveLabelAndValidate(label1)); + } else { + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + } + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc.Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + if (id % 2 == 0) { + ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + } + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty()); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty()); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelIndexDuplicateVersions) { + // By removing labels and adding them again we create duplicate entries for + // the same vertex in the index (they only differ by the timestamp). This test + // checks that duplicates are properly filtered out. + EXPECT_TRUE(storage.CreateIndex(label1)); + EXPECT_TRUE(storage.CreateIndex(label2)); + + { + auto acc = storage.Access(); + for (int i = 0; i < 5; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + + ASSERT_NO_ERROR(acc.Commit()); + } + + { + auto acc = storage.Access(); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + + for (auto vertex : acc.Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.RemoveLabelAndValidate(label1)); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + + for (auto vertex : acc.Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + } + EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelIndexTransactionalIsolation) { + // Check that transactions only see entries they are supposed to see. + EXPECT_TRUE(storage.CreateIndex(label1)); + EXPECT_TRUE(storage.CreateIndex(label2)); + + auto acc_before = storage.Access(); + auto acc = storage.Access(); + auto acc_after = storage.Access(); + + for (int i = 0; i < 5; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(GetIds(acc_before.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc_after.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + + ASSERT_NO_ERROR(acc.Commit()); + + auto acc_after_commit = storage.Access(); + + EXPECT_THAT(GetIds(acc_before.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc_after.Vertices(label1, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc_after_commit.Vertices(label1, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelIndexCountEstimate) { + EXPECT_TRUE(storage.CreateIndex(label1)); + EXPECT_TRUE(storage.CreateIndex(label2)); + + auto acc = storage.Access(); + for (int i = 0; i < 20; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 3 ? label1 : label2)); + } + + EXPECT_EQ(acc.ApproximateVertexCount(label1), 13); + EXPECT_EQ(acc.ApproximateVertexCount(label2), 7); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelPropertyIndexCreateAndDrop) { + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + EXPECT_TRUE(storage.CreateIndex(label1, prop_id)); + { + auto acc = storage.Access(); + EXPECT_TRUE(acc.LabelPropertyIndexExists(label1, prop_id)); + } + EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(label1, prop_id))); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelPropertyIndexExists(label2, prop_id)); + } + EXPECT_FALSE(storage.CreateIndex(label1, prop_id)); + EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(label1, prop_id))); + + EXPECT_TRUE(storage.CreateIndex(label2, prop_id)); + { + auto acc = storage.Access(); + EXPECT_TRUE(acc.LabelPropertyIndexExists(label2, prop_id)); + } + EXPECT_THAT(storage.ListAllIndices().label_property, + UnorderedElementsAre(std::make_pair(label1, prop_id), std::make_pair(label2, prop_id))); + + EXPECT_TRUE(storage.DropIndex(label1, prop_id)); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelPropertyIndexExists(label1, prop_id)); + } + EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(std::make_pair(label2, prop_id))); + EXPECT_FALSE(storage.DropIndex(label1, prop_id)); + + EXPECT_TRUE(storage.DropIndex(label2, prop_id)); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelPropertyIndexExists(label2, prop_id)); + } + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); +} + +// The following three tests are almost an exact copy-paste of the corresponding +// label index tests. We request all vertices with given label and property from +// the index, without range filtering. Range filtering is tested in a separate +// test. + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelPropertyIndexBasic) { + storage.CreateIndex(label1, prop_val); + storage.CreateIndex(label2, prop_val); + + auto acc = storage.Access(); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), IsEmpty()); + + for (int i = 0; i < 10; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(i % 2 ? label1 : label2)); + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue(i))); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc.Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + if (id % 2) { + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue())); + } else { + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + } + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 2, 4, 6, 8)); + + for (auto vertex : acc.Vertices(View::OLD)) { + int64_t id = vertex.GetProperty(prop_id, View::OLD)->ValueInt(); + if (id % 2 == 0) { + ASSERT_NO_ERROR(acc.DeleteVertex(&vertex)); + } + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(1, 3, 5, 7, 9)); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 2, 4, 6, 8)); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), IsEmpty()); + + acc.AdvanceCommand(); + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::OLD), View::OLD), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc.Vertices(label2, prop_val, View::NEW), View::NEW), IsEmpty()); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelPropertyIndexDuplicateVersions) { + storage.CreateIndex(label1, prop_val); + { + auto acc = storage.Access(); + for (int i = 0; i < 5; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue(i))); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + + ASSERT_NO_ERROR(acc.Commit()); + } + + { + auto acc = storage.Access(); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + + for (auto vertex : acc.Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue())); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + + for (auto vertex : acc.Vertices(View::OLD)) { + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue(42))); + } + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelPropertyIndexTransactionalIsolation) { + storage.CreateIndex(label1, prop_val); + + auto acc_before = storage.Access(); + auto acc = storage.Access(); + auto acc_after = storage.Access(); + + for (int i = 0; i < 5; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue(i))); + } + + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, View::NEW), View::NEW), UnorderedElementsAre(0, 1, 2, 3, 4)); + EXPECT_THAT(GetIds(acc_before.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc_after.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + + ASSERT_NO_ERROR(acc.Commit()); + + auto acc_after_commit = storage.Access(); + + EXPECT_THAT(GetIds(acc_before.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc_after.Vertices(label1, prop_val, View::NEW), View::NEW), IsEmpty()); + EXPECT_THAT(GetIds(acc_after_commit.Vertices(label1, prop_val, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4)); +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelPropertyIndexFiltering) { + // We insert vertices with values: + // 0 0.0 1 1.0 2 2.0 3 3.0 4 4.0 + // Then we check all combinations of inclusive and exclusive bounds. + // We also have a mix of doubles and integers to verify that they are sorted + // properly. + + storage.CreateIndex(label1, prop_val); + + { + auto acc = storage.Access(); + + for (int i = 0; i < 10; ++i) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, i % 2 ? PropertyValue(i / 2) : PropertyValue(i / 2.0))); + } + ASSERT_NO_ERROR(acc.Commit()); + } + { + auto acc = storage.Access(); + for (int i = 0; i < 5; ++i) { + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, PropertyValue(i), View::OLD)), + UnorderedElementsAre(2 * i, 2 * i + 1)); + } + + // [1, +inf> + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), + std::nullopt, View::OLD)), + UnorderedElementsAre(2, 3, 4, 5, 6, 7, 8, 9)); + // <1, +inf> + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), + std::nullopt, View::OLD)), + UnorderedElementsAre(4, 5, 6, 7, 8, 9)); + + // <-inf, 3] + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, std::nullopt, + memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); + // <-inf, 3> + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, std::nullopt, + memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(0, 1, 2, 3, 4, 5)); + + // [1, 3] + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), + memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(2, 3, 4, 5, 6, 7)); + // <1, 3] + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), + memgraph::utils::MakeBoundInclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(4, 5, 6, 7)); + // [1, 3> + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(1)), + memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(2, 3, 4, 5)); + // <1, 3> + EXPECT_THAT(GetIds(acc.Vertices(label1, prop_val, memgraph::utils::MakeBoundExclusive(PropertyValue(1)), + memgraph::utils::MakeBoundExclusive(PropertyValue(3)), View::OLD)), + UnorderedElementsAre(4, 5)); + } +} + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_F(IndexTest, LabelPropertyIndexCountEstimate) { + storage.CreateIndex(label1, prop_val); + + auto acc = storage.Access(); + for (int i = 1; i <= 10; ++i) { + for (int j = 0; j < i; ++j) { + auto vertex = CreateVertex(&acc); + ASSERT_NO_ERROR(vertex.AddLabelAndValidate(label1)); + ASSERT_NO_ERROR(vertex.SetPropertyAndValidate(prop_val, PropertyValue(i))); + } + } + + EXPECT_EQ(acc.ApproximateVertexCount(label1, prop_val), 55); + for (int i = 1; i <= 10; ++i) { + EXPECT_EQ(acc.ApproximateVertexCount(label1, prop_val, PropertyValue(i)), i); + } + + EXPECT_EQ(acc.ApproximateVertexCount(label1, prop_val, memgraph::utils::MakeBoundInclusive(PropertyValue(2)), + memgraph::utils::MakeBoundInclusive(PropertyValue(6))), + 2 + 3 + 4 + 5 + 6); +} + +TEST_F(IndexTest, LabelPropertyIndexMixedIteration) { + storage.CreateIndex(label1, prop_val); + + const std::array temporals{TemporalData{TemporalType::Date, 23}, TemporalData{TemporalType::Date, 28}, + TemporalData{TemporalType::LocalDateTime, 20}}; + + std::vector values = { + PropertyValue(false), + PropertyValue(true), + PropertyValue(-std::numeric_limits::infinity()), + PropertyValue(std::numeric_limits::min()), + PropertyValue(-1), + PropertyValue(-0.5), + PropertyValue(0), + PropertyValue(0.5), + PropertyValue(1), + PropertyValue(1.5), + PropertyValue(2), + PropertyValue(std::numeric_limits::max()), + PropertyValue(std::numeric_limits::infinity()), + PropertyValue(""), + PropertyValue("a"), + PropertyValue("b"), + PropertyValue("c"), + PropertyValue(std::vector()), + PropertyValue(std::vector{PropertyValue(0.8)}), + PropertyValue(std::vector{PropertyValue(2)}), + PropertyValue(std::map()), + PropertyValue(std::map{{"id", PropertyValue(5)}}), + PropertyValue(std::map{{"id", PropertyValue(10)}}), + PropertyValue(temporals[0]), + PropertyValue(temporals[1]), + PropertyValue(temporals[2]), + }; + + // Create vertices, each with one of the values above. + { + auto acc = storage.Access(); + for (const auto &value : values) { + auto v = acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, PropertyValue(primary_key_id++)}}); + ASSERT_TRUE(v->AddLabelAndValidate(label1).HasValue()); + ASSERT_TRUE(v->SetPropertyAndValidate(prop_val, value).HasValue()); + } + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Verify that all nodes are in the index. + { + auto acc = storage.Access(); + auto iterable = acc.Vertices(label1, prop_val, View::OLD); + auto it = iterable.begin(); + for (const auto &value : values) { + ASSERT_NE(it, iterable.end()); + auto vertex = *it; + auto maybe_value = vertex.GetProperty(prop_val, View::OLD); + ASSERT_TRUE(maybe_value.HasValue()); + ASSERT_EQ(value, *maybe_value); + ++it; + } + ASSERT_EQ(it, iterable.end()); + } + + auto verify = [&](const std::optional> &from, + const std::optional> &to, + const std::vector &expected) { + auto acc = storage.Access(); + auto iterable = acc.Vertices(label1, prop_val, from, to, View::OLD); + size_t i = 0; + for (auto it = iterable.begin(); it != iterable.end(); ++it, ++i) { + auto vertex = *it; + auto maybe_value = vertex.GetProperty(prop_val, View::OLD); + ASSERT_TRUE(maybe_value.HasValue()); + ASSERT_EQ(*maybe_value, expected[i]); + } + ASSERT_EQ(i, expected.size()); + }; + + // Range iteration with two specified bounds that have the same type should + // yield the naturally expected items. + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(false)), + memgraph::utils::MakeBoundExclusive(PropertyValue(true)), {}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(false)), + memgraph::utils::MakeBoundInclusive(PropertyValue(true)), {PropertyValue(true)}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(false)), + memgraph::utils::MakeBoundExclusive(PropertyValue(true)), {PropertyValue(false)}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(false)), + memgraph::utils::MakeBoundInclusive(PropertyValue(true)), {PropertyValue(false), PropertyValue(true)}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(0)), memgraph::utils::MakeBoundExclusive(PropertyValue(1.8)), + {PropertyValue(0.5), PropertyValue(1), PropertyValue(1.5)}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(0)), memgraph::utils::MakeBoundInclusive(PropertyValue(1.8)), + {PropertyValue(0.5), PropertyValue(1), PropertyValue(1.5)}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(0)), memgraph::utils::MakeBoundExclusive(PropertyValue(1.8)), + {PropertyValue(0), PropertyValue(0.5), PropertyValue(1), PropertyValue(1.5)}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(0)), memgraph::utils::MakeBoundInclusive(PropertyValue(1.8)), + {PropertyValue(0), PropertyValue(0.5), PropertyValue(1), PropertyValue(1.5)}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue("b")), + memgraph::utils::MakeBoundExclusive(PropertyValue("memgraph")), {PropertyValue("c")}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue("b")), + memgraph::utils::MakeBoundInclusive(PropertyValue("memgraph")), {PropertyValue("c")}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue("b")), + memgraph::utils::MakeBoundExclusive(PropertyValue("memgraph")), {PropertyValue("b"), PropertyValue("c")}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue("b")), + memgraph::utils::MakeBoundInclusive(PropertyValue("memgraph")), {PropertyValue("b"), PropertyValue("c")}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(std::vector{PropertyValue(0.8)})), + memgraph::utils::MakeBoundExclusive(PropertyValue(std::vector{PropertyValue("b")})), + {PropertyValue(std::vector{PropertyValue(2)})}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(std::vector{PropertyValue(0.8)})), + memgraph::utils::MakeBoundInclusive(PropertyValue(std::vector{PropertyValue("b")})), + {PropertyValue(std::vector{PropertyValue(2)})}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(std::vector{PropertyValue(0.8)})), + memgraph::utils::MakeBoundExclusive(PropertyValue(std::vector{PropertyValue("b")})), + {PropertyValue(std::vector{PropertyValue(0.8)}), + PropertyValue(std::vector{PropertyValue(2)})}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(std::vector{PropertyValue(0.8)})), + memgraph::utils::MakeBoundInclusive(PropertyValue(std::vector{PropertyValue("b")})), + {PropertyValue(std::vector{PropertyValue(0.8)}), + PropertyValue(std::vector{PropertyValue(2)})}); + verify(memgraph::utils::MakeBoundExclusive( + PropertyValue(std::map{{"id", PropertyValue(5.0)}})), + memgraph::utils::MakeBoundExclusive( + PropertyValue(std::map{{"id", PropertyValue("b")}})), + {PropertyValue(std::map{{"id", PropertyValue(10)}})}); + verify(memgraph::utils::MakeBoundExclusive( + PropertyValue(std::map{{"id", PropertyValue(5.0)}})), + memgraph::utils::MakeBoundInclusive( + PropertyValue(std::map{{"id", PropertyValue("b")}})), + {PropertyValue(std::map{{"id", PropertyValue(10)}})}); + verify(memgraph::utils::MakeBoundInclusive( + PropertyValue(std::map{{"id", PropertyValue(5.0)}})), + memgraph::utils::MakeBoundExclusive( + PropertyValue(std::map{{"id", PropertyValue("b")}})), + {PropertyValue(std::map{{"id", PropertyValue(5)}}), + PropertyValue(std::map{{"id", PropertyValue(10)}})}); + verify(memgraph::utils::MakeBoundInclusive( + PropertyValue(std::map{{"id", PropertyValue(5.0)}})), + memgraph::utils::MakeBoundInclusive( + PropertyValue(std::map{{"id", PropertyValue("b")}})), + {PropertyValue(std::map{{"id", PropertyValue(5)}}), + PropertyValue(std::map{{"id", PropertyValue(10)}})}); + + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(temporals[0])), + memgraph::utils::MakeBoundInclusive(PropertyValue(TemporalData{TemporalType::Date, 200})), + // LocalDateTime has a "higher" type number so it is not part of the range + {PropertyValue(temporals[1])}); + verify(memgraph::utils::MakeBoundExclusive(PropertyValue(temporals[0])), + memgraph::utils::MakeBoundInclusive(PropertyValue(temporals[2])), + {PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(temporals[0])), + memgraph::utils::MakeBoundExclusive(PropertyValue(temporals[2])), + {PropertyValue(temporals[0]), PropertyValue(temporals[1])}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(temporals[0])), + memgraph::utils::MakeBoundInclusive(PropertyValue(temporals[2])), + {PropertyValue(temporals[0]), PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + + // Range iteration with one unspecified bound should only yield items that + // have the same type as the specified bound. + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(false)), std::nullopt, + {PropertyValue(false), PropertyValue(true)}); + verify(std::nullopt, memgraph::utils::MakeBoundExclusive(PropertyValue(true)), {PropertyValue(false)}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(1)), std::nullopt, + {PropertyValue(1), PropertyValue(1.5), PropertyValue(2), PropertyValue(std::numeric_limits::max()), + PropertyValue(std::numeric_limits::infinity())}); + verify(std::nullopt, memgraph::utils::MakeBoundExclusive(PropertyValue(0)), + {PropertyValue(-std::numeric_limits::infinity()), PropertyValue(std::numeric_limits::min()), + PropertyValue(-1), PropertyValue(-0.5)}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue("b")), std::nullopt, + {PropertyValue("b"), PropertyValue("c")}); + verify(std::nullopt, memgraph::utils::MakeBoundExclusive(PropertyValue("b")), + {PropertyValue(""), PropertyValue("a")}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(std::vector{PropertyValue(false)})), + std::nullopt, + {PropertyValue(std::vector{PropertyValue(0.8)}), + PropertyValue(std::vector{PropertyValue(2)})}); + verify(std::nullopt, memgraph::utils::MakeBoundExclusive(PropertyValue(std::vector{PropertyValue(1)})), + {PropertyValue(std::vector()), PropertyValue(std::vector{PropertyValue(0.8)})}); + verify(memgraph::utils::MakeBoundInclusive( + PropertyValue(std::map{{"id", PropertyValue(false)}})), + std::nullopt, + {PropertyValue(std::map{{"id", PropertyValue(5)}}), + PropertyValue(std::map{{"id", PropertyValue(10)}})}); + verify(std::nullopt, + memgraph::utils::MakeBoundExclusive( + PropertyValue(std::map{{"id", PropertyValue(7.5)}})), + {PropertyValue(std::map()), + PropertyValue(std::map{{"id", PropertyValue(5)}})}); + verify(memgraph::utils::MakeBoundInclusive(PropertyValue(TemporalData(TemporalType::Date, 10))), std::nullopt, + {PropertyValue(temporals[0]), PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + verify(std::nullopt, memgraph::utils::MakeBoundExclusive(PropertyValue(TemporalData(TemporalType::Duration, 0))), + {PropertyValue(temporals[0]), PropertyValue(temporals[1]), PropertyValue(temporals[2])}); + + // Range iteration with two specified bounds that don't have the same type + // should yield no items. + for (size_t i = 0; i < values.size(); ++i) { + for (size_t j = i; j < values.size(); ++j) { + if (PropertyValue::AreComparableTypes(values[i].type(), values[j].type())) { + verify(memgraph::utils::MakeBoundInclusive(values[i]), memgraph::utils::MakeBoundInclusive(values[j]), + {values.begin() + i, values.begin() + j + 1}); + } else { + verify(memgraph::utils::MakeBoundInclusive(values[i]), memgraph::utils::MakeBoundInclusive(values[j]), {}); + } + } + } + + // Iteration without any bounds should return all items of the index. + verify(std::nullopt, std::nullopt, values); +} + +TEST_F(IndexTest, LabelPropertyIndexCreateWithExistingPrimaryKey) { + // Create index on primary label and on primary key + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + EXPECT_FALSE(storage.CreateIndex(primary_label, primary_property)); + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + + // Create index on primary label and on secondary property + EXPECT_TRUE(storage.CreateIndex(primary_label, prop_id)); + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 1); + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + { + auto acc = storage.Access(); + EXPECT_TRUE(acc.LabelPropertyIndexExists(primary_label, prop_id)); + } + EXPECT_THAT(storage.ListAllIndices().label_property, UnorderedElementsAre(Pair(primary_label, prop_id))); + + // Create index on primary label + EXPECT_FALSE(storage.CreateIndex(primary_label)); + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 1); + + // Create index on secondary label + EXPECT_TRUE(storage.CreateIndex(label1)); + EXPECT_EQ(storage.ListAllIndices().label.size(), 1); + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 1); +} + +TEST_F(IndexTest, LabelIndexCreateVertexAndValidate) { + { + auto acc = storage.Access(); + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + } + { + auto acc = storage.Access(); + + // Create vertices with CreateVertexAndValidate + for (int i = 0; i < 5; ++i) { + auto vertex = + acc.CreateVertexAndValidate(primary_label, {label1}, {{primary_property, PropertyValue(primary_key_id++)}}); + ASSERT_TRUE(vertex.HasValue()); + } + ASSERT_NO_ERROR(acc.Commit()); + } + { + EXPECT_TRUE(storage.CreateIndex(label1)); + { + auto acc = storage.Access(); + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + } + } + { + EXPECT_TRUE(storage.DropIndex(label1)); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelIndexExists(label1)); + } + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + } + { + auto acc = storage.Access(); + EXPECT_TRUE(storage.CreateIndex(label1)); + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + + for (int i = 0; i < 5; ++i) { + auto vertex = + acc.CreateVertexAndValidate(primary_label, {label1}, {{primary_property, PropertyValue(primary_key_id++)}}); + ASSERT_TRUE(vertex.HasValue()); + } + + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, View::OLD), View::OLD), UnorderedElementsAre(0, 1, 2, 3, 4)); + } +} + +TEST_F(IndexTest, LabelPropertyIndexCreateVertexAndValidate) { + { + auto acc = storage.Access(); + EXPECT_EQ(storage.ListAllIndices().label.size(), 0); + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + } + { + auto acc = storage.Access(); + + // Create vertices with CreateVertexAndValidate + for (int i = 0; i < 5; ++i) { + auto vertex = acc.CreateVertexAndValidate( + primary_label, {label1}, + {{primary_property, PropertyValue(primary_key_id++)}, {prop_id, PropertyValue(vertex_id++)}}); + ASSERT_TRUE(vertex.HasValue()); + } + ASSERT_NO_ERROR(acc.Commit()); + } + { + EXPECT_TRUE(storage.CreateIndex(label1, prop_id)); + { + auto acc = storage.Access(); + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, prop_id, View::OLD), View::OLD), + UnorderedElementsAre(0, 1, 2, 3, 4)); + } + } + { + EXPECT_TRUE(storage.DropIndex(label1, prop_id)); + { + auto acc = storage.Access(); + EXPECT_FALSE(acc.LabelPropertyIndexExists(label1, prop_id)); + } + EXPECT_EQ(storage.ListAllIndices().label_property.size(), 0); + } + { + auto acc = storage.Access(); + EXPECT_TRUE(storage.CreateIndex(label1, prop_id)); + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, prop_id, View::OLD), View::OLD), + UnorderedElementsAre(0, 1, 2, 3, 4)); + + for (int i = 0; i < 5; ++i) { + auto vertex = acc.CreateVertexAndValidate( + primary_label, {label1}, + {{primary_property, PropertyValue(primary_key_id++)}, {prop_id, PropertyValue(vertex_id++)}}); + ASSERT_TRUE(vertex.HasValue()); + } + + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, prop_id, View::NEW), View::NEW), + UnorderedElementsAre(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + EXPECT_THAT(GetPrimaryKeyIds(acc.Vertices(label1, prop_id, View::OLD), View::OLD), + UnorderedElementsAre(0, 1, 2, 3, 4)); + } +} +} // namespace memgraph::storage::v3::tests From a2a6a3855bbd63856c757d0385d608d10f2c7bab Mon Sep 17 00:00:00 2001 From: Jure Bajic Date: Wed, 7 Sep 2022 13:36:10 +0200 Subject: [PATCH 06/10] Remove primary label from vertex (#529) - Remove the primary label from vertex - Pass vertex_validator instead of schema validator - Fix vertex ctors --- src/storage/v3/config.hpp | 1 + src/storage/v3/durability/snapshot.cpp | 7 ++-- src/storage/v3/edge_accessor.cpp | 4 +- src/storage/v3/edge_accessor.hpp | 7 ++-- src/storage/v3/indices.cpp | 18 ++++----- src/storage/v3/indices.hpp | 33 ++++++++--------- .../v3/replication/replication_server.cpp | 4 +- src/storage/v3/schema_validator.cpp | 15 ++++++++ src/storage/v3/schema_validator.hpp | 14 +++++++ src/storage/v3/shard.cpp | 37 ++++++++++--------- src/storage/v3/shard.hpp | 9 +++-- src/storage/v3/vertex.hpp | 14 +------ src/storage/v3/vertex_accessor.cpp | 35 +++++------------- src/storage/v3/vertex_accessor.hpp | 23 ++---------- 14 files changed, 106 insertions(+), 115 deletions(-) diff --git a/src/storage/v3/config.hpp b/src/storage/v3/config.hpp index 9d6dec759..bb99905df 100644 --- a/src/storage/v3/config.hpp +++ b/src/storage/v3/config.hpp @@ -14,6 +14,7 @@ #include #include #include +#include "storage/v3/id_types.hpp" #include "storage/v3/isolation_level.hpp" #include "storage/v3/transaction.hpp" diff --git a/src/storage/v3/durability/snapshot.cpp b/src/storage/v3/durability/snapshot.cpp index 9ae4d00ab..50fd418c1 100644 --- a/src/storage/v3/durability/snapshot.cpp +++ b/src/storage/v3/durability/snapshot.cpp @@ -19,6 +19,7 @@ #include "storage/v3/edge_accessor.hpp" #include "storage/v3/edge_ref.hpp" #include "storage/v3/mvcc.hpp" +#include "storage/v3/schema_validator.hpp" #include "storage/v3/schemas.hpp" #include "storage/v3/vertex_accessor.hpp" #include "storage/v3/vertices_skip_list.hpp" @@ -636,7 +637,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps const std::filesystem::path &wal_directory, uint64_t snapshot_retention_count, VerticesSkipList *vertices, utils::SkipList *edges, NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, Config::Items items, - const SchemaValidator &schema_validator, const std::string &uuid, const std::string_view epoch_id, + const VertexValidator &vertex_validator, const std::string &uuid, const std::string_view epoch_id, const std::deque> &epoch_history, utils::FileRetainer *file_retainer) { // Ensure that the storage directory exists. @@ -718,7 +719,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // here. // TODO(jbajic) Fix snapshot with new schema rules auto ea = EdgeAccessor{edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices, constraints, - items, schema_validator}; + items, vertex_validator}; // Get edge data. auto maybe_props = ea.Properties(View::OLD); @@ -746,7 +747,7 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps auto acc = vertices->access(); for (auto &lgo_vertex : acc) { // The visibility check is implemented for vertices so we use it here. - auto va = VertexAccessor::Create(&lgo_vertex.vertex, transaction, indices, constraints, items, schema_validator, + auto va = VertexAccessor::Create(&lgo_vertex.vertex, transaction, indices, constraints, items, vertex_validator, View::OLD); if (!va) continue; diff --git a/src/storage/v3/edge_accessor.cpp b/src/storage/v3/edge_accessor.cpp index b4d275d69..f0ddd6cc1 100644 --- a/src/storage/v3/edge_accessor.cpp +++ b/src/storage/v3/edge_accessor.cpp @@ -51,11 +51,11 @@ bool EdgeAccessor::IsVisible(const View view) const { } VertexAccessor EdgeAccessor::FromVertex() const { - return {from_vertex_, transaction_, indices_, constraints_, config_, *schema_validator_}; + return {from_vertex_, transaction_, indices_, constraints_, config_, *vertex_validator_}; } VertexAccessor EdgeAccessor::ToVertex() const { - return {to_vertex_, transaction_, indices_, constraints_, config_, *schema_validator_}; + return {to_vertex_, transaction_, indices_, constraints_, config_, *vertex_validator_}; } Result EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) { diff --git a/src/storage/v3/edge_accessor.hpp b/src/storage/v3/edge_accessor.hpp index 8a17b163c..8dfaee31e 100644 --- a/src/storage/v3/edge_accessor.hpp +++ b/src/storage/v3/edge_accessor.hpp @@ -17,6 +17,7 @@ #include "storage/v3/edge_ref.hpp" #include "storage/v3/config.hpp" +#include "storage/v3/id_types.hpp" #include "storage/v3/result.hpp" #include "storage/v3/schema_validator.hpp" #include "storage/v3/transaction.hpp" @@ -36,7 +37,7 @@ class EdgeAccessor final { public: EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction, Indices *indices, Constraints *constraints, Config::Items config, - const SchemaValidator &schema_validator, bool for_deleted = false) + const VertexValidator &vertex_validator, bool for_deleted = false) : edge_(edge), edge_type_(edge_type), from_vertex_(from_vertex), @@ -45,7 +46,7 @@ class EdgeAccessor final { indices_(indices), constraints_(constraints), config_(config), - schema_validator_{&schema_validator}, + vertex_validator_{&vertex_validator}, for_deleted_(for_deleted) {} /// @return true if the object is visible from the current transaction @@ -94,7 +95,7 @@ class EdgeAccessor final { Indices *indices_; Constraints *constraints_; Config::Items config_; - const SchemaValidator *schema_validator_; + const VertexValidator *vertex_validator_; // if the accessor was created for a deleted edge. // Accessor behaves differently for some methods based on this diff --git a/src/storage/v3/indices.cpp b/src/storage/v3/indices.cpp index e69aa9d7e..82e7eadf2 100644 --- a/src/storage/v3/indices.cpp +++ b/src/storage/v3/indices.cpp @@ -13,9 +13,9 @@ #include +#include "storage/v3/id_types.hpp" #include "storage/v3/mvcc.hpp" #include "storage/v3/property_value.hpp" -#include "storage/v3/schema_validator.hpp" #include "storage/v3/schemas.hpp" #include "utils/bound.hpp" #include "utils/logging.hpp" @@ -327,7 +327,7 @@ void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) { LabelIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList::Iterator index_iterator) : self_(self), index_iterator_(index_iterator), - current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->schema_validator_), + current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_), current_vertex_(nullptr) { AdvanceUntilValid(); } @@ -346,7 +346,7 @@ 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{current_vertex_, self_->transaction_, self_->indices_, - self_->constraints_, self_->config_, *self_->schema_validator_}; + self_->constraints_, self_->config_, *self_->vertex_validator_}; break; } } @@ -354,7 +354,7 @@ void LabelIndex::Iterable::Iterator::AdvanceUntilValid() { LabelIndex::Iterable::Iterable(utils::SkipList::Accessor index_accessor, LabelId label, View view, Transaction *transaction, Indices *indices, Constraints *constraints, - Config::Items config, const SchemaValidator &schema_validator) + Config::Items config, const VertexValidator &vertex_validator) : index_accessor_(std::move(index_accessor)), label_(label), view_(view), @@ -362,7 +362,7 @@ LabelIndex::Iterable::Iterable(utils::SkipList::Accessor index_accessor, indices_(indices), constraints_(constraints), config_(config), - schema_validator_(&schema_validator) {} + vertex_validator_(&vertex_validator) {} void LabelIndex::RunGC() { for (auto &index_entry : index_) { @@ -480,7 +480,7 @@ void LabelPropertyIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_time LabelPropertyIndex::Iterable::Iterator::Iterator(Iterable *self, utils::SkipList::Iterator index_iterator) : self_(self), index_iterator_(index_iterator), - current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->schema_validator_), + current_vertex_accessor_(nullptr, nullptr, nullptr, nullptr, self_->config_, *self_->vertex_validator_), current_vertex_(nullptr) { AdvanceUntilValid(); } @@ -520,7 +520,7 @@ void LabelPropertyIndex::Iterable::Iterator::AdvanceUntilValid() { index_iterator_->value, self_->transaction_, self_->view_)) { current_vertex_ = index_iterator_->vertex; current_vertex_accessor_ = VertexAccessor(current_vertex_, self_->transaction_, self_->indices_, - self_->constraints_, self_->config_, *self_->schema_validator_); + self_->constraints_, self_->config_, *self_->vertex_validator_); break; } } @@ -543,7 +543,7 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList::Accessor index_ac const std::optional> &lower_bound, const std::optional> &upper_bound, View view, Transaction *transaction, Indices *indices, Constraints *constraints, - Config::Items config, const SchemaValidator &schema_validator) + Config::Items config, const VertexValidator &vertex_validator) : index_accessor_(std::move(index_accessor)), label_(label), property_(property), @@ -554,7 +554,7 @@ LabelPropertyIndex::Iterable::Iterable(utils::SkipList::Accessor index_ac indices_(indices), constraints_(constraints), config_(config), - schema_validator_(&schema_validator) { + vertex_validator_(&vertex_validator) { // We have to fix the bounds that the user provided to us. If the user // provided only one bound we should make sure that only values of that type // are returned by the iterator. We ensure this by supplying either an diff --git a/src/storage/v3/indices.hpp b/src/storage/v3/indices.hpp index e7ae2d593..01c611c97 100644 --- a/src/storage/v3/indices.hpp +++ b/src/storage/v3/indices.hpp @@ -18,7 +18,6 @@ #include "storage/v3/config.hpp" #include "storage/v3/property_value.hpp" -#include "storage/v3/schema_validator.hpp" #include "storage/v3/transaction.hpp" #include "storage/v3/vertex_accessor.hpp" #include "storage/v3/vertices_skip_list.hpp" @@ -54,8 +53,8 @@ class LabelIndex { }; public: - LabelIndex(Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator) - : indices_(indices), constraints_(constraints), config_(config), schema_validator_{&schema_validator} {} + LabelIndex(Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator) + : indices_(indices), constraints_(constraints), config_(config), vertex_validator_{&vertex_validator} {} /// @throw std::bad_alloc void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx); @@ -75,7 +74,7 @@ class LabelIndex { class Iterable { public: Iterable(utils::SkipList::Accessor index_accessor, LabelId label, View view, Transaction *transaction, - Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator); + Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator); class Iterator { public: @@ -108,14 +107,14 @@ class LabelIndex { Indices *indices_; Constraints *constraints_; Config::Items config_; - const SchemaValidator *schema_validator_; + const VertexValidator *vertex_validator_; }; /// Returns an self with vertices visible from the given transaction. Iterable Vertices(LabelId label, View view, Transaction *transaction) { auto it = index_.find(label); MG_ASSERT(it != index_.end(), "Index for label {} doesn't exist", label.AsUint()); - return {it->second.access(), label, view, transaction, indices_, constraints_, config_, *schema_validator_}; + return {it->second.access(), label, view, transaction, indices_, constraints_, config_, *vertex_validator_}; } int64_t ApproximateVertexCount(LabelId label) { @@ -133,7 +132,7 @@ class LabelIndex { Indices *indices_; Constraints *constraints_; Config::Items config_; - const SchemaValidator *schema_validator_; + const VertexValidator *vertex_validator_; }; class LabelPropertyIndex { @@ -152,8 +151,8 @@ class LabelPropertyIndex { public: LabelPropertyIndex(Indices *indices, Constraints *constraints, Config::Items config, - const SchemaValidator &schema_validator) - : indices_(indices), constraints_(constraints), config_(config), schema_validator_{&schema_validator} {} + const VertexValidator &vertex_validator) + : indices_(indices), constraints_(constraints), config_(config), vertex_validator_{&vertex_validator} {} /// @throw std::bad_alloc void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx); @@ -177,7 +176,7 @@ class LabelPropertyIndex { Iterable(utils::SkipList::Accessor index_accessor, LabelId label, PropertyId property, const std::optional> &lower_bound, const std::optional> &upper_bound, View view, Transaction *transaction, - Indices *indices, Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator); + Indices *indices, Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator); class Iterator { public: @@ -214,17 +213,17 @@ class LabelPropertyIndex { Indices *indices_; Constraints *constraints_; Config::Items config_; - const SchemaValidator *schema_validator_; + const VertexValidator *vertex_validator_; }; Iterable Vertices(LabelId label, PropertyId property, const std::optional> &lower_bound, const std::optional> &upper_bound, View view, Transaction *transaction, - const SchemaValidator &schema_validator) { + const VertexValidator &vertex_validator) { auto it = index_.find({label, property}); MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); return {it->second.access(), label, property, lower_bound, upper_bound, view, - transaction, indices_, constraints_, config_, schema_validator}; + transaction, indices_, constraints_, config_, vertex_validator}; } int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { @@ -253,13 +252,13 @@ class LabelPropertyIndex { Indices *indices_; Constraints *constraints_; Config::Items config_; - const SchemaValidator *schema_validator_; + const VertexValidator *vertex_validator_; }; struct Indices { - Indices(Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator) - : label_index(this, constraints, config, schema_validator), - label_property_index(this, constraints, config, schema_validator) {} + Indices(Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator) + : label_index(this, constraints, config, vertex_validator), + label_property_index(this, constraints, config, vertex_validator) {} // Disable copy and move because members hold pointer to `this`. Indices(const Indices &) = delete; diff --git a/src/storage/v3/replication/replication_server.cpp b/src/storage/v3/replication/replication_server.cpp index 1905071ce..f26615cf9 100644 --- a/src/storage/v3/replication/replication_server.cpp +++ b/src/storage/v3/replication/replication_server.cpp @@ -163,9 +163,9 @@ void Shard::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Bui shard_->constraints_ = Constraints(); shard_->indices_.label_index = - LabelIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->schema_validator_); + LabelIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->vertex_validator_); shard_->indices_.label_property_index = - LabelPropertyIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->schema_validator_); + LabelPropertyIndex(&shard_->indices_, &shard_->constraints_, shard_->config_.items, shard_->vertex_validator_); try { spdlog::debug("Loading snapshot"); auto recovered_snapshot = durability::RecoveredSnapshot{}; diff --git a/src/storage/v3/schema_validator.cpp b/src/storage/v3/schema_validator.cpp index ea46e550b..b69a3b5b8 100644 --- a/src/storage/v3/schema_validator.cpp +++ b/src/storage/v3/schema_validator.cpp @@ -103,4 +103,19 @@ SchemaValidator::SchemaValidator(Schemas &schemas) : schemas_{schemas} {} return std::nullopt; } +VertexValidator::VertexValidator(const SchemaValidator &schema_validator, const LabelId primary_label) + : schema_validator{&schema_validator}, primary_label_{primary_label} {} + +[[nodiscard]] std::optional VertexValidator::ValidatePropertyUpdate(PropertyId property_id) const { + return schema_validator->ValidatePropertyUpdate(primary_label_, property_id); +}; + +[[nodiscard]] std::optional VertexValidator::ValidateAddLabel(LabelId label) const { + return schema_validator->ValidateLabelUpdate(label); +} + +[[nodiscard]] std::optional VertexValidator::ValidateRemoveLabel(LabelId label) const { + return schema_validator->ValidateLabelUpdate(label); +} + } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/schema_validator.hpp b/src/storage/v3/schema_validator.hpp index e64a2ead5..54943a336 100644 --- a/src/storage/v3/schema_validator.hpp +++ b/src/storage/v3/schema_validator.hpp @@ -63,6 +63,20 @@ class SchemaValidator { Schemas &schemas_; }; +struct VertexValidator { + explicit VertexValidator(const SchemaValidator &schema_validator, LabelId primary_label); + + [[nodiscard]] std::optional ValidatePropertyUpdate(PropertyId property_id) const; + + [[nodiscard]] std::optional ValidateAddLabel(LabelId label) const; + + [[nodiscard]] std::optional ValidateRemoveLabel(LabelId label) const; + + const SchemaValidator *schema_validator; + + LabelId primary_label_; +}; + template using ResultSchema = utils::BasicResult, TValue>; diff --git a/src/storage/v3/shard.cpp b/src/storage/v3/shard.cpp index bd90327c9..1260004bf 100644 --- a/src/storage/v3/shard.cpp +++ b/src/storage/v3/shard.cpp @@ -70,9 +70,9 @@ void InsertVertexPKIntoList(auto &container, const PrimaryKey &primary_key) { co auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end, std::optional *vertex, Transaction *tx, View view, Indices *indices, - Constraints *constraints, Config::Items config, const SchemaValidator &schema_validator) { + Constraints *constraints, Config::Items config, const VertexValidator &vertex_validator) { while (it != end) { - *vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, schema_validator, view); + *vertex = VertexAccessor::Create(&it->vertex, tx, indices, constraints, config, vertex_validator, view); if (!*vertex) { ++it; continue; @@ -85,14 +85,14 @@ auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Ite AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, VerticesSkipList::Iterator it) : self_(self), it_(AdvanceToVisibleVertex(it, self->vertices_accessor_.end(), &self->vertex_, self->transaction_, self->view_, - self->indices_, self_->constraints_, self->config_, *self_->schema_validator_)) {} + self->indices_, self_->constraints_, self->config_, *self_->vertex_validator_)) {} VertexAccessor AllVerticesIterable::Iterator::operator*() const { return *self_->vertex_; } AllVerticesIterable::Iterator &AllVerticesIterable::Iterator::operator++() { ++it_; it_ = AdvanceToVisibleVertex(it_, self_->vertices_accessor_.end(), &self_->vertex_, self_->transaction_, self_->view_, - self_->indices_, self_->constraints_, self_->config_, *self_->schema_validator_); + self_->indices_, self_->constraints_, self_->config_, *self_->vertex_validator_); return *this; } @@ -321,7 +321,8 @@ Shard::Shard(const LabelId primary_label, const PrimaryKey min_primary_key, min_primary_key_{min_primary_key}, max_primary_key_{max_primary_key}, schema_validator_{schemas_}, - indices_{&constraints_, config.items, schema_validator_}, + vertex_validator_{schema_validator_, primary_label}, + indices_{&constraints_, config.items, vertex_validator_}, isolation_level_{config.transaction.isolation_level}, config_{config}, snapshot_directory_{config_.durability.storage_directory / durability::kSnapshotDirectory}, @@ -496,10 +497,10 @@ ResultSchema Shard::Accessor::CreateVertexAndValidate( } auto acc = shard_->vertices_.access(); auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert({Vertex{delta, primary_label, primary_properties}}); + auto [it, inserted] = acc.insert({Vertex{delta, primary_properties}}); VertexAccessor vertex_acc{&it->vertex, &transaction_, &shard_->indices_, - &shard_->constraints_, config_, shard_->schema_validator_}; + &shard_->constraints_, config_, shard_->vertex_validator_}; MG_ASSERT(inserted, "The vertex must be inserted here!"); MG_ASSERT(it != acc.end(), "Invalid Vertex accessor!"); @@ -529,7 +530,7 @@ std::optional Shard::Accessor::FindVertex(std::vectorvertex, &transaction_, &shard_->indices_, &shard_->constraints_, config_, - shard_->schema_validator_, view); + shard_->vertex_validator_, view); } Result> Shard::Accessor::DeleteVertex(VertexAccessor *vertex) { @@ -550,7 +551,7 @@ Result> Shard::Accessor::DeleteVertex(VertexAccess vertex_ptr->deleted = true; return std::make_optional(vertex_ptr, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->schema_validator_, true); + config_, shard_->vertex_validator_, true); } Result>>> Shard::Accessor::DetachDeleteVertex( @@ -578,7 +579,7 @@ Result>>> Shar for (const auto &item : in_edges) { auto [edge_type, from_vertex, edge] = item; EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->schema_validator_); + config_, shard_->vertex_validator_); auto ret = DeleteEdge(&e); if (ret.HasError()) { MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); @@ -592,7 +593,7 @@ Result>>> Shar for (const auto &item : out_edges) { auto [edge_type, to_vertex, edge] = item; EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->schema_validator_); + config_, shard_->vertex_validator_); auto ret = DeleteEdge(&e); if (ret.HasError()) { MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); @@ -616,7 +617,7 @@ Result>>> Shar vertex_ptr->deleted = true; return std::make_optional(VertexAccessor{vertex_ptr, &transaction_, &shard_->indices_, - &shard_->constraints_, config_, shard_->schema_validator_, true}, + &shard_->constraints_, config_, shard_->vertex_validator_, true}, std::move(deleted_edges)); } @@ -662,7 +663,7 @@ Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAcc ++shard_->edge_count_; return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->schema_validator_); + config_, shard_->vertex_validator_); } Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, @@ -715,7 +716,7 @@ Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAcc ++shard_->edge_count_; return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->schema_validator_); + config_, shard_->vertex_validator_); } Result> Shard::Accessor::DeleteEdge(EdgeAccessor *edge) { @@ -783,7 +784,7 @@ Result> Shard::Accessor::DeleteEdge(EdgeAccessor *ed --shard_->edge_count_; return std::make_optional(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, - &shard_->constraints_, config_, shard_->schema_validator_, true); + &shard_->constraints_, config_, shard_->vertex_validator_, true); } const std::string &Shard::Accessor::LabelToName(LabelId label) const { return shard_->LabelToName(label); } @@ -1208,20 +1209,20 @@ VerticesIterable Shard::Accessor::Vertices(LabelId label, View view) { VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, View view) { return VerticesIterable(shard_->indices_.label_property_index.Vertices( - label, property, std::nullopt, std::nullopt, view, &transaction_, shard_->schema_validator_)); + label, property, std::nullopt, std::nullopt, view, &transaction_, shard_->vertex_validator_)); } VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) { return VerticesIterable(shard_->indices_.label_property_index.Vertices( label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_, - shard_->schema_validator_)); + shard_->vertex_validator_)); } VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, const std::optional> &lower_bound, const std::optional> &upper_bound, View view) { return VerticesIterable(shard_->indices_.label_property_index.Vertices( - label, property, lower_bound, upper_bound, view, &transaction_, shard_->schema_validator_)); + label, property, lower_bound, upper_bound, view, &transaction_, shard_->vertex_validator_)); } Transaction Shard::CreateTransaction(IsolationLevel isolation_level) { diff --git a/src/storage/v3/shard.hpp b/src/storage/v3/shard.hpp index 8a42e542d..fcc80e4f0 100644 --- a/src/storage/v3/shard.hpp +++ b/src/storage/v3/shard.hpp @@ -79,7 +79,7 @@ class AllVerticesIterable final { Indices *indices_; Constraints *constraints_; Config::Items config_; - const SchemaValidator *schema_validator_; + const VertexValidator *vertex_validator_; const Schemas *schemas_; std::optional vertex_; @@ -102,14 +102,14 @@ class AllVerticesIterable final { AllVerticesIterable(VerticesSkipList::Accessor vertices_accessor, Transaction *transaction, View view, Indices *indices, Constraints *constraints, Config::Items config, - const SchemaValidator &schema_validator) + const VertexValidator &vertex_validator) : vertices_accessor_(std::move(vertices_accessor)), transaction_(transaction), view_(view), indices_(indices), constraints_(constraints), config_(config), - schema_validator_{&schema_validator} {} + vertex_validator_{&vertex_validator} {} Iterator begin() { return {this, vertices_accessor_.begin()}; } Iterator end() { return {this, vertices_accessor_.end()}; } @@ -246,7 +246,7 @@ class Shard final { VerticesIterable Vertices(View view) { return VerticesIterable(AllVerticesIterable(shard_->vertices_.access(), &transaction_, view, &shard_->indices_, &shard_->constraints_, shard_->config_.items, - shard_->schema_validator_)); + shard_->vertex_validator_)); } VerticesIterable Vertices(LabelId label, View view); @@ -519,6 +519,7 @@ class Shard final { uint64_t edge_count_{0}; SchemaValidator schema_validator_; + VertexValidator vertex_validator_; Constraints constraints_; Indices indices_; Schemas schemas_; diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 08ae747b3..4b2fbe9f8 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -28,19 +28,11 @@ namespace memgraph::storage::v3 { struct Vertex { - Vertex(Delta *delta, LabelId primary_label, const std::vector &primary_properties) - : primary_label{primary_label}, keys{primary_properties}, delta{delta} { + Vertex(Delta *delta, const std::vector &primary_properties) : keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } - Vertex(LabelId primary_label, const std::vector &primary_properties) - : primary_label{primary_label}, keys(primary_properties) { - MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, - "Vertex must be created with an initial DELETE_OBJECT delta!"); - } - - LabelId primary_label; KeyStore keys; std::vector labels; @@ -57,8 +49,6 @@ struct Vertex { static_assert(alignof(Vertex) >= 8, "The Vertex should be aligned to at least 8!"); -inline bool VertexHasLabel(const Vertex &vertex, const LabelId label) { - return vertex.primary_label == label || utils::Contains(vertex.labels, label); -} +inline bool VertexHasLabel(const Vertex &vertex, const LabelId label) { return utils::Contains(vertex.labels, label); } } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/vertex_accessor.cpp b/src/storage/v3/vertex_accessor.cpp index ff8b34276..a7e423387 100644 --- a/src/storage/v3/vertex_accessor.cpp +++ b/src/storage/v3/vertex_accessor.cpp @@ -64,12 +64,12 @@ std::pair IsVisible(Vertex *vertex, Transaction *transaction, View v std::optional VertexAccessor::Create(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints, Config::Items config, - const SchemaValidator &schema_validator, View view) { + const VertexValidator &vertex_validator, View view) { if (const auto [exists, deleted] = detail::IsVisible(vertex, transaction, view); !exists || deleted) { return std::nullopt; } - return VertexAccessor{vertex, transaction, indices, constraints, config, schema_validator}; + return VertexAccessor{vertex, transaction, indices, constraints, config, vertex_validator}; } bool VertexAccessor::IsVisible(View view) const { @@ -96,7 +96,7 @@ Result VertexAccessor::AddLabel(LabelId label) { } ResultSchema VertexAccessor::AddLabelAndValidate(LabelId label) { - if (const auto maybe_violation_error = vertex_validator_.ValidateAddLabel(label); maybe_violation_error) { + if (const auto maybe_violation_error = vertex_validator_->ValidateAddLabel(label); maybe_violation_error) { return {*maybe_violation_error}; } utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; @@ -132,7 +132,7 @@ Result VertexAccessor::RemoveLabel(LabelId label) { } ResultSchema VertexAccessor::RemoveLabelAndValidate(LabelId label) { - if (const auto maybe_violation_error = vertex_validator_.ValidateRemoveLabel(label); maybe_violation_error) { + if (const auto maybe_violation_error = vertex_validator_->ValidateRemoveLabel(label); maybe_violation_error) { return {*maybe_violation_error}; } @@ -157,7 +157,7 @@ Result VertexAccessor::HasLabel(LabelId label, View view) const { Delta *delta = nullptr; { deleted = vertex_->deleted; - has_label = VertexHasLabel(*vertex_, label); + has_label = label == vertex_validator_->primary_label_ || VertexHasLabel(*vertex_, label); delta = vertex_->delta; } ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &has_label, label](const Delta &delta) { @@ -227,7 +227,7 @@ Result VertexAccessor::PrimaryLabel(const View view) const { }); if (!exists) return Error::NONEXISTENT_OBJECT; if (!for_deleted_ && deleted) return Error::DELETED_OBJECT; - return vertex_->primary_label; + return vertex_validator_->primary_label_; } Result VertexAccessor::PrimaryKey(const View view) const { @@ -338,7 +338,7 @@ Result VertexAccessor::SetProperty(PropertyId property, const Pro } ResultSchema VertexAccessor::SetPropertyAndValidate(PropertyId property, const PropertyValue &value) { - if (auto maybe_violation_error = vertex_validator_.ValidatePropertyUpdate(property); maybe_violation_error) { + if (auto maybe_violation_error = vertex_validator_->ValidatePropertyUpdate(property); maybe_violation_error) { return {*maybe_violation_error}; } utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; @@ -546,7 +546,7 @@ Result> VertexAccessor::InEdges(View view, const std:: for (const auto &item : in_edges) { const auto &[edge_type, from_vertex, edge] = item; ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_, - *vertex_validator_.schema_validator); + *vertex_validator_); } return std::move(ret); } @@ -626,7 +626,7 @@ Result> VertexAccessor::OutEdges(View view, const std: for (const auto &item : out_edges) { const auto &[edge_type, to_vertex, edge] = item; ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_, - *vertex_validator_.schema_validator); + *vertex_validator_); } return std::move(ret); } @@ -705,21 +705,4 @@ Result VertexAccessor::OutDegree(View view) const { return degree; } -VertexAccessor::VertexValidator::VertexValidator(const SchemaValidator &schema_validator, const Vertex *vertex) - : schema_validator{&schema_validator}, vertex_{vertex} {} - -[[nodiscard]] std::optional VertexAccessor::VertexValidator::ValidatePropertyUpdate( - PropertyId property_id) const { - MG_ASSERT(vertex_ != nullptr, "Cannot validate vertex which is nullptr"); - return schema_validator->ValidatePropertyUpdate(vertex_->primary_label, property_id); -}; - -[[nodiscard]] std::optional VertexAccessor::VertexValidator::ValidateAddLabel(LabelId label) const { - return schema_validator->ValidateLabelUpdate(label); -} - -[[nodiscard]] std::optional VertexAccessor::VertexValidator::ValidateRemoveLabel(LabelId label) const { - return schema_validator->ValidateLabelUpdate(label); -} - } // namespace memgraph::storage::v3 diff --git a/src/storage/v3/vertex_accessor.hpp b/src/storage/v3/vertex_accessor.hpp index 71e297478..33556e3f6 100644 --- a/src/storage/v3/vertex_accessor.hpp +++ b/src/storage/v3/vertex_accessor.hpp @@ -31,39 +31,24 @@ struct Constraints; class VertexAccessor final { private: - struct VertexValidator { - // TODO(jbajic) Beware since vertex is pointer it will be accessed even as nullptr - explicit VertexValidator(const SchemaValidator &schema_validator, const Vertex *vertex); - - [[nodiscard]] std::optional ValidatePropertyUpdate(PropertyId property_id) const; - - [[nodiscard]] std::optional ValidateAddLabel(LabelId label) const; - - [[nodiscard]] std::optional ValidateRemoveLabel(LabelId label) const; - - const SchemaValidator *schema_validator; - - private: - const Vertex *vertex_; - }; friend class Shard; public: // Be careful when using VertexAccessor since it can be instantiated with // nullptr values VertexAccessor(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints, - Config::Items config, const SchemaValidator &schema_validator, bool for_deleted = false) + Config::Items config, const VertexValidator &vertex_validator, bool for_deleted = false) : vertex_(vertex), transaction_(transaction), indices_(indices), constraints_(constraints), config_(config), - vertex_validator_{schema_validator, vertex}, + vertex_validator_{&vertex_validator}, for_deleted_(for_deleted) {} static std::optional Create(Vertex *vertex, Transaction *transaction, Indices *indices, Constraints *constraints, Config::Items config, - const SchemaValidator &schema_validator, View view); + const VertexValidator &vertex_validator, View view); /// @return true if the object is visible from the current transaction bool IsVisible(View view) const; @@ -148,7 +133,7 @@ class VertexAccessor final { Indices *indices_; Constraints *constraints_; Config::Items config_; - VertexValidator vertex_validator_; + const VertexValidator *vertex_validator_; // if the accessor was created for a deleted vertex. // Accessor behaves differently for some methods based on this From 8e1f83acc90fa0c054fda55153a943432fc7afe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A1nos=20Benjamin=20Antal?= Date: Wed, 14 Sep 2022 13:11:14 +0200 Subject: [PATCH 07/10] Reference vertices by id in edges (#532) --- src/query/v2/db_accessor.hpp | 21 +- src/query/v2/procedure/mg_procedure_impl.hpp | 3 +- src/query/v2/trigger_context.hpp | 6 +- src/storage/v3/delta.hpp | 27 +- src/storage/v3/durability/snapshot.cpp | 19 +- src/storage/v3/edge_accessor.cpp | 8 +- src/storage/v3/edge_accessor.hpp | 19 +- src/storage/v3/id_types.hpp | 55 +- src/storage/v3/indices.hpp | 8 +- src/storage/v3/mvcc.hpp | 3 + .../v3/replication/replication_server.cpp | 1 - src/storage/v3/shard.cpp | 262 +- src/storage/v3/shard.hpp | 12 +- src/storage/v3/vertex.hpp | 9 +- src/storage/v3/vertex_accessor.cpp | 171 +- src/storage/v3/vertex_accessor.hpp | 9 +- src/storage/v3/vertex_id.hpp | 32 + tests/unit/CMakeLists.txt | 3 + tests/unit/storage_v3.cpp | 4 +- tests/unit/storage_v3_edge.cpp | 5274 +++++++++++++++++ 20 files changed, 5641 insertions(+), 305 deletions(-) create mode 100644 src/storage/v3/vertex_id.hpp create mode 100644 tests/unit/storage_v3_edge.cpp diff --git a/src/query/v2/db_accessor.hpp b/src/query/v2/db_accessor.hpp index a652ca656..7e7433d69 100644 --- a/src/query/v2/db_accessor.hpp +++ b/src/query/v2/db_accessor.hpp @@ -168,7 +168,8 @@ class VertexAccessor final { auto InEdges(storage::v3::View view, const std::vector &edge_types, const VertexAccessor &dest) const -> storage::v3::Result { - auto maybe_edges = impl_.InEdges(view, edge_types, &dest.impl_); + const auto dest_id = dest.impl_.Id(view).GetValue(); + auto maybe_edges = impl_.InEdges(view, edge_types, &dest_id); if (maybe_edges.HasError()) return maybe_edges.GetError(); return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges)); } @@ -185,7 +186,8 @@ class VertexAccessor final { auto OutEdges(storage::v3::View view, const std::vector &edge_types, const VertexAccessor &dest) const -> storage::v3::Result { - auto maybe_edges = impl_.OutEdges(view, edge_types, &dest.impl_); + const auto dest_id = dest.impl_.Id(view).GetValue(); + auto maybe_edges = impl_.OutEdges(view, edge_types, &dest_id); if (maybe_edges.HasError()) return maybe_edges.GetError(); return iter::imap(MakeEdgeAccessor, std::move(*maybe_edges)); } @@ -205,9 +207,14 @@ class VertexAccessor final { bool operator!=(const VertexAccessor &v) const noexcept { return !(*this == v); } }; -inline VertexAccessor EdgeAccessor::To() const { return VertexAccessor(impl_.ToVertex()); } +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnull-dereference" +// NOLINTNEXTLINE(readability-convert-member-functions-to-static,clang-analyzer-core.NonNullParamChecker) +inline VertexAccessor EdgeAccessor::To() const { return *static_cast(nullptr); } -inline VertexAccessor EdgeAccessor::From() const { return VertexAccessor(impl_.FromVertex()); } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static,clang-analyzer-core.NonNullParamChecker) +inline VertexAccessor EdgeAccessor::From() const { return *static_cast(nullptr); } +#pragma clang diagnostic pop inline bool EdgeAccessor::IsCycle() const { return To() == From(); } @@ -291,13 +298,15 @@ class DbAccessor final { storage::v3::Result InsertEdge(VertexAccessor *from, VertexAccessor *to, const storage::v3::EdgeTypeId &edge_type) { - auto maybe_edge = accessor_->CreateEdge(&from->impl_, &to->impl_, edge_type); + static constexpr auto kDummyGid = storage::v3::Gid::FromUint(0); + auto maybe_edge = accessor_->CreateEdge(from->impl_.Id(storage::v3::View::NEW).GetValue(), + to->impl_.Id(storage::v3::View::NEW).GetValue(), edge_type, kDummyGid); if (maybe_edge.HasError()) return storage::v3::Result(maybe_edge.GetError()); return EdgeAccessor(*maybe_edge); } storage::v3::Result> RemoveEdge(EdgeAccessor *edge) { - auto res = accessor_->DeleteEdge(&edge->impl_); + auto res = accessor_->DeleteEdge(edge->impl_.FromVertex(), edge->impl_.ToVertex(), edge->impl_.Gid()); if (res.HasError()) { return res.GetError(); } diff --git a/src/query/v2/procedure/mg_procedure_impl.hpp b/src/query/v2/procedure/mg_procedure_impl.hpp index 7a9d1fb32..d1e9bb152 100644 --- a/src/query/v2/procedure/mg_procedure_impl.hpp +++ b/src/query/v2/procedure/mg_procedure_impl.hpp @@ -474,9 +474,10 @@ struct mgp_edge { /// the allocator which was used to allocate `this`. using allocator_type = memgraph::utils::Allocator; + // TODO(antaljanosbenjamin): Handle this static assert failure when we will support procedures again // Hopefully EdgeAccessor copy constructor remains noexcept, so that we can // have everything noexcept here. - static_assert(std::is_nothrow_copy_constructible_v); + // static_assert(std::is_nothrow_copy_constructible_v); static mgp_edge *Copy(const mgp_edge &edge, mgp_memory &memory); diff --git a/src/query/v2/trigger_context.hpp b/src/query/v2/trigger_context.hpp index 0c42f8194..4665cf1d5 100644 --- a/src/query/v2/trigger_context.hpp +++ b/src/query/v2/trigger_context.hpp @@ -166,8 +166,10 @@ const char *TriggerEventTypeToString(TriggerEventType event_type); static_assert(std::is_trivially_copy_constructible_v, "VertexAccessor is not trivially copy constructible, move it where possible and remove this assert"); -static_assert(std::is_trivially_copy_constructible_v, - "EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert"); +// TODO(antaljanosbenjamin): Either satisfy this static_assert or move the edge accessors where it is possible when we +// will support triggers. +// static_assert(std::is_trivially_copy_constructible_v, +// "EdgeAccessor is not trivially copy constructible, move it where possible and remove this asssert"); // Holds the information necessary for triggers class TriggerContext { diff --git a/src/storage/v3/delta.hpp b/src/storage/v3/delta.hpp index 64c29b3e1..88a90f8be 100644 --- a/src/storage/v3/delta.hpp +++ b/src/storage/v3/delta.hpp @@ -12,10 +12,12 @@ #pragma once #include +#include #include "storage/v3/edge_ref.hpp" #include "storage/v3/id_types.hpp" #include "storage/v3/property_value.hpp" +#include "storage/v3/vertex_id.hpp" #include "utils/logging.hpp" namespace memgraph::storage::v3 { @@ -173,33 +175,33 @@ struct Delta { uint64_t command_id) : action(Action::SET_PROPERTY), timestamp(timestamp), command_id(command_id), property({key, value}) {} - Delta(AddInEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic *timestamp, - uint64_t command_id) + Delta(AddInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, + std::atomic *timestamp, uint64_t command_id) : action(Action::ADD_IN_EDGE), timestamp(timestamp), command_id(command_id), - vertex_edge({edge_type, vertex, edge}) {} + vertex_edge({edge_type, std::move(vertex_id), edge}) {} - Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic *timestamp, - uint64_t command_id) + Delta(AddOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, + std::atomic *timestamp, uint64_t command_id) : action(Action::ADD_OUT_EDGE), timestamp(timestamp), command_id(command_id), - vertex_edge({edge_type, vertex, edge}) {} + vertex_edge({edge_type, std::move(vertex_id), edge}) {} - Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, + Delta(RemoveInEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, std::atomic *timestamp, uint64_t command_id) : action(Action::REMOVE_IN_EDGE), timestamp(timestamp), command_id(command_id), - vertex_edge({edge_type, vertex, edge}) {} + vertex_edge({edge_type, std::move(vertex_id), edge}) {} - Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, + Delta(RemoveOutEdgeTag /*unused*/, EdgeTypeId edge_type, VertexId vertex_id, EdgeRef edge, std::atomic *timestamp, uint64_t command_id) : action(Action::REMOVE_OUT_EDGE), timestamp(timestamp), command_id(command_id), - vertex_edge({edge_type, vertex, edge}) {} + vertex_edge({edge_type, std::move(vertex_id), edge}) {} Delta(const Delta &) = delete; Delta(Delta &&) = delete; @@ -212,11 +214,12 @@ struct Delta { case Action::RECREATE_OBJECT: case Action::ADD_LABEL: case Action::REMOVE_LABEL: + break; case Action::ADD_IN_EDGE: case Action::ADD_OUT_EDGE: case Action::REMOVE_IN_EDGE: case Action::REMOVE_OUT_EDGE: - break; + std::destroy_at(&vertex_edge.vertex_id); case Action::SET_PROPERTY: property.value.~PropertyValue(); break; @@ -239,7 +242,7 @@ struct Delta { } property; struct { EdgeTypeId edge_type; - Vertex *vertex; + VertexId vertex_id; EdgeRef edge; } vertex_edge; }; diff --git a/src/storage/v3/durability/snapshot.cpp b/src/storage/v3/durability/snapshot.cpp index 50fd418c1..f7ae7b352 100644 --- a/src/storage/v3/durability/snapshot.cpp +++ b/src/storage/v3/durability/snapshot.cpp @@ -22,6 +22,7 @@ #include "storage/v3/schema_validator.hpp" #include "storage/v3/schemas.hpp" #include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertex_id.hpp" #include "storage/v3/vertices_skip_list.hpp" #include "utils/file_locker.hpp" #include "utils/logging.hpp" @@ -93,6 +94,10 @@ namespace memgraph::storage::v3::durability { // IMPORTANT: When changing snapshot encoding/decoding bump the snapshot/WAL // version in `version.hpp`. +namespace { +constexpr auto kDummyLabelId = LabelId::FromUint(0); +} // namespace + // Function used to read information about the snapshot file. SnapshotInfo ReadSnapshotInfo(const std::filesystem::path &path) { // Check magic and version. @@ -444,7 +449,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi // TODO Fix Gid SPDLOG_TRACE("Recovered inbound edge {} with label \"{}\" from vertex {}.", *edge_gid, name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), 1); - vertex.in_edges.emplace_back(get_edge_type_from_id(*edge_type), &from_vertex->vertex, edge_ref); + vertex.in_edges.emplace_back(get_edge_type_from_id(*edge_type), VertexId{kDummyLabelId, {}}, edge_ref); } } @@ -482,7 +487,7 @@ RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipLi // TODO Fix Gid SPDLOG_TRACE("Recovered outbound edge {} with label \"{}\" to vertex {}.", *edge_gid, name_id_mapper->IdToName(snapshot_id_map.at(*edge_type)), 1); - vertex.out_edges.emplace_back(get_edge_type_from_id(*edge_type), &to_vertex->vertex, edge_ref); + vertex.out_edges.emplace_back(get_edge_type_from_id(*edge_type), VertexId{kDummyLabelId, {}}, edge_ref); } // Increment edge count. We only increment the count here because the // information is duplicated in in_edges. @@ -718,8 +723,14 @@ void CreateSnapshot(Transaction *transaction, const std::filesystem::path &snaps // but that isn't an issue because we won't use that part of the API // here. // TODO(jbajic) Fix snapshot with new schema rules - auto ea = EdgeAccessor{edge_ref, EdgeTypeId::FromUint(0UL), nullptr, nullptr, transaction, indices, constraints, - items, vertex_validator}; + auto ea = EdgeAccessor{edge_ref, + EdgeTypeId::FromUint(0UL), + VertexId{kDummyLabelId, {}}, + VertexId{kDummyLabelId, {}}, + transaction, + indices, + constraints, + items}; // Get edge data. auto maybe_props = ea.Properties(View::OLD); diff --git a/src/storage/v3/edge_accessor.cpp b/src/storage/v3/edge_accessor.cpp index f0ddd6cc1..98d95ae9f 100644 --- a/src/storage/v3/edge_accessor.cpp +++ b/src/storage/v3/edge_accessor.cpp @@ -50,13 +50,9 @@ bool EdgeAccessor::IsVisible(const View view) const { return exists && (for_deleted_ || !deleted); } -VertexAccessor EdgeAccessor::FromVertex() const { - return {from_vertex_, transaction_, indices_, constraints_, config_, *vertex_validator_}; -} +const VertexId &EdgeAccessor::FromVertex() const { return from_vertex_; } -VertexAccessor EdgeAccessor::ToVertex() const { - return {to_vertex_, transaction_, indices_, constraints_, config_, *vertex_validator_}; -} +const VertexId &EdgeAccessor::ToVertex() const { return to_vertex_; } Result EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) { utils::MemoryTracker::OutOfMemoryExceptionEnabler oom_exception; diff --git a/src/storage/v3/edge_accessor.hpp b/src/storage/v3/edge_accessor.hpp index 8dfaee31e..deb087abd 100644 --- a/src/storage/v3/edge_accessor.hpp +++ b/src/storage/v3/edge_accessor.hpp @@ -35,26 +35,24 @@ class EdgeAccessor final { friend class Shard; public: - EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, Vertex *from_vertex, Vertex *to_vertex, Transaction *transaction, - Indices *indices, Constraints *constraints, Config::Items config, - const VertexValidator &vertex_validator, bool for_deleted = false) + EdgeAccessor(EdgeRef edge, EdgeTypeId edge_type, VertexId from_vertex, VertexId to_vertex, Transaction *transaction, + Indices *indices, Constraints *constraints, Config::Items config, bool for_deleted = false) : edge_(edge), edge_type_(edge_type), - from_vertex_(from_vertex), - to_vertex_(to_vertex), + from_vertex_(std::move(from_vertex)), + to_vertex_(std::move(to_vertex)), transaction_(transaction), indices_(indices), constraints_(constraints), config_(config), - vertex_validator_{&vertex_validator}, for_deleted_(for_deleted) {} /// @return true if the object is visible from the current transaction bool IsVisible(View view) const; - VertexAccessor FromVertex() const; + const VertexId &FromVertex() const; - VertexAccessor ToVertex() const; + const VertexId &ToVertex() const; EdgeTypeId EdgeType() const { return edge_type_; } @@ -89,13 +87,12 @@ class EdgeAccessor final { private: EdgeRef edge_; EdgeTypeId edge_type_; - Vertex *from_vertex_; - Vertex *to_vertex_; + VertexId from_vertex_; + VertexId to_vertex_; Transaction *transaction_; Indices *indices_; Constraints *constraints_; Config::Items config_; - const VertexValidator *vertex_validator_; // if the accessor was created for a deleted edge. // Accessor behaves differently for some methods based on this diff --git a/src/storage/v3/id_types.hpp b/src/storage/v3/id_types.hpp index 5fe83b3aa..02c98b801 100644 --- a/src/storage/v3/id_types.hpp +++ b/src/storage/v3/id_types.hpp @@ -11,6 +11,7 @@ #pragma once +#include #include #include #include @@ -20,30 +21,36 @@ namespace memgraph::storage::v3 { // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define STORAGE_DEFINE_ID_TYPE(name) \ - class name final { \ - private: \ - explicit name(uint64_t id) : id_(id) {} \ - \ - public: \ - /* Default constructor to allow serialization or preallocation. */ \ - name() = default; \ - \ - static name FromUint(uint64_t id) { return (name){id}; } \ - static name FromInt(int64_t id) { return (name){utils::MemcpyCast(id)}; } \ - uint64_t AsUint() const { return id_; } \ - int64_t AsInt() const { return utils::MemcpyCast(id_); } \ - \ - private: \ - uint64_t id_; \ - }; \ - static_assert(std::is_trivially_copyable::value, "storage::" #name " must be trivially copyable!"); \ - inline bool operator==(const name &first, const name &second) { return first.AsUint() == second.AsUint(); } \ - inline bool operator!=(const name &first, const name &second) { return first.AsUint() != second.AsUint(); } \ - inline bool operator<(const name &first, const name &second) { return first.AsUint() < second.AsUint(); } \ - inline bool operator>(const name &first, const name &second) { return first.AsUint() > second.AsUint(); } \ - inline bool operator<=(const name &first, const name &second) { return first.AsUint() <= second.AsUint(); } \ - inline bool operator>=(const name &first, const name &second) { return first.AsUint() >= second.AsUint(); } +#define STORAGE_DEFINE_ID_TYPE(name) \ + class name final { \ + private: \ + constexpr explicit name(uint64_t id) : id_(id) {} \ + \ + public: \ + /* Default constructor to allow serialization or preallocation. */ \ + constexpr name() = default; \ + \ + constexpr static name FromUint(uint64_t id) { return (name){id}; } \ + constexpr static name FromInt(int64_t id) { return (name){std::bit_cast(id)}; } \ + constexpr uint64_t AsUint() const { return id_; } \ + constexpr int64_t AsInt() const { return std::bit_cast(id_); } \ + \ + private: \ + uint64_t id_; \ + }; \ + static_assert(std::is_trivially_copyable::value, "storage::" #name " must be trivially copyable!"); \ + constexpr inline bool operator==(const name &first, const name &second) { \ + return first.AsUint() == second.AsUint(); \ + } \ + constexpr inline bool operator!=(const name &first, const name &second) { \ + return first.AsUint() != second.AsUint(); \ + } \ + constexpr inline bool operator<(const name &first, const name &second) { return first.AsUint() < second.AsUint(); } \ + constexpr inline bool operator>(const name &first, const name &second) { return first.AsUint() > second.AsUint(); } \ + constexpr inline bool operator<=(const name &first, const name &second) { \ + return first.AsUint() <= second.AsUint(); \ + } \ + constexpr inline bool operator>=(const name &first, const name &second) { return first.AsUint() >= second.AsUint(); } STORAGE_DEFINE_ID_TYPE(Gid); STORAGE_DEFINE_ID_TYPE(LabelId); diff --git a/src/storage/v3/indices.hpp b/src/storage/v3/indices.hpp index 01c611c97..622c46347 100644 --- a/src/storage/v3/indices.hpp +++ b/src/storage/v3/indices.hpp @@ -217,13 +217,13 @@ class LabelPropertyIndex { }; Iterable Vertices(LabelId label, PropertyId property, const std::optional> &lower_bound, - const std::optional> &upper_bound, View view, Transaction *transaction, - const VertexValidator &vertex_validator) { + const std::optional> &upper_bound, View view, + Transaction *transaction) { auto it = index_.find({label, property}); MG_ASSERT(it != index_.end(), "Index for label {} and property {} doesn't exist", label.AsUint(), property.AsUint()); - return {it->second.access(), label, property, lower_bound, upper_bound, view, - transaction, indices_, constraints_, config_, vertex_validator}; + return {it->second.access(), label, property, lower_bound, upper_bound, view, + transaction, indices_, constraints_, config_, *vertex_validator_}; } int64_t ApproximateVertexCount(LabelId label, PropertyId property) const { diff --git a/src/storage/v3/mvcc.hpp b/src/storage/v3/mvcc.hpp index 620cbb82b..ac639ffa4 100644 --- a/src/storage/v3/mvcc.hpp +++ b/src/storage/v3/mvcc.hpp @@ -114,6 +114,9 @@ inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&.. // concurrently (as well as other execution threads). // 1. We need to set the next delta of the new delta to the existing delta. + // TODO(antaljanosbenjamin): clang-tidy detects (in my opinion a false positive) issue in + // `Shard::Accessor::CreateEdge`. + // NOLINTNEXTLINE(clang-analyzer-core.NullDereference) delta->next.store(object->delta, std::memory_order_release); // 2. We need to set the previous delta of the new delta to the object. delta->prev.Set(object); diff --git a/src/storage/v3/replication/replication_server.cpp b/src/storage/v3/replication/replication_server.cpp index f26615cf9..a114c4c83 100644 --- a/src/storage/v3/replication/replication_server.cpp +++ b/src/storage/v3/replication/replication_server.cpp @@ -179,7 +179,6 @@ void Shard::ReplicationServer::SnapshotHandler(slk::Reader *req_reader, slk::Bui shard_->uuid_ = std::move(recovered_snapshot.snapshot_info.uuid); shard_->epoch_id_ = std::move(recovered_snapshot.snapshot_info.epoch_id); const auto &recovery_info = recovered_snapshot.recovery_info; - shard_->edge_id_ = recovery_info.next_edge_id; shard_->timestamp_ = std::max(shard_->timestamp_, recovery_info.next_timestamp); // durability::RecoverIndicesAndConstraints(recovered_snapshot.indices_constraints, &shard_->indices_, diff --git a/src/storage/v3/shard.cpp b/src/storage/v3/shard.cpp index 1260004bf..d3aba866f 100644 --- a/src/storage/v3/shard.cpp +++ b/src/storage/v3/shard.cpp @@ -370,7 +370,6 @@ Shard::Shard(const LabelId primary_label, const PrimaryKey min_primary_key, // &edges_, &edge_count_, &name_id_mapper_, &indices_, &constraints_, config_.items, // &wal_seq_num_); if (info) { - edge_id_ = info->next_edge_id; timestamp_ = std::max(timestamp_, info->next_timestamp); if (info->last_commit_timestamp) { last_commit_timestamp_ = *info->last_commit_timestamp; @@ -563,8 +562,8 @@ Result>>> Shar "accessor when deleting a vertex!"); auto *vertex_ptr = vertex->vertex_; - std::vector> in_edges; - std::vector> out_edges; + std::vector in_edges; + std::vector out_edges; { if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; @@ -576,11 +575,12 @@ Result>>> Shar } std::vector deleted_edges; + const VertexId vertex_id{shard_->primary_label_, vertex_ptr->keys.Keys()}; for (const auto &item : in_edges) { auto [edge_type, from_vertex, edge] = item; - EdgeAccessor e(edge, edge_type, from_vertex, vertex_ptr, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->vertex_validator_); - auto ret = DeleteEdge(&e); + EdgeAccessor e(edge, edge_type, from_vertex, vertex_id, &transaction_, &shard_->indices_, &shard_->constraints_, + config_); + auto ret = DeleteEdge(e.FromVertex(), e.ToVertex(), e.Gid()); if (ret.HasError()) { MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); return ret.GetError(); @@ -592,9 +592,9 @@ Result>>> Shar } for (const auto &item : out_edges) { auto [edge_type, to_vertex, edge] = item; - EdgeAccessor e(edge, edge_type, vertex_ptr, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->vertex_validator_); - auto ret = DeleteEdge(&e); + EdgeAccessor e(edge, edge_type, vertex_id, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, + config_); + auto ret = DeleteEdge(e.FromVertex(), e.ToVertex(), e.Gid()); if (ret.HasError()) { MG_ASSERT(ret.GetError() == Error::SERIALIZATION_ERROR, "Invalid database state!"); return ret.GetError(); @@ -621,80 +621,39 @@ Result>>> Shar std::move(deleted_edges)); } -Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type) { +Result Shard::Accessor::CreateEdge(VertexId from_vertex_id, VertexId to_vertex_id, + const EdgeTypeId edge_type, const Gid gid) { OOMExceptionEnabler oom_exception; - MG_ASSERT(from->transaction_ == to->transaction_, - "VertexAccessors must be from the same transaction when creating " - "an edge!"); - MG_ASSERT(from->transaction_ == &transaction_, - "VertexAccessors must be from the same transaction in when " - "creating an edge!"); + Vertex *from_vertex{nullptr}; + Vertex *to_vertex{nullptr}; - auto *from_vertex = from->vertex_; - auto *to_vertex = to->vertex_; + auto acc = shard_->vertices_.access(); - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; + const auto from_is_local = shard_->IsVertexBelongToShard(from_vertex_id); + const auto to_is_local = shard_->IsVertexBelongToShard(to_vertex_id); + MG_ASSERT(from_is_local || to_is_local, "Trying to create an edge without having a local vertex"); - if (to_vertex != from_vertex) { + if (from_is_local) { + auto it = acc.find(from_vertex_id.primary_key); + MG_ASSERT(it != acc.end(), "Cannot find local vertex"); + from_vertex = &it->vertex; + } + + if (to_is_local) { + auto it = acc.find(to_vertex_id.primary_key); + MG_ASSERT(it != acc.end(), "Cannot find local vertex"); + to_vertex = &it->vertex; + } + + if (from_is_local) { + if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; + if (from_vertex->deleted) return Error::DELETED_OBJECT; + } + if (to_is_local && to_vertex != from_vertex) { if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; if (to_vertex->deleted) return Error::DELETED_OBJECT; } - auto gid = Gid::FromUint(shard_->edge_id_++); - EdgeRef edge(gid); - if (config_.properties_on_edges) { - auto acc = shard_->edges_.access(); - auto *delta = CreateDeleteObjectDelta(&transaction_); - auto [it, inserted] = acc.insert(Edge(gid, delta)); - MG_ASSERT(inserted, "The edge must be inserted here!"); - MG_ASSERT(it != acc.end(), "Invalid Edge accessor!"); - edge = EdgeRef(&*it); - delta->prev.Set(&*it); - } - - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - - // Increment edge count. - ++shard_->edge_count_; - - return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->vertex_validator_); -} - -Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, - Gid gid) { - OOMExceptionEnabler oom_exception; - MG_ASSERT(from->transaction_ == to->transaction_, - "VertexAccessors must be from the same transaction when creating " - "an edge!"); - MG_ASSERT(from->transaction_ == &transaction_, - "VertexAccessors must be from the same transaction in when " - "creating an edge!"); - - auto *from_vertex = from->vertex_; - auto *to_vertex = to->vertex_; - - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; - - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; - } - - // NOTE: When we update the next `edge_id_` here we perform a RMW - // (read-modify-write) operation that ISN'T atomic! But, that isn't an issue - // because this function is only called from the replication delta applier - // that runs single-threadedly and while this instance is set-up to apply - // threads (it is the replica), it is guaranteed that no other writes are - // possible. - shard_->edge_id_ = std::max(shard_->edge_id_, gid.AsUint() + 1); - EdgeRef edge(gid); if (config_.properties_on_edges) { auto acc = shard_->edges_.access(); @@ -706,69 +665,99 @@ Result Shard::Accessor::CreateEdge(VertexAccessor *from, VertexAcc delta->prev.Set(&*it); } - CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex, edge); - from_vertex->out_edges.emplace_back(edge_type, to_vertex, edge); - - CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex, edge); - to_vertex->in_edges.emplace_back(edge_type, from_vertex, edge); - + if (from_is_local) { + CreateAndLinkDelta(&transaction_, from_vertex, Delta::RemoveOutEdgeTag(), edge_type, to_vertex_id, edge); + from_vertex->out_edges.emplace_back(edge_type, to_vertex_id, edge); + } + if (to_is_local) { + CreateAndLinkDelta(&transaction_, to_vertex, Delta::RemoveInEdgeTag(), edge_type, from_vertex_id, edge); + to_vertex->in_edges.emplace_back(edge_type, from_vertex_id, edge); + } // Increment edge count. ++shard_->edge_count_; - return EdgeAccessor(edge, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, &shard_->constraints_, - config_, shard_->vertex_validator_); + return EdgeAccessor(edge, edge_type, std::move(from_vertex_id), std::move(to_vertex_id), &transaction_, + &shard_->indices_, &shard_->constraints_, config_); } -Result> Shard::Accessor::DeleteEdge(EdgeAccessor *edge) { - MG_ASSERT(edge->transaction_ == &transaction_, - "EdgeAccessor must be from the same transaction as the storage " - "accessor when deleting an edge!"); - auto edge_ref = edge->edge_; - auto edge_type = edge->edge_type_; +Result> Shard::Accessor::DeleteEdge(VertexId from_vertex_id, VertexId to_vertex_id, + const Gid edge_id) { + Vertex *from_vertex{nullptr}; + Vertex *to_vertex{nullptr}; - if (config_.properties_on_edges) { - auto *edge_ptr = edge_ref.ptr; + auto acc = shard_->vertices_.access(); - if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; + const auto from_is_local = shard_->IsVertexBelongToShard(from_vertex_id); + const auto to_is_local = shard_->IsVertexBelongToShard(to_vertex_id); - if (edge_ptr->deleted) return std::optional{}; + if (from_is_local) { + auto it = acc.find(from_vertex_id.primary_key); + MG_ASSERT(it != acc.end(), "Cannot find local vertex"); + from_vertex = &it->vertex; } - auto *from_vertex = edge->from_vertex_; - auto *to_vertex = edge->to_vertex_; + if (to_is_local) { + auto it = acc.find(to_vertex_id.primary_key); + MG_ASSERT(it != acc.end(), "Cannot find local vertex"); + to_vertex = &it->vertex; + } - if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + MG_ASSERT(from_is_local || to_is_local, "Trying to delete an edge without having a local vertex"); - if (to_vertex != from_vertex) { - if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; + if (from_is_local) { + if (!PrepareForWrite(&transaction_, from_vertex)) { + return Error::SERIALIZATION_ERROR; + } + MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + } + if (to_is_local && to_vertex != from_vertex) { + if (!PrepareForWrite(&transaction_, to_vertex)) { + return Error::SERIALIZATION_ERROR; + } MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); } - auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { - std::tuple link(edge_type, vertex, edge_ref); - auto it = std::find(edges->begin(), edges->end(), link); + const auto edge_ref = std::invoke([edge_id, this]() -> EdgeRef { + if (!config_.properties_on_edges) { + return EdgeRef(edge_id); + } + auto edge_acc = shard_->edges_.access(); + auto res = edge_acc.find(edge_id); + MG_ASSERT(res != edge_acc.end(), "Cannot find edge"); + return EdgeRef(&*res); + }); + + std::optional edge_type{}; + auto delete_edge_from_storage = [&edge_type, &edge_ref, this](std::vector &edges) mutable { + auto it = std::find_if(edges.begin(), edges.end(), + [&edge_ref](const Vertex::EdgeLink &link) { return std::get<2>(link) == edge_ref; }); if (config_.properties_on_edges) { - MG_ASSERT(it != edges->end(), "Invalid database state!"); - } else if (it == edges->end()) { + MG_ASSERT(it != edges.end(), "Invalid database state!"); + } else if (it == edges.end()) { return false; } - std::swap(*it, *edges->rbegin()); - edges->pop_back(); + edge_type = std::get<0>(*it); + std::swap(*it, *edges.rbegin()); + edges.pop_back(); return true; }; - - auto op1 = delete_edge_from_storage(to_vertex, &from_vertex->out_edges); - auto op2 = delete_edge_from_storage(from_vertex, &to_vertex->in_edges); + // NOLINTNEXTLINE(clang-analyzer-core.NonNullParamChecker) + auto success_on_to = to_is_local ? delete_edge_from_storage(to_vertex->in_edges) : false; + auto success_on_from = from_is_local ? delete_edge_from_storage(from_vertex->out_edges) : false; if (config_.properties_on_edges) { - MG_ASSERT((op1 && op2), "Invalid database state!"); + // Because of the check above, we are sure that the vertex exists. + // One vertex is always local to the shard, so at least one of the operation should always succeed + MG_ASSERT((to_is_local == success_on_to) && (from_is_local == success_on_from), "Invalid database state!"); } else { - MG_ASSERT((op1 && op2) || (!op1 && !op2), "Invalid database state!"); - if (!op1 && !op2) { + // We might get here with self-edges, because without the edge object we cannot detect already deleted edges, thus + // it is possible that both of the operation fails + if (!success_on_to && !success_on_from) { // The edge is already deleted. return std::optional{}; } + + MG_ASSERT((!to_is_local || !from_is_local) || (success_on_to == success_on_from), "Invalid database state!"); } if (config_.properties_on_edges) { @@ -777,14 +766,20 @@ Result> Shard::Accessor::DeleteEdge(EdgeAccessor *ed edge_ptr->deleted = true; } - CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), edge_type, to_vertex, edge_ref); - CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), edge_type, from_vertex, edge_ref); + MG_ASSERT(edge_type.has_value(), "Edge type is not determined"); + + if (from_is_local) { + CreateAndLinkDelta(&transaction_, from_vertex, Delta::AddOutEdgeTag(), *edge_type, to_vertex_id, edge_ref); + } + if (to_is_local) { + CreateAndLinkDelta(&transaction_, to_vertex, Delta::AddInEdgeTag(), *edge_type, from_vertex_id, edge_ref); + } // Decrement edge count. --shard_->edge_count_; - return std::make_optional(edge_ref, edge_type, from_vertex, to_vertex, &transaction_, &shard_->indices_, - &shard_->constraints_, config_, shard_->vertex_validator_, true); + return std::make_optional(edge_ref, *edge_type, std::move(from_vertex_id), std::move(to_vertex_id), + &transaction_, &shard_->indices_, &shard_->constraints_, config_, true); } const std::string &Shard::Accessor::LabelToName(LabelId label) const { return shard_->LabelToName(label); } @@ -936,16 +931,16 @@ void Shard::Accessor::Abort() { break; } case Delta::Action::ADD_IN_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; + Vertex::EdgeLink link{current->vertex_edge.edge_type, current->vertex_edge.vertex_id, + current->vertex_edge.edge}; auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); MG_ASSERT(it == vertex->in_edges.end(), "Invalid database state!"); vertex->in_edges.push_back(link); break; } case Delta::Action::ADD_OUT_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; + Vertex::EdgeLink link{current->vertex_edge.edge_type, current->vertex_edge.vertex_id, + current->vertex_edge.edge}; auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); MG_ASSERT(it == vertex->out_edges.end(), "Invalid database state!"); vertex->out_edges.push_back(link); @@ -957,8 +952,8 @@ void Shard::Accessor::Abort() { break; } case Delta::Action::REMOVE_IN_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; + Vertex::EdgeLink link{current->vertex_edge.edge_type, current->vertex_edge.vertex_id, + current->vertex_edge.edge}; auto it = std::find(vertex->in_edges.begin(), vertex->in_edges.end(), link); MG_ASSERT(it != vertex->in_edges.end(), "Invalid database state!"); std::swap(*it, *vertex->in_edges.rbegin()); @@ -966,8 +961,8 @@ void Shard::Accessor::Abort() { break; } case Delta::Action::REMOVE_OUT_EDGE: { - std::tuple link{current->vertex_edge.edge_type, - current->vertex_edge.vertex, current->vertex_edge.edge}; + Vertex::EdgeLink link{current->vertex_edge.edge_type, current->vertex_edge.vertex_id, + current->vertex_edge.edge}; auto it = std::find(vertex->out_edges.begin(), vertex->out_edges.end(), link); MG_ASSERT(it != vertex->out_edges.end(), "Invalid database state!"); std::swap(*it, *vertex->out_edges.rbegin()); @@ -1208,27 +1203,23 @@ VerticesIterable Shard::Accessor::Vertices(LabelId label, View view) { } VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, View view) { - return VerticesIterable(shard_->indices_.label_property_index.Vertices( - label, property, std::nullopt, std::nullopt, view, &transaction_, shard_->vertex_validator_)); + return VerticesIterable( + shard_->indices_.label_property_index.Vertices(label, property, std::nullopt, std::nullopt, view, &transaction_)); } VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, const PropertyValue &value, View view) { return VerticesIterable(shard_->indices_.label_property_index.Vertices( - label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_, - shard_->vertex_validator_)); + label, property, utils::MakeBoundInclusive(value), utils::MakeBoundInclusive(value), view, &transaction_)); } VerticesIterable Shard::Accessor::Vertices(LabelId label, PropertyId property, const std::optional> &lower_bound, const std::optional> &upper_bound, View view) { - return VerticesIterable(shard_->indices_.label_property_index.Vertices( - label, property, lower_bound, upper_bound, view, &transaction_, shard_->vertex_validator_)); + return VerticesIterable( + shard_->indices_.label_property_index.Vertices(label, property, lower_bound, upper_bound, view, &transaction_)); } Transaction Shard::CreateTransaction(IsolationLevel isolation_level) { - // We acquire the transaction engine lock here because we access (and - // modify) the transaction engine variables (`transaction_id` and - // `timestamp`) below. uint64_t transaction_id{0}; uint64_t start_timestamp{0}; @@ -1742,6 +1733,11 @@ uint64_t Shard::CommitTimestamp(const std::optional desired_commit_tim return *desired_commit_timestamp; } +bool Shard::IsVertexBelongToShard(const VertexId &vertex_id) const { + return vertex_id.primary_label == primary_label_ && vertex_id.primary_key >= min_primary_key_ && + (!max_primary_key_.has_value() || vertex_id.primary_key < *max_primary_key_); +} + bool Shard::SetReplicaRole(io::network::Endpoint endpoint, const replication::ReplicationServerConfig &config) { // We don't want to restart the server if we're already a REPLICA if (replication_role_ == ReplicationRole::REPLICA) { diff --git a/src/storage/v3/shard.hpp b/src/storage/v3/shard.hpp index fcc80e4f0..7738ba8ef 100644 --- a/src/storage/v3/shard.hpp +++ b/src/storage/v3/shard.hpp @@ -44,6 +44,7 @@ #include "storage/v3/transaction.hpp" #include "storage/v3/vertex.hpp" #include "storage/v3/vertex_accessor.hpp" +#include "storage/v3/vertex_id.hpp" #include "storage/v3/vertices_skip_list.hpp" #include "utils/exceptions.hpp" #include "utils/file_locker.hpp" @@ -301,11 +302,11 @@ class Shard final { VertexAccessor *vertex); /// @throw std::bad_alloc - Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type); + Result CreateEdge(VertexId from_vertex_id, VertexId to_vertex_id, EdgeTypeId edge_type, Gid gid); /// Accessor to the deleted edge if a deletion took place, std::nullopt otherwise /// @throw std::bad_alloc - Result> DeleteEdge(EdgeAccessor *edge); + Result> DeleteEdge(VertexId from_vertex_id, VertexId to_vertex_id, Gid edge_id); const std::string &LabelToName(LabelId label) const; const std::string &PropertyToName(PropertyId property) const; @@ -347,9 +348,6 @@ class Shard final { /// @throw std::bad_alloc VertexAccessor CreateVertex(Gid gid, LabelId primary_label); - /// @throw std::bad_alloc - Result CreateEdge(VertexAccessor *from, VertexAccessor *to, EdgeTypeId edge_type, Gid gid); - Shard *shard_; Transaction transaction_; std::optional commit_timestamp_; @@ -505,14 +503,16 @@ class Shard final { uint64_t CommitTimestamp(std::optional desired_commit_timestamp = {}); + [[nodiscard]] bool IsVertexBelongToShard(const VertexId &vertex_id) const; + // Main object storage NameIdMapper name_id_mapper_; LabelId primary_label_; + // The shard's range is [min, max) PrimaryKey min_primary_key_; std::optional max_primary_key_; VerticesSkipList vertices_; utils::SkipList edges_; - 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. diff --git a/src/storage/v3/vertex.hpp b/src/storage/v3/vertex.hpp index 4b2fbe9f8..2ed4c6e4c 100644 --- a/src/storage/v3/vertex.hpp +++ b/src/storage/v3/vertex.hpp @@ -22,23 +22,28 @@ #include "storage/v3/key_store.hpp" #include "storage/v3/property_store.hpp" #include "storage/v3/property_value.hpp" +#include "storage/v3/vertex_id.hpp" #include "utils/algorithm.hpp" #include "utils/spin_lock.hpp" namespace memgraph::storage::v3 { struct Vertex { + using EdgeLink = std::tuple; + Vertex(Delta *delta, const std::vector &primary_properties) : keys{primary_properties}, delta{delta} { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); } + friend bool operator==(const Vertex &vertex, const PrimaryKey &primary_key) { return vertex.keys == primary_key; } + KeyStore keys; std::vector labels; PropertyStore properties; - std::vector> in_edges; - std::vector> out_edges; + std::vector in_edges; + std::vector out_edges; bool deleted{false}; // uint8_t PAD; diff --git a/src/storage/v3/vertex_accessor.cpp b/src/storage/v3/vertex_accessor.cpp index a7e423387..4fd3f1add 100644 --- a/src/storage/v3/vertex_accessor.cpp +++ b/src/storage/v3/vertex_accessor.cpp @@ -198,75 +198,27 @@ Result VertexAccessor::HasLabel(LabelId label, View view) const { } Result VertexAccessor::PrimaryLabel(const View view) const { - bool exists = true; - bool deleted = false; - Delta *delta = nullptr; - { - deleted = vertex_->deleted; - delta = vertex_->delta; + if (const auto result = CheckVertexExistence(view); result.HasError()) { + return result.GetError(); } - ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) { - switch (delta.action) { - 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::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 vertex_validator_->primary_label_; } Result VertexAccessor::PrimaryKey(const View view) const { - bool exists = true; - bool deleted = false; - Delta *delta = nullptr; - { - deleted = vertex_->deleted; - delta = vertex_->delta; - } - ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) { - switch (delta.action) { - 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::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; + if (const auto result = CheckVertexExistence(view); result.HasError()) { + return result.GetError(); } return vertex_->keys.Keys(); } +Result VertexAccessor::Id(View view) const { + if (const auto result = CheckVertexExistence(view); result.HasError()) { + return result.GetError(); + } + return VertexId{vertex_validator_->primary_label_, vertex_->keys.Keys()}; +}; + Result> VertexAccessor::Labels(View view) const { bool exists = true; bool deleted = false; @@ -337,6 +289,43 @@ Result VertexAccessor::SetProperty(PropertyId property, const Pro return std::move(current_value); } +Result VertexAccessor::CheckVertexExistence(View view) const { + bool exists = true; + bool deleted = false; + Delta *delta = nullptr; + { + deleted = vertex_->deleted; + delta = vertex_->delta; + } + ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted](const Delta &delta) { + switch (delta.action) { + 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::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 {}; +} + ResultSchema VertexAccessor::SetPropertyAndValidate(PropertyId property, const PropertyValue &value) { if (auto maybe_violation_error = vertex_validator_->ValidatePropertyUpdate(property); maybe_violation_error) { return {*maybe_violation_error}; @@ -472,20 +461,21 @@ Result> VertexAccessor::Properties(View view } Result> VertexAccessor::InEdges(View view, const std::vector &edge_types, - const VertexAccessor *destination) const { - MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!"); + const VertexId *destination_id) const { bool exists = true; bool deleted = false; - std::vector> in_edges; + std::vector in_edges; Delta *delta = nullptr; { deleted = vertex_->deleted; - if (edge_types.empty() && !destination) { + if (edge_types.empty() && nullptr == destination_id) { in_edges = vertex_->in_edges; } else { for (const auto &item : vertex_->in_edges) { const auto &[edge_type, from_vertex, edge] = item; - if (destination && from_vertex != destination->vertex_) continue; + if (nullptr != destination_id && from_vertex != *destination_id) { + continue; + }; if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end()) continue; in_edges.push_back(item); @@ -494,29 +484,27 @@ Result> VertexAccessor::InEdges(View view, const std:: delta = vertex_->delta; } ApplyDeltasForRead( - transaction_, delta, view, [&exists, &deleted, &in_edges, &edge_types, &destination](const Delta &delta) { + transaction_, delta, view, [&exists, &deleted, &in_edges, &edge_types, destination_id](const Delta &delta) { switch (delta.action) { case Delta::Action::ADD_IN_EDGE: { - if (destination && delta.vertex_edge.vertex != destination->vertex_) break; + if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) 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 link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex, - delta.vertex_edge.edge}; + Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, delta.vertex_edge.edge}; auto it = std::find(in_edges.begin(), in_edges.end(), link); MG_ASSERT(it == in_edges.end(), "Invalid database state!"); in_edges.push_back(link); break; } case Delta::Action::REMOVE_IN_EDGE: { - if (destination && delta.vertex_edge.vertex != destination->vertex_) break; + if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) 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 link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex, - delta.vertex_edge.edge}; + Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, 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()); @@ -542,30 +530,32 @@ Result> VertexAccessor::InEdges(View view, const std:: if (!exists) return Error::NONEXISTENT_OBJECT; if (deleted) return Error::DELETED_OBJECT; std::vector ret; + if (in_edges.empty()) { + return ret; + } ret.reserve(in_edges.size()); + const auto id = VertexId{vertex_validator_->primary_label_, vertex_->keys.Keys()}; for (const auto &item : in_edges) { const auto &[edge_type, from_vertex, edge] = item; - ret.emplace_back(edge, edge_type, from_vertex, vertex_, transaction_, indices_, constraints_, config_, - *vertex_validator_); + ret.emplace_back(edge, edge_type, from_vertex, id, transaction_, indices_, constraints_, config_); } - return std::move(ret); + return ret; } Result> VertexAccessor::OutEdges(View view, const std::vector &edge_types, - const VertexAccessor *destination) const { - MG_ASSERT(!destination || destination->transaction_ == transaction_, "Invalid accessor!"); + const VertexId *destination_id) const { bool exists = true; bool deleted = false; - std::vector> out_edges; + std::vector out_edges; Delta *delta = nullptr; { deleted = vertex_->deleted; - if (edge_types.empty() && !destination) { + if (edge_types.empty() && nullptr == destination_id) { out_edges = vertex_->out_edges; } else { for (const auto &item : vertex_->out_edges) { const auto &[edge_type, to_vertex, edge] = item; - if (destination && to_vertex != destination->vertex_) continue; + if (nullptr != destination_id && to_vertex != *destination_id) continue; if (!edge_types.empty() && std::find(edge_types.begin(), edge_types.end(), edge_type) == edge_types.end()) continue; out_edges.push_back(item); @@ -574,29 +564,27 @@ Result> VertexAccessor::OutEdges(View view, const std: delta = vertex_->delta; } ApplyDeltasForRead( - transaction_, delta, view, [&exists, &deleted, &out_edges, &edge_types, &destination](const Delta &delta) { + transaction_, delta, view, [&exists, &deleted, &out_edges, &edge_types, destination_id](const Delta &delta) { switch (delta.action) { case Delta::Action::ADD_OUT_EDGE: { - if (destination && delta.vertex_edge.vertex != destination->vertex_) break; + if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) 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 link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex, - delta.vertex_edge.edge}; + Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, delta.vertex_edge.edge}; auto it = std::find(out_edges.begin(), out_edges.end(), link); MG_ASSERT(it == out_edges.end(), "Invalid database state!"); out_edges.push_back(link); break; } case Delta::Action::REMOVE_OUT_EDGE: { - if (destination && delta.vertex_edge.vertex != destination->vertex_) break; + if (nullptr != destination_id && delta.vertex_edge.vertex_id != *destination_id) 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 link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex, - delta.vertex_edge.edge}; + Vertex::EdgeLink link{delta.vertex_edge.edge_type, delta.vertex_edge.vertex_id, 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()); @@ -622,13 +610,16 @@ Result> VertexAccessor::OutEdges(View view, const std: if (!exists) return Error::NONEXISTENT_OBJECT; if (deleted) return Error::DELETED_OBJECT; std::vector ret; + if (out_edges.empty()) { + return ret; + } ret.reserve(out_edges.size()); + const auto id = VertexId{vertex_validator_->primary_label_, vertex_->keys.Keys()}; for (const auto &item : out_edges) { const auto &[edge_type, to_vertex, edge] = item; - ret.emplace_back(edge, edge_type, vertex_, to_vertex, transaction_, indices_, constraints_, config_, - *vertex_validator_); + ret.emplace_back(edge, edge_type, id, to_vertex, transaction_, indices_, constraints_, config_); } - return std::move(ret); + return ret; } Result VertexAccessor::InDegree(View view) const { diff --git a/src/storage/v3/vertex_accessor.hpp b/src/storage/v3/vertex_accessor.hpp index 33556e3f6..665e7af64 100644 --- a/src/storage/v3/vertex_accessor.hpp +++ b/src/storage/v3/vertex_accessor.hpp @@ -20,6 +20,7 @@ #include "storage/v3/schema_validator.hpp" #include "storage/v3/transaction.hpp" #include "storage/v3/vertex.hpp" +#include "storage/v3/vertex_id.hpp" #include "storage/v3/view.hpp" namespace memgraph::storage::v3 { @@ -76,6 +77,8 @@ class VertexAccessor final { Result PrimaryKey(View view) const; + Result Id(View view) const; + /// Set a property value and return the old value or error. /// @throw std::bad_alloc ResultSchema SetPropertyAndValidate(PropertyId property, const PropertyValue &value); @@ -94,13 +97,13 @@ class VertexAccessor final { /// @throw std::length_error if the resulting vector exceeds /// std::vector::max_size(). Result> InEdges(View view, const std::vector &edge_types = {}, - const VertexAccessor *destination = nullptr) const; + const VertexId *destination_id = nullptr) const; /// @throw std::bad_alloc /// @throw std::length_error if the resulting vector exceeds /// std::vector::max_size(). Result> OutEdges(View view, const std::vector &edge_types = {}, - const VertexAccessor *destination = nullptr) const; + const VertexId *destination_id = nullptr) const; Result InDegree(View view) const; @@ -128,6 +131,8 @@ class VertexAccessor final { /// @throw std::bad_alloc Result SetProperty(PropertyId property, const PropertyValue &value); + Result CheckVertexExistence(View view) const; + Vertex *vertex_; Transaction *transaction_; Indices *indices_; diff --git a/src/storage/v3/vertex_id.hpp b/src/storage/v3/vertex_id.hpp new file mode 100644 index 000000000..70dc85ec8 --- /dev/null +++ b/src/storage/v3/vertex_id.hpp @@ -0,0 +1,32 @@ +// Copyright 2022 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/v3/id_types.hpp" +#include "storage/v3/key_store.hpp" + +namespace memgraph::storage::v3 { + +// TODO(antaljanosbenjamin): It is possible to use a union of the current primary key and a vertex pointer: for local +// vertices we can spare some space by eliminating copying the primary label and key, however it might introduce some +// overhead for "remove vertices", because of the extra enum that is necessary for this optimization. +struct VertexId { + VertexId(const LabelId primary_label, PrimaryKey primary_key) + : primary_label{primary_label}, primary_key{std::move(primary_key)} {} + LabelId primary_label; + PrimaryKey primary_key; +}; + +inline bool operator==(const VertexId &lhs, const VertexId &rhs) { + return lhs.primary_label == rhs.primary_label && lhs.primary_key == rhs.primary_key; +} +} // namespace memgraph::storage::v3 diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 6af33130e..af54378eb 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -340,6 +340,9 @@ target_link_libraries(${test_prefix}storage_v3_indices mg-storage-v3) add_unit_test(storage_v3_vertex_accessors.cpp) target_link_libraries(${test_prefix}storage_v3_vertex_accessors mg-storage-v3) +add_unit_test(storage_v3_edge.cpp) +target_link_libraries(${test_prefix}storage_v3_edge mg-storage-v3) + # Test mg-query-v2 add_unit_test(query_v2_interpreter.cpp ${CMAKE_SOURCE_DIR}/src/glue/v2/communication.cpp) target_link_libraries(${test_prefix}query_v2_interpreter mg-storage-v3 mg-query-v2 mg-communication) diff --git a/tests/unit/storage_v3.cpp b/tests/unit/storage_v3.cpp index 4163c96a2..41f152ca8 100644 --- a/tests/unit/storage_v3.cpp +++ b/tests/unit/storage_v3.cpp @@ -2195,7 +2195,9 @@ TEST_F(StorageV3, VertexNonexistentLabelPropertyEdgeAPI) { // Modify vertex. ASSERT_TRUE(vertex.AddLabelAndValidate(label1).HasValue()); ASSERT_TRUE(vertex.SetPropertyAndValidate(property1, PropertyValue("value")).HasValue()); - ASSERT_TRUE(acc.CreateEdge(&vertex, &vertex, NameToEdgeTypeId("edge")).HasValue()); + ASSERT_TRUE(acc.CreateEdge(vertex.Id(View::NEW).GetValue(), vertex.Id(View::NEW).GetValue(), NameToEdgeTypeId("edge"), + Gid::FromUint(1)) + .HasValue()); // Check state after (OLD view). ASSERT_EQ(vertex.Labels(View::OLD).GetError(), Error::NONEXISTENT_OBJECT); diff --git a/tests/unit/storage_v3_edge.cpp b/tests/unit/storage_v3_edge.cpp new file mode 100644 index 000000000..4483ebcc6 --- /dev/null +++ b/tests/unit/storage_v3_edge.cpp @@ -0,0 +1,5274 @@ +// Copyright 2022 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 +#include + +#include + +#include "storage/v3/config.hpp" +#include "storage/v3/name_id_mapper.hpp" +#include "storage/v3/property_value.hpp" +#include "storage/v3/shard.hpp" + +namespace memgraph::storage::v3::tests { +using testing::UnorderedElementsAre; + +class StorageEdgeTest : public ::testing::TestWithParam { + protected: + void SetUp() override { + ASSERT_TRUE( + store.CreateSchema(primary_label, {storage::v3::SchemaProperty{primary_property, common::SchemaType::INT}})); + } + + [[nodiscard]] LabelId NameToLabelId(std::string_view label_name) { + return LabelId::FromUint(id_mapper.NameToId(label_name)); + } + + [[nodiscard]] PropertyId NameToPropertyId(std::string_view property_name) { + return PropertyId::FromUint(id_mapper.NameToId(property_name)); + } + + [[nodiscard]] EdgeTypeId NameToEdgeTypeId(std::string_view edge_type_name) { + return EdgeTypeId::FromUint(id_mapper.NameToId(edge_type_name)); + } + + ResultSchema CreateVertex(Shard::Accessor &acc, const PropertyValue &key) { + return acc.CreateVertexAndValidate(primary_label, {}, {{primary_property, key}}); + } + + NameIdMapper id_mapper; + static constexpr int64_t min_primary_key_value{0}; + static constexpr int64_t max_primary_key_value{10000}; + const LabelId primary_label{NameToLabelId("label")}; + Shard store{primary_label, + {PropertyValue{min_primary_key_value}}, + std::vector{PropertyValue{max_primary_key_value}}, + Config{.items = {.properties_on_edges = GetParam()}}}; + const PropertyId primary_property{NameToPropertyId("property")}; +}; + +INSTANTIATE_TEST_CASE_P(EdgesWithProperties, StorageEdgeTest, ::testing::Values(true)); +INSTANTIATE_TEST_CASE_P(EdgesWithoutProperties, StorageEdgeTest, ::testing::Values(false)); + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromSmallerCommit) { + // Create vertices + const PropertyValue from_key{0}; + const PropertyValue to_key{1}; + const PropertyValue non_existing_key{2}; + const auto et = NameToEdgeTypeId("et5"); + const auto edge_id = Gid::FromUint(0U); + auto acc = store.Access(); + const auto [from_id, to_id] = + std::invoke([this, &from_key, &to_key, &acc]() mutable -> std::pair { + auto from_id = CreateVertex(acc, from_key)->Id(View::NEW).GetValue(); + auto to_id = CreateVertex(acc, to_key)->Id(View::NEW).GetValue(); + return std::make_pair(std::move(from_id), std::move(to_id)); + }); + const auto other_et = NameToEdgeTypeId("other"); + const VertexId from_id_with_different_label{NameToLabelId("different_label"), from_id.primary_key}; + const VertexId to_id_with_different_label{NameToLabelId("different_label"), to_id.primary_key}; + const VertexId non_existing_id{primary_label, {non_existing_key}}; + + ASSERT_FALSE(acc.Commit().HasError()); + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex({to_id.primary_key}, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(from_id, to_id, et, edge_id); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.Gid(), edge_id); + ASSERT_EQ(edge.FromVertex(), from_id); + ASSERT_EQ(edge.ToVertex(), to_id); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from.has_value()); + ASSERT_TRUE(vertex_to.has_value()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + { + auto ret = vertex_from->OutEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } +} + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeCreateFromLargerCommit) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertices +// { +// auto acc = store.Access(); +// auto vertex_to = acc.CreateVertex(); +// auto vertex_from = acc.CreateVertex(); +// gid_to = vertex_to.Gid(); +// gid_from = vertex_from.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&from_id, &to_id, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex_from); +// ASSERT_EQ(edge.ToVertex(), *vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeCreateFromSameCommit) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertex +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid_vertex = vertex.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&*vertex, &*vertex, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex); +// ASSERT_EQ(edge.ToVertex(), *vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeCreateFromSmallerAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertices +// { +// auto acc = store.Access(); +// auto vertex_from = acc.CreateVertex(); +// auto vertex_to = acc.CreateVertex(); +// gid_from = vertex_from.Gid(); +// gid_to = vertex_to.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge, but abort the transaction +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&from_id, &to_id, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex_from); +// ASSERT_EQ(edge.ToVertex(), *vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// acc.Abort(); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&from_id, &to_id, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex_from); +// ASSERT_EQ(edge.ToVertex(), *vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeCreateFromLargerAbort) { + // Create vertices + const PropertyValue from_key{0}; + const PropertyValue to_key{1}; + const PropertyValue non_existing_key{2}; + auto acc = store.Access(); + const auto [from_id, to_id] = + std::invoke([this, &from_key, &to_key, &acc]() mutable -> std::pair { + auto from_id = CreateVertex(acc, from_key)->Id(View::NEW).GetValue(); + auto to_id = CreateVertex(acc, to_key)->Id(View::NEW).GetValue(); + return std::make_pair(std::move(from_id), std::move(to_id)); + }); + + const auto et = NameToEdgeTypeId("et5"); + const auto other_et = NameToEdgeTypeId("other"); + const VertexId from_id_with_different_label{NameToLabelId("different_label"), from_id.primary_key}; + const VertexId to_id_with_different_label{NameToLabelId("different_label"), to_id.primary_key}; + const VertexId non_existing_id{primary_label, {non_existing_key}}; + + ASSERT_FALSE(acc.Commit().HasError()); + + // Create edge but abort + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex({to_id.primary_key}, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + const auto edge_id = Gid::FromUint(0U); + + auto res = acc.CreateEdge(from_id, to_id, et, edge_id); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.Gid(), edge_id); + ASSERT_EQ(edge.FromVertex(), from_id); + ASSERT_EQ(edge.ToVertex(), to_id); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + const auto edge_id = Gid::FromUint(1U); + + auto res = acc.CreateEdge(from_id, to_id, et, edge_id); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.Gid(), edge_id); + ASSERT_EQ(edge.FromVertex(), from_id); + ASSERT_EQ(edge.ToVertex(), to_id); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + const auto edge_id = Gid::FromUint(1U); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + { + auto ret = vertex_from->OutEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } +} + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeCreateFromSameAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertex +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid_vertex = vertex.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge, but abort the transaction +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&*vertex, &*vertex, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex); +// ASSERT_EQ(edge.ToVertex(), *vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// acc.Abort(); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&*vertex, &*vertex, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex); +// ASSERT_EQ(edge.ToVertex(), *vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerCommit) { + // Create vertex + const PropertyValue from_key{0}; + const PropertyValue to_key{max_primary_key_value}; + const PropertyValue non_existing_key{2}; + auto acc = store.Access(); + const auto from_id = std::invoke( + [this, &from_key, &acc]() mutable -> VertexId { return CreateVertex(acc, from_key)->Id(View::NEW).GetValue(); }); + const VertexId to_id{primary_label, {to_key}}; + const auto et = NameToEdgeTypeId("et5"); + const auto edge_id = Gid::FromUint(1U); + const auto other_et = NameToEdgeTypeId("other"); + const VertexId to_id_with_different_label{NameToLabelId("different_label"), to_id.primary_key}; + const VertexId non_existing_id{primary_label, {non_existing_key}}; + + ASSERT_FALSE(acc.Commit().HasError()); + + // Create edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_FALSE(acc.FindVertex(to_id.primary_key, View::NEW).has_value()); + + const auto et = NameToEdgeTypeId("et5"); + const auto edge_id = Gid::FromUint(1U); + + auto res = acc.CreateEdge(from_id, to_id, et, edge_id); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.Gid(), edge_id); + ASSERT_EQ(edge.FromVertex(), from_id); + ASSERT_EQ(edge.ToVertex(), to_id); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + { + auto ret = vertex_from->OutEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_from->OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + + // Check edges with filters + + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et}, &to_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Delete edge + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + + const auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0]; + + const auto res = acc.DeleteEdge(edge.FromVertex(), edge.ToVertex(), edge.Gid()); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + { + auto ret = vertex_from->OutEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et}, &to_id)->size(), 1); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et}, &to_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + + // Check edges without filters + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } +} + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeDeleteFromLargerCommit) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertices +// { +// auto acc = store.Access(); +// auto vertex_to = acc.CreateVertex(); +// auto vertex_from = acc.CreateVertex(); +// gid_from = vertex_from.Gid(); +// gid_to = vertex_to.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&from_id, &to_id, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex_from); +// ASSERT_EQ(edge.ToVertex(), *vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Delete edge +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0]; + +// auto res = acc.DeleteEdge(&edge); +// ASSERT_TRUE(res.HasValue()); +// ASSERT_TRUE(res.GetValue()); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeDeleteFromSameCommit) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertex +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid_vertex = vertex.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&*vertex, &*vertex, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex); +// ASSERT_EQ(edge.ToVertex(), *vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Delete edge +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto res = acc.DeleteEdge(&edge); +// ASSERT_TRUE(res.HasValue()); +// ASSERT_TRUE(res.GetValue()); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 0); +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeDeleteFromSmallerAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertices +// { +// auto acc = store.Access(); +// auto vertex_from = acc.CreateVertex(); +// auto vertex_to = acc.CreateVertex(); +// gid_from = vertex_from.Gid(); +// gid_to = vertex_to.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&from_id, &to_id, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex_from); +// ASSERT_EQ(edge.ToVertex(), *vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Delete the edge, but abort the transaction +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0]; + +// auto res = acc.DeleteEdge(&edge); +// ASSERT_TRUE(res.HasValue()); +// ASSERT_TRUE(res.GetValue()); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + +// acc.Abort(); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Delete the edge +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// auto edge = vertex_from->OutEdges(View::NEW).GetValue()[0]; + +// auto res = acc.DeleteEdge(&edge); +// ASSERT_TRUE(res.HasValue()); +// ASSERT_TRUE(res.GetValue()); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &to_id)->size(), 1); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD, {}, &from_id)->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); +// ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// // Check edges without filters +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, EdgeDeleteFromLargerAbort) { + // Create vertex + const PropertyValue from_key{max_primary_key_value}; + const PropertyValue to_key{0}; + const PropertyValue non_existing_key{2}; + auto acc = store.Access(); + const auto to_id = std::invoke( + [this, &to_key, &acc]() mutable -> VertexId { return CreateVertex(acc, to_key)->Id(View::NEW).GetValue(); }); + const VertexId from_id{primary_label, {from_key}}; + const auto et = NameToEdgeTypeId("et5"); + const auto edge_id = Gid::FromUint(1U); + const auto other_et = NameToEdgeTypeId("other"); + const VertexId from_id_with_different_label{NameToLabelId("different_label"), from_id.primary_key}; + const VertexId non_existing_id{primary_label, {non_existing_key}}; + + ASSERT_FALSE(acc.Commit().HasError()); + // Create edge + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_to); + + auto res = acc.CreateEdge(from_id, to_id, et, edge_id); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.Gid(), edge_id); + ASSERT_EQ(edge.FromVertex(), from_id); + ASSERT_EQ(edge.ToVertex(), to_id); + + // Check edges without filters + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + + // Check edges without filters + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Delete the edge, but abort the transaction + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_to); + + const auto edge = vertex_to->InEdges(View::NEW).GetValue()[0]; + + auto res = acc.DeleteEdge(edge.FromVertex(), edge.ToVertex(), edge.Gid()); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + + acc.Abort(); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to->InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Delete the edge + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_to); + + const auto edge = vertex_to->InEdges(View::NEW).GetValue()[0]; + + auto res = acc.DeleteEdge(edge.FromVertex(), edge.ToVertex(), edge.Gid()); + ASSERT_TRUE(res.HasValue()); + ASSERT_TRUE(res.GetValue()); + + // Check edges without filters + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + // Check edges with filters + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et})->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et})->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id)->size(), 1); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &to_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {}, &non_existing_id)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::OLD, {et, other_et}, &from_id_with_different_label)->size(), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW, {et, other_et}, &from_id_with_different_label)->size(), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check whether the edge exists + { + auto acc = store.Access(); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_to); + + // Check edges without filters + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } +} + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, EdgeDeleteFromSameAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_vertex = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create vertex +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid_vertex = vertex.Gid(); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Create edge +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&*vertex, &*vertex, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), *vertex); +// ASSERT_EQ(edge.ToVertex(), *vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Delete the edge, but abort the transaction +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto res = acc.DeleteEdge(&edge); +// ASSERT_TRUE(res.HasValue()); +// ASSERT_TRUE(res.GetValue()); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 0); +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); + +// acc.Abort(); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// { +// auto ret = vertex->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Delete the edge +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// auto et = acc.NameToEdgeType("et5"); + +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto res = acc.DeleteEdge(&edge); +// ASSERT_TRUE(res.HasValue()); +// ASSERT_TRUE(res.GetValue()); + +// // Check edges without filters +// { +// auto ret = vertex->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 0); +// { +// auto ret = vertex->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex); +// ASSERT_EQ(e.ToVertex(), *vertex); +// } +// ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 0); + +// auto other_et = acc.NameToEdgeType("other"); + +// // Check edges with filters +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->InEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->InEdges(View::OLD, {other_et}, &*vertex)->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et})->size(), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {et, other_et})->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {}, &*vertex)->size(), 1); +// ASSERT_EQ(vertex->OutEdges(View::OLD, {other_et}, &*vertex)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check whether the edge exists +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid_vertex, View::NEW); +// ASSERT_TRUE(vertex); + +// // Check edges without filters +// ASSERT_EQ(vertex->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// } + +// NOLINTNEXTLINE(hicpp-special-member-functions) +TEST_P(StorageEdgeTest, VertexDetachDeleteSingleCommit) { + // Create vertices + const PropertyValue from_key{0}; + const PropertyValue to_key{1}; + const PropertyValue non_existing_key{2}; + const auto et = NameToEdgeTypeId("et5"); + const auto edge_id = Gid::FromUint(0U); + auto acc = store.Access(); + const VertexId from_id{primary_label, {from_key}}; + const VertexId to_id{primary_label, {to_key}}; + const VertexId non_existing_id{primary_label, {non_existing_key}}; + + // Create dataset + { + auto acc = store.Access(); + auto vertex_from = CreateVertex(acc, from_key).GetValue(); + auto vertex_to = CreateVertex(acc, to_key).GetValue(); + + auto res = acc.CreateEdge(from_id, to_id, et, edge_id); + ASSERT_TRUE(res.HasValue()); + auto edge = res.GetValue(); + ASSERT_EQ(edge.EdgeType(), et); + ASSERT_EQ(edge.Gid(), edge_id); + ASSERT_EQ(edge.FromVertex(), from_id); + ASSERT_EQ(edge.ToVertex(), to_id); + + // Check edges + ASSERT_EQ(vertex_from.InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_from.InDegree(View::NEW), 0); + { + auto ret = vertex_from.OutEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from.OutDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + { + auto ret = vertex_to.InEdges(View::NEW); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to.InDegree(View::NEW), 1); + auto e = edges[0]; + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to.OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to.OutDegree(View::NEW), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Detach delete vertex + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_TRUE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Delete must fail + { + auto ret = acc.DeleteVertex(&vertex_from.value()); + ASSERT_TRUE(ret.HasError()); + ASSERT_EQ(ret.GetError(), Error::VERTEX_HAS_EDGES); + } + + // Detach delete vertex + { + auto ret = acc.DetachDeleteVertex(&vertex_from.value()); + ASSERT_TRUE(ret.HasValue()); + ASSERT_TRUE(*ret); + } + + // Check edges + ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_from->InEdges(View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->InDegree(View::NEW).GetError(), Error::DELETED_OBJECT); + { + auto ret = vertex_from->OutEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_from->OutEdges(View::NEW).GetError(), Error::DELETED_OBJECT); + ASSERT_EQ(vertex_from->OutDegree(View::NEW).GetError(), Error::DELETED_OBJECT); + { + auto ret = vertex_to->InEdges(View::OLD); + ASSERT_TRUE(ret.HasValue()); + auto edges = ret.GetValue(); + ASSERT_EQ(edges.size(), 1); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); + auto e = edges[0]; + ASSERT_EQ(e.EdgeType(), et); + ASSERT_EQ(e.Gid(), edge_id); + ASSERT_EQ(e.FromVertex(), from_id); + ASSERT_EQ(e.ToVertex(), to_id); + } + ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + + ASSERT_FALSE(acc.Commit().HasError()); + } + + // Check dataset + { + auto acc = store.Access(); + auto vertex_from = acc.FindVertex(from_id.primary_key, View::NEW); + auto vertex_to = acc.FindVertex(to_id.primary_key, View::NEW); + ASSERT_FALSE(vertex_from); + ASSERT_TRUE(vertex_to); + + // Check edges + ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); + ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); + ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); + ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + } +} + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleCommit) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create dataset +// { +// auto acc = store.Access(); +// auto vertex1 = acc.CreateVertex(); +// auto vertex2 = acc.CreateVertex(); + +// gid_vertex1 = vertex1.Gid(); +// gid_vertex2 = vertex2.Gid(); + +// auto et1 = acc.NameToEdgeType("et1"); +// auto et2 = acc.NameToEdgeType("et2"); +// auto et3 = acc.NameToEdgeType("et3"); +// auto et4 = acc.NameToEdgeType("et4"); + +// auto res1 = acc.CreateEdge(&vertex1, &vertex2, et1); +// ASSERT_TRUE(res1.HasValue()); +// auto edge1 = res1.GetValue(); +// ASSERT_EQ(edge1.EdgeType(), et1); +// ASSERT_EQ(edge1.FromVertex(), vertex1); +// ASSERT_EQ(edge1.ToVertex(), vertex2); + +// auto res2 = acc.CreateEdge(&vertex2, &vertex1, et2); +// ASSERT_TRUE(res2.HasValue()); +// auto edge2 = res2.GetValue(); +// ASSERT_EQ(edge2.EdgeType(), et2); +// ASSERT_EQ(edge2.FromVertex(), vertex2); +// ASSERT_EQ(edge2.ToVertex(), vertex1); + +// auto res3 = acc.CreateEdge(&vertex1, &vertex1, et3); +// ASSERT_TRUE(res3.HasValue()); +// auto edge3 = res3.GetValue(); +// ASSERT_EQ(edge3.EdgeType(), et3); +// ASSERT_EQ(edge3.FromVertex(), vertex1); +// ASSERT_EQ(edge3.ToVertex(), vertex1); + +// auto res4 = acc.CreateEdge(&vertex2, &vertex2, et4); +// ASSERT_TRUE(res4.HasValue()); +// auto edge4 = res4.GetValue(); +// ASSERT_EQ(edge4.EdgeType(), et4); +// ASSERT_EQ(edge4.FromVertex(), vertex2); +// ASSERT_EQ(edge4.ToVertex(), vertex2); + +// // Check edges +// { +// auto ret = vertex1.InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.InDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// } +// { +// auto ret = vertex1.OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.OutDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// } +// { +// auto ret = vertex2.InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.InDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// } +// { +// auto ret = vertex2.OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.OutDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Detach delete vertex +// { +// auto acc = store.Access(); +// auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW); +// auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW); +// ASSERT_TRUE(vertex1); +// ASSERT_TRUE(vertex2); + +// auto et1 = acc.NameToEdgeType("et1"); +// auto et2 = acc.NameToEdgeType("et2"); +// auto et3 = acc.NameToEdgeType("et3"); +// auto et4 = acc.NameToEdgeType("et4"); + +// // Delete must fail +// { +// auto ret = acc.DeleteVertex(&*vertex1); +// ASSERT_TRUE(ret.HasError()); +// ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); +// } + +// // Detach delete vertex +// { +// auto ret = acc.DetachDeleteVertex(&*vertex1); +// ASSERT_TRUE(ret.HasValue()); +// ASSERT_TRUE(*ret); +// } + +// // Check edges +// { +// auto ret = vertex1->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// ASSERT_EQ(vertex1->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex1->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex1->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// ASSERT_EQ(vertex1->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex1->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex2->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check dataset +// { +// auto acc = store.Access(); +// auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW); +// auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW); +// ASSERT_FALSE(vertex1); +// ASSERT_TRUE(vertex2); + +// auto et4 = acc.NameToEdgeType("et4"); + +// // Check edges +// { +// auto ret = vertex2->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, VertexDetachDeleteSingleAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_from = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_to = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create dataset +// { +// auto acc = store.Access(); +// auto vertex_from = acc.CreateVertex(); +// auto vertex_to = acc.CreateVertex(); + +// auto et = acc.NameToEdgeType("et5"); + +// auto res = acc.CreateEdge(&vertex_from, &vertex_to, et); +// ASSERT_TRUE(res.HasValue()); +// auto edge = res.GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex_from); +// ASSERT_EQ(edge.ToVertex(), vertex_to); + +// gid_from = vertex_from.Gid(); +// gid_to = vertex_to.Gid(); + +// // Check edges +// ASSERT_EQ(vertex_from.InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from.InDegree(View::NEW), 0); +// { +// auto ret = vertex_from.OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from.OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), vertex_from); +// ASSERT_EQ(e.ToVertex(), vertex_to); +// } +// { +// auto ret = vertex_to.InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to.InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), vertex_from); +// ASSERT_EQ(e.ToVertex(), vertex_to); +// } +// ASSERT_EQ(vertex_to.OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to.OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Detach delete vertex, but abort the transaction +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Delete must fail +// { +// auto ret = acc.DeleteVertex(&from_id); +// ASSERT_TRUE(ret.HasError()); +// ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); +// } + +// // Detach delete vertex +// { +// auto ret = acc.DetachDeleteVertex(&from_id); +// ASSERT_TRUE(ret.HasValue()); +// ASSERT_TRUE(*ret); +// } + +// // Check edges +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex_from->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_from->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex_from->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// acc.Abort(); +// } + +// // Check dataset +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Check edges +// ASSERT_EQ(vertex_from->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::NEW), 0); +// { +// auto ret = vertex_from->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// { +// auto ret = vertex_to->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Detach delete vertex +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_TRUE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// auto et = acc.NameToEdgeType("et5"); + +// // Delete must fail +// { +// auto ret = acc.DeleteVertex(&from_id); +// ASSERT_TRUE(ret.HasError()); +// ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); +// } + +// // Detach delete vertex +// { +// auto ret = acc.DetachDeleteVertex(&from_id); +// ASSERT_TRUE(ret.HasValue()); +// ASSERT_TRUE(*ret); +// } + +// // Check edges +// ASSERT_EQ(vertex_from->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_from->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_from->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex_from->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex_from->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_from->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_from->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex_from->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex_to->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et); +// ASSERT_EQ(e.FromVertex(), *vertex_from); +// ASSERT_EQ(e.ToVertex(), *vertex_to); +// } +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check dataset +// { +// auto acc = store.Access(); +// auto vertex_from = acc.FindVertex(gid_from, View::NEW); +// auto vertex_to = acc.FindVertex(gid_to, View::NEW); +// ASSERT_FALSE(vertex_from); +// ASSERT_TRUE(vertex_to); + +// // Check edges +// ASSERT_EQ(vertex_to->InEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->InEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->InDegree(View::NEW), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::OLD)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::OLD), 0); +// ASSERT_EQ(vertex_to->OutEdges(View::NEW)->size(), 0); +// ASSERT_EQ(*vertex_to->OutDegree(View::NEW), 0); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST_P(StorageEdgeTest, VertexDetachDeleteMultipleAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = GetParam()}}); +// memgraph::storage::Gid gid_vertex1 = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// memgraph::storage::Gid gid_vertex2 = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create dataset +// { +// auto acc = store.Access(); +// auto vertex1 = acc.CreateVertex(); +// auto vertex2 = acc.CreateVertex(); + +// gid_vertex1 = vertex1.Gid(); +// gid_vertex2 = vertex2.Gid(); + +// auto et1 = acc.NameToEdgeType("et1"); +// auto et2 = acc.NameToEdgeType("et2"); +// auto et3 = acc.NameToEdgeType("et3"); +// auto et4 = acc.NameToEdgeType("et4"); + +// auto res1 = acc.CreateEdge(&vertex1, &vertex2, et1); +// ASSERT_TRUE(res1.HasValue()); +// auto edge1 = res1.GetValue(); +// ASSERT_EQ(edge1.EdgeType(), et1); +// ASSERT_EQ(edge1.FromVertex(), vertex1); +// ASSERT_EQ(edge1.ToVertex(), vertex2); + +// auto res2 = acc.CreateEdge(&vertex2, &vertex1, et2); +// ASSERT_TRUE(res2.HasValue()); +// auto edge2 = res2.GetValue(); +// ASSERT_EQ(edge2.EdgeType(), et2); +// ASSERT_EQ(edge2.FromVertex(), vertex2); +// ASSERT_EQ(edge2.ToVertex(), vertex1); + +// auto res3 = acc.CreateEdge(&vertex1, &vertex1, et3); +// ASSERT_TRUE(res3.HasValue()); +// auto edge3 = res3.GetValue(); +// ASSERT_EQ(edge3.EdgeType(), et3); +// ASSERT_EQ(edge3.FromVertex(), vertex1); +// ASSERT_EQ(edge3.ToVertex(), vertex1); + +// auto res4 = acc.CreateEdge(&vertex2, &vertex2, et4); +// ASSERT_TRUE(res4.HasValue()); +// auto edge4 = res4.GetValue(); +// ASSERT_EQ(edge4.EdgeType(), et4); +// ASSERT_EQ(edge4.FromVertex(), vertex2); +// ASSERT_EQ(edge4.ToVertex(), vertex2); + +// // Check edges +// { +// auto ret = vertex1.InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.InDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// } +// { +// auto ret = vertex1.OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1.OutDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// } +// { +// auto ret = vertex2.InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.InDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), vertex1); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// } +// { +// auto ret = vertex2.OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2.OutDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), vertex2); +// ASSERT_EQ(e.ToVertex(), vertex2); +// } +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Detach delete vertex, but abort the transaction +// { +// auto acc = store.Access(); +// auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW); +// auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW); +// ASSERT_TRUE(vertex1); +// ASSERT_TRUE(vertex2); + +// auto et1 = acc.NameToEdgeType("et1"); +// auto et2 = acc.NameToEdgeType("et2"); +// auto et3 = acc.NameToEdgeType("et3"); +// auto et4 = acc.NameToEdgeType("et4"); + +// // Delete must fail +// { +// auto ret = acc.DeleteVertex(&*vertex1); +// ASSERT_TRUE(ret.HasError()); +// ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); +// } + +// // Detach delete vertex +// { +// auto ret = acc.DetachDeleteVertex(&*vertex1); +// ASSERT_TRUE(ret.HasValue()); +// ASSERT_TRUE(*ret); +// } + +// // Check edges +// { +// auto ret = vertex1->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// ASSERT_EQ(vertex1->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex1->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex1->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// ASSERT_EQ(vertex1->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex1->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex2->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } + +// acc.Abort(); +// } + +// // Check dataset +// { +// auto acc = store.Access(); +// auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW); +// auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW); +// ASSERT_TRUE(vertex1); +// ASSERT_TRUE(vertex2); + +// auto et1 = acc.NameToEdgeType("et1"); +// auto et2 = acc.NameToEdgeType("et2"); +// auto et3 = acc.NameToEdgeType("et3"); +// auto et4 = acc.NameToEdgeType("et4"); + +// // Check edges +// { +// auto ret = vertex1->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// { +// auto ret = vertex1->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// { +// auto ret = vertex1->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// { +// auto ret = vertex1->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// { +// auto ret = vertex2->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::NEW), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Detach delete vertex +// { +// auto acc = store.Access(); +// auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW); +// auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW); +// ASSERT_TRUE(vertex1); +// ASSERT_TRUE(vertex2); + +// auto et1 = acc.NameToEdgeType("et1"); +// auto et2 = acc.NameToEdgeType("et2"); +// auto et3 = acc.NameToEdgeType("et3"); +// auto et4 = acc.NameToEdgeType("et4"); + +// // Delete must fail +// { +// auto ret = acc.DeleteVertex(&*vertex1); +// ASSERT_TRUE(ret.HasError()); +// ASSERT_EQ(ret.GetError(), memgraph::storage::Error::VERTEX_HAS_EDGES); +// } + +// // Detach delete vertex +// { +// auto ret = acc.DetachDeleteVertex(&*vertex1); +// ASSERT_TRUE(ret.HasValue()); +// ASSERT_TRUE(*ret); +// } + +// // Check edges +// { +// auto ret = vertex1->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// ASSERT_EQ(vertex1->InEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex1->InDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex1->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex1->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et3); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// } +// ASSERT_EQ(vertex1->OutEdges(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// ASSERT_EQ(vertex1->OutDegree(View::NEW).GetError(), memgraph::storage::Error::DELETED_OBJECT); +// { +// auto ret = vertex2->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->InDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et1); +// ASSERT_EQ(e.FromVertex(), *vertex1); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// std::sort(edges.begin(), edges.end(), [](const auto &a, const auto &b) { return a.EdgeType() < b.EdgeType(); +// }); ASSERT_EQ(edges.size(), 2); ASSERT_EQ(*vertex2->OutDegree(View::OLD), 2); +// { +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et2); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex1); +// } +// { +// auto e = edges[1]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// { +// auto ret = vertex2->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check dataset +// { +// auto acc = store.Access(); +// auto vertex1 = acc.FindVertex(gid_vertex1, View::NEW); +// auto vertex2 = acc.FindVertex(gid_vertex2, View::NEW); +// ASSERT_FALSE(vertex1); +// ASSERT_TRUE(vertex2); + +// auto et4 = acc.NameToEdgeType("et4"); + +// // Check edges +// { +// auto ret = vertex2->InEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->InEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->InDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::OLD); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::OLD), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// { +// auto ret = vertex2->OutEdges(View::NEW); +// ASSERT_TRUE(ret.HasValue()); +// auto edges = ret.GetValue(); +// ASSERT_EQ(edges.size(), 1); +// ASSERT_EQ(*vertex2->OutDegree(View::NEW), 1); +// auto e = edges[0]; +// ASSERT_EQ(e.EdgeType(), et4); +// ASSERT_EQ(e.FromVertex(), *vertex2); +// ASSERT_EQ(e.ToVertex(), *vertex2); +// } +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST(StorageWithProperties, EdgePropertyCommit) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); +// memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid = vertex.Gid(); +// auto et = acc.NameToEdgeType("et5"); +// auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex); +// ASSERT_EQ(edge.ToVertex(), vertex); + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "temporary"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "temporary"); +// } + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_FALSE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_FALSE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST(StorageWithProperties, EdgePropertyAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); +// memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); + +// // Create the vertex. +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid = vertex.Gid(); +// auto et = acc.NameToEdgeType("et5"); +// auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex); +// ASSERT_EQ(edge.ToVertex(), vertex); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Set property 5 to "nandare", but abort the transaction. +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "temporary"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "temporary"); +// } + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_FALSE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// acc.Abort(); +// } + +// // Check that property 5 is null. +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } + +// // Set property 5 to "nandare". +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "temporary"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "temporary"); +// } + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_FALSE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check that property 5 is "nandare". +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } + +// // Set property 5 to null, but abort the transaction. +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_FALSE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// acc.Abort(); +// } + +// // Check that property 5 is "nandare". +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } + +// // Set property 5 to null. +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::NEW)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// { +// auto old_value = edge.SetProperty(property, memgraph::storage::PropertyValue()); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_FALSE(old_value->IsNull()); +// } + +// ASSERT_EQ(edge.GetProperty(property, View::OLD)->ValueString(), "nandare"); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property].ValueString(), "nandare"); +// } + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// // Check that property 5 is null. +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST(StorageWithProperties, EdgePropertySerializationError) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); +// memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid = vertex.Gid(); +// auto et = acc.NameToEdgeType("et5"); +// auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex); +// ASSERT_EQ(edge.ToVertex(), vertex); +// ASSERT_FALSE(acc.Commit().HasError()); +// } + +// auto acc1 = store.Access(); +// auto acc2 = store.Access(); + +// // Set property 1 to 123 in accessor 1. +// { +// auto vertex = acc1.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property1 = acc1.NameToProperty("property1"); +// auto property2 = acc1.NameToProperty("property2"); + +// ASSERT_TRUE(edge.GetProperty(property1, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto old_value = edge.SetProperty(property1, memgraph::storage::PropertyValue(123)); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); +// } + +// ASSERT_TRUE(edge.GetProperty(property1, View::OLD)->IsNull()); +// ASSERT_EQ(edge.GetProperty(property1, View::NEW)->ValueInt(), 123); +// ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property1].ValueInt(), 123); +// } +// } + +// // Set property 2 to "nandare" in accessor 2. +// { +// auto vertex = acc2.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property1 = acc2.NameToProperty("property1"); +// auto property2 = acc2.NameToProperty("property2"); + +// ASSERT_TRUE(edge.GetProperty(property1, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto res = edge.SetProperty(property2, memgraph::storage::PropertyValue("nandare")); +// ASSERT_TRUE(res.HasError()); +// ASSERT_EQ(res.GetError(), memgraph::storage::Error::SERIALIZATION_ERROR); +// } +// } + +// // Finalize both accessors. +// ASSERT_FALSE(acc1.Commit().HasError()); +// acc2.Abort(); + +// // Check which properties exist. +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property1 = acc.NameToProperty("property1"); +// auto property2 = acc.NameToProperty("property2"); + +// ASSERT_EQ(edge.GetProperty(property1, View::OLD)->ValueInt(), 123); +// ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull()); +// { +// auto properties = edge.Properties(View::OLD).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property1].ValueInt(), 123); +// } + +// ASSERT_EQ(edge.GetProperty(property1, View::NEW)->ValueInt(), 123); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// { +// auto properties = edge.Properties(View::NEW).GetValue(); +// ASSERT_EQ(properties.size(), 1); +// ASSERT_EQ(properties[property1].ValueInt(), 123); +// } + +// acc.Abort(); +// } +// } + +// TEST(StorageWithProperties, EdgePropertyClear) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); +// memgraph::storage::Gid gid; +// auto property1 = store.NameToProperty("property1"); +// auto property2 = store.NameToProperty("property2"); +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid = vertex.Gid(); +// auto et = acc.NameToEdgeType("et5"); +// auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex); +// ASSERT_EQ(edge.ToVertex(), vertex); + +// auto old_value = edge.SetProperty(property1, memgraph::storage::PropertyValue("value")); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// ASSERT_EQ(edge.GetProperty(property1, View::OLD)->ValueString(), "value"); +// ASSERT_TRUE(edge.GetProperty(property2, View::OLD)->IsNull()); +// ASSERT_THAT(edge.Properties(View::OLD).GetValue(), +// UnorderedElementsAre(std::pair(property1, memgraph::storage::PropertyValue("value")))); + +// { +// auto old_values = edge.ClearProperties(); +// ASSERT_TRUE(old_values.HasValue()); +// ASSERT_FALSE(old_values->empty()); +// } + +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0); + +// { +// auto old_values = edge.ClearProperties(); +// ASSERT_TRUE(old_values.HasValue()); +// ASSERT_TRUE(old_values->empty()); +// } + +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0); + +// acc.Abort(); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto old_value = edge.SetProperty(property2, memgraph::storage::PropertyValue(42)); +// ASSERT_TRUE(old_value.HasValue()); +// ASSERT_TRUE(old_value->IsNull()); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// ASSERT_EQ(edge.GetProperty(property1, View::OLD)->ValueString(), "value"); +// ASSERT_EQ(edge.GetProperty(property2, View::OLD)->ValueInt(), 42); +// ASSERT_THAT(edge.Properties(View::OLD).GetValue(), +// UnorderedElementsAre(std::pair(property1, memgraph::storage::PropertyValue("value")), +// std::pair(property2, memgraph::storage::PropertyValue(42)))); + +// { +// auto old_values = edge.ClearProperties(); +// ASSERT_TRUE(old_values.HasValue()); +// ASSERT_FALSE(old_values->empty()); +// } + +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0); + +// { +// auto old_values = edge.ClearProperties(); +// ASSERT_TRUE(old_values.HasValue()); +// ASSERT_TRUE(old_values->empty()); +// } + +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// ASSERT_TRUE(edge.GetProperty(property1, View::NEW)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(property2, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW).GetValue().size(), 0); + +// acc.Abort(); +// } +// } + +// // NOLINTNEXTLINE(hicpp-special-member-functions) +// TEST(StorageWithoutProperties, EdgePropertyAbort) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = false}}); +// memgraph::storage::Gid gid = memgraph::storage::Gid::FromUint(std::numeric_limits::max()); +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid = vertex.Gid(); +// auto et = acc.NameToEdgeType("et5"); +// auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex); +// ASSERT_EQ(edge.ToVertex(), vertex); +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto res = edge.SetProperty(property, memgraph::storage::PropertyValue("temporary")); +// ASSERT_TRUE(res.HasError()); +// ASSERT_EQ(res.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); +// } + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// { +// auto res = edge.SetProperty(property, memgraph::storage::PropertyValue("nandare")); +// ASSERT_TRUE(res.HasError()); +// ASSERT_EQ(res.GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); +// } + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// acc.Abort(); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// auto property = acc.NameToProperty("property5"); + +// ASSERT_TRUE(edge.GetProperty(property, View::OLD)->IsNull()); +// ASSERT_EQ(edge.Properties(View::OLD)->size(), 0); + +// ASSERT_TRUE(edge.GetProperty(property, View::NEW)->IsNull()); +// ASSERT_EQ(edge.Properties(View::NEW)->size(), 0); + +// auto other_property = acc.NameToProperty("other"); + +// ASSERT_TRUE(edge.GetProperty(other_property, View::OLD)->IsNull()); +// ASSERT_TRUE(edge.GetProperty(other_property, View::NEW)->IsNull()); + +// acc.Abort(); +// } +// } + +// TEST(StorageWithoutProperties, EdgePropertyClear) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = false}}); +// memgraph::storage::Gid gid; +// { +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// gid = vertex.Gid(); +// auto et = acc.NameToEdgeType("et5"); +// auto edge = acc.CreateEdge(&vertex, &vertex, et).GetValue(); +// ASSERT_EQ(edge.EdgeType(), et); +// ASSERT_EQ(edge.FromVertex(), vertex); +// ASSERT_EQ(edge.ToVertex(), vertex); +// ASSERT_FALSE(acc.Commit().HasError()); +// } +// { +// auto acc = store.Access(); +// auto vertex = acc.FindVertex(gid, View::OLD); +// ASSERT_TRUE(vertex); +// auto edge = vertex->OutEdges(View::NEW).GetValue()[0]; + +// ASSERT_EQ(edge.ClearProperties().GetError(), memgraph::storage::Error::PROPERTIES_DISABLED); + +// acc.Abort(); +// } +// } + +// TEST(StorageWithProperties, EdgeNonexistentPropertyAPI) { +// memgraph::storage::Storage store({.items = {.properties_on_edges = true}}); + +// auto property = store.NameToProperty("property"); + +// auto acc = store.Access(); +// auto vertex = acc.CreateVertex(); +// auto edge = acc.CreateEdge(&vertex, &vertex, acc.NameToEdgeType("edge")); +// ASSERT_TRUE(edge.HasValue()); + +// // Check state before (OLD view). +// ASSERT_EQ(edge->Properties(View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); +// ASSERT_EQ(edge->GetProperty(property, View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); + +// // Check state before (NEW view). +// ASSERT_EQ(edge->Properties(View::NEW)->size(), 0); +// ASSERT_EQ(*edge->GetProperty(property, View::NEW), memgraph::storage::PropertyValue()); + +// // Modify edge. +// ASSERT_TRUE(edge->SetProperty(property, memgraph::storage::PropertyValue("value"))->IsNull()); + +// // Check state after (OLD view). +// ASSERT_EQ(edge->Properties(View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); +// ASSERT_EQ(edge->GetProperty(property, View::OLD).GetError(), memgraph::storage::Error::NONEXISTENT_OBJECT); + +// // Check state after (NEW view). +// ASSERT_EQ(edge->Properties(View::NEW)->size(), 1); +// ASSERT_EQ(*edge->GetProperty(property, View::NEW), memgraph::storage::PropertyValue("value")); + +// ASSERT_FALSE(acc.Commit().HasError()); +// } +} // namespace memgraph::storage::v3::tests From ecda71168cf9bef6a3bcec8c7031e39a3fb6ae91 Mon Sep 17 00:00:00 2001 From: gvolfing <107616712+gvolfing@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:15:19 +0200 Subject: [PATCH 08/10] Implement message based actions (#538) Create shard-side handlers for basic messages Implement the handlers for CreateVertices, CreateEdges and ScanAll. Use or modify the defined messages to interact with individual Shards and test their behavior. Shard is currently being owned by ShardRsm instances. The two top level dispatching functions Read() and Apply() are responsible for read- and write operations respectively. Currently there are a handful of messages that are defined but not utilized, these will be used in the near future, as well as a couple of handler functions with empty implementations. --- src/io/simulator/simulator_handle.hpp | 12 +- src/query/v2/requests.hpp | 338 ++++++++++++++++++-- src/storage/v3/CMakeLists.txt | 3 +- src/storage/v3/shard.cpp | 38 +++ src/storage/v3/shard.hpp | 8 + src/storage/v3/shard_rsm.cpp | 439 ++++++++++++++++++++++++++ src/storage/v3/shard_rsm.hpp | 58 ++++ tests/simulation/CMakeLists.txt | 24 +- tests/simulation/shard_rsm.cpp | 366 +++++++++++++++++++++ 9 files changed, 1238 insertions(+), 48 deletions(-) create mode 100644 src/storage/v3/shard_rsm.cpp create mode 100644 src/storage/v3/shard_rsm.hpp create mode 100644 tests/simulation/shard_rsm.cpp diff --git a/src/io/simulator/simulator_handle.hpp b/src/io/simulator/simulator_handle.hpp index 8a9e77c58..a673b917c 100644 --- a/src/io/simulator/simulator_handle.hpp +++ b/src/io/simulator/simulator_handle.hpp @@ -55,15 +55,17 @@ class SimulatorHandle { void TimeoutPromisesPastDeadline() { const Time now = cluster_wide_time_microseconds_; - - for (auto &[promise_key, dop] : promises_) { + for (auto it = promises_.begin(); it != promises_.end();) { + auto &[promise_key, dop] = *it; if (dop.deadline < now) { - spdlog::debug("timing out request from requester {} to replier {}.", promise_key.requester_address.ToString(), - promise_key.replier_address.ToString()); + spdlog::info("timing out request from requester {} to replier {}.", promise_key.requester_address.ToString(), + promise_key.replier_address.ToString()); std::move(dop).promise.TimeOut(); - promises_.erase(promise_key); + it = promises_.erase(it); stats_.timed_out_requests++; + } else { + ++it; } } } diff --git a/src/query/v2/requests.hpp b/src/query/v2/requests.hpp index 421a7fd54..07770f5aa 100644 --- a/src/query/v2/requests.hpp +++ b/src/query/v2/requests.hpp @@ -16,52 +16,51 @@ #include #include #include +#include #include #include +#include "coordinator/hybrid_logical_clock.hpp" #include "storage/v3/id_types.hpp" #include "storage/v3/property_value.hpp" -/// Hybrid-logical clock -struct Hlc { - uint64_t logical_id; - using Duration = std::chrono::microseconds; - using Time = std::chrono::time_point; - Time coordinator_wall_clock; +namespace memgraph::msgs { - bool operator==(const Hlc &other) const = default; -}; +using coordinator::Hlc; +using storage::v3::LabelId; + +struct Value; struct Label { - size_t id; + LabelId id; }; // TODO(kostasrim) update this with CompoundKey, same for the rest of the file. -using PrimaryKey = std::vector; +using PrimaryKey = std::vector; using VertexId = std::pair; using Gid = size_t; using PropertyId = memgraph::storage::v3::PropertyId; struct EdgeType { - std::string name; + uint64_t id; }; struct EdgeId { - VertexId id; Gid gid; }; -struct Vertex { - VertexId id; - std::vector