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<ConstraintViolation, UniqueConstraints::CreationStatus> UniqueConstraints::CreateConstraint( - LabelId label, const std::set<PropertyId> &properties, utils::SkipList<Vertex>::Accessor vertices) { + LabelId label, const std::set<PropertyId> &properties, VerticesSkipList::Accessor vertices) { if (properties.empty()) { return CreationStatus::EMPTY_PROPERTIES; } @@ -300,7 +300,8 @@ utils::BasicResult<ConstraintViolation, UniqueConstraints::CreationStatus> 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<ConstraintViolation, CreationStatus> CreateConstraint(LabelId label, const std::set<PropertyId> &properties, - utils::SkipList<Vertex>::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<ConstraintViolation, bool> CreateExistenceConstraint( - Constraints *constraints, LabelId label, PropertyId property, utils::SkipList<Vertex>::Accessor vertices) { +inline utils::BasicResult<ConstraintViolation, bool> 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<PropertyId>{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<std::vector<WalDurabilityInfo>> 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<Vertex> *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<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, std::string *uuid, - std::string *epoch_id, - std::deque<std::pair<std::string, uint64_t>> *epoch_history, - utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, - std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper, - Indices *indices, Constraints *constraints, Config::Items items, - uint64_t *wal_seq_num) { +std::optional<RecoveryInfo> RecoverData( + const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, std::deque<std::pair<std::string, uint64_t>> *epoch_history, VerticesSkipList *vertices, + utils::SkipList<Edge> *edges, std::atomic<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 3b0266062..b7fc39005 100644 --- a/src/storage/v3/durability/durability.hpp +++ b/src/storage/v3/durability/durability.hpp @@ -97,18 +97,15 @@ std::optional<std::vector<WalDurabilityInfo>> GetWalFiles(const std::filesystem: // recovery process. /// @throw RecoveryFailure void RecoverIndicesAndConstraints(const RecoveredIndicesAndConstraints &indices_constraints, Indices *indices, - Constraints *constraints, utils::SkipList<Vertex> *vertices); + Constraints *constraints, VerticesSkipList *vertices); /// Recovers data either from a snapshot and/or WAL files. /// @throw RecoveryFailure /// @throw std::bad_alloc -std::optional<RecoveryInfo> RecoverData(const std::filesystem::path &snapshot_directory, - const std::filesystem::path &wal_directory, std::string *uuid, - std::string *epoch_id, - std::deque<std::pair<std::string, uint64_t>> *epoch_history, - utils::SkipList<Vertex> *vertices, utils::SkipList<Edge> *edges, - std::atomic<uint64_t> *edge_count, NameIdMapper *name_id_mapper, - Indices *indices, Constraints *constraints, Config::Items items, - uint64_t *wal_seq_num); +std::optional<RecoveryInfo> RecoverData( + const std::filesystem::path &snapshot_directory, const std::filesystem::path &wal_directory, std::string *uuid, + std::string *epoch_id, std::deque<std::pair<std::string, uint64_t>> *epoch_history, VerticesSkipList *vertices, + utils::SkipList<Edge> *edges, std::atomic<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 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<Vertex> *vertices, +RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices, utils::SkipList<Edge> *edges, std::deque<std::pair<std::string, uint64_t>> *epoch_history, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *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<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, + VerticesSkipList *vertices, utils::SkipList<Edge> *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<std::pair<std::string, uint64_t>> &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<Vertex> *vertices, +RecoveredSnapshot LoadSnapshot(const std::filesystem::path &path, VerticesSkipList *vertices, utils::SkipList<Edge> *edges, std::deque<std::pair<std::string, uint64_t>> *epoch_history, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *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<Vertex> *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, + VerticesSkipList *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, Indices *indices, Constraints *constraints, Config::Items items, const std::string &uuid, std::string_view epoch_id, const std::deque<std::pair<std::string, uint64_t>> &epoch_history, utils::FileRetainer *file_retainer); 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<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices, + const std::optional<uint64_t> last_loaded_timestamp, VerticesSkipList *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<EdgeTypeId, Vertex *, EdgeRef> 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<uint64_t> last_loaded_timestamp, utils::SkipList<Vertex> *vertices, + std::optional<uint64_t> last_loaded_timestamp, VerticesSkipList *vertices, utils::SkipList<Edge> *edges, NameIdMapper *name_id_mapper, std::atomic<uint64_t> *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<Vertex>::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<Vertex>::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<Vertex>::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<Vertex>::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<Vertex>::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 <algorithm> +#include <iterator> +#include <ranges> + +#include "storage/v3/key_store.hpp" +#include "storage/v3/property_value.hpp" + +namespace memgraph::storage::v3 { + +KeyStore::KeyStore(const std::vector<PropertyValue> &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<PropertyValue> KeyStore::Keys() const { + auto keys_map = store_.Properties(); + std::vector<PropertyValue> keys; + keys.reserve(keys_map.size()); + std::ranges::transform( + keys_map, std::back_inserter(keys), + [](std::pair<const PropertyId, PropertyValue> &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 <algorithm> +#include <compare> +#include <functional> + +#include "storage/v3/property_store.hpp" +#include "storage/v3/property_value.hpp" + +namespace memgraph::storage::v3 { + +class KeyStore { + public: + explicit KeyStore(const std::vector<PropertyValue> &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<PropertyValue> 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<PropertyValue>{}); + } + + 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<PropertyValue> &rhs) { + // TODO(antaljanosbenjamin): also compare the schema + return std::ranges::lexicographical_compare(lhs.Keys(), rhs, std::less<PropertyValue>{}); + } + + friend bool operator==(const KeyStore &lhs, const std::vector<PropertyValue> &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 <concepts> +#include <type_traits> + +#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<PropertyValue> &rhs) { + return lhs.vertex.keys == rhs; + } + + friend bool operator<(const LexicographicallyOrderedVertex &lhs, const std::vector<PropertyValue> &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<Vertex>::Iterator it, utils::SkipList<Vertex>::Iterator end, +auto AdvanceToVisibleVertex(VerticesSkipList::Iterator it, VerticesSkipList::Iterator end, std::optional<VertexAccessor> *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<Vertex>::Iterator it, utils::SkipLis return it; } -AllVerticesIterable::Iterator::Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::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::RecoveryInfo>{}; + + 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<VertexAccessor> 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<std::optional<VertexAccessor>> Storage::Accessor::DeleteVertex(VertexAccessor *vertex) { @@ -609,10 +615,10 @@ Result<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA // Obtain the locks by `gid` order to avoid lock cycles. std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { + 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<EdgeAccessor> Storage::Accessor::CreateEdge(VertexAccessor *from, VertexA // Obtain the locks by `gid` order to avoid lock cycles. std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { + 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<std::optional<EdgeAccessor>> Storage::Accessor::DeleteEdge(EdgeAccessor * // Obtain the locks by `gid` order to avoid lock cycles. std::unique_lock<utils::SpinLock> guard_from(from_vertex->lock, std::defer_lock); std::unique_lock<utils::SpinLock> guard_to(to_vertex->lock, std::defer_lock); - if (from_vertex->gid < to_vertex->gid) { + 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<PropertyValue> 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<PropertyValue> 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<Vertex>::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<Vertex>::Iterator it_; + VerticesSkipList::Iterator it_; public: - Iterator(AllVerticesIterable *self, utils::SkipList<Vertex>::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<Vertex>::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<Vertex> vertices_; + VerticesSkipList vertices_; utils::SkipList<Edge> edges_; std::atomic<uint64_t> vertex_id_{0}; std::atomic<uint64_t> 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 <limits> #include <tuple> +#include <type_traits> #include <vector> #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<LabelId> 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<size_t> 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<LexicographicallyOrderedVertex>; +} // 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 <concepts> +#include <iterator> namespace memgraph::utils { template <typename T, typename... Args> @@ -18,4 +19,19 @@ concept SameAsAnyOf = (std::same_as<T, Args> || ...); template <typename T> concept Enum = std::is_enum_v<T>; + +// WithRef, CanReference and Dereferenceable is based on the similarly named concepts in GCC 11.2.0 +// bits/iterator_concepts.h +template <typename T> +using WithRef = T &; + +template <typename T> +concept CanReference = requires { + typename WithRef<T>; +}; + +template <typename T> +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 <limits> + #include <gmock/gmock.h> #include <gtest/gtest.h> +#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<uint64_t>::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<uint64_t>::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<uint64_t>::max()); + Gid gid2 = Gid::FromUint(std::numeric_limits<uint64_t>::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<uint64_t>::max()); + Gid gid2 = Gid::FromUint(std::numeric_limits<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::max()); + Gid gid2 = Gid::FromUint(std::numeric_limits<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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<uint64_t>::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> 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 <algorithm> +#include <string> +#include <vector> + +/** + * gtest/gtest.h must be included before rapidcheck/gtest.h! + */ +#include <gtest/gtest.h> +#include <rapidcheck.h> +#include <rapidcheck/gtest.h> + +#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<std::string> values)) { + RC_PRE(!values.empty()); + + std::vector<PropertyValue> 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 <array> +#include <limits> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#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<PropertyValue, 24> 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>{PropertyValue(33), PropertyValue(std::string("sample")), PropertyValue(-33.33)}), + PropertyValue(std::vector<PropertyValue>{PropertyValue(), PropertyValue(false)}), + PropertyValue(std::map<std::string, PropertyValue>{{"sample", PropertyValue()}, {"key", PropertyValue(false)}}), + PropertyValue(std::map<std::string, PropertyValue>{ + {"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<PropertyValue> vec{PropertyValue(true), PropertyValue(123), PropertyValue()}; + std::map<std::string, PropertyValue> map{{"nandare", PropertyValue(false)}}; + const TemporalData temporal{TemporalType::LocalDateTime, 23}; + std::vector<PropertyValue> 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<PropertyValue> vec{PropertyValue(true), PropertyValue(123), PropertyValue()}; + std::map<std::string, PropertyValue> map{{"nandare", PropertyValue(false)}}; + const TemporalData temporal{TemporalType::LocalDateTime, 23}; + std::map<PropertyId, PropertyValue> 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<PropertyValue> 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<PropertyId, PropertyValue> data{ + // {PropertyId::FromUint(0UL), + // PropertyValue(std::numeric_limits<int64_t>::min())}, + // {PropertyId::FromUint(10UL), PropertyValue(-137438953472L)}, + // {PropertyId::FromUint(std::numeric_limits<uint8_t>::max()), + // PropertyValue(-4294967297L)}, + // {PropertyId::FromUint(256UL), + // PropertyValue(std::numeric_limits<int32_t>::min())}, + // {PropertyId::FromUint(1024UL), PropertyValue(-1048576L)}, + // {PropertyId::FromUint(1025UL), PropertyValue(-65537L)}, + // {PropertyId::FromUint(1026UL), + // PropertyValue(std::numeric_limits<int16_t>::min())}, + // {PropertyId::FromUint(1027UL), PropertyValue(-1024L)}, + // {PropertyId::FromUint(2000UL), PropertyValue(-257L)}, + // {PropertyId::FromUint(3000UL), + // PropertyValue(std::numeric_limits<int8_t>::min())}, + // {PropertyId::FromUint(4000UL), PropertyValue(-1L)}, + // {PropertyId::FromUint(10000UL), PropertyValue(0L)}, + // {PropertyId::FromUint(20000UL), PropertyValue(1L)}, + // {PropertyId::FromUint(30000UL), + // PropertyValue(std::numeric_limits<int8_t>::max())}, + // {PropertyId::FromUint(40000UL), PropertyValue(256L)}, + // {PropertyId::FromUint(50000UL), PropertyValue(1024L)}, + // {PropertyId::FromUint(std::numeric_limits<uint16_t>::max()), + // PropertyValue(std::numeric_limits<int16_t>::max())}, + // {PropertyId::FromUint(65536UL), PropertyValue(65536L)}, + // {PropertyId::FromUint(1048576UL), PropertyValue(1048576L)}, + // {PropertyId::FromUint(std::numeric_limits<uint32_t>::max()), + // PropertyValue(std::numeric_limits<int32_t>::max())}, + {PropertyId::FromUint(4294967296UL), PropertyValue(4294967296L)}, + {PropertyId::FromUint(137438953472UL), PropertyValue(137438953472L)}, + {PropertyId::FromUint(std::numeric_limits<uint64_t>::max()), PropertyValue(std::numeric_limits<int64_t>::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<std::pair<PropertyValue, PropertyValue>> 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>{PropertyValue(test.first.ValueInt())}); + test.second = PropertyValue(std::vector<PropertyValue>{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>{PropertyValue(test.first.ValueList()[0].ValueInt() * -1)}); + test.second = + PropertyValue(std::vector<PropertyValue>{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>{PropertyValue(42), PropertyValue("test")}))); + ASSERT_TRUE( + props.IsPropertyEqual(prop, PropertyValue(std::vector<PropertyValue>{PropertyValue(42), PropertyValue("test")}))); + + // Different length. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::vector<PropertyValue>{PropertyValue(24)}))); + + // Same length, different value. + ASSERT_FALSE( + props.IsPropertyEqual(prop, PropertyValue(std::vector<PropertyValue>{PropertyValue(42), PropertyValue("asdf")}))); + + // Shortened and extended. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::vector<PropertyValue>{PropertyValue(42)}))); + ASSERT_FALSE(props.IsPropertyEqual( + prop, PropertyValue(std::vector<PropertyValue>{PropertyValue(42), PropertyValue("test"), PropertyValue(true)}))); +} + +TEST_F(StorageV3PropertyStore, IsPropertyEqualMap) { + auto prop = PropertyId::FromInt(42); + ASSERT_TRUE(props.SetProperty(prop, PropertyValue(std::map<std::string, PropertyValue>{ + {"abc", PropertyValue(42)}, {"zyx", PropertyValue("test")}}))); + ASSERT_TRUE(props.IsPropertyEqual(prop, PropertyValue(std::map<std::string, PropertyValue>{ + {"abc", PropertyValue(42)}, {"zyx", PropertyValue("test")}}))); + + // Different length. + ASSERT_FALSE( + props.IsPropertyEqual(prop, PropertyValue(std::map<std::string, PropertyValue>{{"fgh", PropertyValue(24)}}))); + + // Same length, different value. + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::map<std::string, PropertyValue>{ + {"abc", PropertyValue(42)}, {"zyx", PropertyValue("testt")}}))); + + // Same length, different key (different length). + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::map<std::string, PropertyValue>{ + {"abc", PropertyValue(42)}, {"zyxw", PropertyValue("test")}}))); + + // Same length, different key (same length). + ASSERT_FALSE(props.IsPropertyEqual(prop, PropertyValue(std::map<std::string, PropertyValue>{ + {"abc", PropertyValue(42)}, {"zyw", PropertyValue("test")}}))); + + // Shortened and extended. + ASSERT_FALSE( + props.IsPropertyEqual(prop, PropertyValue(std::map<std::string, PropertyValue>{{"abc", PropertyValue(42)}}))); + ASSERT_FALSE(props.IsPropertyEqual( + prop, PropertyValue(std::map<std::string, PropertyValue>{ + {"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