Vertex/edge compaction: delete + delta, smaller propertystore + lock, smaller vector

This commit is contained in:
Andreja Tonev 2024-02-21 13:42:45 +01:00
parent 61b9bb0f59
commit 7627dab16b
26 changed files with 572 additions and 230 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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