244 lines
7.6 KiB
C++
244 lines
7.6 KiB
C++
// 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 <atomic>
|
|
|
|
#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<Delta *>(value & ~kMask)};
|
|
} else if (type == kVertex) {
|
|
return Pointer{reinterpret_cast<Vertex *>(value & ~kMask)};
|
|
} else if (type == kEdge) {
|
|
return Pointer{reinterpret_cast<Edge *>(value & ~kMask)};
|
|
} else {
|
|
LOG_FATAL("Invalid pointer type!");
|
|
}
|
|
}
|
|
|
|
void Set(Delta *delta) {
|
|
uintptr_t value = reinterpret_cast<uintptr_t>(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<uintptr_t>(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<uintptr_t>(edge);
|
|
MG_ASSERT((value & kMask) == 0, "Invalid pointer!");
|
|
storage_.store(value | kEdge, std::memory_order_release);
|
|
}
|
|
|
|
private:
|
|
std::atomic<uintptr_t> 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<uint64_t> *timestamp, uint64_t command_id)
|
|
: action(Action::DELETE_OBJECT), timestamp(timestamp), command_id(command_id) {}
|
|
|
|
Delta(RecreateObjectTag, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
|
: action(Action::RECREATE_OBJECT), timestamp(timestamp), command_id(command_id) {}
|
|
|
|
Delta(AddLabelTag, LabelId label, std::atomic<uint64_t> *timestamp, uint64_t command_id)
|
|
: action(Action::ADD_LABEL), timestamp(timestamp), command_id(command_id), label(label) {}
|
|
|
|
Delta(RemoveLabelTag, LabelId label, std::atomic<uint64_t> *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<uint64_t> *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<uint64_t> *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<uint64_t> *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<uint64_t> *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<uint64_t> *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<uint64_t> *timestamp;
|
|
uint64_t command_id;
|
|
PreviousPtr prev;
|
|
std::atomic<Delta *> 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
|