diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 7d568d548..0c418d752 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -182,6 +182,7 @@ import_external_library(rocksdb STATIC -DGFLAGS_NOTHREADS=OFF -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_SKIP_INSTALL_ALL_DEPENDENCY=true + -DPORTABLE=1 BUILD_COMMAND $(MAKE) rocksdb) # Setup libbcrypt diff --git a/src/dbms/inmemory/replication_handlers.cpp b/src/dbms/inmemory/replication_handlers.cpp index 3fc174d3c..7072995ad 100644 --- a/src/dbms/inmemory/replication_handlers.cpp +++ b/src/dbms/inmemory/replication_handlers.cpp @@ -630,8 +630,8 @@ uint64_t InMemoryReplicationHandlers::ReadAndApplyDelta(storage::InMemoryStorage Delta *delta = nullptr; { auto guard = std::shared_lock{edge->lock}; - is_visible = !edge->deleted; - delta = edge->delta; + is_visible = !edge->deleted(); + delta = edge->delta(); } ApplyDeltasForRead(&transaction->GetTransaction(), delta, View::NEW, [&is_visible](const Delta &delta) { switch (delta.action) { diff --git a/src/query/procedure/mg_procedure_impl.cpp b/src/query/procedure/mg_procedure_impl.cpp index 647f3e14d..2c85b3876 100644 --- a/src/query/procedure/mg_procedure_impl.cpp +++ b/src/query/procedure/mg_procedure_impl.cpp @@ -324,7 +324,7 @@ mgp_value_type FromTypedValueType(memgraph::query::TypedValue::Type type) { } } // namespace -bool IsDeleted(const mgp_vertex *vertex) { return vertex->getImpl().impl_.vertex_->deleted; } +bool IsDeleted(const mgp_vertex *vertex) { return vertex->getImpl().impl_.vertex_->deleted(); } bool IsDeleted(const mgp_edge *edge) { return edge->impl.IsDeleted(); } diff --git a/src/query/typed_value.cpp b/src/query/typed_value.cpp index 86d25f01b..e5037158f 100644 --- a/src/query/typed_value.cpp +++ b/src/query/typed_value.cpp @@ -386,12 +386,12 @@ bool TypedValue::ContainsDeleted() const { case Type::Map: return std::ranges::any_of(map_v, [](const auto &item) { return item.second.ContainsDeleted(); }); case Type::Vertex: - return vertex_v.impl_.vertex_->deleted; + return vertex_v.impl_.vertex_->deleted(); case Type::Edge: return edge_v.IsDeleted(); case Type::Path: return std::ranges::any_of(path_v.vertices(), - [](auto &vertex_acc) { return vertex_acc.impl_.vertex_->deleted; }) || + [](auto &vertex_acc) { return vertex_acc.impl_.vertex_->deleted(); }) || std::ranges::any_of(path_v.edges(), [](auto &edge_acc) { return edge_acc.IsDeleted(); }); default: throw TypedValueException("Value of unknown type"); diff --git a/src/storage/v2/constraints/existence_constraints.cpp b/src/storage/v2/constraints/existence_constraints.cpp index 956e0a208..a8f459e95 100644 --- a/src/storage/v2/constraints/existence_constraints.cpp +++ b/src/storage/v2/constraints/existence_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -58,7 +58,7 @@ void ExistenceConstraints::LoadExistenceConstraints(const std::vector<std::strin [[nodiscard]] std::optional<ConstraintViolation> ExistenceConstraints::ValidateVertexOnConstraint( const Vertex &vertex, const LabelId &label, const PropertyId &property) { - if (!vertex.deleted && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) { + if (!vertex.deleted() && utils::Contains(vertex.labels, label) && !vertex.properties.HasProperty(property)) { return ConstraintViolation{ConstraintViolation::Type::EXISTENCE, label, std::set<PropertyId>{property}}; } return std::nullopt; diff --git a/src/storage/v2/disk/label_index.cpp b/src/storage/v2/disk/label_index.cpp index 56986079b..c12e6d669 100644 --- a/src/storage/v2/disk/label_index.cpp +++ b/src/storage/v2/disk/label_index.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -80,7 +80,7 @@ std::unique_ptr<rocksdb::Transaction> DiskLabelIndex::CreateAllReadingRocksDBTra bool DiskLabelIndex::SyncVertexToLabelIndexStorage(const Vertex &vertex, uint64_t commit_timestamp) const { auto disk_transaction = CreateRocksDBTransaction(); - if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta()); maybe_old_disk_key.has_value()) { if (!disk_transaction->Delete(maybe_old_disk_key.value()).ok()) { return false; } diff --git a/src/storage/v2/disk/label_property_index.cpp b/src/storage/v2/disk/label_property_index.cpp index 9a40f03d1..e302dc702 100644 --- a/src/storage/v2/disk/label_property_index.cpp +++ b/src/storage/v2/disk/label_property_index.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -87,7 +87,7 @@ bool DiskLabelPropertyIndex::SyncVertexToLabelPropertyIndexStorage(const Vertex uint64_t commit_timestamp) const { auto disk_transaction = CreateRocksDBTransaction(); - if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta()); maybe_old_disk_key.has_value()) { if (!disk_transaction->Delete(maybe_old_disk_key.value()).ok()) { return false; } diff --git a/src/storage/v2/disk/storage.cpp b/src/storage/v2/disk/storage.cpp index fa9f93ccb..5cf401366 100644 --- a/src/storage/v2/disk/storage.cpp +++ b/src/storage/v2/disk/storage.cpp @@ -118,7 +118,7 @@ constexpr const char *kExistenceConstraintsStr = "existence_constraints"; /// TODO: (andi) Maybe a better way of checking would be if the first delta is DELETE_DESERIALIZED /// then we now that the vertex has only been deserialized and nothing more has been done on it. bool VertexNeedsToBeSerialized(const Vertex &vertex) { - Delta *head = vertex.delta; + Delta *head = vertex.delta(); while (head != nullptr) { if (head->action == Delta::Action::ADD_LABEL || head->action == Delta::Action::REMOVE_LABEL || head->action == Delta::Action::DELETE_OBJECT || head->action == Delta::Action::RECREATE_OBJECT || @@ -131,9 +131,9 @@ bool VertexNeedsToBeSerialized(const Vertex &vertex) { } bool VertexHasLabel(const Vertex &vertex, LabelId label, Transaction *transaction, View view) { - bool deleted = vertex.deleted; + bool deleted = vertex.deleted(); bool has_label = std::find(vertex.labels.begin(), vertex.labels.end(), label) != vertex.labels.end(); - Delta *delta = vertex.delta; + Delta *delta = vertex.delta(); ApplyDeltasForRead(transaction, delta, view, [&deleted, &has_label, label](const Delta &delta) { switch (delta.action) { case Delta::Action::REMOVE_LABEL: { @@ -170,9 +170,9 @@ bool VertexHasLabel(const Vertex &vertex, LabelId label, Transaction *transactio } PropertyValue GetVertexProperty(const Vertex &vertex, PropertyId property, Transaction *transaction, View view) { - bool deleted = vertex.deleted; + bool deleted = vertex.deleted(); PropertyValue value = vertex.properties.GetProperty(property); - Delta *delta = vertex.delta; + Delta *delta = vertex.delta(); ApplyDeltasForRead(transaction, delta, view, [&deleted, &value, property](const Delta &delta) { switch (delta.action) { case Delta::Action::SET_PROPERTY: { @@ -625,7 +625,7 @@ std::unordered_set<Gid> DiskStorage::MergeVerticesFromMainCacheWithLabelIndexCac gids.insert(vertex.gid); if (VertexHasLabel(vertex, label, transaction, view)) { spdlog::trace("Loaded vertex with gid: {} from main index storage to label index", vertex.gid.ToString()); - uint64_t ts = utils::GetEarliestTimestamp(vertex.delta); + uint64_t ts = utils::GetEarliestTimestamp(vertex.delta()); /// TODO: here are doing serialization and then later deserialization again -> expensive LoadVertexToLabelIndexCache(transaction, utils::SerializeVertexAsKeyForLabelIndex(label, vertex.gid), utils::SerializeVertexAsValueForLabelIndex(label, vertex.labels, vertex.properties), @@ -676,7 +676,7 @@ std::unordered_set<Gid> DiskStorage::MergeVerticesFromMainCacheWithLabelProperty for (const auto &vertex : main_cache_acc) { gids.insert(vertex.gid); if (label_property_filter(vertex, label, property, view)) { - uint64_t ts = utils::GetEarliestTimestamp(vertex.delta); + uint64_t ts = utils::GetEarliestTimestamp(vertex.delta()); LoadVertexToLabelPropertyIndexCache( transaction, utils::SerializeVertexAsKeyForLabelPropertyIndex(label, property, vertex.gid), utils::SerializeVertexAsValueForLabelPropertyIndex(label, vertex.labels, vertex.properties), @@ -761,7 +761,7 @@ std::unordered_set<Gid> DiskStorage::MergeVerticesFromMainCacheWithLabelProperty auto prop_value = GetVertexProperty(vertex, property, transaction, view); if (VertexHasLabel(vertex, label, transaction, view) && IsPropertyValueWithinInterval(prop_value, lower_bound, upper_bound)) { - uint64_t ts = utils::GetEarliestTimestamp(vertex.delta); + uint64_t ts = utils::GetEarliestTimestamp(vertex.delta()); LoadVertexToLabelPropertyIndexCache( transaction, utils::SerializeVertexAsKeyForLabelPropertyIndex(label, property, vertex.gid), utils::SerializeVertexAsValueForLabelPropertyIndex(label, vertex.labels, vertex.properties), @@ -946,7 +946,7 @@ Result<EdgeAccessor> DiskStorage::DiskAccessor::CreateEdge(VertexAccessor *from, auto *from_vertex = from->vertex_; auto *to_vertex = to->vertex_; - if (from_vertex->deleted || to_vertex->deleted) return Error::DELETED_OBJECT; + if (from_vertex->deleted() || to_vertex->deleted()) return Error::DELETED_OBJECT; auto *disk_storage = static_cast<DiskStorage *>(storage_); auto gid = storage::Gid::FromUint(disk_storage->edge_id_.fetch_add(1, std::memory_order_acq_rel)); @@ -1189,12 +1189,12 @@ bool DiskStorage::DeleteEdgeFromConnectivityIndex(Transaction *transaction, cons return check_result.GetError(); } - if (vertex.deleted) { + if (vertex.deleted()) { continue; } /// NOTE: this deletion has to come before writing, otherwise RocksDB thinks that all entries are deleted - if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta()); maybe_old_disk_key.has_value()) { if (!DeleteVertexFromDisk(transaction, vertex.gid.ToString(), maybe_old_disk_key.value())) { return StorageManipulationError{SerializationError{}}; } @@ -1431,7 +1431,7 @@ std::vector<EdgeAccessor> DiskStorage::OutEdges(const VertexAccessor *src_vertex const VertexAccessor *destination, Transaction *transaction, View view) { /// Check whether the vertex is deleted in the current tx only if View::NEW is requested - if (view == View::NEW && src_vertex->vertex_->deleted) return {}; + if (view == View::NEW && src_vertex->vertex_->deleted()) return {}; const std::string src_vertex_gid = src_vertex->Gid().ToString(); rocksdb::ReadOptions ro; @@ -1468,9 +1468,9 @@ std::vector<EdgeAccessor> DiskStorage::OutEdges(const VertexAccessor *src_vertex auto dst_vertex_gid = utils::ExtractDstVertexGidFromEdgeValue(edge_val_str); if (!destination) { auto dst_vertex = FindVertex(dst_vertex_gid, transaction, view); - /// TODO: (andi) I think dst_vertex->deleted should be unnecessary + /// TODO: (andi) I think dst_vertex->deleted() should be unnecessary /// Check whether the vertex is deleted in the current tx only if View::NEW is requested - if (!dst_vertex.has_value() || (view == View::NEW && dst_vertex->vertex_->deleted)) + if (!dst_vertex.has_value() || (view == View::NEW && dst_vertex->vertex_->deleted())) return std::optional<EdgeAccessor>{}; return CreateEdgeFromDisk(src_vertex, &*dst_vertex, transaction, edge_type_id, edge_gid, properties_str, @@ -1478,7 +1478,7 @@ std::vector<EdgeAccessor> DiskStorage::OutEdges(const VertexAccessor *src_vertex } /// This is needed for filtering /// Second check not needed I think - if (dst_vertex_gid != destination->Gid() || destination->vertex_->deleted) { + if (dst_vertex_gid != destination->Gid() || destination->vertex_->deleted()) { return std::optional<EdgeAccessor>{}; } @@ -1495,7 +1495,7 @@ std::vector<EdgeAccessor> DiskStorage::InEdges(const VertexAccessor *dst_vertex, const std::vector<EdgeTypeId> &edge_types, const VertexAccessor *source, Transaction *transaction, View view) { /// Check whether the vertex is deleted in the current tx only if View::NEW is requested - if (view == View::NEW && dst_vertex->vertex_->deleted) return {}; + if (view == View::NEW && dst_vertex->vertex_->deleted()) return {}; const std::string dst_vertex_gid = dst_vertex->Gid().ToString(); rocksdb::ReadOptions ro; @@ -1533,14 +1533,14 @@ std::vector<EdgeAccessor> DiskStorage::InEdges(const VertexAccessor *dst_vertex, auto src_vertex = FindVertex(src_vertex_gid, transaction, view); /// Check whether the vertex is deleted in the current tx only if View::NEW is requested /// TODO: (andi) Second check I think isn't necessary - if (!src_vertex.has_value() || (view == View::NEW && src_vertex->vertex_->deleted)) + if (!src_vertex.has_value() || (view == View::NEW && src_vertex->vertex_->deleted())) return std::optional<EdgeAccessor>{}; return CreateEdgeFromDisk(&*src_vertex, dst_vertex, transaction, edge_type_id, edge_gid, properties_str, edge_gid_str, kDeserializeTimestamp); } /// TODO: (andi) 2nd check not needed I think - if (src_vertex_gid != source->Gid() || source->vertex_->deleted) { + if (src_vertex_gid != source->Gid() || source->vertex_->deleted()) { return std::optional<EdgeAccessor>{}; } return CreateEdgeFromDisk(source, dst_vertex, transaction, edge_type_id, edge_gid, properties_str, edge_gid_str, @@ -1817,7 +1817,7 @@ void DiskStorage::DiskAccessor::UpdateObjectsCountOnAbort() { switch (prev.type) { case PreviousPtr::Type::VERTEX: { auto *vertex = prev.vertex; - Delta *current = vertex->delta; + Delta *current = vertex->delta(); while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == transaction_id) { switch (current->action) { case Delta::Action::DELETE_DESERIALIZED_OBJECT: @@ -1847,7 +1847,7 @@ void DiskStorage::DiskAccessor::UpdateObjectsCountOnAbort() { } current = current->next.load(std::memory_order_acquire); } - vertex->delta = current; + vertex->delta(current); if (current != nullptr) { current->prev.Set(vertex); } diff --git a/src/storage/v2/disk/unique_constraints.cpp b/src/storage/v2/disk/unique_constraints.cpp index 3c17530c2..2ab245add 100644 --- a/src/storage/v2/disk/unique_constraints.cpp +++ b/src/storage/v2/disk/unique_constraints.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -216,7 +216,7 @@ bool DiskUniqueConstraints::SyncVertexToUniqueConstraintsStorage(const Vertex &v auto disk_transaction = std::unique_ptr<rocksdb::Transaction>( kvstore_->db_->BeginTransaction(rocksdb::WriteOptions(), rocksdb::TransactionOptions())); - if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta); maybe_old_disk_key.has_value()) { + if (auto maybe_old_disk_key = utils::GetOldDiskKeyOrNull(vertex.delta()); maybe_old_disk_key.has_value()) { spdlog::trace("Found old disk key {} for vertex {}", maybe_old_disk_key.value(), vertex.gid.ToString()); if (auto status = disk_transaction->Delete(maybe_old_disk_key.value()); !status.ok()) { return false; diff --git a/src/storage/v2/durability/snapshot.cpp b/src/storage/v2/durability/snapshot.cpp index eee099870..c20d718ca 100644 --- a/src/storage/v2/durability/snapshot.cpp +++ b/src/storage/v2/durability/snapshot.cpp @@ -1881,8 +1881,8 @@ void CreateSnapshot(Storage *storage, Transaction *transaction, const std::files Delta *delta = nullptr; { auto guard = std::shared_lock{edge.lock}; - is_visible = !edge.deleted; - delta = edge.delta; + is_visible = !edge.deleted(); + delta = edge.delta(); } ApplyDeltasForRead(transaction, delta, View::OLD, [&is_visible](const Delta &delta) { switch (delta.action) { diff --git a/src/storage/v2/edge.hpp b/src/storage/v2/edge.hpp index bdb224dfb..42959d743 100644 --- a/src/storage/v2/edge.hpp +++ b/src/storage/v2/edge.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -16,6 +16,7 @@ #include "storage/v2/delta.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/property_store.hpp" +#include "storage/v2/tco_delta.hpp" #include "utils/logging.hpp" #include "utils/rw_spin_lock.hpp" @@ -24,7 +25,7 @@ namespace memgraph::storage { struct Vertex; struct Edge { - Edge(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) { + Edge(Gid gid, Delta *delta) : gid(gid), tco_delta(delta) { MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT || delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT, "Edge must be created with an initial DELETE_OBJECT delta!"); @@ -32,14 +33,21 @@ struct Edge { Gid gid; + mutable utils::RWSpinLock lock; PropertyStore properties; - mutable utils::RWSpinLock lock; - bool deleted; + // bool deleted; // uint8_t PAD; // uint16_t PAD; - Delta *delta; + // Delta *delta; + + TcoDelta tco_delta; + + bool deleted() const { return tco_delta.deleted(); } + void deleted(bool in) { tco_delta.deleted(in); } + Delta *delta() const { return tco_delta.delta(); } + void delta(Delta *in) { tco_delta.delta(in); } }; static_assert(alignof(Edge) >= 8, "The Edge should be aligned to at least 8!"); diff --git a/src/storage/v2/edge_accessor.cpp b/src/storage/v2/edge_accessor.cpp index 62a9f4bcd..d13157889 100644 --- a/src/storage/v2/edge_accessor.cpp +++ b/src/storage/v2/edge_accessor.cpp @@ -31,7 +31,7 @@ bool EdgeAccessor::IsDeleted() const { if (!storage_->config_.salient.items.properties_on_edges) { return false; } - return edge_.ptr->deleted; + return edge_.ptr->deleted(); } bool EdgeAccessor::IsVisible(const View view) const { @@ -47,7 +47,7 @@ bool EdgeAccessor::IsVisible(const View view) const { deleted = std::find_if(from_vertex_->out_edges.begin(), from_vertex_->out_edges.end(), [&](const auto &out_edge) { return std::get<2>(out_edge) == edge_; }) == from_vertex_->out_edges.end(); - delta = from_vertex_->delta; + delta = from_vertex_->delta(); } ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) { switch (delta.action) { @@ -80,8 +80,8 @@ bool EdgeAccessor::IsVisible(const View view) const { Delta *delta = nullptr; { auto guard = std::shared_lock{edge_.ptr->lock}; - deleted = edge_.ptr->deleted; - delta = edge_.ptr->delta; + deleted = edge_.ptr->deleted(); + delta = edge_.ptr->delta(); } ApplyDeltasForRead(transaction_, delta, view, [&](const Delta &delta) { switch (delta.action) { @@ -112,11 +112,11 @@ VertexAccessor EdgeAccessor::FromVertex() const { return VertexAccessor{from_ver VertexAccessor EdgeAccessor::ToVertex() const { return VertexAccessor{to_vertex_, storage_, transaction_}; } VertexAccessor EdgeAccessor::DeletedEdgeFromVertex() const { - return VertexAccessor{from_vertex_, storage_, transaction_, for_deleted_ && from_vertex_->deleted}; + return VertexAccessor{from_vertex_, storage_, transaction_, for_deleted_ && from_vertex_->deleted()}; } VertexAccessor EdgeAccessor::DeletedEdgeToVertex() const { - return VertexAccessor{to_vertex_, storage_, transaction_, for_deleted_ && to_vertex_->deleted}; + return VertexAccessor{to_vertex_, storage_, transaction_, for_deleted_ && to_vertex_->deleted()}; } Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, const PropertyValue &value) { @@ -127,7 +127,7 @@ Result<storage::PropertyValue> EdgeAccessor::SetProperty(PropertyId property, co if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; - if (edge_.ptr->deleted) return Error::DELETED_OBJECT; + if (edge_.ptr->deleted()) return Error::DELETED_OBJECT; using ReturnType = decltype(edge_.ptr->properties.GetProperty(property)); std::optional<ReturnType> current_value; utils::AtomicMemoryBlock atomic_memory_block{ @@ -160,7 +160,7 @@ Result<bool> EdgeAccessor::InitProperties(const std::map<storage::PropertyId, st if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; - if (edge_.ptr->deleted) return Error::DELETED_OBJECT; + if (edge_.ptr->deleted()) return Error::DELETED_OBJECT; if (!edge_.ptr->properties.InitProperties(properties)) return false; utils::AtomicMemoryBlock atomic_memory_block{[&properties, transaction_ = transaction_, edge_ = edge_]() { @@ -182,7 +182,7 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> EdgeAc if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; - if (edge_.ptr->deleted) return Error::DELETED_OBJECT; + if (edge_.ptr->deleted()) return Error::DELETED_OBJECT; using ReturnType = decltype(edge_.ptr->properties.UpdateProperties(properties)); std::optional<ReturnType> id_old_new_change; @@ -205,7 +205,7 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::ClearProperties() { if (!PrepareForWrite(transaction_, edge_.ptr)) return Error::SERIALIZATION_ERROR; - if (edge_.ptr->deleted) return Error::DELETED_OBJECT; + if (edge_.ptr->deleted()) return Error::DELETED_OBJECT; using ReturnType = decltype(edge_.ptr->properties.Properties()); std::optional<ReturnType> properties; @@ -230,9 +230,9 @@ Result<PropertyValue> EdgeAccessor::GetProperty(PropertyId property, View view) Delta *delta = nullptr; { auto guard = std::shared_lock{edge_.ptr->lock}; - deleted = edge_.ptr->deleted; + deleted = edge_.ptr->deleted(); value.emplace(edge_.ptr->properties.GetProperty(property)); - delta = edge_.ptr->delta; + delta = edge_.ptr->delta(); } ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &value, property](const Delta &delta) { switch (delta.action) { @@ -269,7 +269,7 @@ Result<uint64_t> EdgeAccessor::GetPropertySize(PropertyId property, View view) c if (!storage_->config_.salient.items.properties_on_edges) return 0; auto guard = std::shared_lock{edge_.ptr->lock}; - Delta *delta = edge_.ptr->delta; + Delta *delta = edge_.ptr->delta(); if (!delta) { return edge_.ptr->properties.PropertySize(property); } @@ -294,9 +294,9 @@ Result<std::map<PropertyId, PropertyValue>> EdgeAccessor::Properties(View view) Delta *delta = nullptr; { auto guard = std::shared_lock{edge_.ptr->lock}; - deleted = edge_.ptr->deleted; + deleted = edge_.ptr->deleted(); properties = edge_.ptr->properties.Properties(); - delta = edge_.ptr->delta; + delta = edge_.ptr->delta(); } ApplyDeltasForRead(transaction_, delta, view, [&exists, &deleted, &properties](const Delta &delta) { switch (delta.action) { diff --git a/src/storage/v2/indices/indices_utils.hpp b/src/storage/v2/indices/indices_utils.hpp index 52938a1db..180279c9b 100644 --- a/src/storage/v2/indices/indices_utils.hpp +++ b/src/storage/v2/indices/indices_utils.hpp @@ -60,8 +60,8 @@ inline bool AnyVersionHasLabel(const Vertex &vertex, LabelId label, uint64_t tim { auto guard = std::shared_lock{vertex.lock}; has_label = utils::Contains(vertex.labels, label); - deleted = vertex.deleted; - delta = vertex.delta; + deleted = vertex.deleted(); + delta = vertex.delta(); } if (!deleted && has_label) { return true; @@ -116,8 +116,8 @@ inline bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, Prop bool current_value_equal_to_value; { auto guard = std::shared_lock{vertex.lock}; - delta = vertex.delta; - deleted = vertex.deleted; + delta = vertex.delta(); + deleted = vertex.deleted(); has_label = utils::Contains(vertex.labels, label); // Avoid IsPropertyEqual if already not possible if (delta == nullptr && (deleted || !has_label)) return false; @@ -184,10 +184,10 @@ inline bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, const Delta *delta = nullptr; { auto guard = std::shared_lock{vertex.lock}; - deleted = vertex.deleted; + deleted = vertex.deleted(); has_label = utils::Contains(vertex.labels, label); current_value_equal_to_value = vertex.properties.IsPropertyEqual(key, value); - delta = vertex.delta; + delta = vertex.delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -233,7 +233,7 @@ inline bool CurrentVersionHasLabelProperty(const Vertex &vertex, LabelId label, template <typename TIndexAccessor> inline void TryInsertLabelIndex(Vertex &vertex, LabelId label, TIndexAccessor &index_accessor) { - if (vertex.deleted || !utils::Contains(vertex.labels, label)) { + if (vertex.deleted() || !utils::Contains(vertex.labels, label)) { return; } @@ -243,7 +243,7 @@ inline void TryInsertLabelIndex(Vertex &vertex, LabelId label, TIndexAccessor &i template <typename TIndexAccessor> inline void TryInsertLabelPropertyIndex(Vertex &vertex, std::pair<LabelId, PropertyId> label_property_pair, TIndexAccessor &index_accessor) { - if (vertex.deleted || !utils::Contains(vertex.labels, label_property_pair.first)) { + if (vertex.deleted() || !utils::Contains(vertex.labels, label_property_pair.first)) { return; } auto value = vertex.properties.GetProperty(label_property_pair.second); diff --git a/src/storage/v2/inmemory/storage.cpp b/src/storage/v2/inmemory/storage.cpp index 0c7bde1a0..e79bdea69 100644 --- a/src/storage/v2/inmemory/storage.cpp +++ b/src/storage/v2/inmemory/storage.cpp @@ -315,11 +315,11 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdge(VertexAccesso } if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; + if (from_vertex->deleted()) return Error::DELETED_OBJECT; if (to_vertex != from_vertex) { if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; + if (to_vertex->deleted()) return Error::DELETED_OBJECT; } if (storage_->config_.salient.items.enable_schema_metadata) { @@ -404,11 +404,11 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::CreateEdgeEx(VertexAcces } if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - if (from_vertex->deleted) return Error::DELETED_OBJECT; + if (from_vertex->deleted()) return Error::DELETED_OBJECT; if (to_vertex != from_vertex) { if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - if (to_vertex->deleted) return Error::DELETED_OBJECT; + if (to_vertex->deleted()) return Error::DELETED_OBJECT; } if (storage_->config_.salient.items.enable_schema_metadata) { @@ -482,7 +482,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetFrom(EdgeAccessor if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; - if (edge_ptr->deleted) return Error::DELETED_OBJECT; + if (edge_ptr->deleted()) return Error::DELETED_OBJECT; } std::unique_lock<utils::RWSpinLock> guard_old_from(old_from_vertex->lock, std::defer_lock); @@ -508,14 +508,14 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetFrom(EdgeAccessor } if (!PrepareForWrite(&transaction_, old_from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!old_from_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!old_from_vertex->deleted(), "Invalid database state!"); if (!PrepareForWrite(&transaction_, new_from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!new_from_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!new_from_vertex->deleted(), "Invalid database state!"); if (to_vertex != old_from_vertex && to_vertex != new_from_vertex) { if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!to_vertex->deleted(), "Invalid database state!"); } auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { @@ -587,7 +587,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor * if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; - if (edge_ptr->deleted) return Error::DELETED_OBJECT; + if (edge_ptr->deleted()) return Error::DELETED_OBJECT; } std::unique_lock<utils::RWSpinLock> guard_from(from_vertex->lock, std::defer_lock); @@ -613,14 +613,14 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeSetTo(EdgeAccessor * } if (!PrepareForWrite(&transaction_, old_to_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!old_to_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!old_to_vertex->deleted(), "Invalid database state!"); if (!PrepareForWrite(&transaction_, new_to_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!new_to_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!new_to_vertex->deleted(), "Invalid database state!"); if (from_vertex != old_to_vertex && from_vertex != new_to_vertex) { if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!from_vertex->deleted(), "Invalid database state!"); } auto delete_edge_from_storage = [&edge_type, &edge_ref, this](auto *vertex, auto *edges) { @@ -684,7 +684,7 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAcces guard = std::unique_lock{edge_ptr->lock}; if (!PrepareForWrite(&transaction_, edge_ptr)) return Error::SERIALIZATION_ERROR; - if (edge_ptr->deleted) return Error::DELETED_OBJECT; + if (edge_ptr->deleted()) return Error::DELETED_OBJECT; } auto *from_vertex = edge->from_vertex_; @@ -705,10 +705,10 @@ Result<EdgeAccessor> InMemoryStorage::InMemoryAccessor::EdgeChangeType(EdgeAcces } if (!PrepareForWrite(&transaction_, from_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!from_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!from_vertex->deleted(), "Invalid database state!"); if (!PrepareForWrite(&transaction_, to_vertex)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!to_vertex->deleted, "Invalid database state!"); + MG_ASSERT(!to_vertex->deleted(), "Invalid database state!"); auto change_edge_type_in_storage = [&edge_type, &edge_ref, &new_edge_type, this](auto *vertex, auto *edges) { std::tuple<EdgeTypeId, Vertex *, EdgeRef> link(edge_type, vertex, edge_ref); @@ -901,8 +901,8 @@ void InMemoryStorage::InMemoryAccessor::FastDiscardOfDeltas(uint64_t oldest_acti case PreviousPtr::Type::VERTEX: { // safe because no other txn can be reading this while we have engine lock auto &vertex = *prev.vertex; - vertex.delta = nullptr; - if (vertex.deleted) { + vertex.delta(nullptr); + if (vertex.deleted()) { DMG_ASSERT(delta.action == Delta::Action::RECREATE_OBJECT); current_deleted_vertices.push_back(vertex.gid); } @@ -911,8 +911,8 @@ void InMemoryStorage::InMemoryAccessor::FastDiscardOfDeltas(uint64_t oldest_acti case PreviousPtr::Type::EDGE: { // safe because no other txn can be reading this while we have engine lock auto &edge = *prev.edge; - edge.delta = nullptr; - if (edge.deleted) { + edge.delta(nullptr); + if (edge.deleted()) { DMG_ASSERT(delta.action == Delta::Action::RECREATE_OBJECT); current_deleted_edges.push_back(edge.gid); } @@ -1006,7 +1006,7 @@ void InMemoryStorage::InMemoryAccessor::Abort() { case PreviousPtr::Type::VERTEX: { auto *vertex = prev.vertex; auto guard = std::unique_lock{vertex->lock}; - Delta *current = vertex->delta; + Delta *current = vertex->delta(); while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { switch (current->action) { @@ -1103,18 +1103,18 @@ void InMemoryStorage::InMemoryAccessor::Abort() { } case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { - vertex->deleted = true; + vertex->deleted(true); my_deleted_vertices.push_back(vertex->gid); break; } case Delta::Action::RECREATE_OBJECT: { - vertex->deleted = false; + vertex->deleted(false); break; } } current = current->next.load(std::memory_order_acquire); } - vertex->delta = current; + vertex->delta(current); if (current != nullptr) { current->prev.Set(vertex); } @@ -1124,7 +1124,7 @@ void InMemoryStorage::InMemoryAccessor::Abort() { case PreviousPtr::Type::EDGE: { auto *edge = prev.edge; auto guard = std::lock_guard{edge->lock}; - Delta *current = edge->delta; + Delta *current = edge->delta(); while (current != nullptr && current->timestamp->load(std::memory_order_acquire) == transaction_.transaction_id) { switch (current->action) { @@ -1134,12 +1134,12 @@ void InMemoryStorage::InMemoryAccessor::Abort() { } case Delta::Action::DELETE_DESERIALIZED_OBJECT: case Delta::Action::DELETE_OBJECT: { - edge->deleted = true; + edge->deleted(true); my_deleted_edges.push_back(edge->gid); break; } case Delta::Action::RECREATE_OBJECT: { - edge->deleted = false; + edge->deleted(false); break; } case Delta::Action::REMOVE_LABEL: @@ -1154,7 +1154,7 @@ void InMemoryStorage::InMemoryAccessor::Abort() { } current = current->next.load(std::memory_order_acquire); } - edge->delta = current; + edge->delta(current); if (current != nullptr) { current->prev.Set(edge); } @@ -1572,13 +1572,13 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_ case PreviousPtr::Type::VERTEX: { Vertex *vertex = prev.vertex; auto vertex_guard = std::unique_lock{vertex->lock}; - if (vertex->delta != &delta) { + if (vertex->delta() != &delta) { // Something changed, we're not the first delta in the chain // anymore. continue; } - vertex->delta = nullptr; - if (vertex->deleted) { + vertex->delta(nullptr); + if (vertex->deleted()) { DMG_ASSERT(delta.action == memgraph::storage::Delta::Action::RECREATE_OBJECT); current_deleted_vertices.push_back(vertex->gid); } @@ -1587,13 +1587,13 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_ case PreviousPtr::Type::EDGE: { Edge *edge = prev.edge; auto edge_guard = std::unique_lock{edge->lock}; - if (edge->delta != &delta) { + if (edge->delta() != &delta) { // Something changed, we're not the first delta in the chain // anymore. continue; } - edge->delta = nullptr; - if (edge->deleted) { + edge->delta(nullptr); + if (edge->deleted()) { DMG_ASSERT(delta.action == memgraph::storage::Delta::Action::RECREATE_OBJECT); current_deleted_edges.push_back(edge->gid); } @@ -1729,14 +1729,14 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_ // EXPENSIVE full scan, is only run if an IN_MEMORY_ANALYTICAL transaction involved any deletions // TODO: implement a fast internal iteration inside the skip_list (to avoid unnecessary find_node calls), - // accessor.remove_if([](auto const & item){ return item.delta == nullptr && item.deleted;}); + // accessor.remove_if([](auto const & item){ return item.delta == nullptr && item.deleted();}); // alternatively, an auxiliary data structure within skip_list to track these, hence a full scan wouldn't be needed // we will wait for evidence that this is needed before doing so. if (need_full_scan_vertices) { auto vertex_acc = vertices_.access(); for (auto &vertex : vertex_acc) { // a deleted vertex which as no deltas must have come from IN_MEMORY_ANALYTICAL deletion - if (vertex.delta == nullptr && vertex.deleted) { + if (vertex.delta() == nullptr && vertex.deleted()) { vertex_acc.remove(vertex); } } @@ -1747,7 +1747,7 @@ void InMemoryStorage::CollectGarbage(std::unique_lock<utils::ResourceLock> main_ auto edge_acc = edges_.access(); for (auto &edge : edge_acc) { // a deleted edge which as no deltas must have come from IN_MEMORY_ANALYTICAL deletion - if (edge.delta == nullptr && edge.deleted) { + if (edge.delta() == nullptr && edge.deleted()) { edge_acc.remove(edge); } } diff --git a/src/storage/v2/inmemory/unique_constraints.cpp b/src/storage/v2/inmemory/unique_constraints.cpp index e08965eab..ea2e41da4 100644 --- a/src/storage/v2/inmemory/unique_constraints.cpp +++ b/src/storage/v2/inmemory/unique_constraints.cpp @@ -58,8 +58,8 @@ bool LastCommittedVersionHasLabelProperty(const Vertex &vertex, LabelId label, c bool has_label; { auto guard = std::shared_lock{vertex.lock}; - delta = vertex.delta; - deleted = vertex.deleted; + delta = vertex.delta(); + deleted = vertex.deleted(); has_label = utils::Contains(vertex.labels, label); size_t i = 0; @@ -145,8 +145,8 @@ bool AnyVersionHasLabelProperty(const Vertex &vertex, LabelId label, const std:: { auto guard = std::shared_lock{vertex.lock}; has_label = utils::Contains(vertex.labels, label); - deleted = vertex.deleted; - delta = vertex.delta; + deleted = vertex.deleted(); + delta = vertex.delta(); // Avoid IsPropertyEqual if already not possible if (delta == nullptr && (deleted || !has_label)) return false; @@ -331,7 +331,7 @@ bool InMemoryUniqueConstraints::SingleThreadConstraintValidation::operator()( std::optional<ConstraintViolation> InMemoryUniqueConstraints::DoValidate( const Vertex &vertex, utils::SkipList<Entry>::Accessor &constraint_accessor, const LabelId &label, const std::set<PropertyId> &properties) { - if (vertex.deleted || !utils::Contains(vertex.labels, label)) { + if (vertex.deleted() || !utils::Contains(vertex.labels, label)) { return std::nullopt; } auto values = vertex.properties.ExtractPropertyValues(properties); @@ -440,7 +440,7 @@ bool InMemoryUniqueConstraints::ConstraintExists(LabelId label, const std::set<P std::optional<ConstraintViolation> InMemoryUniqueConstraints::Validate(const Vertex &vertex, const Transaction &tx, uint64_t commit_timestamp) const { - if (vertex.deleted) { + if (vertex.deleted()) { return std::nullopt; } diff --git a/src/storage/v2/mvcc.hpp b/src/storage/v2/mvcc.hpp index 1cf057d9d..4e98d8bb1 100644 --- a/src/storage/v2/mvcc.hpp +++ b/src/storage/v2/mvcc.hpp @@ -94,8 +94,8 @@ inline std::size_t ApplyDeltasForRead(Transaction const *transaction, const Delt /// proceed with a write operation. template <typename TObj> inline bool PrepareForWrite(Transaction *transaction, TObj *object) { - if (object->delta == nullptr) return true; - auto ts = object->delta->timestamp->load(std::memory_order_acquire); + if (object->delta() == nullptr) return true; + auto ts = object->delta()->timestamp->load(std::memory_order_acquire); if (ts == transaction->transaction_id || ts < transaction->start_timestamp) { return true; } @@ -174,21 +174,21 @@ inline void CreateAndLinkDelta(Transaction *transaction, TObj *object, Args &&.. // concurrently (as well as other execution threads). // 1. We need to set the next delta of the new delta to the existing delta. - delta->next.store(object->delta, std::memory_order_release); + delta->next.store(object->delta(), std::memory_order_release); // 2. We need to set the previous delta of the new delta to the object. delta->prev.Set(object); // 3. We need to set the previous delta of the existing delta to the new // delta. After this point the garbage collector will be able to see the new // delta but won't modify it until we are done with all of our modifications. - if (object->delta) { - object->delta->prev.Set(delta); + if (object->delta()) { + object->delta()->prev.Set(delta); } // 4. Finally, we need to set the object's delta to the new delta. The garbage // collector and other transactions will acquire the object lock to read the // delta from the object. Because the lock is held during the whole time this // modification is being done, everybody else will wait until we are fully // done with our modification before they read the object's delta value. - object->delta = delta; + object->delta(delta); } } // namespace memgraph::storage diff --git a/src/storage/v2/property_store.cpp b/src/storage/v2/property_store.cpp index e6e4dbbaf..ff39d1ed5 100644 --- a/src/storage/v2/property_store.cpp +++ b/src/storage/v2/property_store.cpp @@ -78,7 +78,7 @@ namespace { // ++ -> size of property ID (2 bits) // ++ -> size of payload OR size of payload size indicator (2 bits) // -// When encoding integers (`int64_t` and `uint64_t`) they are compressed so that +// When encoding integers (`int64_t` and `uint32_t`) they are compressed so that // they are stored into 1, 2, 4 or 8 bytes depending on their value. // // The size of the metadata field is very important because it is encoded with @@ -93,7 +93,7 @@ enum class Size : uint8_t { INT64 = 0x03, }; -uint64_t SizeToByteSize(Size size) { +uint32_t SizeToByteSize(Size size) { switch (size) { case Size::INT8: return 1; @@ -143,13 +143,13 @@ const uint8_t kShiftIdSize = 2; // - encoded value // * STRING // - type; payload size is used to indicate whether the string size is -// encoded as `uint8_t`, `uint16_t`, `uint32_t` or `uint64_t` +// encoded as `uint8_t`, `uint16_t`, `uint32_t` or `uint32_t` // - encoded property ID // - encoded string size // - string data // * LIST // - type; payload size is used to indicate whether the list size is encoded -// as `uint8_t`, `uint16_t`, `uint32_t` or `uint64_t` +// as `uint8_t`, `uint16_t`, `uint32_t` or `uint32_t` // - encoded property ID // - encoded list size // - list items @@ -159,12 +159,12 @@ const uint8_t kShiftIdSize = 2; // + encoded item data // * MAP // - type; payload size is used to indicate whether the map size is encoded -// as `uint8_t`, `uint16_t`, `uint32_t` or `uint64_t` +// as `uint8_t`, `uint16_t`, `uint32_t` or `uint32_t` // - encoded property ID // - encoded map size // - map items // + type; id size is used to indicate whether the key size is encoded as -// `uint8_t`, `uint16_t`, `uint32_t` or `uint64_t`; payload size is used +// `uint8_t`, `uint16_t`, `uint32_t` or `uint32_t`; payload size is used // as described above for the inner payload type // + encoded key size // + encoded key data @@ -175,9 +175,9 @@ const uint8_t kShiftIdSize = 2; // - encoded property ID // - value saved as Metadata // + type; id size is used to indicate whether the temporal data type is encoded -// as `uint8_t`, `uint16_t`, `uint32_t` or `uint64_t`; payload size used to +// as `uint8_t`, `uint16_t`, `uint32_t` or `uint32_t`; payload size used to // indicate whether the microseconds are encoded as `uint8_t`, `uint16_t, `uint32_t -// or `uint64_t` +// or `uint32_t` // + encoded temporal data type value // + encoded microseconds value @@ -210,7 +210,7 @@ class Writer { Writer() = default; - Writer(uint8_t *data, uint64_t size) : data_(data), size_(size) {} + Writer(uint8_t *data, uint32_t size) : data_(data), size_(size) {} std::optional<MetadataHandle> WriteMetadata() { if (data_ && pos_ + 1 > size_) return std::nullopt; @@ -250,19 +250,19 @@ class Writer { std::optional<Size> WriteDouble(double value) { return WriteUint(utils::MemcpyCast<uint64_t>(value)); } - bool WriteBytes(const uint8_t *data, uint64_t size) { + bool WriteBytes(const uint8_t *data, uint32_t size) { if (data_ && pos_ + size > size_) return false; if (data_) memcpy(data_ + pos_, data, size); pos_ += size; return true; } - bool WriteBytes(const char *data, uint64_t size) { + bool WriteBytes(const char *data, uint32_t size) { static_assert(std::is_same_v<uint8_t, unsigned char>); return WriteBytes(reinterpret_cast<const uint8_t *>(data), size); } - uint64_t Written() const { return pos_; } + uint32_t Written() const { return pos_; } private: template <typename T, typename V> @@ -279,14 +279,14 @@ class Writer { } uint8_t *data_{nullptr}; - uint64_t size_{0}; - uint64_t pos_{0}; + uint32_t size_{0}; + uint32_t pos_{0}; }; // Helper class used to read data from the binary stream. class Reader { public: - Reader(const uint8_t *data, uint64_t size) : data_(data), size_(size), pos_(0) {} + Reader(const uint8_t *data, uint32_t size) : data_(data), size_(size), pos_(0) {} std::optional<Metadata> ReadMetadata() { if (pos_ + 1 > size_) return std::nullopt; @@ -366,33 +366,33 @@ class Reader { return utils::MemcpyCast<double>(*value); } - bool ReadBytes(uint8_t *data, uint64_t size) { + bool ReadBytes(uint8_t *data, uint32_t size) { if (pos_ + size > size_) return false; memcpy(data, data_ + pos_, size); pos_ += size; return true; } - bool ReadBytes(char *data, uint64_t size) { return ReadBytes(reinterpret_cast<uint8_t *>(data), size); } + bool ReadBytes(char *data, uint32_t size) { return ReadBytes(reinterpret_cast<uint8_t *>(data), size); } - bool VerifyBytes(const uint8_t *data, uint64_t size) { + bool VerifyBytes(const uint8_t *data, uint32_t size) { if (pos_ + size > size_) return false; if (memcmp(data, data_ + pos_, size) != 0) return false; pos_ += size; return true; } - bool VerifyBytes(const char *data, uint64_t size) { + bool VerifyBytes(const char *data, uint32_t size) { return VerifyBytes(reinterpret_cast<const uint8_t *>(data), size); } - bool SkipBytes(uint64_t size) { + bool SkipBytes(uint32_t size) { if (pos_ + size > size_) return false; pos_ += size; return true; } - uint64_t GetPosition() const { return pos_; } + uint32_t GetPosition() const { return pos_; } private: template <typename T> @@ -405,8 +405,8 @@ class Reader { } const uint8_t *data_; - uint64_t size_; - uint64_t pos_; + uint32_t size_; + uint32_t pos_; }; // Function used to encode a PropertyValue into a byte stream. @@ -499,8 +499,8 @@ std::optional<TemporalData> DecodeTemporalData(Reader &reader) { return TemporalData{static_cast<TemporalType>(*type_value), *microseconds_value}; } -std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { - uint64_t temporal_data_size = 0; +std::optional<uint32_t> DecodeTemporalDataSize(Reader &reader) { + uint32_t temporal_data_size = 0; auto metadata = reader.ReadMetadata(); if (!metadata || metadata->type != Type::TEMPORAL_DATA) return std::nullopt; @@ -567,7 +567,7 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { if (!size) return false; std::vector<PropertyValue> list; list.reserve(*size); - for (uint64_t i = 0; i < *size; ++i) { + for (uint32_t i = 0; i < *size; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; PropertyValue item; @@ -581,7 +581,7 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { auto size = reader->ReadUint(payload_size); if (!size) return false; std::map<std::string, PropertyValue> map; - for (uint64_t i = 0; i < *size; ++i) { + for (uint32_t i = 0; i < *size; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; auto key_size = reader->ReadUint(metadata->id_size); @@ -606,7 +606,7 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { } } -[[nodiscard]] bool DecodePropertyValueSize(Reader *reader, Type type, Size payload_size, uint64_t &property_size) { +[[nodiscard]] bool DecodePropertyValueSize(Reader *reader, Type type, Size payload_size, uint32_t &property_size) { switch (type) { case Type::EMPTY: { return false; @@ -640,9 +640,9 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { auto size = reader->ReadUint(payload_size); if (!size) return false; - uint64_t list_property_size = SizeToByteSize(payload_size); + uint32_t list_property_size = SizeToByteSize(payload_size); - for (uint64_t i = 0; i < *size; ++i) { + for (uint32_t i = 0; i < *size; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; @@ -657,9 +657,9 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { auto size = reader->ReadUint(payload_size); if (!size) return false; - uint64_t map_property_size = SizeToByteSize(payload_size); + uint32_t map_property_size = SizeToByteSize(payload_size); - for (uint64_t i = 0; i < *size; ++i) { + for (uint32_t i = 0; i < *size; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; @@ -720,7 +720,7 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { auto const size = reader->ReadUint(payload_size); if (!size) return false; auto size_val = *size; - for (uint64_t i = 0; i != size_val; ++i) { + for (uint32_t i = 0; i != size_val; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; if (!SkipPropertyValue(reader, metadata->type, metadata->payload_size)) return false; @@ -731,7 +731,7 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { auto const size = reader->ReadUint(payload_size); if (!size) return false; auto size_val = *size; - for (uint64_t i = 0; i != size_val; ++i) { + for (uint32_t i = 0; i != size_val; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; auto key_size = reader->ReadUint(metadata->id_size); @@ -811,7 +811,7 @@ std::optional<uint64_t> DecodeTemporalDataSize(Reader &reader) { auto size = reader->ReadUint(payload_size); if (!size) return false; if (*size != list.size()) return false; - for (uint64_t i = 0; i < *size; ++i) { + for (uint32_t i = 0; i < *size; ++i) { auto metadata = reader->ReadMetadata(); if (!metadata) return false; if (!ComparePropertyValue(reader, metadata->type, metadata->payload_size, list[i])) return false; @@ -909,7 +909,7 @@ enum class ExpectedPropertyStatus { } [[nodiscard]] ExpectedPropertyStatus DecodeExpectedPropertySize(Reader *reader, PropertyId expected_property, - uint64_t &size) { + uint32_t &size) { auto metadata = reader->ReadMetadata(); if (!metadata) return ExpectedPropertyStatus::MISSING_DATA; @@ -1016,7 +1016,7 @@ enum class ExpectedPropertyStatus { } } -[[nodiscard]] ExpectedPropertyStatus FindSpecificPropertySize(Reader *reader, PropertyId property, uint64_t &size) { +[[nodiscard]] ExpectedPropertyStatus FindSpecificPropertySize(Reader *reader, PropertyId property, uint32_t &size) { ExpectedPropertyStatus ret = ExpectedPropertyStatus::SMALLER; while ((ret = DecodeExpectedPropertySize(reader, property, size)) == ExpectedPropertyStatus::SMALLER) { } @@ -1043,12 +1043,12 @@ enum class ExpectedPropertyStatus { // Struct used to return info about the property position and buffer size. struct SpecificPropertyAndBufferInfo { - uint64_t property_begin; - uint64_t property_end; - uint64_t property_size; - uint64_t all_begin; - uint64_t all_end; - uint64_t all_size; + uint32_t property_begin; + uint32_t property_end; + uint32_t property_size; + uint32_t all_begin; + uint32_t all_end; + uint32_t all_size; }; // Function used to find the position where the property should be in the data @@ -1063,10 +1063,10 @@ struct SpecificPropertyAndBufferInfo { // // @sa FindSpecificProperty SpecificPropertyAndBufferInfo FindSpecificPropertyAndBufferInfo(Reader *reader, PropertyId property) { - uint64_t property_begin = reader->GetPosition(); - uint64_t property_end = reader->GetPosition(); - uint64_t all_begin = reader->GetPosition(); - uint64_t all_end = reader->GetPosition(); + uint32_t property_begin = reader->GetPosition(); + uint32_t property_end = reader->GetPosition(); + uint32_t all_begin = reader->GetPosition(); + uint32_t all_end = reader->GetPosition(); while (true) { auto ret = HasExpectedProperty(reader, property); if (ret == ExpectedPropertyStatus::MISSING_DATA) { @@ -1084,8 +1084,8 @@ SpecificPropertyAndBufferInfo FindSpecificPropertyAndBufferInfo(Reader *reader, } // All data buffers will be allocated to a power of 8 size. -uint64_t ToPowerOf8(uint64_t size) { - uint64_t mod = size % 8; +uint32_t ToPowerOf8(uint32_t size) { + uint32_t mod = size % 8; if (mod == 0) return size; return size - mod + 8; } @@ -1123,23 +1123,23 @@ const uint8_t kUseLocalBuffer = 0x01; // Helper functions used to retrieve/store `size` and `data` from/into the // `buffer_`. -std::pair<uint64_t, uint8_t *> GetSizeData(const uint8_t *buffer) { - uint64_t size; +std::pair<uint32_t, uint8_t *> GetSizeData(const uint8_t *buffer) { + uint32_t size; uint8_t *data; - memcpy(&size, buffer, sizeof(uint64_t)); - memcpy(&data, buffer + sizeof(uint64_t), sizeof(uint8_t *)); + memcpy(&size, buffer, sizeof(uint32_t)); + memcpy(&data, buffer + sizeof(uint32_t), sizeof(uint8_t *)); return {size, data}; } struct BufferInfo { - uint64_t size; + uint32_t size; uint8_t *data{nullptr}; bool in_local_buffer; }; template <size_t N> BufferInfo GetBufferInfo(const uint8_t (&buffer)[N]) { - uint64_t size = 0; + uint32_t size = 0; const uint8_t *data = nullptr; bool in_local_buffer = false; std::tie(size, data) = GetSizeData(buffer); @@ -1156,9 +1156,9 @@ BufferInfo GetBufferInfo(const uint8_t (&buffer)[N]) { return {size, non_const_data, in_local_buffer}; } -void SetSizeData(uint8_t *buffer, uint64_t size, uint8_t *data) { - memcpy(buffer, &size, sizeof(uint64_t)); - memcpy(buffer + sizeof(uint64_t), &data, sizeof(uint8_t *)); +void SetSizeData(uint8_t *buffer, uint32_t size, uint8_t *data) { + memcpy(buffer, &size, sizeof(uint32_t)); + memcpy(buffer + sizeof(uint32_t), &data, sizeof(uint8_t *)); } } // namespace @@ -1171,7 +1171,7 @@ PropertyStore::PropertyStore(PropertyStore &&other) noexcept { } PropertyStore &PropertyStore::operator=(PropertyStore &&other) noexcept { - uint64_t size; + uint32_t size; uint8_t *data; std::tie(size, data) = GetSizeData(buffer_); if (size % 8 == 0) { @@ -1186,7 +1186,7 @@ PropertyStore &PropertyStore::operator=(PropertyStore &&other) noexcept { } PropertyStore::~PropertyStore() { - uint64_t size; + uint32_t size; uint8_t *data; std::tie(size, data) = GetSizeData(buffer_); if (size % 8 == 0) { @@ -1204,11 +1204,11 @@ PropertyValue PropertyStore::GetProperty(PropertyId property) const { return value; } -uint64_t PropertyStore::PropertySize(PropertyId property) const { +uint32_t PropertyStore::PropertySize(PropertyId property) const { auto data_size_localbuffer = GetBufferInfo(buffer_); Reader reader(data_size_localbuffer.data, data_size_localbuffer.size); - uint64_t property_size = 0; + uint32_t property_size = 0; if (FindSpecificPropertySize(&reader, property, property_size) != ExpectedPropertyStatus::EQUAL) return 0; return property_size; } @@ -1276,7 +1276,7 @@ std::map<PropertyId, PropertyValue> PropertyStore::Properties() const { } bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value) { - uint64_t property_size = 0; + uint32_t property_size = 0; if (!value.IsNull()) { Writer writer; EncodeProperty(&writer, property, value); @@ -1284,7 +1284,7 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value) } bool in_local_buffer = false; - uint64_t size; + uint32_t size; uint8_t *data; std::tie(size, data) = GetSizeData(buffer_); if (size % 8 != 0) { @@ -1346,7 +1346,7 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value) // We need to enlarge/shrink the buffer. bool current_in_local_buffer = false; uint8_t *current_data = nullptr; - uint64_t current_size = 0; + uint32_t current_size = 0; if (new_size <= sizeof(buffer_) - 1) { // Use the local buffer. buffer_[0] = kUseLocalBuffer; @@ -1400,14 +1400,14 @@ bool PropertyStore::SetProperty(PropertyId property, const PropertyValue &value) template <typename TContainer> bool PropertyStore::DoInitProperties(const TContainer &properties) { - uint64_t size = 0; + uint32_t size = 0; uint8_t *data = nullptr; std::tie(size, data) = GetSizeData(buffer_); if (size != 0) { return false; } - uint64_t property_size = 0; + uint32_t property_size = 0; { Writer writer; for (const auto &[property, value] : properties) { @@ -1521,7 +1521,7 @@ void PropertyStore::SetBuffer(const std::string_view buffer) { return; } - uint64_t size = 0; + uint32_t size = 0; uint8_t *data = nullptr; size = buffer.size(); if (buffer.size() == sizeof(buffer_) - 1) { // use local buffer diff --git a/src/storage/v2/property_store.hpp b/src/storage/v2/property_store.hpp index eee83f5df..eb6b90015 100644 --- a/src/storage/v2/property_store.hpp +++ b/src/storage/v2/property_store.hpp @@ -48,7 +48,7 @@ class PropertyStore { /// Returns the size of the encoded property in bytes. /// Returns 0 if the property does not exist. /// The time complexity of this function is O(n). - uint64_t PropertySize(PropertyId property) const; + uint32_t PropertySize(PropertyId property) const; /// Checks whether the property `property` exists in the store. The time /// complexity of this function is O(n). @@ -119,7 +119,8 @@ class PropertyStore { template <typename TContainer> bool DoInitProperties(const TContainer &properties); - uint8_t buffer_[sizeof(uint64_t) + sizeof(uint8_t *)]; + // uint8_t buffer_[sizeof(uint64_t) + sizeof(uint8_t *)]; + uint8_t buffer_[sizeof(uint32_t) + sizeof(uint8_t *)]; }; } // namespace memgraph::storage diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp index 536a504a0..ce5356ccf 100644 --- a/src/storage/v2/storage.cpp +++ b/src/storage/v2/storage.cpp @@ -293,7 +293,7 @@ Result<std::optional<std::unordered_set<Vertex *>>> Storage::Accessor::PrepareDe if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - if (vertex_ptr->deleted) { + if (vertex_ptr->deleted()) { continue; } } @@ -328,8 +328,8 @@ EdgeInfoForDeletion Storage::Accessor::PrepareDeletableEdges(const std::unordere // add nodes which need to be detached on the other end of the edge if (detach) { for (auto *vertex_ptr : vertices) { - std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; - std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; + TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; + TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; { auto vertex_lock = std::shared_lock{vertex_ptr->lock}; @@ -348,7 +348,7 @@ EdgeInfoForDeletion Storage::Accessor::PrepareDeletableEdges(const std::unordere // also add edges which we want to delete from the query for (const auto &edge_accessor : edges) { - if (edge_accessor->from_vertex_->deleted || edge_accessor->to_vertex_->deleted) { + if (edge_accessor->from_vertex_->deleted() || edge_accessor->to_vertex_->deleted()) { continue; } partial_src_vertices.insert(edge_accessor->from_vertex_); @@ -390,7 +390,7 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::ClearEdgesOn } if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); + MG_ASSERT(!vertex_ptr->deleted(), "Invalid database state!"); // MarkEdgeAsDeleted allocates additional memory // and CreateAndLinkDelta needs memory @@ -449,7 +449,7 @@ Result<std::optional<std::vector<EdgeAccessor>>> Storage::Accessor::DetachRemain auto vertex_lock = std::unique_lock{vertex_ptr->lock}; if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); + MG_ASSERT(!vertex_ptr->deleted(), "Invalid database state!"); auto mid = std::partition( edges_attached_to_vertex->begin(), edges_attached_to_vertex->end(), [this, &set_for_erasure](auto &edge) { @@ -520,14 +520,14 @@ Result<std::vector<VertexAccessor>> Storage::Accessor::TryDeleteVertices(const s if (!PrepareForWrite(&transaction_, vertex_ptr)) return Error::SERIALIZATION_ERROR; - MG_ASSERT(!vertex_ptr->deleted, "Invalid database state!"); + MG_ASSERT(!vertex_ptr->deleted(), "Invalid database state!"); if (!vertex_ptr->in_edges.empty() || !vertex_ptr->out_edges.empty()) { return Error::VERTEX_HAS_EDGES; } CreateAndLinkDelta(&transaction_, vertex_ptr, Delta::RecreateObjectTag()); - vertex_ptr->deleted = true; + vertex_ptr->deleted(true); deleted_vertices.emplace_back(vertex_ptr, storage_, &transaction_, true); } @@ -536,9 +536,9 @@ Result<std::vector<VertexAccessor>> Storage::Accessor::TryDeleteVertices(const s } void Storage::Accessor::MarkEdgeAsDeleted(Edge *edge) { - if (!edge->deleted) { + if (!edge->deleted()) { CreateAndLinkDelta(&transaction_, edge, Delta::RecreateObjectTag()); - edge->deleted = true; + edge->deleted(true); storage_->edge_count_.fetch_sub(1, std::memory_order_acq_rel); } } diff --git a/src/storage/v2/tco_delta.hpp b/src/storage/v2/tco_delta.hpp new file mode 100644 index 000000000..e5971d870 --- /dev/null +++ b/src/storage/v2/tco_delta.hpp @@ -0,0 +1,30 @@ +// Copyright 2024 Memgraph Ltd. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source +// License, and you may not use this file except in compliance with the Business Source License. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +#pragma once + +#include "storage/v2/delta.hpp" + +namespace memgraph::storage { + +struct TcoDelta { + TcoDelta(Delta *in) : ptr_{in} { deleted(false); } + + bool deleted() const { return (uint64_t)ptr_ & 0x01; } + void deleted(bool in) { ptr_ = delta() + in; } + Delta *delta() const { return (Delta *)((uint64_t)ptr_ & 0xFFFFFFFFFFFFFFFE); } + void delta(Delta *in) { ptr_ = in + deleted(); } + + private: + Delta *ptr_; +}; + +} // namespace memgraph::storage diff --git a/src/storage/v2/tco_vector.hpp b/src/storage/v2/tco_vector.hpp new file mode 100644 index 000000000..3880479ff --- /dev/null +++ b/src/storage/v2/tco_vector.hpp @@ -0,0 +1,288 @@ +// Copyright 2024 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 <iterator> +#include <vector> + +namespace memgraph::storage { + +#include <stdexcept> + +template <typename T> +class TcoVector { + private: + T *data_; + uint32_t size_; // max 4 million + uint32_t capacity_; // max 4 million + + public: + TcoVector() : data_(nullptr), size_(0), capacity_(0) {} + + TcoVector(TcoVector &in) { + reserve(in.size_); + memcpy(data_, in.data_, in.size_ * sizeof(T)); + size_ = in.size_; + } + TcoVector(TcoVector &&in) : data_{in.data_}, size_{in.size_}, capacity_{in.capacity_} { + if (&in != this) { + in.data_ = nullptr; + in.size_ = 0; + in.capacity_ = 0; + } + } + TcoVector<T> &operator=(TcoVector<T> &in) { + reserve(in.size_); + memcpy(data_, in.data_, in.size_ * sizeof(T)); + size_ = in.size_; + return *this; + } + TcoVector<T> &operator=(TcoVector<T> &&in) { + if (&in != this) { + data_ = in.data_; + size_ = in.size_; + capacity_ = in.capacity_; + in.data_ = nullptr; + in.size_ = 0; + in.capacity_ = 0; + } + return *this; + } + ~TcoVector() { delete[] data_; } + + TcoVector(const std::vector<T> &in) { + reserve(in.size()); + memcpy(data_, in.data(), in.size() * sizeof(T)); + size_ = in.size(); + } + + void push_back(const T &value) { + if (size_ >= capacity_) { + reserve(capacity_ == 0 ? 1 : 2 * capacity_); + } + data_[size_++] = value; + } + + template <typename... Args> + void emplace_back(Args &&...args) { + if (size_ >= capacity_) { + reserve(capacity_ == 0 ? 1 : 2 * capacity_); + } + new (&data_[size_++]) T{std::forward<Args>(args)...}; + } + + void pop_back() { + if (size_) { + auto back = std::move(data_[--size_]); + } + } + + T &operator[](unsigned int index) { return data_[index]; } + + const T &operator[](unsigned int index) const { return data_[index]; } + + T &at(unsigned int index) { + if (index >= size_) { + throw std::out_of_range("Index out of range"); + } + return data_[index]; + } + + const T &at(unsigned int index) const { + if (index >= size_) { + throw std::out_of_range("Index out of range"); + } + return data_[index]; + } + + unsigned int size() const { return size_; } + + unsigned int capacity() const { return capacity_; } + + void reserve(unsigned int new_capacity) { + // TODO Test if >= max uin32_t + if (new_capacity <= capacity_) { + return; + } + + // T *new_data = new T[new_capacity]; + const auto size = ((sizeof(T) + 7) / 8) * 8; + T *new_data = reinterpret_cast<T *>(new uint8_t[new_capacity * size]); + memcpy(new_data, data_, size_ * sizeof(T)); + + delete[] data_; + data_ = new_data; + capacity_ = new_capacity; + } + + void resize(unsigned int new_size) { + if (new_size > capacity_) { + reserve(new_size); + } + if (new_size > size_) { + for (unsigned int i = size_; i < new_size; ++i) { + data_[i] = T(); + } + } + size_ = new_size; + } + + T &back() { return data_[size_ - 1]; } + + const T &back() const { return data_[size_ - 1]; } + + bool empty() const { return size_ == 0; } + + class Iterator { + private: + T *ptr; + friend class TcoVector<T>; + + public: + using iterator_category = std::contiguous_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T *; + using reference = T &; + + Iterator(T *p) : ptr(p) {} + + Iterator &operator++() { + ++ptr; + return *this; + } + + Iterator operator++(int) { + Iterator temp = *this; + ++(*this); + return temp; + } + + Iterator &operator--() { + --ptr; + return *this; + } + + Iterator operator--(int) { + Iterator temp = *this; + --(*this); + return temp; + } + + T &operator*() const { return *ptr; } + + T *operator->() const { return ptr; } + + bool operator<(const Iterator &other) const { return ptr < other.ptr; } + bool operator>(const Iterator &other) const { return ptr > other.ptr; } + bool operator==(const Iterator &other) const { return ptr == other.ptr; } + bool operator<=(const Iterator &other) const { return ptr <= other.ptr; } + bool operator>=(const Iterator &other) const { return ptr >= other.ptr; } + bool operator!=(const Iterator &other) const { return ptr != other.ptr; } + + std::ptrdiff_t operator-(const Iterator &rv) const { return ptr - rv.ptr; } + }; + + class ReverseIterator { + private: + T *ptr; + friend class TcoVector<T>; + + public: + using iterator_category = std::contiguous_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T *; + using reference = T &; + + ReverseIterator(T *p) : ptr(p) {} + + ReverseIterator &operator++() { + --ptr; + return *this; + } + + ReverseIterator operator++(int) { + ReverseIterator temp = *this; + ++(*this); + return temp; + } + + ReverseIterator &operator--() { + ++ptr; + return *this; + } + + ReverseIterator operator--(int) { + ReverseIterator temp = *this; + --(*this); + return temp; + } + + T &operator*() const { return *ptr; } + + T *operator->() const { return ptr; } + + bool operator<(const ReverseIterator &other) const { return ptr < other.ptr; } + bool operator>(const ReverseIterator &other) const { return ptr > other.ptr; } + bool operator==(const ReverseIterator &other) const { return ptr == other.ptr; } + bool operator<=(const ReverseIterator &other) const { return ptr <= other.ptr; } + bool operator>=(const ReverseIterator &other) const { return ptr >= other.ptr; } + bool operator!=(const ReverseIterator &other) const { return ptr != other.ptr; } + + std::ptrdiff_t operator-(const ReverseIterator &rv) const { return rv.ptr - ptr; } + }; + + Iterator begin() { return Iterator(data_); } + + Iterator end() { return Iterator(data_ + size_); } + + const Iterator begin() const { return Iterator(data_); } + + const Iterator end() const { return Iterator(data_ + size_); } + + ReverseIterator rbegin() { return ReverseIterator(data_ + size_ - 1); } + + ReverseIterator rend() { return ReverseIterator(data_ - 1); } + + const ReverseIterator rbegin() const { return ReverseIterator(data_ + size_ - 1); } + + const ReverseIterator rend() const { return ReverseIterator(data_ - 1); } + + void erase(Iterator pos) { + if (pos >= begin() && pos < end()) { + Iterator next = pos; + ++next; + while (next != end()) { + *pos.ptr = std::move(*next); + ++pos; + ++next; + } + --size_; + } else { + throw std::out_of_range("Iterator out of range"); + } + } + + void erase(Iterator pos, Iterator end_itr) { + if (pos < begin() || pos >= end() || end_itr < pos || end_itr > end()) { + throw std::out_of_range("Iterator out of range"); + } + for (auto itr = pos; itr < end_itr; ++itr, --end_itr) { + erase(itr); + } + } +}; + +static_assert(sizeof(TcoVector<int>) == 16); + +} // namespace memgraph::storage diff --git a/src/storage/v2/vertex.hpp b/src/storage/v2/vertex.hpp index 41021b436..4c11bdc87 100644 --- a/src/storage/v2/vertex.hpp +++ b/src/storage/v2/vertex.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -11,6 +11,8 @@ #pragma once +#include <alloca.h> +#include <iterator> #include <limits> #include <tuple> #include <vector> @@ -19,12 +21,15 @@ #include "storage/v2/edge_ref.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/property_store.hpp" +#include "storage/v2/tco_delta.hpp" +#include "storage/v2/tco_vector.hpp" #include "utils/rw_spin_lock.hpp" namespace memgraph::storage { struct Vertex { - Vertex(Gid gid, Delta *delta) : gid(gid), deleted(false), delta(delta) { + Vertex(Gid gid, Delta *delta) : gid(gid), tco_delta(delta) { + tco_delta.deleted(false); MG_ASSERT(delta == nullptr || delta->action == Delta::Action::DELETE_OBJECT || delta->action == Delta::Action::DELETE_DESERIALIZED_OBJECT, "Vertex must be created with an initial DELETE_OBJECT delta!"); @@ -33,20 +38,28 @@ struct Vertex { const Gid gid; std::vector<LabelId> labels; - PropertyStore properties; - std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; - std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; + TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> in_edges; + TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> out_edges; mutable utils::RWSpinLock lock; - bool deleted; - // uint8_t PAD; - // uint16_t PAD; + PropertyStore properties; + // bool deleted; 88 vs 112 == 24 + // // uint8_t PAD; + // // uint16_t PAD; - Delta *delta; + // Delta *delta; + + TcoDelta tco_delta; + + bool deleted() const { return tco_delta.deleted(); } + void deleted(bool in) { tco_delta.deleted(in); } + Delta *delta() const { return tco_delta.delta(); } + void delta(Delta *in) { tco_delta.delta(in); } }; static_assert(alignof(Vertex) >= 8, "The Vertex should be aligned to at least 8!"); +static_assert(sizeof(Vertex) == 8 + 24 + 16 + 16 + 12 + 4 + 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; } diff --git a/src/storage/v2/vertex_accessor.cpp b/src/storage/v2/vertex_accessor.cpp index ef0a6ab3e..76b0f88d4 100644 --- a/src/storage/v2/vertex_accessor.cpp +++ b/src/storage/v2/vertex_accessor.cpp @@ -40,8 +40,8 @@ std::pair<bool, bool> IsVisible(Vertex const *vertex, Transaction const *transac Delta *delta = nullptr; { auto guard = std::shared_lock{vertex->lock}; - deleted = vertex->deleted; - delta = vertex->delta; + deleted = vertex->deleted(); + delta = vertex->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -105,7 +105,7 @@ Result<bool> VertexAccessor::AddLabel(LabelId label) { auto guard = std::unique_lock{vertex_->lock}; if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; + if (vertex_->deleted()) return Error::DELETED_OBJECT; if (std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end()) return false; utils::AtomicMemoryBlock atomic_memory_block{[transaction = transaction_, vertex = vertex_, &label]() { @@ -135,7 +135,7 @@ Result<bool> VertexAccessor::RemoveLabel(LabelId label) { auto guard = std::unique_lock{vertex_->lock}; if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; + if (vertex_->deleted()) return Error::DELETED_OBJECT; auto it = std::find(vertex_->labels.begin(), vertex_->labels.end(), label); if (it == vertex_->labels.end()) return false; @@ -162,9 +162,9 @@ Result<bool> VertexAccessor::HasLabel(LabelId label, View view) const { Delta *delta = nullptr; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); has_label = std::find(vertex_->labels.begin(), vertex_->labels.end(), label) != vertex_->labels.end(); - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -209,9 +209,9 @@ Result<std::vector<LabelId>> VertexAccessor::Labels(View view) const { Delta *delta = nullptr; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); labels = vertex_->labels; - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -259,7 +259,7 @@ Result<PropertyValue> VertexAccessor::SetProperty(PropertyId property, const Pro if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; + if (vertex_->deleted()) return Error::DELETED_OBJECT; auto current_value = vertex_->properties.GetProperty(property); // We could skip setting the value if the previous one is the same to the new @@ -297,7 +297,7 @@ Result<bool> VertexAccessor::InitProperties(const std::map<storage::PropertyId, if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; + if (vertex_->deleted()) return Error::DELETED_OBJECT; bool result{false}; utils::AtomicMemoryBlock atomic_memory_block{ [&result, &properties, storage = storage_, transaction = transaction_, vertex = vertex_]() { @@ -333,7 +333,7 @@ Result<std::vector<std::tuple<PropertyId, PropertyValue, PropertyValue>>> Vertex if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; + if (vertex_->deleted()) return Error::DELETED_OBJECT; using ReturnType = decltype(vertex_->properties.UpdateProperties(properties)); std::optional<ReturnType> id_old_new_change; @@ -367,7 +367,7 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::ClearProperties() { if (!PrepareForWrite(transaction_, vertex_)) return Error::SERIALIZATION_ERROR; - if (vertex_->deleted) return Error::DELETED_OBJECT; + if (vertex_->deleted()) return Error::DELETED_OBJECT; using ReturnType = decltype(vertex_->properties.Properties()); std::optional<ReturnType> properties; @@ -397,9 +397,9 @@ Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view Delta *delta = nullptr; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); value = vertex_->properties.GetProperty(property); - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -441,7 +441,7 @@ Result<PropertyValue> VertexAccessor::GetProperty(PropertyId property, View view Result<uint64_t> VertexAccessor::GetPropertySize(PropertyId property, View view) const { { auto guard = std::shared_lock{vertex_->lock}; - Delta *delta = vertex_->delta; + Delta *delta = vertex_->delta(); if (!delta) { return vertex_->properties.PropertySize(property); } @@ -465,9 +465,9 @@ Result<std::map<PropertyId, PropertyValue>> VertexAccessor::Properties(View view Delta *delta = nullptr; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); properties = vertex_->properties.Properties(); - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -585,7 +585,7 @@ Result<EdgesVertexAccessorResult> VertexAccessor::InEdges(View view, const std:: int64_t expanded_count = 0; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); expanded_count = static_cast<int64_t>(vertex_->in_edges.size()); // TODO: a better filter copy if (edge_types.empty() && !destination) { @@ -598,7 +598,7 @@ Result<EdgesVertexAccessorResult> VertexAccessor::InEdges(View view, const std:: in_edges.emplace_back(edge_type, from_vertex, edge); } } - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -675,7 +675,7 @@ Result<EdgesVertexAccessorResult> VertexAccessor::OutEdges(View view, const std: int64_t expanded_count = 0; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); expanded_count = static_cast<int64_t>(vertex_->out_edges.size()); if (edge_types.empty() && !destination) { out_edges = vertex_->out_edges; @@ -687,7 +687,7 @@ Result<EdgesVertexAccessorResult> VertexAccessor::OutEdges(View view, const std: out_edges.emplace_back(edge_type, to_vertex, edge); } } - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -749,9 +749,9 @@ Result<size_t> VertexAccessor::InDegree(View view) const { Delta *delta = nullptr; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); degree = vertex_->in_edges.size(); - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas @@ -805,9 +805,9 @@ Result<size_t> VertexAccessor::OutDegree(View view) const { Delta *delta = nullptr; { auto guard = std::shared_lock{vertex_->lock}; - deleted = vertex_->deleted; + deleted = vertex_->deleted(); degree = vertex_->out_edges.size(); - delta = vertex_->delta; + delta = vertex_->delta(); } // Checking cache has a cost, only do it if we have any deltas diff --git a/src/storage/v2/vertex_accessor.hpp b/src/storage/v2/vertex_accessor.hpp index 18fad3dcc..6d0836fcf 100644 --- a/src/storage/v2/vertex_accessor.hpp +++ b/src/storage/v2/vertex_accessor.hpp @@ -27,7 +27,7 @@ class Storage; struct Constraints; struct Indices; struct EdgesVertexAccessorResult; -using edge_store = std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>>; +using edge_store = TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>>; class VertexAccessor final { private: diff --git a/src/storage/v2/vertex_info_cache.hpp b/src/storage/v2/vertex_info_cache.hpp index a5acbd7af..af420efcc 100644 --- a/src/storage/v2/vertex_info_cache.hpp +++ b/src/storage/v2/vertex_info_cache.hpp @@ -1,4 +1,4 @@ -// Copyright 2023 Memgraph Ltd. +// Copyright 2024 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 @@ -13,6 +13,7 @@ #include "storage/v2/edge_direction.hpp" #include "storage/v2/edge_ref.hpp" #include "storage/v2/id_types.hpp" +#include "storage/v2/tco_vector.hpp" #include "storage/v2/view.hpp" #include "absl/container/flat_hash_map.h" @@ -86,7 +87,7 @@ struct VertexInfoCache final { void Invalidate(Vertex const *vertex, PropertyId property_key); - using EdgeStore = std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>>; + using EdgeStore = TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>>; auto GetInEdges(View view, Vertex const *src_vertex, Vertex const *dst_vertex, const std::vector<EdgeTypeId> &edge_types) const -> detail::optref<const EdgeStore>; diff --git a/src/storage/v2/vertex_info_helpers.hpp b/src/storage/v2/vertex_info_helpers.hpp index 7c8e2a652..0e9a77970 100644 --- a/src/storage/v2/vertex_info_helpers.hpp +++ b/src/storage/v2/vertex_info_helpers.hpp @@ -144,8 +144,8 @@ inline auto Properties_ActionMethod(std::map<PropertyId, PropertyValue> &propert } template <EdgeDirection dir> -inline auto Edges_ActionMethod(std::vector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> &edges, - std::vector<EdgeTypeId> const &edge_types, Vertex const *destination) { +inline auto Edges_ActionMethod(TcoVector<std::tuple<EdgeTypeId, Vertex *, EdgeRef>> &edges, + TcoVector<EdgeTypeId> const &edge_types, Vertex const *destination) { auto const predicate = [&, destination](Delta const &delta) { if (destination && delta.vertex_edge.vertex != destination) return false; if (!edge_types.empty() &&