// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #pragma once #include #include "storage/v2/edge_ref.hpp" #include "storage/v2/id_types.hpp" #include "storage/v2/property_value.hpp" #include "utils/logging.hpp" namespace memgraph::storage { // Forward declarations because we only store pointers here. struct Vertex; struct Edge; struct Delta; // This class stores one of three pointers (`Delta`, `Vertex` and `Edge`) // without using additional memory for storing the type. The type is stored in // the pointer itself in the lower bits. All of those structures contain large // items in themselves (e.g. `uint64_t`) that require the pointer to be aligned // to their size (for `uint64_t` it is 8). That means that the pointer will // always be a multiple of 8 which implies that the lower 3 bits of the pointer // will always be 0. We can use those 3 bits to store information about the type // of the pointer stored (2 bits). class PreviousPtr { private: static constexpr uintptr_t kDelta = 0b01UL; static constexpr uintptr_t kVertex = 0b10UL; static constexpr uintptr_t kEdge = 0b11UL; static constexpr uintptr_t kMask = 0b11UL; public: enum class Type { NULLPTR, DELTA, VERTEX, EDGE, }; struct Pointer { Pointer() = default; explicit Pointer(Delta *delta) : type(Type::DELTA), delta(delta) {} explicit Pointer(Vertex *vertex) : type(Type::VERTEX), vertex(vertex) {} explicit Pointer(Edge *edge) : type(Type::EDGE), edge(edge) {} Type type{Type::NULLPTR}; Delta *delta{nullptr}; Vertex *vertex{nullptr}; Edge *edge{nullptr}; }; PreviousPtr() : storage_(0) {} PreviousPtr(const PreviousPtr &other) noexcept : storage_(other.storage_.load(std::memory_order_acquire)) {} Pointer Get() const { uintptr_t value = storage_.load(std::memory_order_acquire); if (value == 0) { return {}; } uintptr_t type = value & kMask; if (type == kDelta) { return Pointer{reinterpret_cast(value & ~kMask)}; } else if (type == kVertex) { return Pointer{reinterpret_cast(value & ~kMask)}; } else if (type == kEdge) { return Pointer{reinterpret_cast(value & ~kMask)}; } else { LOG_FATAL("Invalid pointer type!"); } } void Set(Delta *delta) { uintptr_t value = reinterpret_cast(delta); MG_ASSERT((value & kMask) == 0, "Invalid pointer!"); storage_.store(value | kDelta, std::memory_order_release); } void Set(Vertex *vertex) { uintptr_t value = reinterpret_cast(vertex); MG_ASSERT((value & kMask) == 0, "Invalid pointer!"); storage_.store(value | kVertex, std::memory_order_release); } void Set(Edge *edge) { uintptr_t value = reinterpret_cast(edge); MG_ASSERT((value & kMask) == 0, "Invalid pointer!"); storage_.store(value | kEdge, std::memory_order_release); } private: std::atomic storage_; }; inline bool operator==(const PreviousPtr::Pointer &a, const PreviousPtr::Pointer &b) { if (a.type != b.type) return false; switch (a.type) { case PreviousPtr::Type::VERTEX: return a.vertex == b.vertex; case PreviousPtr::Type::EDGE: return a.edge == b.edge; case PreviousPtr::Type::DELTA: return a.delta == b.delta; case PreviousPtr::Type::NULLPTR: return b.type == PreviousPtr::Type::NULLPTR; } throw 1; } inline bool operator!=(const PreviousPtr::Pointer &a, const PreviousPtr::Pointer &b) { return !(a == b); } struct Delta { enum class Action { // Used for both Vertex and Edge DELETE_OBJECT, RECREATE_OBJECT, SET_PROPERTY, // Used only for Vertex ADD_LABEL, REMOVE_LABEL, ADD_IN_EDGE, ADD_OUT_EDGE, REMOVE_IN_EDGE, REMOVE_OUT_EDGE, }; // Used for both Vertex and Edge struct DeleteObjectTag {}; struct RecreateObjectTag {}; struct SetPropertyTag {}; // Used only for Vertex struct AddLabelTag {}; struct RemoveLabelTag {}; struct AddInEdgeTag {}; struct AddOutEdgeTag {}; struct RemoveInEdgeTag {}; struct RemoveOutEdgeTag {}; Delta(DeleteObjectTag, std::atomic *timestamp, uint64_t command_id) : action(Action::DELETE_OBJECT), timestamp(timestamp), command_id(command_id) {} Delta(RecreateObjectTag, std::atomic *timestamp, uint64_t command_id) : action(Action::RECREATE_OBJECT), timestamp(timestamp), command_id(command_id) {} Delta(AddLabelTag, LabelId label, std::atomic *timestamp, uint64_t command_id) : action(Action::ADD_LABEL), timestamp(timestamp), command_id(command_id), label(label) {} Delta(RemoveLabelTag, LabelId label, std::atomic *timestamp, uint64_t command_id) : action(Action::REMOVE_LABEL), timestamp(timestamp), command_id(command_id), label(label) {} Delta(SetPropertyTag, PropertyId key, const PropertyValue &value, std::atomic *timestamp, uint64_t command_id) : action(Action::SET_PROPERTY), timestamp(timestamp), command_id(command_id), property({key, value}) {} Delta(AddInEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic *timestamp, uint64_t command_id) : action(Action::ADD_IN_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} Delta(AddOutEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic *timestamp, uint64_t command_id) : action(Action::ADD_OUT_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} Delta(RemoveInEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic *timestamp, uint64_t command_id) : action(Action::REMOVE_IN_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} Delta(RemoveOutEdgeTag, EdgeTypeId edge_type, Vertex *vertex, EdgeRef edge, std::atomic *timestamp, uint64_t command_id) : action(Action::REMOVE_OUT_EDGE), timestamp(timestamp), command_id(command_id), vertex_edge({edge_type, vertex, edge}) {} Delta(const Delta &) = delete; Delta(Delta &&) = delete; Delta &operator=(const Delta &) = delete; Delta &operator=(Delta &&) = delete; ~Delta() { switch (action) { case Action::DELETE_OBJECT: case Action::RECREATE_OBJECT: case Action::ADD_LABEL: case Action::REMOVE_LABEL: case Action::ADD_IN_EDGE: case Action::ADD_OUT_EDGE: case Action::REMOVE_IN_EDGE: case Action::REMOVE_OUT_EDGE: break; case Action::SET_PROPERTY: property.value.~PropertyValue(); break; } } Action action; // TODO: optimize with in-place copy std::atomic *timestamp; uint64_t command_id; PreviousPtr prev; std::atomic next{nullptr}; union { LabelId label; struct { PropertyId key; storage::PropertyValue value; } property; struct { EdgeTypeId edge_type; Vertex *vertex; EdgeRef edge; } vertex_edge; }; }; static_assert(alignof(Delta) >= 8, "The Delta should be aligned to at least 8!"); } // namespace memgraph::storage